I am currently working in C#, and I need to insert a new record into one table, get the new primary key value, and then use that as a foreign key reference in inserting several more records. The Database is MS SQL Server 2003. All help is appreciated!
The way to get the identity of the inserted row is with the SCOPE_IDENTITY() function. If you're using stored procedures then this would look something like the following to return the row identity as an output parameter.
CREATE PROCEDURE dbo.MyProcedure
(
#RowId INT = NULL OUTPUT
)
AS
INSERT INTO MyTable
(
Column1
,Column2
,...
)
VALUES
(
#Param1
,#Param2
,...
);
SET #RowId = SCOPE_IDENTITY();
You can then use this value for any subsequent inserts (alternatively, if you can pass the data all into the stored procedure, then you can use it in the remainder of the procedure body).
If you're passing the SQL in dynamically then you use much the same technique, but with a single string with statement delimiters (also ; in SQL), e.g.:
var sql = "INSERT INTO MyTable (Column1, Column2, ...) VALUES (#P1, #P2, ...);" +
"SELECT SCOPE_IDENTITY();";
Then if you execute this using ExecuteScalar you'll be able to get the identity back as the scalar result and cast it to the right type. Alternatively you could build up the whole batch in one go, e.g.
var sql = "DECLARE #RowId INT;" +
"INSERT INTO MyTable (Column1, Column2, ...) VALUES (#P1, #P2, ...);" +
"SET #RowId = SCOPE_IDENTITY();" +
"INSERT INTO MyOtherTable (Column1, ...) VALUES (#P3, #P4, ...);";
This may not be exactly the right syntax, and you may need to use SET NOCOUNT ON; at the start (my mind is rusty as I rarely use dynamic SQL) but it should get you on the right track.
The best way of doing this is the use SCOPE_IDENTITY() function in TSQL. This should be executed as part of the insert i.e.
SqlCommand cmd = new SqlCommand(#"
INSERT INTO T (Name) VALUES(#Name)
SELECT SCOPE_IDENTITY() As TheId", conn);
cmd.AddParameter("#Name", SqlDbType.VarChar, 50).Value = "Test";
int tId = (int)cmd.ExecuteScalar();
Alternatively you can assign SCOPE_IDENTITY() to a variable to be used in successive statements. e.g.
DECLARE #T1 int
INSERT INTO T (Name) VALUES('Test')
SELECT #T1 = SCOPE_IDENTITY()
INSERT INTO T2 (Name, TId) VALUES('Test', #T1)
If you are just using SQL then check Duncan's answer. If however you are using LINQ then you can create the entity, save it to the DB and the ID parameter will be populated automatically.
Given a user entity and a user table it might look like this:
using(var db = new DataContext()) {
var user = new User { Name = "Jhon" };
db.Users.InsertOnSubmit(user);
db.SubmitChanges();
/* At this point the user.ID field will have the primary key from the database */
}
Related
I have two tables and I need to update values in them via a stored procedure. Tried too much to update but some times it update the first table only, others the second or even fail due to cannot allow duplicates. Also when it updates the WHOLE data in the table becomes the same as the new updated ones. I've now reached to this error after all these lines of codes
Cannot insert the value NULL into column 'Emp_ID',table 'DatePics'; column does not allow nulls. UPDATE fails.The statement has been terminated
Here is the SQL code :
ALTER procedure [dbo].[UpdateEmp]
#EmpName nvarchar(100),
#Nationality nvarchar(30),
#Passport nvarchar(20),
#ContractDate date,
#HealthDate date
AS
BEGIN
set nocount on;
DECLARE #IDs table (ID int )
UPDATE Employee SET
EmpName=#EmpName, Nationality=#Nationality, Visa=#Visa, Passport=#Passport,
ReceivedDate=#ReceivedDate,IDIssue=#IDIssue, IDExpiry=#IDExpiry, Sponsor=#Sponsor
output inserted.ID into #IDs (ID)
WHERE ID = #ID
UPDATE DatePics SET
FingerDate=#FingerDate, ContractDate=#ContractDate, HealthDate=#HealthDate
where Emp_ID in (select ID from #IDs);
END
After writing the stored procedure code, I wrote the C# code like this:
private void updatebtn_Click(object sender, EventArgs e)
{
SqlCommand cmd = new SqlCommand();
cmd.Connection = db.con;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "UpdateEmp";
cmd.Parameters.AddWithValue("#EmpName", NameSeartxt.Text);
cmd.Parameters.AddWithValue("#Nationality", NatSeartxt.Text);
cmd.Parameters.AddWithValue("#Passport", PassSeartxt.Text);
cmd.Parameters.AddWithValue("#ContractDate", ContractSeartxt.Text);
cmd.Parameters.AddWithValue("#HealthDate", HealthSeartxt.Text);
db.con.Open();
int up = cmd.ExecuteNonQuery();
if (up > 0)
{
MessageBox.Show("Update done ", "DONE !");
SearNametxt.Text = "";
}
else
{
MessageBox.Show("Failed to update", "FAIL !");
SearNametxt.Text = "";
}
db.con.Close();
}
Any clue?
I can see three problems with your query. 1 You declare ID, but don't assign it before using it, so it will always be NULL for the first query, so this will never update any rows:
DECLARE #ID int
UPDATE FrstTable SET
EmpName=#EmpName, Nationality=#Nationality, Passport=#Passport
WHERE ID = #ID
Secondly, you are using SCOPE_IDENTITY to attempt to get the ID of the record that has been updated. You can't do that, SCOPE_IDENTITY will return the last inserted ID, it is not affected by updates. You will need to use OUTPUT to get the Updated ID:
DECLARE #IDs TABLE (ID INT);
UPDATE FirstTable
OUTPUT inserted.ID INTO #Ids (ID)
SET EmpName = #EmpName,
Nationality = #Nationality,
Passport = #Passport;
Thirdly, your second update statement has no where clause, so will update the entire table:
UPDATE ScndTable
SET Emp_ID=#ID, ContractDate=#ContractDate, HealthDate=#HealthDate
WHERE EmpID IN (SELECT ID FROM #Ids);
Your stored procedure looks weird to me. I believe there should be a WHERE cluase for the second UPDATE otherwise it will always update the whole ScndTable table. set #ID = SCOPE_IDENTITY(); seems to be reduntant here. Are you trying to perform insert into ScndTable if there's no corresponding Emp_ID there? Finnaly explicitly create a transaction to update either both tables or none.
Hope it helps!
Please assign the value of #ID variable, before executing the first update statement.
I think you are trying to update some row, so you can pass the 'id' value from the CSHARP code. When you use the SCOPE_IDENTITY, you will get the last inserted value. Try to pass the ID value from the front end.
I would like to know something .
I try to retrieve 2 files .
One is register for a group 2 , and one for a group 10 .
So the field is Files.Group .
One user is register to the group 1 and the group 10.
This is the query I use to retrieve files .
SELECT Files.Id, Files.Name, Files.Date, Files.Path, Files.[Group] FROM Files WHERE Files.[Group] = " + param + "ORDER BY Files.Id DESC"
Param is a cookie who get the group, creating a chain like this 2|10 .
This doesn't work actually.. And i don't know how can I pass in the query the two groups. Should I separate them by a coma ? like Files.Group = 2,10 ?
Or is it something else ? To pass 2 parameters ?
Baseline Structure
I don't have your entire structure so I have created the following simplified version of it:
CREATE TABLE [dbo].[Files]
(
[ID] INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
[Name] NVARCHAR(64) NOT NULL,
[Group] INT NOT NULL -- Probably have a non-unique index over this.
);
GO
INSERT INTO [dbo].[Files] ([Name], [Group]) VALUES (N'My File 1', 1);
INSERT INTO [dbo].[Files] ([Name], [Group]) VALUES (N'My File 2', 2);
INSERT INTO [dbo].[Files] ([Name], [Group]) VALUES (N'My File 3', 3);
INSERT INTO [dbo].[Files] ([Name], [Group]) VALUES (N'My File 4', 2);
INSERT INTO [dbo].[Files] ([Name], [Group]) VALUES (N'My File 5', 3);
INSERT INTO [dbo].[Files] ([Name], [Group]) VALUES (N'My File 6', 5);
Temp Table
You can insert the split values into a temp table and use a WHERE EXISTS against it - probably yielding decent performance.
-- This would be passed in from C#.
DECLARE #GroupsParam NVARCHAR(64) = N'2|3';
-- This is your SQL command, possibly a SPROC.
DECLARE #GroupsXML XML = N'<split><s>' + REPLACE(#GroupsParam, N'|', N'</s><s>') + '</s></split>';
-- Create an in-memory temp table to hold the temp data.
DECLARE #Groups TABLE
(
[ID] INT PRIMARY KEY
);
-- Insert the records into the temp table.
INSERT INTO #Groups ([ID])
SELECT x.value('.', 'INT')
FROM #GroupsXML.nodes('/split/s') as records(x);
-- Use a WHERE EXISTS; which should have extremely good performance.
SELECT [F].[Name], [F].[Group] FROM [dbo].[Files] AS [F]
WHERE EXISTS (SELECT 1 FROM #Groups AS [G] WHERE [G].[ID] = [F].[Group]);
Table-Values Parameters (SQL 2008+ Only)
SQL 2008 has a neat feature where you can send tables as parameters to the database. Clearly this will only work if you are using SqlCommands correctly (Executing Parameterized SQL Statements), unlike your example (appending user-created values to a SQL string is extremely bad practice - learn how to use parameters) - as you need to pass in a DataTable which you can't do with a simple string value.
In order to use this you first need to create the value type:
CREATE TYPE [dbo].[IntList] AS TABLE
([Value] INT);
GO
Next we will do things properly and used a stored procedure - as this is a static query and there are some performance implications of using a sproc (query plan caching).
CREATE PROCEDURE [dbo].[GetFiles]
#Groups [dbo].[IntList] READONLY
AS BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
SELECT [F].[Name], [F].[Group] FROM [dbo].[Files] AS [F]
WHERE EXISTS (SELECT 1 FROM #Groups AS [G] WHERE [G].[Value] = [F].[Group]);
END
GO
Next we need to hit this from C#, which is pretty straight-forward as we can create a table to do the call.
public static void GetFilesByGroups(string groupsQuery)
{
GetFilesByGroups(groupsQuery.Split('|').Select(x => int.Parse(x)));
}
public static void GetFilesByGroups(params int[] groups)
{
GetFilesByGroups((IEnumerable<int>)groups);
}
public static void GetFilesByGroups(IEnumerable<int> groups)
{
// Create the DataTable that will contain our groups values.
var table = new DataTable();
table.Columns.Add("Value", typeof(int));
foreach (var group in groups)
table.Rows.Add(group);
using (var connection = CreateConnection())
using (var command = connection.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "[dbo].[GetFiles]";
// Add the table like any other parameter.
command.Parameters.AddWithValue("#Groups", table);
using (var reader = command.ExecuteReader())
{
// ...
}
}
}
Remember: Table-Valued Parameters are only supported on SQL 2008 and later.
Edit: I would like to point out that there is likely a cross-over point in terms of performance between dknaack's answer and the temp table approach. His will likely be faster for a small set of search-groups; where the temp table approach would probably be faster for a large set of search-groups. There is a possibility that table-valued parameters would nearly always be faster. This is all just theory based on what I know about how the SQL query engine works: temp table might do a merge or hash join where the TVP would hopefully do a nested loop. I haven't done any profiling (and haven't received enough upvotes to motivate me to do so) so I can't say for certain.
Description
You should use SqlParameter to prevent Sql injections. Use the IN Statetment to pass in a comma seperated list of you group ids.
Sample
// value from cookie
string groups = "2,10,99";
// Build where clause and params
List<string> where = new List<string>();
List<SqlParameter> param = new List<SqlParameter>();
foreach(string group in groups.Split(','))
{
int groupId = Int32.Parse(group);
string paramName = string.Format("#Group{0}", groupId);
where.Add(paramName);
param.Add(new SqlParameter(paramName, groupId));
}
// create command
SqlConnection myConnection = new SqlConnection("My ConnectionString");
SqlCommand command = new SqlCommand("SELECT Files.Id, Files.Name, Files.Date, " +
"Files.Path, Files.[Group] " +
"FROM Files " +
"WHERE Files.[Group] in (" + string.Join(",", param) + ")" +
"ORDER BY Files.Id DESC", myConnection);
command.Parameters.AddRange(param.ToArray());
More Information
MSDN - IN (Transact-SQL)
C# SqlParameter Example
You're probably (depending on your database) looking at using this:
IN (2, 10)
rather than an = operator.
Note that constructing SQL using string concatenation like this can expose your code to SQL injection vulnerabilities, and using a properly parameterised SQL query is generally better practice. However, in your case, where you have an indeterminate number of parameters, it is harder to achieve in practice.
You need to set Param in cookie to create a chain like 2,10.
Then, instead of using = you need to use in () like this:
SELECT Files.Id, Files.Name, Files.Date, Files.Path, Files.[Group] FROM Files WHERE Files.[Group] in (" + param + ") ORDER BY Files.Id DESC"
Another thing that you got wrong was missing a space in param + "ORDER part.
Hi i'm having trouble with inserting incremented userid inside my db below is my table,stored proc,and my code.
CREATE TABLE [dbo].[Assignment2]
(
userID int PRIMARY KEY IDENTITY(1,1),
Name varchar(255) NOT NULL,
Age int NOT NULL,
Hobbies varchar(255)
)
and a stored procedure
ALTER PROCEDURE [db].[p_Assignment2_ins]
#userID int,
#Name nvarchar(100),
#Age int,
#Hobbies nvarchar(100)
AS
INSERT INTO [DB].[db].[Assignment2]
([Name]
,[Age]
,[Hobbies])
VALUES
(#Name
,#Age
,#Hobbies)
If ##Error <> 0
Return -1
Select #userID = ##Identity // this one just get the latest id that we inserted right?
Return 0
I have some question :
I want to know how do we insert the UserID from the code behind because If the table is empty at first shouldn't we insert a data first into the table
How do we generate an AutoIncrementID from codebehind and insert it
SqlConnection conn = new SqlConnection(ts.ConnMethod());
SqlCommand cmd = new SqlCommand("p_Assignment2_ins", conn);
cmd.CommandType = CommandType.StoredProcedure;
//I'm missing how we should add the IncrementedID
cmd.Parameters.AddWithValue("#Name", TextBox1.Text);
cmd.Parameters.AddWithValue("#Age", TextBox2.Text);
cmd.Parameters.AddWithValue("#Hobbies", TextBox3.Text);
conn.Open();
cmd.ExecuteNonQuery();
conn.Close();
Any help is really appreciated thanks
Select #userID = ##Identity // this one just get the latest id that we inserted right?
No, you should use SCOPE_IDENTITY(). ##Identity returns the last id, yes, but not the last id from your insert. If you have a trigger, for example, that also produces an insert on a another table as a result of your insert, you will get the id inserted on the other table by that trigger.
Here's a nice article explaining the differences.
As far as inserting autoincrement values on your table; you can't do this unless you disable the constraint first but in essence, this shouldn't be necessary unless you are doing some sort of data import. In your scenario, you should be able to just insert values on the other columns except the identity field with autoincrement. The database will take care of inserting the appropriate value for you.
If you have AUTOINCREMENT field, you SHOULD NOT insert and generate values at all. DB is doing all by itself. So, remove the code that inserts ID-s
I am reading data from csv file and adding data in database. At time of inserting data into database I want to update data with same primary key.
e.g.) I am using two Columns Bar-codes (PK) and Quantity. So, when I insert data from csv file similar barcode quantity will get added.
Can anyone help me? I am using C#.NET and SQL.
Thanks,
Rushabh Shah.
check out the merge keyword. it should do pretty much waht you're asking for.
here's a stored proc that should do it for you.
CREATE PROCEDURE dbo.InsertBarcodeData
#Barcode varchar(255),
#Quantity int
AS
BEGIN
SET NOCOUNT ON;
MERGE myTableName AS target
USING (SELECT #Barcode, #Quantity) AS source (BarCode, Quantity)
ON (target.Barcode= source.Barcode)
WHEN MATCHED THEN
UPDATE SET Quantity = source.Quantity + target.Quantity
WHEN NOT MATCHED THEN
INSERT (BarCode, Quantity)
VALUES (source.BarCode, source.Quantity)
END;
GO
create procedure InsertOrUpdateSales
(
#bar_code nvarchar(100),
#quantity int
)
as
if exists (select * from sales where bar_code = #bar_code)
update sales set quantity = quantity + #quantity where bar_code = #bar_code
else
insert into sales ( bar_code, quantity) values ( #bar_code, #quantity )
go
And
public static void InsertOrUpdateSales(string connection, string barCode, int quantity)
{
using(SqlConnection conn = new SqlConnection(connection))
{
using(SqlCommand comm = new SqlCommand("InsertOrUpdateSales", conn))
{
comm.CommandType = CommandType.StoredProcedure;
comm.Paramters.AddWithValue("#bar_code", barCode);
comm.Paramters.AddWithValue("#quantity", quantity);
comm.ExecuteNonQuery();
}
}
}
Alternatively, if you want to use the merge statement (as #Chris Lively and #nathan gonzalez mentioned) you could get really fancy and do it like this:
BULK INSERT the data from the CSV file to an empty temp table.
MERGE the temp table with the existing table.
TRUNCATE the temp table.
This might give you the best results. (For certain values of "best".)
If you can assume that there is already an existing entry for all of the bar codes in the table you could do this with a Stored procedure with two incominig parameters (#BarCodeID and #AdditionalQuantity)
UPDATE yourTable SET Quantity = Quantity + #AdditionalQuantity WHERE BarCode = #BarCodeID
You can add a Trigger to the table. When ever something is inserted in the table, you can have it run a stored procedure.
I have a table with autoincremented primary key. In my code I am trying to receive the new autoincremented value when I execute each 'insert' query. Is there a way to do it programatically?
Thanks.
UPD:
Assume I have a table:
TABLE User ( userID INT NOT NULL AUTO_INCREMENT, name VARCHAR( 25 ) NOT NULL , email VARCHAR( 50 ) NOT NULL , UNIQUE ( userID ) );
And I when I insert new values (name and email) to this table I want automatically receive newly generated userID. Ideally I am looking for any ways to do that with a single transaction and without stored procedures.
Have your sql/stored proc return scope_identity() or if you are using Linq2SQL or EF the entity used for insertion gets the new id.
In the stored proc it is:
ALTER proc [dbo].[SaveBuild](
#ID int = 0 output,
#Name varchar(150)=null,
#StageID int,
#Status char(1)=null
)
as
SET NOCOUNT ON
Insert into Builds
(name, StageID, status)
values (#Name, #StageID, #Status)
select #ID = scope_identity()
RETURN #ID
In the C# code you have:
public int SaveBuild(ref int id, ref string Name)
{
SqlCommand cmd = GetNewCmd("dbo.SaveBuild");
cmd.Parameters.Add("#ID", SqlDbType.Int).Value = id;
cmd.Parameters["#ID"].Direction = ParameterDirection.InputOutput;
cmd.Parameters.Add("#Name", SqlDbType.VarChar).Value = Name;
cmd.Parameters.Add("#StageID", SqlDbType.Int).Value = 0;
ExecuteNonQuery(cmd);
id = (int)cmd.Parameters["#ID"].Value;
return id;
}
Dependent upon your situation, you might be better off using table-valued parameters to pass your inserts to a stored procedure, then use OUTPUT INSERTED to return a table-valued parameter from your stored procedure.
It will drastically reduce the number of hits required if you're processing multiple items.
Are you limited to building SQL on the client and sending it to the server? Cause if you can use a stored procedure, this is easy to do. In the stored proc, do the insert and then, either
Select Scope_Identity() as the last statement in the stored proc., or
Use a output parameter to the stored proc, (say named #NewPKValue) and make the last statement:
Set #NewPKValue = Scope_Identity()
Otherwise, you need to send a batch of commands to the server that include two statements, the insert, and Select Scope_Identity() and execute the batch as though it was a select statement
You could use the SQL statement SELECT scope_identity().