First a bit of background:
I developed a method of retrieving rows from a table in SQL in batches or pages, by using IEnumerable and yield. It works great when you need read only access but not so well when you need to do updates to the underlying data as well.
So I wrote a method that takes in a generic DataTable, and builds up an update statement which then gets passed to SQL along with the entire DataTable as a table valued parameter.
The method looks like this:
string[] validColumns = SQL_Columns.Split(',');
foreach(DataColumn column in p_UpdatesTable.Columns)
{
if(!validColumns.Contains(column.ColumnName))
{
throw new Exception("Column '" + column.ColumnName + "' is not valid for this table");
}
}
//Establish SQL Connection
using (SqlConnection sqlConnection = new SqlConnection(connectionString))
{
sqlConnection.Open();
StringBuilder commandBuilder = new StringBuilder();
commandBuilder.Append("UPDATE Table SET ");
List<string> columnsToUpdate = new List<string>(p_UpdatesTable.Columns.Count);
foreach(DataColumn column in p_UpdatesTable.Columns)
{
if (!column.ColumnName.Equals("UID", StringComparison.InvariantCultureIgnoreCase))
{
StringBuilder columnBuilder = new StringBuilder();
columnBuilder.Append(column.ColumnName);
columnBuilder.Append(" = U.");
columnBuilder.Append(column.ColumnName);
columnsToUpdate.Add(columnBuilder.ToString());
}
}
commandBuilder.Append(string.Join(",", columnsToUpdate.ToArray()));
commandBuilder.Append(" FROM #UpdateTable AS U WHERE UID = U.UID");
using (SqlCommand sqlCommand = new SqlCommand(commandBuilder.ToString(), sqlConnection))
{
SqlParameter updateTableParameter = sqlCommand.Parameters.Add("UpdateTable", SqlDbType.Structured);
updateTableParameter.Value = p_UpdatesTable;
int rowsAffected = sqlCommand.ExecuteNonQuery();
if(rowsAffected != p_UpdatesTable.Rows.Count)
{
throw new Exception("Update command affected " + rowsAffected + " rows out of the " + p_UpdatesTable.Rows.Count + " expected.");
}
}
sqlConnection.Dispose();
}
I then built this method to populate the update table:
private void AddUpdate(ref DataTable p_UpdateTable, string p_ColumnName, long p_uid, object p_value)
{
if(!StronglyTypedDataset.Columns.Contains(p_ColumnName))
{
throw new ArgumentException("Table '" + p_ColumnName + "' does not exist in table", "p_ColumnName");
}
if(!p_UpdateTable.Columns.Contains(p_ColumnName))
{
DataColumn columnToAdd = p_UpdateTable.Columns.Add(p_ColumnName);
columnToAdd.DataType = StronglyTypedDataset.Columns.Cast<DataColumn>().Where(c => c.ColumnName.Equals(p_ColumnName)).First().DataType;
}
var existingRow = p_UpdateTable.Rows.Cast<DataRow>().Where(r => Convert.ToInt64(r["UID"]) == p_uid).FirstOrDefault();
if(existingRow != null)
{
existingRow[p_ColumnName] = p_value;
}
else
{
DataRow newRow = p_UpdateTable.NewRow();
newRow["UID"] = p_uid;
newRow[p_ColumnName] = p_value;
p_UpdateTable.Rows.Add(newRow);
}
}
There are a few times where I need to call this so this is more of a convenience method than anything else.
Now the problem: there is a possibility where I add a bunch of columns and values for one UID, but for another I might add more columns or not add values for existing columns. The problem with this is the update as it is will obliterate whatever is in the database already with a null value, which I don't want unless I explicitly say "make this null".
I was thinking of getting around this by supplying a value as a default, which I can then check for in my update statement and then using a CASE in the UPDATE statement that checks for this value, and using the original value (so in essence I could just ignore the "U." before the column name). The problem is that the table is generic so there could be anything in there, and in the case of actual data that somehow matches my default value, things would break.
I should note that this update table will be built up into a batch and a batch updated at once, not on a row by row basis.
Is there a value that is guaranteed not to be used, perhaps a GUID (I know there could still be a collision) or something like that?
An example:
Say my table looks like this after one row:
| UID | column 1 | column 2 |
row 1 | 1 | x | y |
On the second row it looks like this:
| UID | column 1 | column 2 | column 3 |
row 1 | 1 | x | y | ? |
row 2 | 2 | x | y | z |
The value for row 1 column 3 never gets set, so it gets defaulted to null. When I use my update statement, SQL will go and set that value to null even if there is something already in the table, but I don't want it to update the field for that row at all since I didn't specify a value for it.
I want to be able to put a value in place of the ? instead of it defaulting to null, so then I can change the update statement to something like UPDATE Table SET Column1 = U.Column1, Column2 = U.Column2, Column3 = CASE WHEN U.Column3 = somevalue THEN Column3 ELSE U.Column3 END FROM #UpdateTable U.
You could make yourself a little Maybe<T> that can either be an actual value for T, in which case you push through the update, or it could be a special not-a-value. It could look something like this:
public sealed class Maybe<T> {
private readonly T value;
private readonly bool hasValue;
private Maybe() {
hasValue = false;
}
public readonly Maybe<T> Nothing = new Maybe();
public Maybe(T value) {
this.value = value;
hasValue = true;
}
public T Value {
get {
return value;
}
}
public bool HasValue {
get {
return value;
}
}
}
Which you could use like this:
private void AddUpdate<T>(DataTable p_UpdateTable, string p_ColumnName, long p_uid, Maybe<T> p_value) {
// ...
if(existingRow != null) {
if(p_value.HasValue)
existingRow[p_ColumnName] = p_value.Value;
}
else {
DataRow newRow = p_UpdateTable.NewRow();
newRow["UID"] = p_uid;
if(p_value.HasValue)
newRow[p_ColumnName] = p_value.Value;
p_UpdateTable.Rows.Add(newRow);
}
// ...
}
You don't need the ref for the DataTable parameter, by the way.
I wound up taking a slightly different route, not using an identifier but using another column entirely.
My AddUpdate looks like this now:
private void AddUpdate(DataTable p_UpdateTable, string p_ColumnName, long p_uid, object p_value)
{
if (!StronglyTypedDataSet.Columns.Contains(p_ColumnName))
{
throw new ArgumentException("Table '" + p_ColumnName + "' does not exist in table", "p_ColumnName");
}
if (!p_UpdateTable.Columns.Contains(p_ColumnName))
{
var matchingColumn = StronglyTypedDataSet.Columns.Cast<DataColumn>().Where(c => c.ColumnName.Equals(p_ColumnName)).First();
DataColumn columnToAdd = p_UpdateTable.Columns.Add(p_ColumnName, matchingColumn.DataType);
columnToAdd.MaxLength = matchingColumn.MaxLength;
DataColumn setNullColumn = p_UpdateTable.Columns.Add(p_ColumnName + "_null", typeof(bool));
setNullColumn.DefaultValue = false;
}
var existingRow = p_UpdateTable.Rows.Cast<DataRow>().Where(r => Convert.ToInt64(r["UID"]) == p_uid).FirstOrDefault();
if (existingRow != null)
{
existingRow[p_ColumnName] = p_value;
if (p_value == null || p_value == DBNull.Value)
{
existingRow[p_ColumnName + "_null"] = true;
}
}
else
{
DataRow newRow = p_UpdateTable.NewRow();
newRow["UID"] = p_uid;
newRow[p_ColumnName] = p_value;
if (p_value == null || p_value == DBNull.Value)
{
newRow[p_ColumnName + "_null"] = true;
}
p_UpdateTable.Rows.Add(newRow);
}
}
This way, if a column gets defaulted to null because it gets added only after some rows get added to the table, I can do a check in my update statement that doesn't update the value.
The update statement looks like this now:
string[] validColumns = SQL_Columns.Split(',');
var trimmed = validColumns.Select(c => c.Trim());
foreach(DataColumn column in p_UpdatesTable.Columns)
{
if(!column.ColumnName.EndsWith("_null") && !trimmed.Contains(column.ColumnName))
{
throw new Exception("Column '" + column.ColumnName + "' is not valid for table");
}
}
string tableTypeName = "dbo.UpdateSpecific" + Guid.NewGuid().ToString().Replace("-", "").Replace("{", "").Replace("}", "");
StringBuilder tableTypeBuilder = new StringBuilder();
tableTypeBuilder.Append("CREATE TYPE ");
tableTypeBuilder.Append(tableTypeName);
tableTypeBuilder.Append(" AS TABLE (");
List<string> tableTypeColumns = new List<string>(p_UpdatesTable.Columns.Count);
StringBuilder commandBuilder = new StringBuilder();
commandBuilder.Append("UPDATE Table SET ");
List<string> columnsToUpdate = new List<string>(p_UpdatesTable.Columns.Count);
foreach (DataColumn column in p_UpdatesTable.Columns)
{
//build command to create table type
StringBuilder columnTypeBuilder = new StringBuilder();
columnTypeBuilder.Append("[");
columnTypeBuilder.Append(column.ColumnName);
columnTypeBuilder.Append("] ");
if(column.DataType == typeof(int))
{
columnTypeBuilder.Append("INT");
}
else if(column.DataType == typeof(long))
{
columnTypeBuilder.Append("BIGINT");
}
else if(column.DataType == typeof(bool))
{
columnTypeBuilder.Append("BIT");
}
else if(column.DataType == typeof(string))
{
columnTypeBuilder.Append("VARCHAR(");
columnTypeBuilder.Append(column.MaxLength);
columnTypeBuilder.Append(")");
}
else if(column.DataType == typeof(byte[]))
{
columnTypeBuilder.Append("IMAGE");
}
tableTypeColumns.Add(columnTypeBuilder.ToString());
//build actual update statement
if (!column.ColumnName.Equals("UID", StringComparison.InvariantCultureIgnoreCase) && !column.ColumnName.EndsWith("_null"))
{
StringBuilder columnBuilder = new StringBuilder();
columnBuilder.Append(column.ColumnName);
columnBuilder.Append(" = (CASE WHEN U.");
columnBuilder.Append(column.ColumnName);
columnBuilder.Append(" IS NULL THEN (CASE WHEN ISNULL(U.");
columnBuilder.Append(column.ColumnName);
columnBuilder.Append("_null, 0) = 1 THEN U.");
columnBuilder.Append(column.ColumnName);
columnBuilder.Append(" ELSE C.");
columnBuilder.Append(column.ColumnName);
columnBuilder.Append(" END) ELSE U.");
columnBuilder.Append(column.ColumnName);
columnBuilder.Append(" END)");
columnsToUpdate.Add(columnBuilder.ToString());
}
}
tableTypeBuilder.Append(string.Join(",", tableTypeColumns.ToArray()));
tableTypeBuilder.Append(")");
commandBuilder.Append(string.Join(",", columnsToUpdate.ToArray()));
commandBuilder.Append(" FROM Table AS C JOIN #UpdateTable AS U ON C.UID = U.UID");
//Establish SQL Connection
using (SqlConnection sqlConnection = new SqlConnection(context.strContext[(int)eCCE_Context._CONNECTION_STRING]))
{
sqlConnection.Open();
try
{
using (SqlCommand createTableTypeCommand = new SqlCommand(tableTypeBuilder.ToString(), sqlConnection))
{
createTableTypeCommand.ExecuteNonQuery();
}
using (SqlCommand sqlCommand = new SqlCommand(commandBuilder.ToString(), sqlConnection))
{
SqlParameter updateTableParameter = sqlCommand.Parameters.Add("#UpdateTable", SqlDbType.Structured);
updateTableParameter.Value = p_UpdatesTable;
updateTableParameter.TypeName = tableTypeName;
int rowsAffected = sqlCommand.ExecuteNonQuery();
if (rowsAffected != p_UpdatesTable.Rows.Count)
{
throw new Exception("Update command affected " + rowsAffected + " rows out of the " + p_UpdatesTable.Rows.Count + " expected.");
}
}
}
finally
{
string dropStatement = "IF EXISTS (SELECT * FROM sys.types st JOIN sys.schemas ss ON st.schema_id = ss.schema_id WHERE st.name = N'"+ tableTypeName.Substring(tableTypeName.IndexOf(".")+1) +"' AND ss.name = N'dbo') DROP TYPE " + tableTypeName;
using (SqlCommand dropTableTypeCommand = new SqlCommand(dropStatement, sqlConnection))
{
dropTableTypeCommand.ExecuteNonQuery();
}
}
sqlConnection.Dispose();
}
Tested and working :)
Related
I have table
itemID
storeID
qty
103
LAB
20
I want to add qantity of specific item for example:'103' stored in warehouse 'LAB'.
public void addQuantity(string store, string item, int qty)
{
con.Open();
string sql = "SELECT qty,warehouse.storeID,item.itemID FROM Item,warehouse,stocker WHERE stocker.storeID=warehouse.storeID AND stocker.itemID=item.itemID AND warehouse.storeID='"+store+"' AND Item.itemID='"+item+"' ";
using (MySqlDataAdapter adapter = new MySqlDataAdapter(sql, con))
{
using (DataTable tempTable = new DataTable())
{
adapter.Fill(tempTable);
if (tempTable.Rows.Count == 0) throw new Exception("No such product");
foreach (DataRow r in tempTable.Rows)
{
int newQty = (int)r["qty"] + qty;
if (newQty > 0)
{
r["qty"] = newQty;
qty = 0;
break;
}
else
{
MessageBox.Show("error");
}
}
using (MySqlCommandBuilder cb = new MySqlCommandBuilder(adapter))
{
adapter.UpdateCommand = cb.GetUpdateCommand();// there is error
adapter.Update(tempTable);
}
}
}
con.Close();
}
it says:"Dynamic SQL generation is not supported for multiple base tables".
what would you advice me?
If qty is integer column, you may try to:
Increment its current value by some value:
using (var updateCommand = new MySqlCommand())
{
updateCommand.CommandText = "UPDATE mytable t SET t.qty = t.qty + #newQty WHERE *...Your WHERE clause...*`"
updateCommand.Parameters.AddWithValue("#newQty", newQtyValue);`
// ...
}
Or append entire new value:
using (var updateCommand = new MySqlCommand())
{
updateCommand.CommandText = "UPDATE mytable t SET t.qty = #newQtyValue WHERE *...Your WHERE clause...*`"
updateCommand.Parameters.AddWithValue("#newQtyValue", newQtyValue);`
// ...
}
As #sticky bit said, it is preferred to use Command.Parameters.AddWithValue instead of string concat/interpolation.
I already have this code to make stringBuilder for every employee, I get all the employeesId from another table . But if I get more than 1000 employees, I get the error ORA-07195 , I know this error is related to a Maximum of expression in a list . Therefore how can I send every 500 employees to my query in Data Access Objects.
Public List<GraphModel> countRequestCreatedByTypeDefaulPage(int year, int month, String employeeID)
{
int count = 0;
int countEmployeess = 0;
string employeesid = "";
DataView dv = _employeeOverrideBO.getRelatedEmployees(year, month, employeeID);
StringBuilder listEmployees = new StringBuilder();
for (int i = 0; i < countEmployees; i += 500)
{
foreach (DataRowView rowView in dv)
{
DataRow row = rowView.Row;
String employee = row["EMPLOYEE_ID"].ToString();
if (count > 0)
listEmployees.Append(",");
listEmployees.Append("'").Append(employee).Append("'");
count++;
}
}
countEmployeess++;
employeesid = listEmployees.ToString();
return _requestDAO.countRequestCreatedByTypeDefaulPage(employeesid);
Also this is my query in Data Access Object
public List<GraphModel> countRequestCreatedByTypeDefaulPage(string employeesIds)
{
String sql = " select NVL(TO_CHAR(RR.REASON_NM_NEW), 'Total') as SERIES1, count(*) AS VAL" +
" from REQUEST R, REQUEST_PERSON RP, REQUEST_REASON RR " +
" WHERE R.STATUS IN ('CREATED', 'PENDING APPROVAL', 'APPROVED BY MANAGER', 'APPROVED', 'IN PROCESS') " +
" AND R.REQUEST_ID = RP.REQUEST_ID" +
" AND RP.REQUEST_ROLE = 'REQUESTOR' " +
" AND RR.REASON_ID = R.REASON_ID" +
" AND RP.EMPLOYEE_ID IN (" + employeesIds + ") " +
" group by rollup (RR.REASON_NM_NEW) " +
" ORDER BY count(*) DESC";
OracleCommand cmd = new OracleCommand(sql);
try
{
DataTable dataTable = Data_base_Access.executeSQL(cmd, ConfigurationManager.ConnectionStrings["stage"].ToString());
return (GraphModel.convertToList(dataTable));
}
catch (Exception ex)
{
Log.writeError("Request DAO", ex);
throw new DataAccessException("There was an error counting the open requests");
}
}
Also this query get the count to list called GraphModel
public static List<GraphModel> convertToList(System.Data.DataTable dataTable)
{
List<GraphModel> list = new List<GraphModel>();
foreach (DataRow dtRow in dataTable.Rows)
{
list.Add(convertToGraphModel(dtRow));
}
return list;
}
public static GraphModel convertToGraphModel(DataRow dtRow)
{
GraphModel graphModel = new GraphModel();
if (dtRow.Table.Columns.Contains("SERIES1") && dtRow["SERIES1"] != DBNull.Value)
{
graphModel.SERIES1 = Convert.ToString(dtRow["SERIES1"]);
}
if (dtRow.Table.Columns.Contains("SERIES2") && dtRow["SERIES2"] != DBNull.Value)
{
graphModel.SERIES2 = Convert.ToString(dtRow["SERIES2"]);
}
if (dtRow.Table.Columns.Contains("VAL") && dtRow["VAL"] != DBNull.Value)
{
graphModel.VAL = Convert.ToInt32(dtRow["VAL"]);
}
return graphModel;
}
}
I really appreciate your help because I am research a lot and I dont know what can I do
Split the list into 1000 item lists and change the query into this:
" AND (RP.EMPLOYEE_ID IN (" + ids_1_1000 + ") OR RP.EMPLOYEE_ID IN (" + ids_1001_2000 + "))" +
One of the features I love most about Oracle is the Oracle Call Interface (OCI) that lets you access some of the more powerful features or Oracle with programming languages. In particular, for this example, the ability to do bulk inserts should prove very helpful.
If, instead of the approach you have above, which is trying to insert thousands of literals into a single SQL statement, you put those values into a table and do a join, I think you will:
Spare the shared pool from having to compile a really nasty SQL statement
Eliminate the need to escape strings or worry about SQL Injection
Have a lightning fast query that substitutes a join (bread and butter for a database) for a giant in-list
Step 1: Create a GTT:
create global temporary table employee_list (
employee_id varchar2(100) not null
) on commit preserve rows;
The GTT is based on a session, so even if you have this code running in multiple instances, each GTT will act as a blank slate for each instance -- no possibility of collisions of data.
Step 2: Within your code, create a transaction to handle the fact that you need the insert to the table and the select on the data to occur as part of the same transaction:
OracleTransaction trans = conn.BeginTransaction(IsolationLevel.ReadCommitted);
Step 3: Use ODP.net's bulk insert capabilities to insert all of your employee Ids at once. I encourage you to benchmark this versus inserting them one at a time. You'll be amazed. If you have more than 50,000, then maybe you need to break the up into chunks, but with a single field, I think this should be more than adequate:
// string[] employeesIds
OracleCommand cmd = new OracleCommand("insert into employee_list values (:EMPLOYEE)",
conn);
cmd.Transaction = trans;
cmd.Parameters.Add(new OracleParameter("EMPLOYEE", OracleDbType.Varchar2));
cmd.Parameters[0].Value = employeesIds;
cmd.ArrayBindCount = employeesIds.Length;
cmd.ExecuteNonQuery();
Note employeeIds should be an array.
Step 4: Change your SQL from an in-list to a join:
select NVL(TO_CHAR(RR.REASON_NM_NEW), 'Total') as SERIES1, count(*) AS VAL
from
REQUEST R,
REQUEST_PERSON RP,
REQUEST_REASON RR,
employee_list e -- added this
WHERE R.STATUS IN ('CREATED', 'PENDING APPROVAL', 'APPROVED BY MANAGER',
'APPROVED', 'IN PROCESS')
AND R.REQUEST_ID = RP.REQUEST_ID
AND RP.REQUEST_ROLE = 'REQUESTOR'
AND RR.REASON_ID = R.REASON_ID
AND RP.EMPLOYEE_ID = e.employee_id -- changed this
group by rollup (RR.REASON_NM_NEW)
ORDER BY count(*) DESC
And here's what it would all look like together:
public List<GraphModel> countRequestCreatedByTypeDefaulPage(string[] employeesIds)
{
OracleTransaction trans = conn.BeginTransaction(IsolationLevel.ReadCommitted);
OracleCommand cmd = new OracleCommand("insert into employee_list values (:EMPLOYEE)",
conn);
cmd.Transaction = trans;
cmd.Parameters.Add(new OracleParameter("EMPLOYEE", OracleDbType.Varchar2));
cmd.Parameters[0].Value = employeesIds;
cmd.ArrayBindCount = employeesIds.Length;
cmd.ExecuteNonQuery();
String sql = ""; // code from above goes here
cmd = new OracleCommand(sql, conn);
cmd.Transaction = trans;
DataTable dataTable = null;
try
{
dataTable = Data_base_Access.executeSQL(cmd,
ConfigurationManager.ConnectionStrings["stage"].ToString());
return (GraphModel.convertToList(dataTable));
}
catch (Exception ex)
{
Log.writeError("Request DAO", ex);
throw new DataAccessException("There was an error counting the open requests");
}
finally
{
trans.Rollback();
}
return dataTable;
}
I resolved my problem with this code..
public List<GraphModel> countRequestCreatedByTypeDefaulPage(int year, int month, String employeeID)
{
int count = 0;
int countEmployees = 0;
Dictionary<string, int> dataChart = new Dictionary<string, int>();
DataView dv = _employeeOverrideBO.getRelatedEmployeesRequests(year, month, employeeID);
StringBuilder listEmployees = new StringBuilder();
foreach (DataRowView rowView in dv)
{
if (countEmployees == 500)
{
List<GraphModel> listReturn = _requestDAO.countRequestCreatedByTypeDefaulPage(listEmployees.ToString());
foreach(GraphModel model in listReturn){
if (dataChart.ContainsKey(model.SERIES1))
{
dataChart[model.SERIES1] = dataChart[model.SERIES1] + model.VAL;
}
else
{
dataChart[model.SERIES1] = model.VAL;
}
}
listEmployees = new StringBuilder();
count = 0;
countEmployees = 0;
}
DataRow row = rowView.Row;
String employee = row["EMPLOYEE_ID"].ToString();
if (count > 0)
listEmployees.Append(",");
listEmployees.Append("'").Append(employee).Append("'");
count++;
countEmployees++;
}
//Last Call
List<GraphModel> listReturnLast = _requestDAO.countRequestCreatedByTypeDefaulPage(listEmployees.ToString());
foreach (GraphModel model in listReturnLast) {
if (dataChart.ContainsKey(model.SERIES1))
{
dataChart[model.SERIES1] = dataChart[model.SERIES1] + model.VAL;
}
else
{
dataChart[model.SERIES1] = model.VAL;
}
}
List<GraphModel> list = new List<GraphModel>();
foreach (KeyValuePair<string, int> entry in dataChart)
{
GraphModel model = new GraphModel();
model.SERIES1 = entry.Key;
model.VAL = entry.Value;
list.Add(model);
}
return list;
}
I am trying to do this task.
I have this table in my database.
items_table
------------------
Item_Name | Item ID
A | 1
B | 1
C | 2
D | 2
E | Null
F |
G | 1
H |
I | Null
Select * from items_table where Item_ID is Null or Item_ID is empty
Loop(while there are items without Item_ID)
Check the count of first Item_ID
if first Item_ID count is less than 8
(update items_table values (current Item_ID ) where Item_Name is current row Item_Name )
otherwise check next Item_ID
If no Item_ID count is less than 8, insert max((Item_ID)+1) where Item_Name is current row Item_Name
For the above table this code should do something like this.
E,F,H,I have Group_ID null or empty
Now i have to insert Item_ID for all these items.
First check count of all existing Item_IDs in the table. If any item_ID is used with less than 8 items, than insert that Item_ID for current Item. If no Item_ID has count less than 8, than create a new Item_ID which should be maximum Item_ID + 1.
I am trying to write this but can't figure out how can i loop through rows and count IDs than insert existing one or new one.
private static void FIllGroupID(string connectionString)
{
string queryStringNoGroupID =
"Use Items select * from table_items_shelves where Item_ID is Null or Item_ID = '';";
SqlCommand GetAllWithoutID = new SqlCommand(queryStringNoGroupID);
DataTable DataTableAllWithoutID = new DataTable();
SqlDataAdapter adapterAllWithoutID = new SqlDataAdapter(GetAllWithoutID);
adapterAllWithoutID.Fill(DataTableAllWithoutID);
foreach (DataRow row in DataTableAllWithoutID.Rows)
{
}
}
How do i loop through existing item_ids and count them. If count is less than 8 than insert the same ID in current row or else create max(item_id)+1 and insert that.
Now who is going to remove negative votes from the question?
const string str = #"Data Source=localhost;Initial Catalog=Items;Integrated Security=True";
static void Main(string[] args)
{
const string connectionString = str;
DataTable DataTableAllWithoutID = new DataTable();
#if !test
string queryString = "select * from table_items_shelves;";
SqlDataAdapter adapterAllWithoutID = new SqlDataAdapter(queryString, connectionString);
adapterAllWithoutID.Fill(DataTableAllWithoutID);
adapterAllWithoutID.Dispose();
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
string insertString = "Update table_items_shelves Set Item_ID = #Item_ID where Item_Name = '#key';";
SqlCommand insertCommand = new SqlCommand(insertString, connection);
insertCommand.Parameters.Add("#Item_ID", SqlDbType.Int);
insertCommand.Parameters.Add("#key", SqlDbType.NVarChar);
#else
DataTableAllWithoutID.Columns.Add("Item_Name", typeof(string));
DataTableAllWithoutID.Columns.Add("Item_ID", typeof(object));
foreach (List<object> row in input)
{
DataRow newRow = DataTableAllWithoutID.Rows.Add();
newRow.ItemArray = row.ToArray();
}
#endif
//this code will get empty items
List<DataRow> nullOrEmpty = DataTableAllWithoutID.AsEnumerable()
.Where(x => x.Field<object>("Item_ID") == null)
.ToList();
//this creates a dictionary of valid items
Dictionary<int, List<DataRow>> dict = DataTableAllWithoutID.AsEnumerable()
.Where(x => x.Field<object>("Item_ID") != null)
.GroupBy(x => x.Field<object>("Item_ID"), x => x)
.ToDictionary(x => Convert.ToInt32(x.Key), x => (List<DataRow>)x.ToList());
//create IEnumerator for the null items
IEnumerator<DataRow> emptyRows = nullOrEmpty.GetEnumerator();
Boolean noMoreEmptyRows = false;
if (emptyRows != null)
{
foreach (int key in dict.Keys)
{
Console.WriteLine(key.ToString());
//get count of items
int count = dict[key].Count;
int itemID = (int)key;
for (int index = count; count < 8; count++)
{
if (emptyRows.MoveNext())
{
//get an item from the null list
emptyRows.Current["Item_ID"] = itemID;
insertCommand.Parameters["#Item_ID"].Value = itemID;
insertCommand.Parameters["#key"].Value = emptyRows.Current["Item_Name"];
insertCommand.ExecuteNonQuery();
Console.WriteLine("current item ID " + itemID);
Console.WriteLine("current count " + count);
//Console.ReadKey();
}//end if
else
{
noMoreEmptyRows = true;
break;
}//end else
}//end for
if (noMoreEmptyRows)
break;
}//end foreach
if (!noMoreEmptyRows)
{
//increment key to one greater than max value
int maxKey = dict.Keys.Max() + 1;
int count = 0;
while (emptyRows.MoveNext())
{
//get an item from the null list
emptyRows.Current["Item_ID"] = maxKey.ToString();
insertCommand.Parameters["#Item_ID"].Value = maxKey.ToString();
insertCommand.Parameters["#key"].Value = emptyRows.Current["Item_ID"];
insertCommand.ExecuteNonQuery();
count++;
if (count == 8)
{
maxKey++;
count = 0;
}
}
}
}//end if
#if test
foreach (DataRow row in DataTableAllWithoutID.Rows)
{
Console.WriteLine("Item_Name : {0} Item ID : {1}",
row["Item_Name"], row["Item_ID"]);
}
#endif
FIllGroupID(str);
Console.ReadKey();
}
I've created a program to mine and import data from about 75 spreadsheets into an oracle table. I'm able to connect, iterate through sheets, and grab cells and rows seemingly fine. The problem is if the excel sheet was saved with grouped rows collapsed, it skips the rows.
I can't find anywhere if there is an extended property or reg setting to allow me to possibly expand the groups on entry? Not sure how to getting around a collapsed group (not merged cell , those I can process without a problem).
code bits :
//Starting where I iterate through a particular sheet
var connectionString = string.Format("Provider=Microsoft.ACE.OLEDB.12.0; data source={0}; Extended Properties=\"Excel 12.0;HDR=NO;IMEX=1;ReadOnly=0\"", fileName);
OleDbConnection objConn = new OleDbConnection(connectionString);
try
{
objConn.Open();
System.Data.DataTable dt = objConn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, new object[] { null, null, null, "TABLE" });
if (dt != null)
{
foreach (DataRow row in dt.Rows)
{
var adapter = new OleDbDataAdapter("SELECT F1,F2,F3,F4,F5,F6,F7,F8,F9,F10,F11,F12 FROM [" + row["TABLE_NAME"].ToString() + "]", connectionString);
var ds = new DataSet();
try
{
adapter.Fill(ds, "anyname");
}
catch
{
break;
}
DataTable data = ds.Tables[0];
int rownum = 0;
// <a bunch of variable declarations>
foreach (DataRow row_b in data.Rows)
{
// start slogging through the rows
rownum = rownum++;
// <reset some variables>
if (rownum == 1) // Catch valid scripts that contain a number
{
foreach (DataColumn column in data.Columns)
{
if (column.ToString() == "F1")
{
// <processing code for this column>
}
if (column.ToString() == "F2")
{
// <processing code for this column>
}
if (column.ToString() == "F3")
{
// <you get the picture>
}
}
}
if (rownum == 3)
{
// <moving along through the rows...different processing>
}
// <..rows 4-11..>
if (rownum > 12 )
{
// <more value assignment>
}
string allvals = APPLICATION + E_USER + STEP_DESC + VARIATIONS + STATUS + STOPS_TESTING + ISSUE_NUM + ISSUE_COMMENTS + ADDITIONAL_INFO;
allvals = allvals.Trim();
//Don't want sheets that come across as Print Area this shouldn't affect the row processing
isPrintArea = 0;
if (BOOKSHEET.Contains("Print_Area"))
{
isPrintArea = 1;
}
Boolean addornot=false;
if (cb_forallscripts.Checked == true)
{
addornot = (STEP_NUM != 0 &&
allvals != "" &&
isPrintArea == 0 &&
SCRIPT_NUM != 0);
}
else
{
addornot = (STEP_NUM != 0 &&
allvals != "" &&
isPrintArea == 0 &&
SCRIPT_NUM != 0 &&
runScripts.Contains(SCRIPT_NUM.ToString()));
}
if (addornot)
{
//<connect to our Oracle db, I set up oCmd outside this>
OracleCommand oCmd = new OracleCommand();
oCmd.Connection = oConn;
oCmd.CommandType = CommandType.Text;
oCmd.Parameters.Add("STEP_NUM", STEP_NUM);
// <... bunch of parameters ...>
oCmd.Parameters.Add("script", SCRIPT);
oCmd.CommandText = "<My insert statement> ";
oCmd.ExecuteNonQuery();
}
}
}
}
}
catch ( <error processing>)
{ }
Rows.OutlineLevel should be the VBA property you are looking for. It can be read or set. See this page for Microsoft's rather terse description. Range.ClearOutline will, as it says, clear the outline for a specified range, as explained here.
I'm writing a code generator and am getting stuck on determining the nullable status of a stored procedure result set Column. I can query the DataType just fine but neither the datareader object nor a data table column contain the correct nullable value of my column.
public List<DataColumn> GetColumnInfoFromStoredProcResult(string schema, string storedProcName)
{
//build sql text
var sb = new StringBuilder();
sb.Append("SET FMTONLY OFF; SET FMTONLY ON; \n");//this is how EF4.1 did so I copied..not sure why the repeat
sb.Append(String.Format("exec {0}.{1} ", schema, storedProcName));
var prms = GetStoredProcedureParameters(schema: schema, sprocName: storedProcName);
var count = 1;
foreach (var param in prms)
{
sb.Append(String.Format("{0}=null", param.Name));
if (count < prms.Count)
{
sb.Append(", ");
}
count++;
}
sb.Append("\n SET FMTONLY OFF; SET FMTONLY OFF;");
var dataTable = new DataTable();
//var list = new List<DataColumn>();
using (var sqlConnection = this.SqlConnection)
{
using (var sqlAdapter = new SqlDataAdapter(sb.ToString(), sqlConnection))
{
if (sqlConnection.State != ConnectionState.Open) sqlConnection.Open();
sqlAdapter.SelectCommand.ExecuteReader(CommandBehavior.KeyInfo);
sqlConnection.Close();
sqlAdapter.Fill(dataTable);
}
//using (var sqlCommand = new SqlCommand())
//{
// sqlCommand.CommandText = sb.ToString();
// sqlCommand.CommandType = CommandType.Text;
// sqlCommand.Connection = sqlConnection;
// if (sqlConnection.State != ConnectionState.Open) sqlConnection.Open();
// var dr = sqlCommand.ExecuteReader(CommandBehavior.SchemaOnly);
// var whateva = dr.GetSchemaTable();
// foreach (DataColumn col in whateva.Columns)
// {
// list.Add(col);
// }
//}
}
var list = dataTable.Columns.Cast<DataColumn>().ToList();
return list;
}
I'm trying to end up with something similar to the the Entities Framework creation of a complex type from a stored procedure. Can I hijack that functionality?
On this example the Id column.. tblJobId (not my naming convention) would never be null.. But I selected null as ImNull and it has all the same properties so how does EF determine if the corresponding C# data type should be nullable or not?
Has anybody done this..
Ideas are appreciated.
The secret was to use Schema Only and fill a dataset not datatable. Now the AllowDbNull property on the datacolumn properly displays the nullable status of the return value.
This was it...
public List<DataColumn> GetColumnInfoFromStoredProcResult(string schema, string storedProcName)
{
//build sql text
var sb = new StringBuilder();
sb.Append("SET FMTONLY OFF; SET FMTONLY ON; \n");//this is how EF4.1 did so I copied..not sure why the repeat
sb.Append(String.Format("exec {0}.{1} ", schema, storedProcName));
var prms = GetStoredProcedureParameters(schema: schema, sprocName: storedProcName);
var count = 1;
foreach (var param in prms)
{
sb.Append(String.Format("{0}=null", param.Name));
if (count < prms.Count)
{
sb.Append(", ");
}
count++;
}
sb.Append("\n SET FMTONLY OFF; SET FMTONLY OFF;");
var ds = new DataSet();
using (var sqlConnection = this.SqlConnection)
{
using (var sqlAdapter = new SqlDataAdapter(sb.ToString(), sqlConnection))
{
if (sqlConnection.State != ConnectionState.Open) sqlConnection.Open();
sqlAdapter.SelectCommand.ExecuteReader(CommandBehavior.SchemaOnly);
sqlConnection.Close();
sqlAdapter.FillSchema(ds, SchemaType.Source, "MyTable");
}
}
var list = ds.Tables[0].Columns.Cast<DataColumn>().ToList();
return list;
}
public List<SqlParamInfo> GetStoredProcedureParameters(string schema, string sprocName)
{
var sqlText = String.Format(
#"SELECT
[Name] = N'#RETURN_VALUE',
[ID] = 0,
[Direction] = 6,
[UserType] = NULL,
[SystemType] = N'int',
[Size] = 4,
[Precision] = 10,
[Scale] = 0
WHERE
OBJECTPROPERTY(OBJECT_ID(N'{0}.{1}'), 'IsProcedure') = 1
UNION
SELECT
[Name] = CASE WHEN p.name <> '' THEN p.name ELSE '#RETURN_VALUE' END,
[ID] = p.parameter_id,
[Direction] = CASE WHEN p.is_output = 0 THEN 1 WHEN p.parameter_id > 0 AND p.is_output = 1 THEN 3 ELSE 6 END,
[UserType] = CASE WHEN ut.is_assembly_type = 1 THEN SCHEMA_NAME(ut.schema_id) + '.' + ut.name ELSE NULL END,
[SystemType] = CASE WHEN ut.is_assembly_type = 0 AND ut.user_type_id = ut.system_type_id THEN ut.name WHEN ut.is_user_defined = 1 OR ut.is_assembly_type = 0 THEN st.name WHEN ut.is_table_type =1 Then 'STRUCTURED' ELSE 'UDT' END,
[Size] = CONVERT(int, CASE WHEN st.name IN (N'text', N'ntext', N'image') AND p.max_length = 16 THEN -1 WHEN st.name IN (N'nchar', N'nvarchar', N'sysname') AND p.max_length >= 0 THEN p.max_length/2 ELSE p.max_length END),
[Precision] = p.precision,
[Scale] = p.scale
FROM
sys.all_parameters p
INNER JOIN sys.types ut ON p.user_type_id = ut.user_type_id
LEFT OUTER JOIN sys.types st ON ut.system_type_id = st.user_type_id AND ut.system_type_id = st.system_type_id
WHERE
object_id = OBJECT_ID(N'{0}.{1}')
ORDER BY 2", schema, sprocName);
using (var sqlConnection = this.SqlConnection)
{
using (var sqlCommand = new SqlCommand())
{
if (sqlConnection.State != ConnectionState.Open) sqlConnection.Open();
sqlCommand.Connection = sqlConnection;
sqlCommand.CommandType = CommandType.Text;
sqlCommand.CommandText = sqlText;
var dr = sqlCommand.ExecuteReader();
var result = new List<SqlParamInfo>();
while (dr.Read())
{
if (Convert.ToString(dr["Name"]) != "#RETURN_VALUE")
{
result.Add(new SqlParamInfo(dr));
}
}
return result;
}
}
}
Assume, that every column which comes from SP can be null - this is a valid assumption because stored procedure - its a kind of data abstraction layer and thus its code can change but still produce valid results.
If column was non-nullable yesterday it means nothing for today. So - all the columns which come from SP resultsets are nullable by design.
Update.
Assuming that table t1 has column Id INT IDENTITY PRIMARY KEY
Your stored proc looks like this:
CREATE PROC p1
AS
BEGIN
SELECT Id FROM t1
END
So it will never return an Id = NULL, but this is the SP - an abstraction of data, so - tomorrow i'll modify it like this:
CREATE PROC p1
AS
BEGIN
SELECT Id FROM t1
UNION
SELECT NULL
END
So, now it returns NULL - think about this. The difference in understanding of data abstraction