184

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?

tpower
  • 56,100
  • 19
  • 68
  • 100
  • I do not get the interest of trying to do it with ONE insert, when doing it with 2 inserts works perfectly well. Do you mean you want to make sure the 2 inserts are both completed? Then you'll have to check this commit/rollback instruction. – Philippe Grondier Oct 06 '08 at 17:56
  • 4
    I would be happy with two inserts, its just that the identities that need to be inserted into the link table are the identities generated in the first insert. – tpower Oct 23 '08 at 09:38

11 Answers11

276

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.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • 38
    @Joel, great question. Presumably someone wished for an alternative reality and you were the bearer of bad news. ;) – Kirk Woll Sep 10 '11 at 03:07
  • 1
    Best Answer! I wasted so much time trying to figure out how to chain inserts in a single query. Thank you!! – MonkeyDoug Apr 18 '13 at 20:33
  • 15
    This doesn't solve the problem. He wants to insert data read from Object_Table. I.e. a `insert into ... select ...` statement. How does the above code read or loop through the Object_Table data. You still then need to use a table variable which the asker didn't want to do. – hofnarwillie Apr 25 '13 at 15:25
  • 1
    @hofnarwillie is right, with this solution you still have to *explicitly* declare a loop. – Fortunato Dec 02 '13 at 09:52
  • 12
    Sure this solves the problem. Maybe I didn't write _all_ of the code for this, but then the OP didn't share all of the columns he wanted to copy, either. The features demonstrated in this answer will allow the OP to do what he's asking... run a query to create a record, get the ID of the new record, and use that ID for a second record in an atomic way. The OP already knows how to do an insert/select. This is the piece he was missing. – Joel Coehoorn Nov 17 '15 at 14:42
  • where does @ObjectID come from? do you need to declare it somewhere? – peractio Mar 10 '20 at 23:04
  • @peractio It's there as an example, to show you can mix the DataID result with something that came earlier. It might be declared earlier, in client code via query parameter, or as an argument to a stored procedure. – Joel Coehoorn Mar 10 '20 at 23:35
40

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

Cade Roux
  • 88,164
  • 40
  • 182
  • 265
23

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.

Community
  • 1
  • 1
tpower
  • 56,100
  • 19
  • 68
  • 100
  • [If you are on SQL Server 2008 see my answer here for how to work around this error](http://stackoverflow.com/questions/7408204/is-there-a-way-to-do-another-query-within-the-insert-query/7415501#7415501) – Martin Smith Feb 11 '12 at 17:33
  • I don't think this is a valid answer as it uses database tables which add significance over head in high load database systems. The best solution is the one given by "Sergei Zinovyev" which is an addition to the high ranking answer above. Note, the high ranking answer is incorrect without the clause "SET XACT_ABORT ON;". – Ahmed Aug 12 '20 at 09:04
  • variable names are super confusing – Vasil Valchev Oct 15 '21 at 14:11
19

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
Sergei Zinovyev
  • 1,238
  • 14
  • 14
  • Only your answer is the most relevant and correct one. The approved answer is not the best. The highest voted answer has a flaw that you have mentioned. – Ahmed Aug 11 '20 at 09:51
6

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.

Bob Probst
  • 9,533
  • 8
  • 32
  • 41
  • Why hasn't anyone else upvoted you? The stored procedure is the obvious and best way. Combine your answer with Joel Coehoorn's answer and you get the best answer! – Rhyous Dec 06 '14 at 21:57
4

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.

Craig
  • 11,614
  • 13
  • 44
  • 62
  • 3
    The actions are atomic if they're wrapped in a transaction, not "more or less" atomic. What's not necessarily guaranteed is the level of isolation, unless you specify so. – Dave Markle Nov 28 '11 at 21:00
4

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.

devio
  • 36,858
  • 7
  • 80
  • 143
3

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?

David Aldridge
  • 51,479
  • 8
  • 68
  • 96
2

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.

Carlton Jenke
  • 2,975
  • 3
  • 26
  • 30
-3
-- ================================================
-- 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
rink.attendant.6
  • 44,500
  • 61
  • 101
  • 156
-3

//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.

Developer
  • 8,390
  • 41
  • 129
  • 238
Brion
  • 11
  • 2
  • if something goes wrong in the middle of inserts? Your inserts will be incomplete. right? If doing so.. do you have a rollback function to treat it ? If not.. you have a problem with your data integrity. – devasia2112 Jul 23 '12 at 19:49
  • 7
    -1. This answer appears to be using MySQL methods in PHP. The question is tagged [tag:sql] and [tag:sql-server], with no mention of MySQL or PHP. – mskfisher Jan 02 '14 at 21:56