I am creating a stored procedure to create a new customer so for instance,
CREATE PROCEDURE Customer_Create
#customer_arg
#type_arg
AS
BEGIN
INSERT INTO Customer (Customer_id, Type_id)
VALUES (#Customer_arg,#type_arg)
End;
If I have several foreign keys in my statement and they are all ID's is there a way for me to pull the NEXT ID number automatically without having to know what it would be off the top of my head when I run the execute statement? I would like to just have it pull the fact that the ID will be 2 because the previous record was 1
EXECUTE Customer_Create 16,2
Is it something wnith output? If so how does this work code wise
I suspect that what you want to do is return the new id after the record is inserted. For that:
CREATE PROCEDURE Customer_Create (
#customer_arg,
#type_arg,
#NewCustomerId int output
) AS
BEGIN
INSERT INTO Customer(Customer_id, Type_id)
VALUES (#Customer_arg, #type_arg);
#NewCustomerId = scope_identity();
End;
There are several other choices for getting the identity, which are explained here.
To get to the last inserted IDENTITY value you should use the OUTPUT clause like this:
DECLARE #IdentValues TABLE(v INT);
INSERT INTO dbo.IdentityTest
OUTPUT INSERTED.id INTO #IdentValues(v)
DEFAULT VALUES;
SELECT v AS IdentityValues FROM #IdentValues;
There are several other mechanisms like ##IDENTITY but they all have significant problems. See my Identity Crisis article for details.
In your case you can also experiment with #IDENTITY like this
DECLARE #NextID int
--insert statement goes here
SET #NextID = ##Identity`
Here are couple good resources for getting familiar with this
http://blog.sqlauthority.com/2007/03/25/sql-server-identity-vs-scope_identity-vs-ident_current-retrieve-last-inserted-identity-of-record/
http://blog.sqlauthority.com/2013/03/26/sql-server-identity-fields-review-sql-queries-2012-joes-2-pros-volume-2-the-sql-query-techniques-tutorial-for-sql-server-2012/
Related
My database contains three tables called Object_Table, Data_Table and Link_Table. The link table just contains two columns, the identity of an object record and an identity of a data record.
I want to copy the data from DATA_TABLE where it is linked to one given object identity and insert corresponding records into Data_Table and Link_Table for a different given object identity.
I can do this by selecting into a table variable and the looping through doing two inserts for each iteration.
Is this the best way to do it?
Edit : I want to avoid a loop for two reason, the first is that I'm lazy and a loop/temp table requires more code, more code means more places to make a mistake and the second reason is a concern about performance.
I can copy all the data in one insert but how do get the link table to link to the new data records where each record has a new id?
In one statement: No.
In one transaction: Yes
BEGIN TRANSACTION
DECLARE #DataID int;
INSERT INTO DataTable (Column1 ...) VALUES (....);
SELECT #DataID = scope_identity();
INSERT INTO LinkTable VALUES (#ObjectID, #DataID);
COMMIT
The good news is that the above code is also guaranteed to be atomic, and can be sent to the server from a client application with one sql string in a single function call as if it were one statement. You could also apply a trigger to one table to get the effect of a single insert. However, it's ultimately still two statements and you probably don't want to run the trigger for every insert.
You still need two INSERT statements, but it sounds like you want to get the IDENTITY from the first insert and use it in the second, in which case, you might want to look into OUTPUT or OUTPUT INTO: http://msdn.microsoft.com/en-us/library/ms177564.aspx
The following sets up the situation I had, using table variables.
DECLARE #Object_Table TABLE
(
Id INT NOT NULL PRIMARY KEY
)
DECLARE #Link_Table TABLE
(
ObjectId INT NOT NULL,
DataId INT NOT NULL
)
DECLARE #Data_Table TABLE
(
Id INT NOT NULL Identity(1,1),
Data VARCHAR(50) NOT NULL
)
-- create two objects '1' and '2'
INSERT INTO #Object_Table (Id) VALUES (1)
INSERT INTO #Object_Table (Id) VALUES (2)
-- create some data
INSERT INTO #Data_Table (Data) VALUES ('Data One')
INSERT INTO #Data_Table (Data) VALUES ('Data Two')
-- link all data to first object
INSERT INTO #Link_Table (ObjectId, DataId)
SELECT Objects.Id, Data.Id
FROM #Object_Table AS Objects, #Data_Table AS Data
WHERE Objects.Id = 1
Thanks to another answer that pointed me towards the OUTPUT clause I can demonstrate a solution:
-- now I want to copy the data from from object 1 to object 2 without looping
INSERT INTO #Data_Table (Data)
OUTPUT 2, INSERTED.Id INTO #Link_Table (ObjectId, DataId)
SELECT Data.Data
FROM #Data_Table AS Data INNER JOIN #Link_Table AS Link ON Data.Id = Link.DataId
INNER JOIN #Object_Table AS Objects ON Link.ObjectId = Objects.Id
WHERE Objects.Id = 1
It turns out however that it is not that simple in real life because of the following error
the OUTPUT INTO clause cannot be on
either side of a (primary key, foreign
key) relationship
I can still OUTPUT INTO a temp table and then finish with normal insert. So I can avoid my loop but I cannot avoid the temp table.
I want to stress on using
SET XACT_ABORT ON;
for the MSSQL transaction with multiple sql statements.
See: https://msdn.microsoft.com/en-us/library/ms188792.aspx
They provide a very good example.
So, the final code should look like the following:
SET XACT_ABORT ON;
BEGIN TRANSACTION
DECLARE #DataID int;
INSERT INTO DataTable (Column1 ...) VALUES (....);
SELECT #DataID = scope_identity();
INSERT INTO LinkTable VALUES (#ObjectID, #DataID);
COMMIT
It sounds like the Link table captures the many:many relationship between the Object table and Data table.
My suggestion is to use a stored procedure to manage the transactions. When you want to insert to the Object or Data table perform your inserts, get the new IDs and insert them to the Link table.
This allows all of your logic to remain encapsulated in one easy to call sproc.
If you want the actions to be more or less atomic, I would make sure to wrap them in a transaction. That way you can be sure both happened or both didn't happen as needed.
You might create a View selecting the column names required by your insert statement, add an INSTEAD OF INSERT Trigger, and insert into this view.
Before being able to do a multitable insert in Oracle, you could use a trick involving an insert into a view that had an INSTEAD OF trigger defined on it to perform the inserts. Can this be done in SQL Server?
Insert can only operate on one table at a time. Multiple Inserts have to have multiple statements.
I don't know that you need to do the looping through a table variable - can't you just use a mass insert into one table, then the mass insert into the other?
By the way - I am guessing you mean copy the data from Object_Table; otherwise the question does not make sense.
//if you want to insert the same as first table
$qry = "INSERT INTO table (one, two, three) VALUES('$one','$two','$three')";
$result = #mysql_query($qry);
$qry2 = "INSERT INTO table2 (one,two, three) VVALUES('$one','$two','$three')";
$result = #mysql_query($qry2);
//or if you want to insert certain parts of table one
$qry = "INSERT INTO table (one, two, three) VALUES('$one','$two','$three')";
$result = #mysql_query($qry);
$qry2 = "INSERT INTO table2 (two) VALUES('$two')";
$result = #mysql_query($qry2);
//i know it looks too good to be right, but it works and you can keep adding query's just change the
"$qry"-number and number in #mysql_query($qry"")
I have 17 tables this has worked in.
-- ================================================
-- Template generated from Template Explorer using:
-- Create Procedure (New Menu).SQL
--
-- Use the Specify Values for Template Parameters
-- command (Ctrl-Shift-M) to fill in the parameter
-- values below.
--
-- This block of comments will not be included in
-- the definition of the procedure.
-- ================================================
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE InsetIntoTwoTable
(
#name nvarchar(50),
#Email nvarchar(50)
)
AS
BEGIN
SET NOCOUNT ON;
insert into dbo.info(name) values (#name)
insert into dbo.login(Email) values (#Email)
END
GO
I want to insert multiple rows of data into a MySQL database, but only when my order_id field is unique. This is the current query I have, which doesn't work. Lets say a record with an order_id of 2 is already in the table:
INSERT INTO conversion
(user_id,url_id,order_id,sale,commission,transaction_date,process_date)
VALUES (1,1,1,'32',0.3995,'2010-11-15 12:15:18','2010-11-15 12:15:18'),
(3,6,2,'*not-available*',0.001975,'2010-11-15 12:15:18','2010-11-15 12:15:18')
WHERE (order_id <> 3);
Any help is appreciated.
Tom
Solved by using REPLACE.
Example:
REPLACE INTO conversion (user_id,url_id,order_id,sale,commission,transaction_date,process_date) VALUES (1,1,3,'32',0.3995,'2010-11-15 12:50:31','2010-11-15 12:50:31'),(1,2,2,'*not-available*',0.001975,'2010-11-15 12:50:31','2010-11-15 12:50:31');
url: http://dev.mysql.com/doc/refman/5.0/en/replace.html
Thanks all.
INSERT doesn't support the WHERE clause because if you're inserting it implies that the record doesn't currently exist, so therefore there would be nothing for the WHERE clause to look at.
The way to do it in the example you've given is simply not to call the INSERT statement if the order_id field in your insert doesn't match the criteria you want.
If you're calling INSERT multiple times, you'd have some sort of code (either SQL or an external program) which loops through the rows you're inserting; this would be where you'd filter it.
If I am in a similar situation, I would create a stored procedure to handle the logic of figuring out whether an order_id already exists.
--Run this first
--It will create a stored procedure call InsertConversion
--Begin of stored procedure
CREATE PROCEDURE InsertConversion
#user_id int,
#url_id int,
#order_id int,
#sale varchar(5),
#commission money,
#transaction_date datetime,
#process_date datetime
AS
BEGIN
SET NOCOUNT ON;
if not exists(select order_id from conversion where order_id = #order_id)
begin
INSERT INTO conversion(user_id, url_id, order_id, sale, commission, transaction_date, process_date)
VALUES(#user_id, #url_id, #order_id, #sale, #commission, #transaction_date, #process_date)
end
END
GO
--End of stored procedure
Once the store procedure created, you can execute it and pass in the same values as you would pass into an INSERT/VALUES statement:
exec InsertConversion 1,1,1,'32',0.3995,'2010-11-15 12:15:18','2010-11-15 12:15:18'
exec InsertConversion 3,6,2,'*not-available*',0.001975,'2010-11-15 12:15:18','2010-11-15 12:15:18'
If you want to be fancy, you can include a couple of 'print' statement in the store procedure to tell you whether it inserts the record.
I want a stored procedure to return the primary key of the new record after it gets executed. I think it will be returned by OUT parameter in the procedure. But how to select the newly inserted row ID ? I don't want to use select MAX(row_id) as it is a multi user environment.
Any procedure sample will be appreciated.
My platform is ISeries DB2 V5 R4. Thanks.
Edit
The row id Column is not an identity column. It uses a sequence for the key which gets generated via a trigger before insert on table.
Edit
Here is what I am trying to do
Begin Stored procedure
Insert into Employees;
(row id gets automatically generated by trigger)
Return row id ;
I want to avoid a select in returning row id.
just set the out parameter to the column that contains the PK.
CREATE PROCEDURE DB2TBL.DO_STUFF (IN Param1 INT, IN Param2 CHAR(32),OUT Param3 INT)
/* Param1 is primary key */
LANGUAGE SQL
P1: BEGIN
DECLARE OUTPARAM INT;
/* Do the stored procedure */
SET OUTPARAM = Param1;
--UPDATED---
Hi Popo,
First off could you give more detail on what you mean when you say the rowid is assigned by a trigger?
If you had a real identity column you would use the IDENTITY_VAL_LOCAL() function like this right after the INSERT: SELECT IDENTITY_VAL_LOCAL() INTO myrowid FROM SYSIBM.SYSDUMMY1; I'm not 100% on that syntax because I generally use embedded SQL and it works differently there so you might have to play with it. IBM documentation is at http://publib.boulder.ibm.com/infocenter/iseries/v6r1m0/index.jsp?topic=/db2/rbafzscaidentity.htm.
However since you are doing something more complicated, I think this alternate method might work. You'll need to re-format your INSERT to be wrapped in a SELECT.
SELECT myrowid
INTO myrowid
FROM FINAL TABLE (
INSERT INTO myfile (myrowid, other_stuff) VALUES (default, 'blah')
)
You'll need to adjust for the proper field names and so on but I think this will do the trick. There's not much documentation but if you want to see it go to http://publib.boulder.ibm.com/infocenter/iseries/v6r1m0/index.jsp?topic=/db2/rbafzbackup.htm and scroll all the way down to the bottom of the page.
Cheers
CREATE PROCEDURE ASF_InsertNewAuthorRequest
(IN #REQUESTTYPE CHAR(1), IN #UserID VARCHAR(18), IN #DATECREATED TIMESTAMP, IN #REQUESTSTATUS CHAR(1))
LANGUAGE SQL
DYNAMIC RESULT SETS 1
P1: BEGIN
DECLARE cursor1 CURSOR WITH RETURN for
SELECT IDENTITY_VAL_LOCAL FROM SYSIBM.SYSDUMMY1;
INSERT INTO AFS_REQUEST
( REQUESTTYPE, "UserID", DATECREATED, REQUESTSTATUS )
VALUES
( #REQUESTTYPE, #UserID, #DATECREATED, #REQUESTSTATUS );
OPEN cursor1;
END P1
INSERT INTO [User] (columns)
OUTPUT inserted.userId
VALUES (#values)
This will return the newly created userId column value... very simple.
For several tables that have identity fields, we are implementing a Row Level Security scheme using Views and Instead Of triggers on those views. Here is a simplified example structure:
-- Table
CREATE TABLE tblItem (
ItemId int identity(1,1) primary key,
Name varchar(20)
)
go
-- View
CREATE VIEW vwItem
AS
SELECT *
FROM tblItem
-- RLS Filtering Condition
go
-- Instead Of Insert Trigger
CREATE TRIGGER IO_vwItem_Insert ON vwItem
INSTEAD OF INSERT
AS BEGIN
-- RLS Security Checks on inserted Table
-- Insert Records Into Table
INSERT INTO tblItem (Name)
SELECT Name
FROM inserted;
END
go
If I want to insert a record and get its identity, before implementing the RLS Instead Of trigger, I used:
DECLARE #ItemId int;
INSERT INTO tblItem (Name)
VALUES ('MyName');
SELECT #ItemId = SCOPE_IDENTITY();
With the trigger, SCOPE_IDENTITY() no longer works - it returns NULL. I've seen suggestions for using the OUTPUT clause to get the identity back, but I can't seem to get it to work the way I need it to. If I put the OUTPUT clause on the view insert, nothing is ever entered into it.
-- Nothing is added to #ItemIds
DECLARE #ItemIds TABLE (ItemId int);
INSERT INTO vwItem (Name)
OUTPUT INSERTED.ItemId INTO #ItemIds
VALUES ('MyName');
If I put the OUTPUT clause in the trigger on the INSERT statement, the trigger returns the table (I can view it from SQL Management Studio). I can't seem to capture it in the calling code; either by using an OUTPUT clause on that call or using a SELECT * FROM ().
-- Modified Instead Of Insert Trigger w/ Output
CREATE TRIGGER IO_vwItem_Insert ON vwItem
INSTEAD OF INSERT
AS BEGIN
-- RLS Security Checks on inserted Table
-- Insert Records Into Table
INSERT INTO tblItem (Name)
OUTPUT INSERTED.ItemId
SELECT Name
FROM inserted;
END
go
-- Calling Code
INSERT INTO vwItem (Name)
VALUES ('MyName');
The only thing I can think of is to use the IDENT_CURRENT() function. Since that doesn't operate in the current scope, there's an issue of concurrent users inserting at the same time and messing it up. If the entire operation is wrapped in a transaction, would that prevent the concurrency issue?
BEGIN TRANSACTION
DECLARE #ItemId int;
INSERT INTO tblItem (Name)
VALUES ('MyName');
SELECT #ItemId = IDENT_CURRENT('tblItem');
COMMIT TRANSACTION
Does anyone have any suggestions on how to do this better?
I know people out there who will read this and say "Triggers are EVIL, don't use them!" While I appreciate your convictions, please don't offer that "suggestion".
You could try SET CONTEXT_INFO from the trigger to be read by CONTEXT_INFO() in the client.
We use it the other way to pass info into the trigger but would work in reverse.
Have you in this case tried ##identity? You mentioned both scope_Identity() and identity_current() but not ##identity.
My database contains three tables called Object_Table, Data_Table and Link_Table. The link table just contains two columns, the identity of an object record and an identity of a data record.
I want to copy the data from DATA_TABLE where it is linked to one given object identity and insert corresponding records into Data_Table and Link_Table for a different given object identity.
I can do this by selecting into a table variable and the looping through doing two inserts for each iteration.
Is this the best way to do it?
Edit : I want to avoid a loop for two reason, the first is that I'm lazy and a loop/temp table requires more code, more code means more places to make a mistake and the second reason is a concern about performance.
I can copy all the data in one insert but how do get the link table to link to the new data records where each record has a new id?
In one statement: No.
In one transaction: Yes
BEGIN TRANSACTION
DECLARE #DataID int;
INSERT INTO DataTable (Column1 ...) VALUES (....);
SELECT #DataID = scope_identity();
INSERT INTO LinkTable VALUES (#ObjectID, #DataID);
COMMIT
The good news is that the above code is also guaranteed to be atomic, and can be sent to the server from a client application with one sql string in a single function call as if it were one statement. You could also apply a trigger to one table to get the effect of a single insert. However, it's ultimately still two statements and you probably don't want to run the trigger for every insert.
You still need two INSERT statements, but it sounds like you want to get the IDENTITY from the first insert and use it in the second, in which case, you might want to look into OUTPUT or OUTPUT INTO: http://msdn.microsoft.com/en-us/library/ms177564.aspx
The following sets up the situation I had, using table variables.
DECLARE #Object_Table TABLE
(
Id INT NOT NULL PRIMARY KEY
)
DECLARE #Link_Table TABLE
(
ObjectId INT NOT NULL,
DataId INT NOT NULL
)
DECLARE #Data_Table TABLE
(
Id INT NOT NULL Identity(1,1),
Data VARCHAR(50) NOT NULL
)
-- create two objects '1' and '2'
INSERT INTO #Object_Table (Id) VALUES (1)
INSERT INTO #Object_Table (Id) VALUES (2)
-- create some data
INSERT INTO #Data_Table (Data) VALUES ('Data One')
INSERT INTO #Data_Table (Data) VALUES ('Data Two')
-- link all data to first object
INSERT INTO #Link_Table (ObjectId, DataId)
SELECT Objects.Id, Data.Id
FROM #Object_Table AS Objects, #Data_Table AS Data
WHERE Objects.Id = 1
Thanks to another answer that pointed me towards the OUTPUT clause I can demonstrate a solution:
-- now I want to copy the data from from object 1 to object 2 without looping
INSERT INTO #Data_Table (Data)
OUTPUT 2, INSERTED.Id INTO #Link_Table (ObjectId, DataId)
SELECT Data.Data
FROM #Data_Table AS Data INNER JOIN #Link_Table AS Link ON Data.Id = Link.DataId
INNER JOIN #Object_Table AS Objects ON Link.ObjectId = Objects.Id
WHERE Objects.Id = 1
It turns out however that it is not that simple in real life because of the following error
the OUTPUT INTO clause cannot be on
either side of a (primary key, foreign
key) relationship
I can still OUTPUT INTO a temp table and then finish with normal insert. So I can avoid my loop but I cannot avoid the temp table.
I want to stress on using
SET XACT_ABORT ON;
for the MSSQL transaction with multiple sql statements.
See: https://msdn.microsoft.com/en-us/library/ms188792.aspx
They provide a very good example.
So, the final code should look like the following:
SET XACT_ABORT ON;
BEGIN TRANSACTION
DECLARE #DataID int;
INSERT INTO DataTable (Column1 ...) VALUES (....);
SELECT #DataID = scope_identity();
INSERT INTO LinkTable VALUES (#ObjectID, #DataID);
COMMIT
It sounds like the Link table captures the many:many relationship between the Object table and Data table.
My suggestion is to use a stored procedure to manage the transactions. When you want to insert to the Object or Data table perform your inserts, get the new IDs and insert them to the Link table.
This allows all of your logic to remain encapsulated in one easy to call sproc.
If you want the actions to be more or less atomic, I would make sure to wrap them in a transaction. That way you can be sure both happened or both didn't happen as needed.
You might create a View selecting the column names required by your insert statement, add an INSTEAD OF INSERT Trigger, and insert into this view.
Before being able to do a multitable insert in Oracle, you could use a trick involving an insert into a view that had an INSTEAD OF trigger defined on it to perform the inserts. Can this be done in SQL Server?
Insert can only operate on one table at a time. Multiple Inserts have to have multiple statements.
I don't know that you need to do the looping through a table variable - can't you just use a mass insert into one table, then the mass insert into the other?
By the way - I am guessing you mean copy the data from Object_Table; otherwise the question does not make sense.
//if you want to insert the same as first table
$qry = "INSERT INTO table (one, two, three) VALUES('$one','$two','$three')";
$result = #mysql_query($qry);
$qry2 = "INSERT INTO table2 (one,two, three) VVALUES('$one','$two','$three')";
$result = #mysql_query($qry2);
//or if you want to insert certain parts of table one
$qry = "INSERT INTO table (one, two, three) VALUES('$one','$two','$three')";
$result = #mysql_query($qry);
$qry2 = "INSERT INTO table2 (two) VALUES('$two')";
$result = #mysql_query($qry2);
//i know it looks too good to be right, but it works and you can keep adding query's just change the
"$qry"-number and number in #mysql_query($qry"")
I have 17 tables this has worked in.
-- ================================================
-- Template generated from Template Explorer using:
-- Create Procedure (New Menu).SQL
--
-- Use the Specify Values for Template Parameters
-- command (Ctrl-Shift-M) to fill in the parameter
-- values below.
--
-- This block of comments will not be included in
-- the definition of the procedure.
-- ================================================
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE InsetIntoTwoTable
(
#name nvarchar(50),
#Email nvarchar(50)
)
AS
BEGIN
SET NOCOUNT ON;
insert into dbo.info(name) values (#name)
insert into dbo.login(Email) values (#Email)
END
GO