Runing ExecuteNonQuery in C# for each different row - c#

So I have a database that has student information and I want to add a randomly generated grade for each one of them.
I created a method that generates a random grade letter between A-F.
However, when I update my database table all the courses that the students are taking get the same grade I want to give each different course a different grade .
command.CommandText = "UPDATE CurrentCourses SET CurrenteGrade ='" +
RandomLetter(grades) + "'";
command.ExecuteNonQuery();
I believe that if there is a way to run this query for each different row it would solve my problem. But I couldn't really get it to work. I used Microsoft Access to create my database.
Thank you

The update statement affects all the rows in the database table:
"UPDATE CurrentCourses SET CurrenteGrade ='" +
RandomLetter(grades) + "'"
Without a WHERE clause, this code could be run in a loop 100 times with a random grade each time, but every row in the table will say whatever grade was randomized last. If the last loop picked 'E' as the random grade, then all rows in the table will be graded E, depite the fact that they have, in the previous 5 seconds, changed grade 99 times already (all rows change each time the code is run. They only stop changing when the code stops being executed)
If you want to change all course rows to the same grade:
sqlCommand.CommandText = "UPDATE CurrentCourses SET CurrenteGrade = ? WHERE Course_ID = ?";
And then populate the parameters of the SqlCommand:
sqlCommand.Parameters.AddWithValue("grade", RandomLetter(grades));
sqlCommand.Parameters.AddWithValue("course", "SoftwareEngineering101");
This is the sort of thing you'd run many times (in a loop maybe) with a different course ID each time. The idea is that you just change the parameter values, then re-run the query:
sqlCommand.CommandText = "UPDATE CurrentCourses SET CurrenteGrade = ? WHERE Course_ID = ?";
sqlCommand.Parameters.AddWithValue("grade", "a"); //dummy values
sqlCommand.Parameters.AddWithValue("course", "a"); //dummy values
//the loop does the real work, repeatedly overwiting param values and running:
foreach(var course in myCoursesArray){
sqlCommand.Parameters["grade"] = RandomLetter(grades);
sqlCommand.Parameters["course"] = course;
sqlCommand.ExecuteNonQuery()
}
With access, using ? for parameter placeholders in the SQL, it is important that you then add your paramters in the same order as the ? marks appear in the sql. The names are irrelevant - this is not so in more powerful DB systems like sqlserver, where the SQL has named parameters and the names given in the c# code do matter. In our Access based code though, the only thing that matters about the name is to use it when overwriting the parameter value with a new one in the loop
Note; there are good reasons to avoid using .AddWithValue, but I won't get into those here. It's more important to avoid using string concatenation to build values into your SQLs. See bobbytables.com for more info

First your exact question: You need a WHERE statement that filters the update down to just a single row. This is usually done with an ID number or other unique identifier for the specific row(student in this case).
Second: Concatenating strings together with raw data can lead to errors and also security issues. For example, things like having a single quote in your string data will cause havoc. You should use up SqlParameters. https://www.dotnetperls.com/sqlparameter

You could first query for each row in the table(s) that contain the students and the course.
And then for each row in the data set execute your method to update that row with a random letter grade.
for example
foreach (DataRow dr in ds.tables[0].rows)
{
command.CommandText = "UPDATE CurrentCourses SET CurrenteGrade ='" +
RandomLetter(grades) + "'" + "WHERE PRIMARYKEY = dr.id"
command.ExecuteNonQuery();
}

Try something like;
UPDATE CurrentCourses SET CurrenteGrade = (select top 1 gradeName from grades ORDER BY NEWID())
In your code, you are not setting different courses for students. Because before executing the query, your query takes just one grade and update all the rows.
You can perform it using SQL easily.

