I need to update a table called Calculated in my database, but because I have so many values that I have stored in my system as variables to add/update in the table, I created a separate table in the database called Database Relationships.
This Database Relationships table has a column called Calculated Value which holds all the field names of the Calculated table. The other column, System Field holds all the names of variables that I have created and given values to, which are of all string type and that relate to the corresponding Calculated Value
So by using a FOREACH loop
OleDbDataAdapter relationshipAdapter = new OleDbDataAdapter(relationshipCmd);
DataTable relationshipTable = new DataTable();
relationshipAdapter.Fill(relationshipTable);
string update = "Update [Calculated] SET ";
int i = 0;
int len = relationshipTable.Rows.Count;
foreach (DataRow drr in relationshipTable.Rows)
{
string calc = drr["Calculated Field"].ToString();
var sys = drr["System Field"].ToString();
if (i == len - 1)
{
update += "[" + calc + "] = " + sys + ";";
}
else
{
update += "[" + calc + "] = " + sys + ", ";
}
i++
}
update += "WHERE [NSN] = '" + NSN + "';";
OleDbCommand updateCmd = new OleDbCommand(update);
But this is not working, because after some debugging(?) I did a simple Console.WriteLine(sys) and it would print out the string in the System Field column, instead of the variable with the same name in the system.
I am currently using Microsoft Access as my database platform.
I think having "intermediate" table for temporary storing values in runtime for future saving in another table sounds little bid complicated for me.
If you want to map variables at runtime with column name in the database - use dictionary for example:
Dictionary<string, string> myValues = new Dictionary<string, string>();
Using in the application:
myValues["SomeColumn"] = "your value";
Then saving data to database will be:
var updateCmd = new OleDbCommand();
var query = new StringBuilder();
foreach(KeyValuePair<string, string> value in myValues)
{
string columnName = value.Key;
query.AppendLine($", {columnName} = ?");
var param = new OleDbParameter("#v", value.Value);
// Name of parameter not important, only order
}
if(query.Length > 0)
{
query.Remove(0, 1); // Remove first ',' character
query.Insert("Update [Calculated] SET ");
query.AppendLine("$WHERE [NSN] = '{NSN}';");
}
updateCmd.CommandText = query.ToString();
Very important: you need to use OleDbParameter for passing values to the query.
In you foreach loop, use this:
if (i == len - 1)
{
update += "[" + calc + "] = " + this.GetType().GetProperty(sys).GetValue(this, null)+ ";";
}
else
{
update += "[" + calc + "] = " + this.GetType().GetProperty(sys).GetValue(this, null)+ ", ";
}
The code above assumes that the variables are scoped in the same scope where you are generating your Sql.
In loop , you use the condition:
if (i == len - 1)
but you never change "len" or "i" value in the code.
Related
Regarding a question and its related answer, I'm trying to create a new table type using SQL queries and run a join query on data which are of this table type.
I put both queries in the same script, i.e. create type and select but it is not working. My query is as below (the whole code is brought in the end of question):
IF TYPE_ID(N'BranchMappingType') IS NULL
CREATE TYPE BranchMappingType As Table
(COL_ServerID int,
COL_ServerSchema Common._SMALL_,
COL_TableName Common._SMALL_,
COL_BranchNo Common._SMALL_,
COL_BranchSchema Common._SMALL_ );
SELECT [VMA_BranchID] FROM [VIP].[VipMapping]
JOIN #TVP ON
VMA_ServerID=COL_ServerID AND
VMA_ServerSchema=COL_ServerSchema AND
VMA_TableName=COL_TableName AND
VMA_BranchNo=COL_BranchNo AND
VMA_BranchSchema=COL_BranchSchema;
I will execute my query using SqlDataReader.ExecuteReader but It will throw exception:
Column, parameter, or variable #TVP. : Cannot find data type BranchMappingType.
But when I run 1st part of query, i.e., IF TYPE_ID(...) ... CREATE TYPE it will execute with no errors and then running the second part i.e. Select from ... join #TVP works flawlessly.
It seems newly created table type is not recognized when I run both queries with single execution of SqlDataReader.ExecuteReader.
I want to know what is the reason and If I've made some mistakes during my implementation or not? I prefer to run both queries with one-time calling of SqlDataReader.ExecuteReader method.
Here is full code for better inspection:
List<int> keys = new List<int>();
if (foreignKeyInfoList == null || foreignKeyInfoList.Count == 0)
return new StatusResponseKeysList { ErrorCode = ErrorCodes.ERROR_CODE_MINUS_019_NULL_OR_EMPTY_INPUT_ARGUMENT };
const string ID_FIELD = "VMA_BranchID";
const string STRUCTURED_DATA_TABLE_NAME = "#TVP";
const string COL1_SERVER_ID = "COL_ServerID";
const string COL2_SERVER_SCHEMA = "COL_ServerSchema";
const string COL3_TABLE_NAME = "COL_TableName";
const string COL4_BRANCH_NO = "COL_BranchNo";
const string COL5_BRANCH_SCHEMA = "COL_BranchSchema";
//const string COL6_TABLE_ID = "COL_TableID";
const string MAP1_SERVER_ID = "VMA_ServerID";
const string MAP2_SERVER_SCHEMA = "VMA_ServerSchema";
const string MAP3_TABLE_NAME = "VMA_TableName";
const string MAP4_BRANCH_NO = "VMA_BranchNo";
const string MAP5_BRANCH_SCHEMA = "VMA_BranchSchema";
const string TYPE_NAME = "BranchMappingType";
//const string MAP6_TABLE_ID = "VMA_TableID";
string sqlQuery = $"IF TYPE_ID(N'{TYPE_NAME}') IS NULL" + " " +
$"CREATE TYPE {TYPE_NAME} As Table" + " " +
$"({COL1_SERVER_ID} int," + " " +
$"{COL2_SERVER_SCHEMA} Common._SMALL_," + " " +
$"{COL3_TABLE_NAME} Common._SMALL_," + " " +
$"{COL4_BRANCH_NO} Common._SMALL_," + " " +
$"{COL5_BRANCH_SCHEMA} Common._SMALL_" + " " +
$"); ";
sqlQuery += $"SELECT [{ID_FIELD}] " +
$"FROM {Settings.VIP_MAPPING_TABLE} " +
$"JOIN {STRUCTURED_DATA_TABLE_NAME} ON " +
$"{MAP1_SERVER_ID}={COL1_SERVER_ID} " +
$" AND {MAP2_SERVER_SCHEMA}={COL2_SERVER_SCHEMA} " +
$" AND {MAP3_TABLE_NAME}={COL3_TABLE_NAME} " +
$" AND {MAP4_BRANCH_NO}={COL4_BRANCH_NO} " +
$" AND {MAP5_BRANCH_SCHEMA}={COL5_BRANCH_SCHEMA} ";// +
//$" AND {MAP6_TABLE_ID}={COL6_TABLE_ID}";
DataTable dataTable = new DataTable(TYPE_NAME);
dataTable.Columns.Add(COL1_SERVER_ID, typeof(int));
dataTable.Columns.Add(COL2_SERVER_SCHEMA, typeof(string));
dataTable.Columns.Add(COL3_TABLE_NAME, typeof(string));
dataTable.Columns.Add(COL4_BRANCH_NO, typeof(string));
dataTable.Columns.Add(COL5_BRANCH_SCHEMA, typeof(string));
//dataTable.Columns.Add(COL6_TABLE_ID, typeof(int));
for (int i = 0; i < foreignKeyInfoList.Count; i++)
{
ForeignKeyLookupModelInBranchSide oneKeySet = foreignKeyInfoList[i];
DataRow row = dataTable.NewRow();
row[COL1_SERVER_ID] = Int32.Parse(oneKeySet.ServerID);
row[COL2_SERVER_SCHEMA] = oneKeySet.ServerSchema;
row[COL3_TABLE_NAME] = oneKeySet.TableName;
row[COL4_BRANCH_NO] = oneKeySet.BranchNo;
row[COL5_BRANCH_SCHEMA] = oneKeySet.BranchSchema;
dataTable.Rows.Add(row);
}
try
{
using (SqlConnection sqlConnection = new SqlConnection(Settings.connectionString))
{
SqlCommand command = new SqlCommand(sqlQuery, sqlConnection);
SqlParameter p = command.Parameters.Add(STRUCTURED_DATA_TABLE_NAME, SqlDbType.Structured);
p.Value = dataTable;
p.TypeName = TYPE_NAME;
sqlConnection.Open();
SqlDataReader reader = command.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
for (int i = 0; i < reader.FieldCount; i++)
{
keys.Add(reader.GetInt32(i));
}
}
}
else
{
}
sqlConnection.Close();
}
return new StatusResponseKeysList
{
keysList = keys,
ErrorCode = ErrorCodes.ERROR_CODE_MINUS_019_NULL_OR_EMPTY_INPUT_ARGUMENT
};
}
catch (Exception e)
{
//Exception caught here !!!!!!!:
//"Column, parameter, or variable #TVP. : Cannot find data type BranchMappingType."
if (e.GetType() == typeof(SqlException))
{
//this is a db error
return new StatusResponseKeysList
{
ErrorCode = ErrorCodes.ERROR_CODE_MINUS_014_DATABASE_EXCEPTION
};
}
else
{
//other types of erros (not a DB-error)
return new StatusResponseKeysList
{
ErrorCode = ErrorCodes.ERROR_CODE_MINUS_001_COMMON_ERROR
};
}
}
The table type must exist before the parameterized query that uses the type is executed. The reason is the database engine first creates an internal table in tempdb matching the table type defined on the server before the batch executes. It then bulk inserts the TVP value into tempdb using the structured parameter value and meta-data provided by the API. Only after the table is loaded does the batch execute, where it can use the TVP value.
The implication is that one cannot create the table type and use it in the same batch that uses the type as a parameter.
I am making a application about sync the database's data.
I have a source DB and a target DB (which is empty).
When the application runs, it creates the same columns in the target DB's table, and additionally adds an UpdateYN column.
Then it inserts data from the source DB's table into the target DB's table.
It means two tables have same data except for the UpdateYN column.
I already built a code above. But now I want to make like this.
When I updated a Source DB's table column, I just want to updated only that row in target DB's table.
So, before update, need to compare two rows, and check the values but I don't know how to handle it.
Please give me some link about that or share your knowledge, Thanks.
Code is below.
Note: source tables column name is not a fixed value
DataTable DeptDt = null;
// Get Source DB table
DeptDt = DataSetMaker.Instance.FromMsSql(SourceDBPath, SourceDBName, SourceDBLoginID, SourceDBLoginPass, SourceDBDeptTableName);
if (DeptDt != null)
{
Tool.Log(LogKinds.DEPT, LogLevels.INFORMATION, SourceKind, "Starting Dept Sync");
if (DeptDt.Rows.Count == 0)
{
Tool.Log(LogKinds.DEPT, LogLevels.WARNNING, SourceKind, "There`s no Dept Datas.");
}
else
{
// PK of Source Table
string DeptPk = AppConfigHelper.GetAppString(Words.SourceDeptPKColumn);
// add # like #deptcode
string DeptPkVar = Util.ColNameToVarName(DeptPk);
// wrapped like [deptcode]
DeptPk = Util.GetActualColName(DeptPk);
List<string> ErrorPks = Tool.GetSyncErrorPks(LogKinds.DEPT, LogSections.SOURCE);
foreach (DataRow row in DeptDt.Rows)
{
string pk = row[DeptPk] + "";
if (IsOnlyErrorSync && !ErrorPks.Contains(pk))
{
continue;
}
try
{
List<SqlParameter> pms = new List<SqlParameter>();
// Add all Source table's columns to pms
foreach (DataColumn item in DeptDt.Columns)
{
var sp = new SqlParameter("#" + item.ColumnName, row[item.ColumnName]);
pms.Add(sp);
}
// Update and insert rows to Target Table. method is below
// ShadowDeptTable is Target Table
QueryHelper.InsertOrUpdateShadowTables(Words.ShadowDeptTable,
new SqlParameter[]{new SqlParameter(DeptPkVar,pk)}, pms.ToArray());
}
catch (Exception ex)
{
Tool.SetSyncError(pk, LogKinds.DEPT, LogSections.SOURCE, ex.ToString());
}
}
if (IsOnlyErrorSync)
{
Tool.ClearSyncError(LogKinds.DEPT, LogSections.SOURCE);
}
Tool.Log(LogKinds.DEPT, LogLevels.INFORMATION, SourceKind,"Dept Sync completed.");
}
}
else
{
Tool.ErrorLog(LogKinds.DEPT, LogSections.SOURCE, SourceKind, " Dept sync was failed");
}
public static int InsertOrUpdateShadowTables(string table, SqlParameter[] keys, params SqlParameter[] pms)
{
int st = 0;
// Count Target tables rows by PK - To check that row is exist in Target Table
StringBuilder sb = new StringBuilder(string.Format("select count(*) from {0} ", table));
string where = string.Empty;
if (keys.Length > 0)
{
where += " where ";
var temp = keys.Select(a => "[" + a.ParameterName.Substring(1, a.ParameterName.Length - 1) + "]"
+ " = '" + a.Value + "'").ToArray();
where += string.Join(" and ", temp);
}
sb = sb.Append(where);
// get connection string of target table
string constr = Util.GetPropVal(Words.PropConnectionString);
// count target table's rows
var obj = SqlHelper.ExecuteScalar(constr, CommandType.Text, sb.ToString(), keys);
int cnt = Convert.ToInt32(obj);
sb = sb.Clear();
// insert
sb = sb.Append("insert into " + table + "(");
string cols = null;
string vals = null;
List<SqlParameter> pmlist = new List<SqlParameter>(keys);
pmlist.AddRange(pms);
var merged = keys.Union(pmlist).GroupBy(p => p.ParameterName).Select(e => e.First());
cols = string.Join(",",
merged.Select(a =>
"[" + a.ParameterName.Substring(1, a.ParameterName.Length - 1) + "]"));
vals = string.Join(",",
merged.Select(a => "'" + a.Value + "'"));
sb = sb.Append(cols);
sb = sb.Append(") values(");
sb = sb.Append(vals);
sb = sb.Append(")");
// Update rows(the row exists in Target table)
if (cnt > 0)
{
sb = sb.Clear();
sb = sb.Append("update " + table + " set ");
sb = sb.Append(string.Join(",", pms.Select(a =>
"[" + a.ParameterName.Substring(1, a.ParameterName.Length - 1) + "]" + " = "
+ "'" + a.Value + "'")));
sb = sb.Append(where);
// Update query excutes here
obj = SqlHelper.ExecuteScalar(constr, CommandType.Text, sb.ToString());
// Set UpdateYN = U
sb = sb.Clear();
sb = sb.Append("update " + table + " set [UpdateYN] = 'U'");
sb = sb.Append(where);
obj = SqlHelper.ExecuteScalar(constr, CommandType.Text, sb.ToString());
}
// Insert rows
else
{
// Insert query executes here
obj = SqlHelper.ExecuteScalar(constr, CommandType.Text, sb.ToString());
// set UpdateYN = I
sb = sb.Clear();
sb = sb.Append("update " + table + " set [UpdateYN] = 'I'");
sb = sb.Append(where);
obj = SqlHelper.ExecuteScalar(constr, CommandType.Text, sb.ToString());
}
return Convert.ToInt32(obj);
}
I am in the process of fixing some of our bad sql queries that are vulnerable to sql injection. Most are straight queries with no inputs, but our search field takes search terms that are not parameterised. A snippet is below:
using (var db = ORMLite.Open())
{
StringBuilder sb = new StringBuilder();
sb.Append("select * from column1, column2");
if (terms.Count() > 0)
{
sb.Append("where (column1 like '%#term0%' or " + column2 + " like '%#term0%') ");
if (terms.Count() > 1)
{
for (int i = 1; i < terms.Count(); i++)
{
sb.Append("and (column1 like '%#term" + i + "%' or " + column2 + " like '%#term" + i + "%') ");
}
}
}
List<POCO> testQuery = db.Select<POCO>(sb.ToString());
}
The #term components are where I intend to use parameters (they used to be of the form '" + term[i] + '", but any term with malicious code would just be inserted. When I move to my select statement, I would like to add the parameters. This is normally done as so:
List testQuery = db.Select(sb.ToString(), new { term0 = "t", term1 = "te", term2 = "ter" });
However I can have any number of terms (term.count() is the number of terms). How can I pass in an anonymous object with any number of terms? Or is there a better method?
I'm looking for almost the same thing in Postgresql. Based on this SO question
the answer looks like "you have to perform multiple queries."
I can get the unique row IDs from my table given the partial parameterized
query, and then directly paste those unique IDs back into the query -- since those
row IDs will be safe.
Here's an example of what I mean, but the c# is probably wrong (sorry):
string query = "SELECT unique_id FROM table WHERE (column1 LIKE '%#term%' OR column2 LIKE '%#term%')";
string safeIDs;
List uniqueRowIDs = db.Select(query, new {term = term[0]});
for (int i = 1; i < terms.Count(); i++) {
// Loop to narrow down the rows by adding the additional conditions.
safeIDs = uniqueRowIDs.Aggregate( (current, next) => current + string.Format(", '{0}'", next) );
uniqueRowIDs = db.Select(
query + string.Format(" AND unique_id IN ({0})", safeIDs),
new {term = term[i]});
}
// And finally make the last query for the chosen rows:
safeIDs = uniqueRowIDs.Aggregate( (current, next) => current + string.Format(", '{0}'", next) );
List testQuery = db.Select(string.Format("SELECT * FROM table WHERE unique_id IN ({0});", safeIDs));
Another option for your case specifically could be to just get all of the values that
are like term0 using a parameterized query and then, within the c# program, compare
all of the results against the remaining terms the user entered.
I am building a simple Point of Sale program and working on a "search invoice" button that allows up to 3 search criteria (InvoiceID , ClientName, and ClientID). These are the names of 3 of the columns in the table named "Invoicing".
InvoiceID is the key column of type Int32, ClientName is of type String, ClientID is of type Int32. ClientName and ClientID searches work perfect.
MY PROBLEM: If I include InvoiceID in the select query, I get the following error. And I have spent a few days trying to figure it out.
ERROR: Database Error: Datatype mismatch in criteria expression.
Can you more experienced programmers help me out? thank you!
String connectionString = #"Provider=Microsoft.ACE.OLEDB.12.0;Data" + #" Source=TESTDB.accdb";
String tableName = "Invoicing";
String query = String.Format("select * from [{0}] where", tableName);
//ADD IN SEARCH CRITERIA
int filled = 0;
if (invoiceBox.Text != "") { query += " InvoiceID='" + invoiceBox.Text+"'"; filled += 1; }
/*if (DateCheckBox.Checked == true)
{
if (filled>=1) { query += " and DateNTime='" + monthCalendar1.SelectionStart.ToString() + "'"; filled += 1; }
else { query += " DateNTime='" + monthCalendar1.SelectionStart.ToString()+ "'"; filled += 1; }
}
* */
if (ClientNameBox.Text != "") //Doesnot work
{
if (filled >= 1) { query += " and Client='" + ClientNameBox.Text + "'"; filled += 1; }
else { query += " Client='" + ClientNameBox.Text + "'"; filled += 1; }
}
if (ClientIDBox.Text != "") //THIS search criteria works!!!!
{
if (filled >= 1) { query += " and ClientID='" + ClientIDBox.Text + "'"; filled += 1; }
else { query += " ClientID='" + ClientIDBox.Text + "'"; filled += 1; }
}
//CHECK IF SEARCH CRITERIA ARE PRESENT
if (filled < 1) { MessageBox.Show("At least One Search criteria above is required"); return; }
DataSet dsInvoicing = new DataSet();
OleDbConnection conn = new OleDbConnection(connectionString);
try
{
//Open Database Connection
conn.Open();
OleDbDataAdapter daInvoicing = new OleDbDataAdapter(query, conn);
//Fill the DataSet
daInvoicing.Fill(dsInvoicing, tableName);
//MessageBox.Show("dsInventory has "+dsInventory.Tables[0].Rows.Count+" search results");
conn.Close();
this.dataGridView1.DataSource = dsInvoicing.Tables[0];
}
catch (OleDbException exp){ MessageBox.Show("Database Error: " + exp.Message.ToString());}
Need more information? I will post up more if I haven't provided enough.
DATABASE INFORMATION or other.
Thank you very much to all programmers.
Looks like the data type of InvoiceID in your database is some numeric kind. While in query you are treating it as string. Try not to wrap InvoiceID value in single quotes.
Can I add a new record into access database which has 15 columns? It's very unconvinient for me to using this sql:
insert into Account(col1,col2,col3,col4,col5,col6,col7,col8,col9,col10,col11,col12,col13,col14,col15) Values(val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,val13,val14,val15)
There are 2 List consist columns name (name) and values (info). Example:
name[1]="col1";
info[1]="val1";
Is the ordinary of columns name important? Can I use this Sql:
insert into Account(col1) Values(val1)
After that I use an "update" sql and a "for-loop" to set values?
I also get an error:
The changes you requested to the table were not successful because they would create duplicate values in the index, primary key, or relationship. Change the data in the field or fields that contain duplicate data, remove the index, or redefine the index to permit duplicate entries and try again.
Thank you so much. :)
Do one insert before for loop and update in the for loop.
A sample code is given below.
string Query = "insert into Account(col1) Values(val1)";
// execute it
for(int i=1;i<name.count -1;i++)
{
Query = "Update Account set " + name[i] + " = " + info[i] + " where col1 = val1";
// execute
}
Code not tested.
public static void insert(String[] name ,String[] info)
{
String Names = "";
String Cols = "";
for(int i=0;i < name.Length;i++)
{
Names += (Names == "" ? "" : ", ") + name[i];
Cols += (Cols == "" ? "" : ", ") + "'" + info[i] + "'";
}
String Query = "insert into Account (" + Names + ") Values (" + Cols + ")";
Console.WriteLine(Query);
}
Code not tested and note that I added single quotes for values assuming that all values are string type.