SQL Insert/ Counter Increment - c#

I'm trying to make a program that allows me to store trends in an SQL Table. I need to add a hashtag entered into a textbox into the database if it doesn't already exist, and then increment a counter by 1.
The first column is "HashTag" and the second is "Counter" which has char(10) and int properties respectively.
I'm new to SQL, so it's posing a bit of a problem. This is what I have so far.
SqlConnection connection = new SqlConnection();
connection.ConnectionString = (#"Data Source=.\SQLEXPRESS;AttachDbFilename=C:\Users\Jordan Moffat\Desktop\coursework\WindowsFormsApplication\WindowsFormsApplication\HashTags.mdf;Integrated Security=True;Connect Timeout=30;User Instance=True");
connection.Open();
HashTagReader r = new HashTagReader();
if (r.HashTagSearch(s))
MessageBox.Show("I Found it!");
else
{
SqlCommand myCommand = new SqlCommand("INSERT INTO tblHashTag (HashTag, Counter) " + "Values (s, ++)", connection);
myCommand.ExecuteNonQuery();
}
connection.Close();
Any suggestions?

Change the Counter column to Identity(1,1) and it will auto increment. You can easily do this through SQL Management Studio.
Then change your query to:
SqlCommand myCommand = new SqlCommand("INSERT INTO tblHashTag (HashTag) Values ('" + s + '")", connection);
Note: I believe SqlCommand inherits from DbCommand which implements IDisposable. You should wrap these objects with a using() statement, like so to clean up any unmanaged resources:
using(SqlCommand myCommand = new SqlCommand("INSERT INTO tblHashTag (HashTag) Values ('" + s + '")", connection))
{
...
}

If you're on SQL 2008+, you have access to the merge statement. Something like so:
CREATE TABLE #tmp
(
[HashTag] VARCHAR(10) NOT NULL ,
[Counter] INT NOT NULL
);
MERGE [#tmp] AS t
USING
(
SELECT [HashTag] ,
[Counter]
FROM ( VALUES ( '#kitties', 3) ) AS f ( [HashTag], [Counter] )
) AS s
ON t.[HashTag] = s.[HashTag]
WHEN NOT MATCHED BY TARGET
THEN
INSERT ( [HashTag], [Counter] )
VALUES
( s.[HashTag] ,
s.[Counter]
)
WHEN MATCHED
THEN
UPDATE
SET
t.[Counter] += s.[Counter]
OUTPUT
$ACTION ,
INSERTED.* ,
DELETED.*;
I'm using an output clause here just so that it says what it's doing. Execute the merge statement multiple times and see how the output changes. If you're feeling frisky, wrap this in a stored procedure that takes two parameters (hashtag and counter) and you've got yourself something nice. Enjoy!

To add to Chris' answer, to avoid duplicate inserts you should (if you 're on SQL Server 2005 or higher) add a unique index to the HashTag column to enforce the restriction.
Then in the code you should use a WHERE NOT EXISTS clause. See here: SQL Server insert if not exists best practice
So you'd wind up with:
"INSERT INTO tblHashTag (HashTag)
Values ('" + s + "')
WHERE NOT EXISTS (SELECT HashTag
FROM tblHashTag
WHERE HashTag = '" + s + '")"

You can use Parameters to avoid sql injection :
string hashtag = "Your HashTagValue";
string counter = "Your Counter Value";
SqlCommand myCommand = new SqlCommand("INSERT INTO tblHashTag (HashTag, Counter) Values (#HashTag,#Counter)", connection);
myCommand.Parameters.Add("#HashTag", SqlDbType.varchar,50).Value = hashtag; //Your hashTagvalue
myCommand.Parameters.Add("#Counter", SqlDbType.varchar,50).Value = counter; //Your Counter Value
myCommand.ExecuteNonQuery();

Related

How to get last inserted record ID using C# and DB2

Using C# with DB2 in my application. Want to get last inserted record id to pass in another SQL statement. Please find below my code:
DB2Command myCommand = new DB2Command();
DB2Connection con = new DB2Connection(ConnectionString);
myCommand.Connection = con;
try
{
myCommand.CommandType = CommandType.Text;
myCommand.CommandText = "SELECT ID FROM FINAL TABLE (INSERT INTO MyTable(col1,col2) "
+ " VALUES (val1, val2)";
con.Open();
lastInstertedId = myCommand.ExecuteNonQuery();
}
But it is throwing error:
The operation failed because the operation is not supported with the type of the specified table.
If I change CommandText to below given line then it works fine but retorns 1(effected row number).
myCommand.CommandText = "INSERT INTO MyTable(col1,col2) "
+ " VALUES (val1, val2)";
Please let me know, how to get last inserted record Id.
i think the sql should be:
myCommand.CommandText = "SELECT ID FROM NEW TABLE (INSERT INTO MyTable(col1,col2) "
+ " VALUES (val1, val2)";
What we did in DB2 was to simply expose the trigger transition table NEW TABLE in the from-clause.
So, when you put an INSERT statement into the from clause that insert statement is executed and in the process a transition table is produced.
That transition table, which includes all the modifications of any before triggers can then be queried.
https://www.ibm.com/developerworks/community/blogs/SQLTips4DB2LUW/entry/select_from_insert?lang=en
notice the FINAL has been replaced by NEW
Should be something like below.
DB2Command myCommand = new DB2Command();
DB2Connection con = new DB2Connection(ConnectionString);
myCommand.Connection = con;
try
{
myCommand.CommandType = CommandType.Text;
myCommand.CommandText = "SELECT ID FROM FINAL TABLE (INSERT INTO MyTable(col1,col2) "
+ " VALUES (val1, val2)";
Integer lastInstertedId = (Integer)myCommand.ExecuteScalar();
}
I assume your table is ORGANIZE BY COLUMN. Those tables do not currently support data-change-table-reference
$ db2 "create table m(i int) organize by column"
DB20000I The SQL command completed successfully.
$ db2 "select * from final table(insert into m values 1)"
SQL1667N The operation failed because the operation is not supported with the
type of the specified table. Specified table: "PAUL.M". Table type:
"ORGANIZE BY COLUMN". Operation: "FINAL|NEW|OLD TABLE". SQLSTATE=42858
$ db2 "create table m(i int) organize by row"
DB20000I The SQL command completed successfully.
$ db2 "select * from final table(insert into m values 1)"
I
-----------
1
1 record(s) selected.

Create a MySQL sproc from c#, delimiter issues

I'm trying to create stored procedures from a c# program. It typically reads the sproc definition from a text file, and then run it against the chosen database.
My SQL script file looks like this:
DROP PROCEDURE IF EXISTS MySproc;
DELIMITER //
CREATE PROCEDURE MySproc(
IN Id BIGINT,
IN Reference VARCHAR(255),
IN Bla VARCHAR(255)
)
BEGIN
INSERT INTO TableA(`Id`, `Reference`) VALUES(Id, Reference);
INSERT INTO TableB(`Id`, `Bla`) VALUES(Id, Bla);
END
//
DELIMITER ;
and this works fine in the workbench.
I then execute it with this type of c# code:
using (MySqlCommand sqlCommand = _mySqlConnection.CreateCommand())
{
sqlCommand.Connection = _mySqlConnection;
sqlCommand.CommandText = scriptfile;
sqlCommand.CommandType = CommandType.Text;
sqlCommand.ExecuteNonQuery();
}
And it errors with:
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 'DELIMITER //
CREATE PROCEDURE MySproc( IN Id BIGINT
' at line 1
If I remove the DELIMITER // stuff, then it still parses the semi colons between BEGIN and END as a delimiter for the outer statement, and it errors with:
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 'END' at line 31
Any idea how I can set either the Command or the something int he script so that it works? Not even sure if the error actually comes from MySQL itself, or from the MySqlCommand library (MySql.Data.6.9.9). And MySQL server is 5.6.25, InnoDB tables.
Can you try replacing the DELIMITER // with something like delimiter $$
Pointless question, sorry, it's embarrassing. This morning, I created a unit test following the top part of this page: https://dev.mysql.com/doc/connector-net/en/connector-net-programming-stored-using.html
and it works fine:
[TestMethod]
public void TestSprocCreationFromMySqlDoc()
{
// from https://dev.mysql.com/doc/connector-net/en/connector-net-programming-stored-using.html
MySqlConnection conn = new MySqlConnection();
conn.ConnectionString = "server=localhost;user=root;database=test;port=3306;password=;";
MySqlCommand cmd = new MySqlCommand();
try
{
Console.WriteLine("Connecting to MySQL...");
conn.Open();
cmd.Connection = conn;
cmd.CommandText = "DROP PROCEDURE IF EXISTS add_emp";
cmd.ExecuteNonQuery();
cmd.CommandText = "DROP TABLE IF EXISTS emp";
cmd.ExecuteNonQuery();
cmd.CommandText = "CREATE TABLE emp (empno INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, first_name VARCHAR(20), last_name VARCHAR(20), birthdate DATE)";
cmd.ExecuteNonQuery();
cmd.CommandText = "CREATE PROCEDURE add_emp(" +
"IN fname VARCHAR(20), IN lname VARCHAR(20), IN bday DATETIME, OUT empno INT)" +
"BEGIN INSERT INTO emp(first_name, last_name, birthdate) " +
"VALUES(fname, lname, DATE(bday)); SET empno = LAST_INSERT_ID(); END";
cmd.ExecuteNonQuery();
}
finally
{
conn.Close();
}
}
Then, I did another test with my own statements (trying to replicate file read from disk, with crlf) and it works too!
cmd.CommandText =
"DROP PROCEDURE IF EXISTS MySproc; " + Environment.NewLine +
"CREATE PROCEDURE MySproc(" + Environment.NewLine +
"IN Id BIGINT," + Environment.NewLine +
"IN Reference VARCHAR(255)," + Environment.NewLine +
"IN Bla VARCHAR(255))" + Environment.NewLine +
"BEGIN " + Environment.NewLine +
"INSERT INTO TableA(`Id`, `Reference`) VALUES(Id, Reference); " + Environment.NewLine +
"INSERT INTO TableB(`Id`, `Bla`) VALUES(Id, Bla); " + Environment.NewLine +
"END";
cmd.ExecuteNonQuery();
And then I ran my original application (that read scripts from files) and it runs ok as well! So I can't explain it. I'm wondering if I did something to the MySql server that affected all connections, or did something in one connection that stayed on, affecting all connections, until a reboot.

Delete from multiple SQL tables with C# command

I want to delete from multiple SQL tables with a C# command but I get always error:
Invalid syntax near ",".
Here is code so far:
string connectionString = #"Data Source=" + System.IO.File.ReadAllText("Server.ini") + ";" + "Initial Catalog=" + "lin2world" + ";" + "User ID=" + System.IO.File.ReadAllText("User.ini") + ";" + "Password=" + System.IO.File.ReadAllText("Password.ini");
string sql = "DELETE FROM user_data, user_item, user_ActiveSkill, user_blocklist, user_deleted, user_friend, user_henna, user_history, user_log, user_macro, user_macroinfo, user_newbie, user_nobless, user_punish, user_recipe, user_skill, user_sociality, user_subjob WHERE char_id='" + textBox1.Text + "' ";
SqlConnection connection = new SqlConnection(connectionString);
SqlDataAdapter dataadapter = new SqlDataAdapter(sql, connection);
DataSet ds = new DataSet();
connection.Open();
dataadapter.Fill(ds, "char_id");
connection.Close();
MessageBox.Show("Character Deleted!!");
You can only delete from one table in a delete statement.
Use multiple delete statements within a transaction to perform the deletes
In case your not familiar with transactions in C# here is an example
using (var Conn = new SqlConnection(_ConnectionString))
{
SqlTransaction trans = null;
try
{
Conn.Open();
trans = Conn.BeginTransaction();
using (SqlCommand Com = new SqlCommand(ComText, Conn, trans))
{
/* DB work */
}
trans.Commit();
}
catch (Exception Ex)
{
if (trans != null) trans.Rollback();
return -1;
}
}
If you want to do something similar to this, you can pass all tables as a list and still do what you want.
DECLARE db_cursor CURSOR FOR
SELECT name
FROM master.dbo.sysdatabases
WHERE name IN ("user_data","user_item","user_ActiveSkill","user_blocklist","user_deleted","user_friend","user_henna","user_history","user_log","user_macro","user_macroinfo","user_newbie","user_nobless","user_punish","user_recipe","user_skill","user_sociality","user_subjob") -- use these databases
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #name
WHILE ##FETCH_STATUS = 0
BEGIN
DELETE FROM #name WHERE char_id='whatever'
FETCH NEXT FROM db_cursor INTO #name
END
Edit: It seems like these are all related tables -- if you have a proper relationship with cascade: delete, you should only have to delete the main record and its children will all take care of themselves
You cannot delete from multiple tables in one statement within SQL Server. You will need multiple statements, one for each table:
DELETE FROM user_data WHERE PersonID = '2';
DELETE FROM user_item WHERE PersonID = '2';
etc etc
And fire each one off individually, (making sure not to violate any FKs) or create a stored procedure and fire that off (to minimise open and closing of db connections).
My experience of using triggers tells me not to use triggers, at times they will not fire when they should!

SQL server - inserting a string with a single quotation mark

I iterate over an external source and get a list of strings. I then insert them into the DB using:
SqlCommand command = new SqlCommand(commandString, connection);
command.ExecuteNonQuery();
Where commandString is an insert into command. i.e.
insert into MyTable values (1, "Frog")
Sometimes the string contains ' or " or \ and the insert fails.
Is there an elegant way to solve this (i.e. #"" or similar)?
Parameters.
insert into MyTable values (#id, #name)
And
int id = 1;
string name = "Fred";
SqlCommand command = new SqlCommand(commandString, connection);
command.Parameters.AddWithValue("id", id);
command.Parameters.AddWithValue("name", name);
command.ExecuteNonQuery();
Now name can have any number of quotes and it'll work fine. More importantly it is now safe from sql injection.
Tools like "dapper" (freely available on NuGet) make this easier:
int id = 1;
string name = "Fred";
connection.Execute("insert into MyTable values (#id, #name)",
new { id, name });
You should look into using parameterized queries. This will allow you insert the data no matter the content and also help you avoid possible future SQL injection.
http://csharp-station.com/Tutorial/AdoDotNet/Lesson06
http://www.c-sharpcorner.com/uploadfile/puranindia/parameterized-query-and-sql-injection-attacks/

Sql Server temporary table disappears

I'm creating a temporary table, #ua_temp, which is a subset of regular table. I don't get an error, but when I try to SELECT from #ua_temp in the second step, it's not found. If I remove the #, a table named ua_temp is created.
I've used the exact same technique from created the table with SELECT INTO elsewhere. It runs fine, so I don't think it has anything to do with database settings. Can anyone see the problem?
// Create temporary table
q = new StringBuilder(200);
q.Append("select policy_no, name, amt_due, due_date, hic, grp, eff_dt, lis_prem, lis_grp, lis_co_pay_lvl, ");
q.Append("lep_prem, lapsed, dn_code, [filename], created_dt, created_by ");
q.Append("into #ua_temp from elig_ua_response ");
q.Append("where [filename] = #fn1 or [filename] = #fn2 ");
sc = new SqlCommand(q.ToString(), db);
sc.Parameters.Add(new SqlParameter("#fn1", sFn));
sc.Parameters.Add(new SqlParameter("#fn2", sFn2));
int r = sc.ExecuteNonQuery();
MessageBox.Show(r.ToString() + " rows");
// Rosters
q = new StringBuilder(200);
q.Append("select policy_no,name,amt_due,due_date,hic,grp,eff_dt,");
q.Append("lis_prem,lis_grp,lis_co_pay_lvl,lep_prem,lapsed,dn_code,[filename] ");
q.Append("from #ua_temp where (lis_prem > 0.00 or lep_prem > 0.00) ");
q.Append("and [filename] = #fn order by name");
sc.CommandText = q.ToString();
sc.Parameters.Clear();
sc.Parameters.Add(new SqlParameter("#fn", sFn));
sda = new SqlDataAdapter(sc);
sda.Fill(ds, "LIS LEP Roster");
To answer some of the obvious questions: This program was running fine using the source table, elig_ua_response. The reason for introducing the temp table was that I want to delete some of the rows for this particular report. I put brackets around the column [filename] while testing to be sure it's not a key word issue. The second SELECT works fine if you replace #ua_temp with elig_ua_response. I've tried different names for the temp table. The MessageBox showing the number of rows was just for debugging purposes; it doesn't affect the problem.
Joe Zack's comment is what helped me understand what's happening here. A very clear and concise explanation. This should be an answer so that it's more visible to people arriving here from a google search.
SqlCommand calls sql with parameters via sp_executesql when there are parameters, which means your temp table gets created inside (and then clean up in) a stored procedure so it's not available to future calls - even when they share the same connection
I think the solution to your problem is to combine the creation of the temp table and selecting from that temp table into one query (see code snippet #3 below). Executing the command twice (as you do in the code in your question) seems to work ok if you are not using command parameters, but fails if they are introduced. I tested a few different approaches and here's what I found.
1) WORKS OK: Use same command object, no command parameters, execute command twice:
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
const string query = #"
CREATE TABLE #temp
([ID] INT NOT NULL, [Name] VARCHAR(20) NOT NULL)
INSERT INTO #temp VALUES(1, 'User 1')
INSERT INTO #temp VALUES(2, 'User 2')";
cmd.CommandType = CommandType.Text;
cmd.CommandText = query;
cmd.ExecuteNonQuery();
cmd.CommandText = "SELECT * FROM #temp";
using (var sda = new SqlDataAdapter(cmd))
{
var ds = new DataSet();
sda.Fill(ds);
foreach (DataRow row in ds.Tables[0].Rows)
Console.WriteLine("{0} - {1}", row["ID"], row["Name"]);
}
}
}
2) FAILS: Use same command object, command parameters, execute command twice:
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
const string query = #"
CREATE TABLE #temp
([ID] INT NOT NULL, [Name] VARCHAR(20) NOT NULL)
INSERT INTO #temp VALUES(1, #username1)
INSERT INTO #temp VALUES(2, #username2)
";
cmd.CommandType = CommandType.Text;
cmd.CommandText = query;
cmd.Parameters.Add("#username1", SqlDbType.VarChar).Value ="First User";
cmd.Parameters.Add("#username2", SqlDbType.VarChar).Value ="Second User";
cmd.ExecuteNonQuery();
cmd.Parameters.Clear();
cmd.CommandText = "SELECT * FROM #temp";
using(var sda = new SqlDataAdapter(cmd))
{
var ds = new DataSet();
sda.Fill(ds);
foreach(DataRow row in ds.Tables[0].Rows)
Console.WriteLine("{0} - {1}", row["ID"], row["Name"]);
}
}
}
3) WORKS OK: Use same command object, command parameters, execute command once only:
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
const string query = #"
CREATE TABLE #temp
([ID] INT NOT NULL, [Name] VARCHAR(20) NOT NULL)
INSERT INTO #temp VALUES(1, #username1)
INSERT INTO #temp VALUES(2, #username2)
SELECT * FROM #temp
";
cmd.CommandType = CommandType.Text;
cmd.CommandText = query;
cmd.Parameters.Add("#username1", SqlDbType.VarChar).Value ="First User";
cmd.Parameters.Add("#username2", SqlDbType.VarChar).Value ="Second User";
using (var sda = new SqlDataAdapter(cmd))
{
var ds = new DataSet();
sda.Fill(ds);
foreach (DataRow row in ds.Tables[0].Rows)
Console.WriteLine("{0} - {1}", row["ID"], row["Name"]);
}
}
}
This works. Apparently, if the SqlParameters are in the step that creates the table, the table is not left behind for the next step. Once the table is created, the SqlParameters can be used in a separate step for the INSERT.
// Create temporary file dropping members from termed groups.
q = new StringBuilder(500);
q.Append("create table #ua_param ");
q.Append("([ID] int not null, fn varchar(50) not null) ");
sc = new SqlCommand(q.ToString(), db);
sc.ExecuteNonQuery();
q = new StringBuilder(500);
q.Append("insert into #ua_param values(1,#fn1) ");
q.Append("insert into #ua_param values(2,#fn2) ");
sc = new SqlCommand(q.ToString(), db);
sc.Parameters.Add(new SqlParameter("#fn1", sFn));
sc.Parameters.Add(new SqlParameter("#fn2", sFn2));
sc.ExecuteNonQuery();
q = new StringBuilder(500);
q.Append("select policy_no, name, amt_due, due_date, hic, grp, eff_dt, lis_prem, lis_grp, lis_co_pay_lvl, ");
q.Append("lep_prem, lapsed, dn_code, [filename], created_dt, created_by ");
q.Append("into #ua_temp from elig_ua_response inner join #ua_param on [filename] = fn ");
sc.Parameters.Clear();
sc.CommandText = q.ToString();
sc.CommandTimeout = 1800;
sc.ExecuteNonQuery();
Its because the temp table is just that. Temporary. You might consider doing your operations in a stored procedure.
Beyond rolling it into a stored procedure as suggested by #Daniel A White, you can look at BOL article and search for global temporary tables. Also a brief write up on Temporary Tables. Either approach should keep the temporary table alive.
I had the same problem. I tried the SeaDrive solution and it works, however my tests make me believe that the query execution "flushes" something between "ADO.NET/SQLDriver" and the MS SQL Server.
So, you need to isolate the "CREATE TABLE" statement and submit it to the database before to use it with "INSERT INTO". Composed commands joining CREATE and INSERT in one unique statement doesn't work, unless you can give up the parameters.
#TEMP tables only are accessible within the same session or SPID. So if you want to reuse it you need to reuse the connection you used to generate it.
Working example with Dapper:
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
var expected = Guid.NewGuid();
// creating the temp table with NO PARAMETERS PASSED IN is the key part.
conn.Execute("CREATE TABLE #MyTemp (ID UNIQUEIDENTIFIER NOT NULL PRIMARY KEY);");
// now that the temp table is created, you can run queries with params as
// much as you want.
conn.Execute("INSERT INTO #MyTemp (ID) VALUES (#ID)", new { ID = expected });
var actual = conn.Query<Guid>("SELECT ID FROM #MyTemp;").Single();
Assert.Equal(expected, actual); // proof it worked
}
Using a stored proc makes sense for this sort of thing.
If for some reason that's not feasible, then make sure you are using the same connection for the temp table creation as you are for the temp table selection, else the temp table won't be visible. (it might be that you have this issue randomly if you're using connection pooling.) Alternately, use a real, physical table or even a global temp table (##global_tmp vs #local_tmp), but in either case you'll need to devise a scheme/protocol such that multiple processes aren't trying to create/delete/write to that table.
Again, I'll stress that a stored proc would be a good route, if possible.

Categories

Resources