You can do it with pure T-SQL:
declare #idColumn int
DECLARE #MIN INT=1; --We define minimum value, it can be generated.
DECLARE #MAX INT=100; --We define maximum value, it can be generated.
select #idColumn = min( Id ) from CurrentCourses
while #idColumn is not null
begin
Update CurrentCourses
SET CurrenteGrade = #MIN+FLOOR((#MAX-#MIN+1)*RAND(CONVERT(VARBINARY,NEWID())));
select #idColumn = min( Id ) from CurrentCourses where Id > #idColumn
end
the code above is looping over all the records (replace Id with your primary key) and generates random number between 1-100 (see comments where you can set new values) and updates the random number in each CurrentGrade record.
format and concatenate that command into your CommandText and execute ExecuteNonQuery()

Using this UPDATE command without any WHERE-clause will affect every record each time. Note that the random letter is generated before the query is executed. Therfore the query will run with a single grade letter.
You could run this query for each course in turn with an appropriate WHERE-clause that selects one course each time. But this is not efficient.
Or, much better, you could apply a random function in SQL itself, that is evaluated for each record (i.e. let MySQL choose a random grade).
UPDATE CurrentCourses
SET CurrenteGrade = SUBSTRING('ABCDEF', FLOOR(RAND() * 6) + 1, 1)
In your code
command.CommandText = #"UPDATE CurrentCourses
SET CurrenteGrade = SUBSTRING('ABCDEF', FLOOR(RAND() * 6) + 1, 1)";
command.ExecuteNonQuery();
This requires no loops and no command parameters.
Note that RAND() returns a random number between 0.0 and 1.0 (including 0.0 but excluding 1.0). Therefore FLOOR(RAND() * 6) generates a whole number in the range [0 .. 5]. 1 is added to get a number in the range [1 .. 6] used as index in the string 'ABCDEF' for the SUBSTRING function that cuts out one letter.

Please put the where condition in your update query.Without where condition it will update all data in the table.

Related

ADO.NET and SQLite single cell select performance

I want to create simple database in runtime, fill it with data from internal resource and then read each record through loop. Previously I used LiteDb for that but I couldn't squeeze time anymore so
I choosed SQLite.
I think there are few things to improve I am not aware of.
Database creation process:
First step is to create table
using var create = transaction.Connection.CreateCommand();
create.CommandText = "CREATE TABLE tableName (Id TEXT PRIMARY KEY, Value TEXT) WITHOUT ROWID";
create.ExecuteNonQuery();
Next insert command is defined
var insert = transaction.Connection.CreateCommand();
insert.CommandText = "INSERT OR IGNORE INTO tableName VALUES (#Id, #Record)";
var idParam = insert.CreateParameter();
var valueParam = insert.CreateParameter();
idParam.ParameterName = "#" + IdColumn;
valueParam.ParameterName = "#" + ValueColumn;
insert.Parameters.Add(idParam);
insert.Parameters.Add(valueParam);
Through loop each value is inserted
idParameter.Value = key;
valueParameter.Value = value.ValueAsText;
insert.Parameters["#Id"] = idParameter;
insert.Parameters["#Value"] = valueParameter;
insert.ExecuteNonQuery();
Transaction commit transaction.Commit();
Create index
using var index = transaction.Connection.CreateCommand();
index.CommandText = "CREATE UNIQUE INDEX idx_tableName ON tableName(Id);";
index.ExecuteNonQuery();
And after that i perform milion selects (to retrieve single value):
using var command = _connection.CreateCommand();
command.CommandText = "SELECT Value FROM tableName WHERE Id = #id;";
var param = command.CreateParameter();
param.ParameterName = "#id";
param.Value = id;
command.Parameters.Add(param);
return command.ExecuteReader(CommandBehavior.SingleResult).ToString();
For all select's one connection is shared and never closed. Insert is quite fast (less then minute) but select's are very troublesome here. Is there a way to improve them?
Table is quite big (around ~2 milions records) and Value contains quite heavy serialized objects.
System.Data.SQLite provider is used and connection string contains this additional options: Version=3;Journal Mode=Off;Synchronous=off;
If you go for performance, you need to consider this: each independent SELECT command is a roundtrip to the DB with some extra costs. It's similar to a N+1 select problem in case of parent-child relations.
The best thing you can do is to get a LIST of items (values):
SELECT Value FROM tableName WHERE Id IN (1, 2, 3, 4, ...);
Here's a link on how to code that: https://www.mikesdotnetting.com/article/116/parameterized-in-clauses-with-ado-net-and-linq
You could have the select command not recreated for every Id but created once and only executed for every Id. From your code it seems every select is CreateCommand/CreateParameters and so on. See this for example: https://learn.microsoft.com/en-us/dotnet/api/system.data.idbcommand.prepare?view=net-5.0 - you run .Prepare() once and then only execute (they don't need to be NonQuery)
you could then try to see if you can be faster with ExecuteScalar and not having reader created for one data result, like so: https://learn.microsoft.com/en-us/dotnet/api/system.data.idbcommand.executescalar?view=net-5.0
If scalar will not prove to be faster then you could try to use .SingleRow instead of .SingleResult in your ExecuteReader for possible performance optimisations. According to this: https://learn.microsoft.com/en-us/dotnet/api/system.data.commandbehavior?view=net-5.0 it might work. I doubt that but if first two don't help, why not try it too.

