SQL paramaterisation in C#, using SQL declared variables - c#

I have the following SQL query which I am sending from a C# program:
DECLARE #id as int
SELECT #id = max(fault_catagory_ident) FROM fault_catagory_list
INSERT INTO fault_catagory_list (fault_catagory_ident, fault_catagory)
VALUES (#id + 1, 'TEST')
SELECT #id + 1
The 'fault_catagory' value is coming from my program, but the ident value needs to be the next number in line (primary key) from the existing table in the database. My C# code is parameterising values for security.
I have two problems:
How can I get the #id + 1 value returned to my program (executeNonQuery doesn't return anything)?
How can I get #id as a parametarised value for the insert command?
I am wondering if my primary key could be automated in some way?
I want to carry all this out in one single query, as there will be a risk of multiple logins running this same query. If any happened to run simultainiously, the #id value may get duplicated and one would fail.
Apologies if there isn't enough info here, I'm on a learning curve!
Any advice would be greatly appreciated. Thanks in advance.

I think that you will find everything you need in this example provided in MSDN:
https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcommand.executescalar?view=dotnet-plat-ext-5.0
in short:
to return a single parameter from a query ull use ExecuteScalar()
parameters are added to a query through the SqlCommand class provided in System.Data.SqlClient.
cheers!

Thanks for the advice, think I have found the solution...
Firstly I need to recreate the table with the primary key column using the IDENTITY constraint (makes sense to use this now I know it exists!). Found a guide here (though it will mean some rebuilding of primary/foreign key links) https://www.datameer.com/blog/how-to-add-an-identity-to-an-existing-column-in-sql/
Then in C# program, use SqlCommand.executeScalar to return the identity value
Int identReturn = 0;
identReturn = (Int32)cmd.ExecuteScalar();
Thanks again for the responses.

While I don't agree with the logic in which you are choosing identity row values for the inserted rows, you could certainly acheive that using IDENTITY() column attribute in the SQL table definition.
In case you have multiple SELECTs in your SQL command,
everytime you have a SELECT in your sql command, a new table is added to the passed in dataset to the data adapter.
string sql = "
DECLARE #id as int
SELECT #id = max(fault_catagory_ident) FROM fault_catagory_list
INSERT INTO fault_catagory_list (fault_catagory_ident, fault_catagory)
VALUES (#id + 1, 'TEST')
SELECT #id + 1";
SqlCommand cmd = new SqlCommand(sql, conn); // assuming you already set connection
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
da.Fill(ds);
Console.WriteLine( ds.Tables[0].Rows[0][0]); // this will print #id
Console.WriteLine( ds.Tables[1].Rows[0][0]); // this will print #id + 1

Related

SQL -Create table if not exists, and insert value

I check my SQL database to see if a column exists if not create, but I wanted to insert a string in that column, but only if the column didnĀ“t exist.
Otherwise I handle that information in my C# code.
So far I have this code :
string query = "IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'tabela' AND COLUMN_NAME = 'coluna') ALTER TABLE tabela ADD coluna varchar(50)" ;
SqlCommand command = new SqlCommand(query, con);
command.ExecuteNonQuery();
How should I do ?
Change your query to execute a block after the IF (psuedocode):
IF NOT EXISTS(...)
BEGIN
ALTER TABLE MyTable ...;
INSERT INTO MyTable ...;
END
Be sure to put semicolons at the end of the ALTER and INSERT commands, since you are sending these in a single command from an application, so SQL Server will see them as being all on one line.
You can write a trigger for each SELECT from the database object. In that you can first check if the column exists, and then you can do the needful. This you can achieve entirely by using SQL triggres (or even stored procedures
), C# has nothing to do with it :)
For more details on triggers, you can check this out

How to insert data from 2 different sources into 2 different tables in SQL server using Web application?

Ok, basically i have created Web application in Visual Studio 2012 Ultimate and one page has this design:
I have also created 3 tables in database :
products:
[id]
[name]
auctions:
[id]
[productid]
[lastbider]
[bidvalue]
[lastbid]
users:
[id]
[name]
[password]
[bids]
The thing i need is when i click on button it needs to insert text from Textbox into [name] from products table and also insert number from Listbox into [bidvalue] from auctions table. How do i do this, do i need 2 SqlCommands?
Yes, basically you are trying to perform INSERT into two different tables and so you need to perform two different INSERT operations. Though, I suspect you probably want to perform an UPDATE instead but can't say for sure unless you clarify it.
Again, you can perform both the operation in single shot using a stored procedure like
create procedure usp_addData(#name varchar(10), #bidvalue INT)
as
begin
//Perform first insert/update in products table using the #name parameter
//perform second insert/update in auctions table using the #bidvalue parameter
end
Call this stored procedure from your application instead of making two different DB calls.
*** Omitted C# code samples to keep the answer simple. You can get many example of calling stored procedure from C# in SO.
Try this and just call
InsertData("Insert into Products (Name) Values ('" + txtName.Text + "'); Insert Into Auctions (bidvalue) Values ('" + lstBidValues.SelectedValue + "');");
public void InsertData(string query)
{
using (SqlConnection con = new SqlConnection("Your connection string here"))
{
if (con.State != ConnectionState.Open)
con.Open();
using (SqlCommand command = con.CreateCommand())
{
command.CommandText = query;
command.ExecuteNonQuery();
}
}
}
Since you will need the ProductId in your Auctions table, you will need to do an insert in Products and then get the productId you created in your sql to add it to your Auctions table together with the bidValue that you have. This can be done with one SqlCommand that calls your stored procedure which should look like below...
BEGIN TRANSACTION
INSERT INTO PRODUCTS VALUES('PRODUCT NAME')
DECLARE #newProductId int = SELECT SCOPE_IDENTITY()
INSERT INTO AUCTIONS(ProductId, lastBidder, bidValue, lastbid)
VALUES (#newProductId, 'yourlastbidder, 'bidValue', 'lastbid')
END TRANSACTION

Double records in result set

I have found a strange phenomena on MSSQL server.
Let say we have a table:
CREATE TABLE [testTable]
(
[ID] [numeric](11, 0) NOT NULL,
[Updated] [datetime] NULL,
PRIMARY KEY (ID)
);
I do a simple select based on Updated field:
SELECT TOP 10000 ID, Updated
FROM testTable
WHERE Updated>='2013-05-22 08:55:12.152'
ORDER BY Updated
And now comes the fun part: how can I have in result set double records - I mean same ID in 2 records with different Updated value.
For me it seems to be, that the Updated datetime value was changed and it was included one more time in result set. But is it possible?
UPDATE:
Source code I using for downloading data from SQL server:
using (SqlCommand cmd = new SqlCommand(sql, Connection) { CommandTimeout = commandTimeout })
{
using (System.Data.SqlClient.SqlDataAdapter adapter = new System.Data.SqlClient.SqlDataAdapter(cmd))
{
DataTable retVal = new DataTable();
adapter.Fill(retVal);
return retVal;
}
}
Connection = SqlConnection
sql = "SELECT TOP 10000 ...."
Your question seems to lack some details but here's my ideas.
The first case I'd think of would be that you are somehow selecting those IDs twice (could be a join, group by, ...). Please manually check inside your table (in MSSQL Server rather than inside a function or method) to see if there is dupplicated IDs. If there is, the issue is that your Primary Key hasn't been set correctly. Otherwise, you will need to provide all the relevant code that is used to select the data in order to get more help.
Another case might be that someone or something altered the primary key so it is on both ID and Updated, allowing the same ID to be inserted twice as long as the Updated field doesn't match too.
You may also try this query to see if it gets dupplicated IDs inside your context:
SELECT ID
from testTable
ORDER BY ID
I hope this helps.

c# last insert id using table adapter

I'm trying to get the last inserted id out of the database. I've tried many different code snippets but still having no luck. Here's the code I'm using for the inserts. I need the last insert id from the first to insert as nId in the second. I've tried SELECT ##IDENTITY but null value errors appeared which I couldn't locate. Can anybody show me the correct code please?
// Insert new user
daUsers.Insert(textBoxUsername.Text, textBoxPassword.Text);
// Insert new Twitter OAuth
daTwitterOAuth.Insert(nId, textBoxConsumerKey.Text, textBoxConsumerSecret.Text, textBoxToken.Text, textBoxTokenSecret.Text);
If you're trying to add a new record, you should be calling TableAdapter.Insert, not TableAdapter.Update
You can't create a second command to the database and use ##IDENTITY to get back the ID that was previously generated... this has to be returned within the same session, otherwise SQL would have no idea which ID you are expecting to receive.
If you set the execute mode of the table adapter to scalar, then the ID will be the return value of the method call. Please see this Question and Answer on the same issue that you're experiencing.
Someone has mentioned ##Identity which is fine, but what if somebody else performs an insert elsewhere before you reach that line in SQL?
I'd do this.
Create Procedure [Proc]
#Id as int output = 0
as
Insert into Table
Select * from AnotherTable
SET #Id = (SELECT Scope_Identity)
Then in .NET add your output parameter.
Been there already! In the code above I'm using an insert method, changed the execute mode to scalar and this is my sql:
INSERT INTO [dbTblUsers]([strUsername], [strPassword])
VALUES (#p1,#p2);
SELECT ##IDENTITY;
How do I cast ##identity into an int? Like this?
nId = (int)daUsers.InsertQuery(textBoxUsername.Text, textBoxPassword.Text);

Possible to insert with a Table Parameter, and also retrieve identity values?

I'm trying to insert records using a high performance table parameter method ( http://www.altdevblogaday.com/2012/05/16/sql-server-high-performance-inserts/ ), and I'm curious if it's possible to retrieve back the identity values for each record I insert.
At the moment, the answer appears to be no - I insert the data, then retrieve back the identity values, and they don't match. Specifically, they don't match about 75% of the time, and they don't match in unpredictable ways. Here's some code that replicates this issue:
// Create a datatable with 100k rows
DataTable dt = new DataTable();
dt.Columns.Add(new DataColumn("item_id", typeof(int)));
dt.Columns.Add(new DataColumn("comment", typeof(string)));
for (int i = 0; i < 100000; i++) {
dt.Rows.Add(new object[] { 0, i.ToString() });
}
// Insert these records and retrieve back the identity
using (SqlConnection conn = new SqlConnection("Data Source=localhost;Initial Catalog=testdb;Integrated Security=True")) {
conn.Open();
using (SqlCommand cmd = new SqlCommand("proc_bulk_insert_test", conn)) {
cmd.CommandType = CommandType.StoredProcedure;
// Adding a "structured" parameter allows you to insert tons of data with low overhead
SqlParameter param = new SqlParameter("#mytable", SqlDbType.Structured);
param.Value = dt;
cmd.Parameters.Add(param);
SqlDataReader dr = cmd.ExecuteReader();
// Set all the records' identity values
int i = 0;
while (dr.Read()) {
dt.Rows[i].ItemArray = new object[] { dr.GetInt32(0), dt.Rows[i].ItemArray[1] };
i++;
}
dr.Close();
}
// Do all the records' ID numbers match what I received back from the database?
using (SqlCommand cmd = new SqlCommand("SELECT * FROM bulk_insert_test WHERE item_id >= #base_identity ORDER BY item_id ASC", conn)) {
cmd.Parameters.AddWithValue("#base_identity", (int)dt.Rows[0].ItemArray[0]);
SqlDataReader dr = cmd.ExecuteReader();
DataTable dtresult = new DataTable();
dtresult.Load(dr);
}
}
The database is defined using this SQL server script:
CREATE TABLE bulk_insert_test (
item_id int IDENTITY (1, 1) NOT NULL PRIMARY KEY,
comment varchar(20)
)
GO
CREATE TYPE bulk_insert_table_type AS TABLE ( item_id int, comment varchar(20) )
GO
CREATE PROCEDURE proc_bulk_insert_test
#mytable bulk_insert_table_type READONLY
AS
DECLARE #TableOfIdentities TABLE (IdentValue INT)
INSERT INTO bulk_insert_test (comment)
OUTPUT Inserted.item_id INTO #TableOfIdentities(IdentValue)
SELECT comment FROM #mytable
SELECT * FROM #TableOfIdentities
Here's the problem: the values returned from proc_bulk_insert_test are not in the same order as the original records were inserted. Therefore, I can't programmatically assign each record the item_id value I received back from the OUTPUT statement.
It seems like the only valid solution is to SELECT back the entire list of records I just inserted, but frankly I'd prefer any solution that would reduce the amount of data piped across my SQL Server's network card. Does anyone have better solutions for large inserts while still retrieving identity values?
EDIT: Let me try clarifying the question a bit more. The problem is that I would like my C# program to learn what identity values SQL Server assigned to the data that I just inserted. The order isn't essential; but I would like to be able to take an arbitrary set of records within C#, insert them using the fast table parameter method, and then assign their auto-generated ID numbers in C# without having to requery the entire table back into memory.
Given that this is an artificial test set, I attempted to condense it into as small of a readable bit of code as possible. Let me describe what methods I have used to resolve this issue:
In my original code, in the application this example came from, I would insert about 15 million rows using 15 million individual insert statements, retrieving back the identity value after each insert. This worked but was slow.
I revised the code using high performance table parameters for insertion. I would then dispose of all of the objects in C#, and read back from the database the entire objects. However, the original records had dozens of columns with lots of varchar and decimal values, so this method was very network traffic intensive, although it was fast and it worked.
I now began research to figure out whether it was possible to use the table parameter insert, while asking SQL Server to just report back the identity values. I tried scope_identity() and OUTPUT but haven't been successful so far on either.
Basically, this problem would be solved if SQL Server would always insert the records in exactly the order I provided them. Is it possible to make SQL server insert records in exactly the order they are provided in a table value parameter insert?
EDIT2: This approach seems very similar to what Cade Roux cites below:
http://www.sqlteam.com/article/using-the-output-clause-to-capture-identity-values-on-multi-row-inserts
However, in the article, the author uses a magic unique value, "ProductNumber", to connect the inserted information from the "output" value to the original table value parameter. I'm trying to figure out how to do this if my table doesn't have a magic unique value.
Your TVP is an unordered set, just like a regular table. It only has order when you specify as such. Not only do you not have any way to indicate actual order here, you're also just doing a SELECT * at the end with no ORDER BY. What order do you expect here? You've told SQL Server, effectively, that you don't care. That said, I implemented your code and had no problems getting the rows back in the right order. I modified the procedure slightly so that you can actually tell which identity value belongs to which comment:
DECLARE #TableOfIdentities TABLE (IdentValue INT, comment varchar(20))
INSERT INTO bulk_insert_test (comment)
OUTPUT Inserted.item_id, Inserted.comment
INTO #TableOfIdentities(IdentValue, comment)
SELECT comment FROM #mytable
SELECT * FROM #TableOfIdentities
Then I called it using this code (we don't need all the C# for this):
DECLARE #t bulk_insert_table_type;
INSERT #t VALUES(5,'foo'),(2,'bar'),(3,'zzz');
SELECT * FROM #t;
EXEC dbo.proc_bulk_insert_test #t;
Results:
1 foo
2 bar
3 zzz
If you want to make sure the output is in the order of identity assignment (which isn't necessarily the same "order" that your unordered TVP has), you can add ORDER BY item_id to the last select in your procedure.
If you want to insert into the destination table so that your identity values are in an order that is important to you, then you have a couple of options:
add a column to your TVP and insert the order into that column, then use a cursor to iterate over the rows in that order, and insert one at a time. Still more efficient than calling the entire procedure for each row, IMHO.
add a column to your TVP that indicates order, and use an ORDER BY on the insert. This isn't guaranteed, but is relatively reliable, particularly if you eliminate parallelism issues using MAXDOP 1.
In any case, you seem to be placing a lot of relevance on ORDER. What does your order actually mean? If you want to place some meaning on order, you shouldn't be doing so using an IDENTITY column.
You specify no ORDER BY on this: SELECT * FROM #TableOfIdentities so there's no guarantee of order. If you want them in the same order they were sent, do an INNER JOIN in that to the data that was inserted with an ORDER BY which matches the order the rows were sent in.

Categories