Looping through a SQL Server column using C# without a duplicate output

I have a simple SQL Server database that has two columns, States and Capitals. When I select button2, I would like to loop through the States column and randomly select a different state for a specified number of loops (the user will enter this number in another textbox) without selecting the same state twice.
The selected state must appear in textBox1. I've programmed textBox2 with a series of switch statement to capture the capitals. I'm having problems with seeing the same state more than once.
int count = 0;
private void button2_Click(object sender, EventArgs e)
{
if (count == Double.Parse(textBox3.Text) - 1)
this.Close();
count++;
String connectionString = #"Data Source=DESKTOP-N2F01G5\MRMARLEE;Initial Catalog=States_Capitals;Integrated Security=True";
con.ConnectionString = connectionString;
SqlCommand cmd = new SqlCommand();
cmd.Connection = con;
cmd.CommandText = "SELECT TOP 1 * FROM StatesandCapitals ORDER BY NEWID() ";
String result = "";
using (con)
{
con.Open();
cmd.ExecuteNonQuery();
result = cmd.ExecuteScalar().ToString();
}
textBox1.Text = result;
}
The typical solution to this problem is to grab as many as you want all in the same query. If they want 5 items, instead of selecting TOP 1 five times in a loop, just select TOP 5 once. Put the results into a List or array in memory you reference later.
Additionally, don't pull the states for TextBox2 separately. Get this all down to the one query that retrieves all the data you need. Generally speaking, the fewer round-trips to the DB, the better your application will perform.
Firstly, there is no need of ExcecuteNonQuery().
ORDER BY NEWID() - The sample size is small (No. of states would typically be < 100?) so it may happen that even though the values of NEWID() are different each time you run the query, the ORDER may get the same State. Also, NEWID() has performance issues.
I suggest a C# + T-SQL approach as follows:
Declare a global instance of Random Class - Random rnd = new Random();
Get the total number of states - SELECT COUNT(*) FROM StatesandCapitals into a C# int variable (intNumStates) using ExecuteScalar().
Now, get a random number between 1 and intNumStates and assign it to another C# int variable say intRndStateNum - int intRndStateNum = rnd.Next(1, intNumStates + 1)
Finally, run query using ROW_NUMBER() equating it with the random number:
WITH RandomState AS
(
SELECT States, RANDOM_NUMBER() OVER (ORDER BY States) AS RowNum
FROM StatesandCapitals
)
SELECT States, RowNum
FROM RandomState
WHERE RowNum = ?
Replace ? with intRndStateNum as a Parameter sent to the query (I am not writing this code assuming you know this).

MySql.Data.MySqlClient for C#, time expires for a large MySql table

I am using MySql.Data.MySqlClient for C# in one of my applications.
At certain point, my .net application selects, from one large MySql table, which :
has two columns,
one column is the primary key ( PRI, int(11), auto increment )
other column is the column that contains some text, ( varchar(8000) )
one example entry for this column is:
info1=a;info2=b;cIndex=1;info4=d;info5=0.33;
just by chance, the entries on this very large table that includes the text segment "'%cIndex=1;%'" are before from the entries that includes the text segment for larger cIndex values, such as 34.
For instance:
the entry with a column2 value of
"info1=a;info2=b;cIndex=1;info4=d;info5=0.33"; is the 45566th entry
on this database,
whereas the entry with column2 value of
"info1=a;info2=b;cIndex=34;info4=d;info5=0.33;" is the 10,000,000th
entry on the table.
So basically, the entries with lower cIndex values are before from
the entries with higher cIndex values.
Key difference for this table from my earlier applications is that, the table I am running sql statement is "very large" by my standards;
with 2 columns
yet 24.6 million rows.
and with a data structure I mentioned above.
An example of the sql statement I am running from within my application is:
SELECT column2 FROM myLargeTable WHERE (column2 LIKE
'%info3=1;%') AND (column2 LIKE '%cIndex=0;%');
And, the method I am using in my c# application in order to run this query for a result is basically as follows:
public static string getInfoFromDb(string tbl, string col, string whr)
{
string myInfo = "error";
string sqlStr = "SELECT " + col + " from " + tbl + " " + whr + " " + " LIMIT 1 ";
MySqlConnection cn = new MySqlConnection(cstr);
cn.Open();
try
{
MySqlCommand co = new MySqlCommand(sqlStr, cn);
MySqlDataReader rd = co.ExecuteReader();
while (rd.Read()) { myInfo = rd.GetString(col); }
co.Dispose();
}
catch { /* do nothing */}
cn.Close();
return myInfo;
}
(So, the code is pretty much simple and it worked, for long years, for my databases where the tables sizes was much smaller at around, say, 70000+ rows)
My question is:
my sql statement returns the correct result when it is run from the
editor page of Toad-for-MySql, for any cIndex value - either it is a
smaller value such as 1, or a larger value such as 34. In any case,
the sql statement returns the correct result.
And, expectedly, this sql statement returns the correct result when the same sql statement is run, from within my C# application this time, when the cIndex value is small such as 1, i.e. SELECT column2 FROM myLargeTable WHERE (column2 LIKE '%info3=1;%') AND (column2 LIKE '%cIndex=1;%') returns the correct result.
But, interestingly, and this time unexpectedly, the very same sql statement returns error saying timeout expired when the same sql statement is run, from within my C# application, when the cIndex value of interest in the sql statement is larger, such as 34. i.e. SELECT column2 FROM myLargeTable WHERE (column2 LIKE '%info3=1;%') AND (column2 LIKE '%cIndex=34;%')
Any help is very much appreciated.
Thanks in advance for your time and considerations.
I figured out that the error is due to timeout for larger tables.
So, apparently, adding this line of code into my method solved the problem:
co.CommandTimeout = 600; // keep this value relatively large while working with large databases

How to get the last number in a database column then increment it to include in another record?

I have been given the task of rewriting and old work application from classic .asp to ASP.NET that includes a database table that does not have an auto incremented primary key. We want to continue to use this table to maintain database integrity (it also has 80,000+ records!). The problem that I am running into is that I need to be able to pull the last item from the ID column of the database table regardless of how old the record is, increment that number and then include it in the new record to be inserted as the new record's ID number. How would I go about doing this? I have tried the ListItem, DataReader, DataTables, Generic Lists (as objects), and ArrayLists. I can pull the information and store it, but I cannot get the last item in the collection by itself. Any suggestions would be appreciated.
protected void GetPrimaryKey()
{
string strSQL = "";
try
{
OleDbConnection dbConn = new OleDbConnection();
dbConn.ConnectionString = System.Web.Configuration.WebConfigurationManager.ConnectionString["ConnectionString"].ToString();
strSQL = "SELECT observationID FROM Observation";
OleDbCommand myCmd = new OleDbCommand(strSQL, dbConn);
OleDbReader reader;
ListItem item;
if (dbConn.State == ConnectionState.Colsed) dbConn.Open();
reader = myCmd.ExecuteReader();
while (reader.Read())
{
item = new ListItem();
item.Text = reader["observationID"].ToString();
}
reader.Close();
dbConn.Close();
myCmd.Dispose();
}
}
Populating the list is where this code is at. The last item still needs to be found then incremented, and the returned to the submit button event handler that starts this whole process. I know this code is missing a lot, but I didn't want to send my entire commented mess. Again, any help is appreciated. Thank You.
SELECT TOP 1 ObservationId FROM Observarion ORDER BY ObservationId DESC
This will return the last row id
If more than one person try to get this value to insert, you will run into an issue where you end up with the same Ids, unless that column is unique and will throw an error.
To minimize issues, you can do an inline select in your insert statement.
INSERT INTO Observation (ObservationId) VALUES(SELECT TOP 1 (ObservationId + 1) As NewObservationId FROM Observation ORDER BY ObservationId DESC)
Not sure if my syntax is completely correct but it should lead you in the right direction.
Try get the max observation ID in sql statement:
SELECT MAX(observationID) FROM Observation
Then increment it.
SELECT MAX(observationID) FROM Observation
will always return the max value regardless of how old the record is
just ask for next value autoincrement id from table:
SELECT IDENT_CURRENT('table_name');
enjoy xD.
Why don't you do your query like this?:
SELECT Top 1 observationID FROM Observation order by desc
If you have some sort of parameters or configuration table, I suggest you store the last value there and retrieve/update it each time you do an insert. This will prevent any issues in case you have 2 or more clients trying to insert a new record at the same time.

Update multiple fields

First of all i'm working with the MySQL-Connector / Net and a MySQL Database.
And please don't tell me that i should use using or i don't have try and catch. I have but i just wanted to post a low amount of code.
I want to update multiple fields at one but it is not working. I'm getting an syntax error.
(from comments)I'm getting this error:
MySql.Data.MySqlClient.MySqlException (0x80004005): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '+ 1, allC = allC + 50' at line 1
sql_command.CommandText = "Update Test Set x1 + 1 And allC + ?Ammount Where = 1;";
sql_command.Parameters.Add("?Ammount", MySqlDbType.Int32).Value = dataTable.Tables[0].Rows.Count;
sql_command.ExecuteNonQuery();
But isn't this right?
I don't really need the where clause because it is just a number table i would say, so there is not more than one row.
But it is also not working if it try it so:
sql_command.CommandText = "Update Test Set x1 + 1 And all + ?Ammount;";
And there is another question i have.
If i want to get one entry from a database and it's just really one, which is the easiest way to do that?
That's it, but how can i save this record in a string with a low amount of code?
sql_command.CommandText = "Select ID From Customer Order By ID Desc Limit 1;";
Assign your expressions to a column and use a comma (,) instead of And:
Update Test
Set x1 = x1 + 1, all = all + ?Ammount
Where = 1;
Also, your WHERE clause is invalid, but I need to more info to fix it for you.
Your SQL syntax for the UPDATE statement is broken beyond repair:
Update Test Set x1 + 1 And all + ?Ammount Where = 1;
To which field WHERE = 1 should refer to?
Which field should receive value of the x1 + 1?
I don't believe this question can be answered properly unless it is significantly revised; please provide some kind of a description on what you want to do with the UPDATE statement.
Also, last SELECT statement can be replaced by the following:
SELECT MAX(ID) FROM Customer;
This would be a bit more readable.

Categories

Resources