How can I INSERT data into two tables simultaneously in SQL Server? - sql

Let's say my table structure looks something like this:
CREATE TABLE [dbo].[table1] (
[id] [int] IDENTITY(1,1) NOT NULL,
[data] [varchar](255) NOT NULL,
CONSTRAINT [PK_table1] PRIMARY KEY CLUSTERED ([id] ASC)
)
CREATE TABLE [dbo].[table2] (
[id] [int] IDENTITY(1,1) NOT NULL,
[table1_id] [int] NOT NULL,
[data] [varchar](255) NOT NULL,
CONSTRAINT [PK_table2] PRIMARY KEY CLUSTERED ([id] ASC)
)
The [id] field of the first table corresponds to the [table1_id] field of the second. What I would like to do is insert data into both tables in a single transaction. Now I already know how to do this by doing INSERT-SELECT-INSERT, like this:
BEGIN TRANSACTION;
DECLARE #id [int];
INSERT INTO [table1] ([data]) VALUES ('row 1');
SELECT #id = SCOPE_IDENTITY();
INSERT INTO [table2] ([table1_id], [data]) VALUES (#id, 'more of row 1');
COMMIT TRANSACTION;
That's all good and fine for small cases like that where you're only inserting maybe a handful of rows. But what I need to do is insert a couple hundred thousand rows, or possibly even a million rows, all at once. The data is coming from another table, so if I was only inserting it into a single table, it would be easy, I'd just have to do this:
INSERT INTO [table] ([data])
SELECT [data] FROM [external_table];
But how would I do this and split the data into [table1] and [table2], and still update [table2] with the appropriate [table1_id] as I'm doing it? Is that even possible?

Try this:
insert into [table] ([data])
output inserted.id, inserted.data into table2
select [data] from [external_table]
UPDATE: Re:
Denis - this seems very close to what I want to do, but perhaps you could fix the following SQL statement for me? Basically the [data] in [table1] and the [data] in [table2] represent two different/distinct columns from [external_table]. The statement you posted above only works when you want the [data] columns to be the same.
INSERT INTO [table1] ([data])
OUTPUT [inserted].[id], [external_table].[col2]
INTO [table2] SELECT [col1]
FROM [external_table]
It's impossible to output external columns in an insert statement, so I think you could do something like this
merge into [table1] as t
using [external_table] as s
on 1=0 --modify this predicate as necessary
when not matched then insert (data)
values (s.[col1])
output inserted.id, s.[col2] into [table2]
;

I was also struggling with this problem, and find that the best way is to use a CURSOR.
I have tried Denis solution with OUTPUT, but as he mentiond, it's impossible to output external columns in an insert statement, and the MERGE can't work when insert multiple rows by select.
So, i've used a CURSOR, for each row in the outer table, i've done a INSERT, then use the ##IDENTITY for another INSERT.
DECLARE #OuterID int
DECLARE MY_CURSOR CURSOR
LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
SELECT ID FROM [external_Table]
OPEN MY_CURSOR
FETCH NEXT FROM MY_CURSOR INTO #OuterID
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO [Table] (data)
SELECT data
FROM [external_Table] where ID = #OuterID
INSERT INTO [second_table] (FK,OuterID)
VALUES(#OuterID,##identity)
FETCH NEXT FROM MY_CURSOR INTO #OuterID
END
CLOSE MY_CURSOR
DEALLOCATE MY_CURSOR

Keep a look out for SQL Server to support the 'INSERT ALL' Statement. Oracle has it already, it looks like this (SQL Cookbook):
insert all
when loc in ('NEW YORK', 'BOSTON') THEN
into dept_east(deptno, dname, loc) values(deptno, dname, loc)
when loc in ('CHICAGO') THEN
into dept_mid(deptno, dname, loc) values(deptno, dname, loc)
else
into dept_west(deptno, dname, loc) values(deptno, dname, loc)
select deptno, dname, loc
from dept

BEGIN TRANSACTION;
DECLARE #tblMapping table(sourceid int, destid int)
INSERT INTO [table1] ([data])
OUTPUT source.id, new.id
Select [data] from [external_table] source;
INSERT INTO [table2] ([table1_id], [data])
Select map.destid, source.[more data]
from [external_table] source
inner join #tblMapping map on source.id=map.sourceid;
COMMIT TRANSACTION;

Create table #temp1
(
id int identity(1,1),
name varchar(50),
profession varchar(50)
)
Create table #temp2
(
id int identity(1,1),
name varchar(50),
profession varchar(50)
)
-----main query ------
insert into #temp1(name,profession)
output inserted.name,inserted.profession into #temp2
select 'Shekhar','IT'

You could write a stored procedure that iterates over the transaction that you have proposed. The iterator would be the cursor for the table that contains the source data.

Another option is to run the two inserts separately, leaving the FK column null, then running an update to poulate it correctly.
If there is nothing natural stored within the two tables that match from one record to another (likely) then create a temporary GUID column and populate this in your data and insert to both fields. Then you can update with the proper FK and null out the GUIDs.
E.g.:
CREATE TABLE [dbo].[table1] (
[id] [int] IDENTITY(1,1) NOT NULL,
[data] [varchar](255) NOT NULL,
CONSTRAINT [PK_table1] PRIMARY KEY CLUSTERED ([id] ASC),
JoinGuid UniqueIdentifier NULL
)
CREATE TABLE [dbo].[table2] (
[id] [int] IDENTITY(1,1) NOT NULL,
[table1_id] [int] NULL,
[data] [varchar](255) NOT NULL,
CONSTRAINT [PK_table2] PRIMARY KEY CLUSTERED ([id] ASC),
JoinGuid UniqueIdentifier NULL
)
INSERT INTO Table1....
INSERT INTO Table2....
UPDATE b
SET table1_id = a.id
FROM Table1 a
JOIN Table2 b on a.JoinGuid = b.JoinGuid
WHERE b.table1_id IS NULL
UPDATE Table1 SET JoinGuid = NULL
UPDATE Table2 SET JoinGuid = NULL

Related

How to insert data in multiple tables using single query in SQL Server?

I'm trying to insert data into multiple tables if it doesn't already exist. I can't seem to figure this out at all.
Table 1:
CREATE TABLE [dbo].[search_results]
(
[company_id] [int] NULL,
[title] [text] NULL,
[link] [text] NULL,
[domain] [text] NULL,
[index] [int] NULL,
[id] [int] PRIMARY KEY IDENTITY(1,1) NOT NULL
)
Table 2:
CREATE TABLE [dbo].[statements]
(
[statement_link_id] [int] NULL,
[statement_page] [text] NULL,
[statement_text_location] [text] NULL,
[statement_description] [text] NULL,
[statement_description_html] [text] NULL,
[statement] [int] NULL,
[id] [int] PRIMARY KEY IDENTITY(1,1) NOT NULL
)
This is what I want to do:
check to see if the company_id and the link already exist in the table or not.
SELECT *
FROM search_results
WHERE company_id = 4 AND link = 'https://test.com';
If the data does not exist, insert it into two tables
INSERT INTO search_results (company_id, link, title, domain)
VALUES (4, 'https://test.com', 'title', 'test.com');
and also insert the search_result last inserted id to the following table. corporate_statement value is always 1
INSERT INTO corporate_statements (statement_link_id, corporate_statement)
VALUES (743, 1);
I'm trying this based on what I found on SO
DECLARE #result AS TABLE (id int, company_id int, link text, title text, domain text);
WITH cte AS
(
SELECT *
FROM (VALUES (4, 'https://test.com', null, null)) AS t(company_id, link, title, domain)
)
INSERT INTO #result
SELECT *
FROM
(INSERT INTO dbo.search_results (company_id, link, title, domain)
OUTPUT inserted.*
SELECT * FROM cte
WHERE NOT EXISTS (SELECT * FROM dbo.[search_results]
WHERE company_id = cte.company_id
AND CAST(link AS varchar(250)) = CAST(cte.link AS varchar(50))
)) r
SELECT * FROM #result;
Even trying with a single insert statement, I get the following error:
Msg 213, Level 16, State 1, Line 8
Column name or number of supplied values does not match table definition.
As you can see, I also tried to cast it to varchar since it was throwing error when I hadn't. How can update this?
To me - this seems a lot cleaner, and it also will be a lot simpler to understand (and maintain!) in the future:
-- check to see if your data already exists
IF NOT EXISTS (SELECT *
FROM search_results
WHERE company_id = 4 AND link = 'https://test.com')
BEGIN TRY
BEGIN TRANSACTION
-- if not -> insert into the first table
INSERT INTO search_results (company_id, link, title, domain)
VALUES (4, 'https://test.com', 'title', 'test.com');
-- grab the last identity value from that previous INSERT
DECLARE #LastId INT;
SELECT #LastId = SCOPE_IDENTITY();
-- insert into the second table
INSERT INTO corporate_statements (statement_link_id, corporate_statement)
VALUES (#LastId, 1);
COMMIT;
END TRY
BEGIN CATCH
-- in case of an error rollback the full transaction
ROLLBACK;
END CATCH;
and you're done. Or am I missing something? I think this would be doing what you're described in the intro of your post - not necessarily what you're showing in your code...

Ambiguous column name... but only sometimes?

So apparently this has Ambiguous column name 'LocationID':
DECLARE #temptable table (LocationID int)
INSERT INTO #temptable SELECT LocationID FROM inserted;
INSERT INTO dbo.LocationsPlants (LocationID, PlantID)
(SELECT LocationID, PlantID FROM dbo.Plants CROSS JOIN #temptable)
Which I can fix by altering the bottom line to:
(SELECT T.LocationID, PlantID FROM dbo.Plants CROSS JOIN #temptable AS T)
But this identical query on another table does NOT have ambiguous column IncotermsID:
DECLARE #temptable table (IncotermsID int)
INSERT INTO #temptable SELECT IncotermsID FROM inserted;
INSERT INTO dbo.IncotermsPlants (IncotermsID, PlantID)
(SELECT IncotermsID,PlantID FROM dbo.Plants CROSS JOIN #temptable)
I'm puzzled. Table structures:
dbo.Locations:
[LocationID] [int] NOT NULL,
[LocationTypeID] [int] NOT NULL,
[Title] [varchar](100) NULL
dbo.Incoterms:
[IncotermsID] [int] IDENTITY(1,1) NOT NULL,
[Incoterm] [varchar](20) NOT NULL
The problem is most likely a column named LocationID in the Plants table, which is why the query is confused as to which LocationID column to be returned, from Plants or from #temptable?
As #GordonLinoff mentioned, it's a good practice (I'd say best practice) to always alias your tables used in joins or correlated queries, and do so for their associated columns as well.
The reason this only happens "sometimes" is because in your second query, there is a single IncotermsID exists in only one table of the two used in your CROSS APPLY.

What's the query needed to copy data from one table to another two?

I recently changed my database structure and now I want to copy from my old table Transfers to the new ones I just created (Orders and Orders_Transfer):
--old table to copy from
-- table 'Transfers'
CREATE TABLE [dbo].[Transfers] (
[Id] int IDENTITY(1,1) NOT NULL,
[Date] datetime NOT NULL,
[Memo] nvarchar(max) NULL,
[Employee_Id] int NULL,
[InventoryFrom_Id] int NOT NULL,
[InventoryTo_Id] int NOT NULL,
);
-- new tables to copy to
-- table 'Orders'
CREATE TABLE [dbo].[Orders] (
[Id] int IDENTITY(1,1) NOT NULL,
[Date] datetime NOT NULL,
[Memo] nvarchar(max) NULL,
[Employee_Id] int NULL
);
-- Creating table 'Orders_Transfer'
CREATE TABLE [dbo].[Orders_Transfer] (
[Id] int NOT NULL,--foreign key on Orders.Id
[InventoryFrom_Id] int NOT NULL,
[InventoryTo_Id] int NOT NULL
);
I want to iterate through the old Transfers table and copy some part of it to Orders (Date, Memo, Employee) and the rest to Orders_Transfer (InventoryFrom_Id, InventoryTo_Id). The Id column in Orders_Transfer is also a FK on Orders.Id so I want to copy the auto-generated value as well.
I read about the scope_identity() function and the OUTPUT clause, but I’m a beginner to SQL and can’t put it all together.
I’m using SQL Server 2008
What is the query I need to do this? Any help would be appreciated, thanks!
I would create an OldId column on the Orders table to store the old primary key:
ALTER TABLE [dbo].[Orders] ADD [OldId] INT;
Then copy in the old data:
INSERT INTO [dbo].[Orders]
(
[OldId],[Date],[Memo],[EmployeeID]
)
SELECT [Id] AS [OldId],[Date],[Memo],[EmployeeID]
FROM [dbo].[Transfers];
Copy the remaining data using the OldId:
INSERT INTO [dbo].[Orders_Transfer]
(
[Id],
[InventoryFrom_Id],
[InventoryTo_Id]
)
SELECT
O.Id, T.[InventoryFrom_Id], T.[InventoryTo_Id]
FROM [dbo].[Orders] O
INNER JOIN [dbo].[Transfers] T
ON O.[OldId] = T.[Id];
And drop the OldId column:
ALTER TABLE [dbo].[Orders] DROP COLUMN [OldId];
To keep the Id values in sync you are going to need to use IDENTITY_INSERT.
SET IDENTITY_INSERT dbo.Orders ON;
/* Insert data into the Orders table */
INSERT INTO [dbo].[Orders]
([Id]
,[Date]
,[Memo]
,[Employee_Id])
SELECT Id
, Date
, Memo
, Employee_Id
FROM Transfers;
SET IDENTITY_INSERT dbo.Orders OFF;
/* Insert data into the Orders_Transfer table */
INSERT INTO [dbo].[Ordres_Transfer]
([Id]
,[InventoryFrom_ID]
,[InventoryTo_ID]
SELECT Id
, InventoryFrom_ID
, InentoryTo_ID
FROM Transfers;
Insert into newtable select * from oldtable //if same schema
insert into newtable(col1,col2,col3) select col1,col2,col3 from old table // for different table schema

Select identity of primary table to which record is inserted

I have a table let us say TableA. I have created a trigger which is fired on Insertion, Update or Deletion of record in TableA and inserts record in another table let us say TableB. Now where am I finding difficulty is from some of the stored procedures of TableA I am returning scope_identity() of inserted record. That must be returned from TableA's record while I am getting it from TableB. Is there any way to solve this issue? I want scope_identity() from TableA only, NOT from TableB. Awaiting for your valuable response.
Thanks in advance..
SCOPE_IDENTITY() should work in that situation, it should be scoped to your stored proc and the insert to TableA, not the insert done in the trigger.
CREATE TABLE [dbo].[TableA](
[ID] [int] IDENTITY(1,1) NOT NULL,
[TestColumn] [nvarchar](50) NULL
) ON [PRIMARY]
CREATE TABLE [dbo].[TableB](
[ID] [int] IDENTITY(1,1) NOT NULL,
[TestColumn] [nvarchar](50) NULL
) ON [PRIMARY]
CREATE TRIGGER [dbo].[TableA_AIDU]
ON [dbo].[TableA]
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO TableB ( TestColumn )
SELECT 'TableA Modified'
END
CREATE PROCEDURE [dbo].[sp_InsertToTableA]
#ID INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
INSERT TableA ( TestColumn )
SELECT 'Insert from sp_InsertToTableA'
SET #ID = SCOPE_IDENTITY()
END
The stored proc returns the ID from TableA, and the TableB insert is triggered as expected. Are you sure you're not using ##IDENTITY instead of SCOPE_IDENTITY()?

Use trigger how to copy just inserted row

I'm using SQL Server 2008, and I have a trigger which I want to copy any rows in the My_Table into a archive History_Table table.
How to copy the entire old content of the table into the archive each time someone inserts a new row?
My table structure is
CREATE TABLE [dbo].[Stu_Table]
(
[Stu_Id] [int] NOT NULL,
[Stu_Name] [varchar] (15) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Stu_Class] [int] NULL
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Stu_Table] ADD CONSTRAINT [PK_Stu_Table] PRIMARY KEY CLUSTERED ([Stu_Id]) ON [PRIMARY]
GO
My archive table structure is
CREATE TABLE [dbo].[Stu_TableHistory]
(
[Stu_Id] [int] NOT NULL,
[Stu_Name] [varchar] (15) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Stu_Class] [int] NULL
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Stu_TableHistory] ADD CONSTRAINT [PK_Stu_TableHistory] PRIMARY KEY CLUSTERED ([Stu_Id]) ON [PRIMARY]
GO
My trigger syntax is
Create TRIGGER [dbo].[HistoryKeep]
ON [dbo].[Stu_Table]
INSTEAD OF INSERT
AS
BEGIN
IF((SELECT COUNT(*) FROM Stu_Table WHERE Stu_Id = (SELECT Stu_Id FROM INSERTED)) >= 1)
BEGIN
INSERT INTO dbo.Stu_TableHistory( Stu_Id, Stu_Name, Stu_Class )
SELECT Stu_Id, Stu_Name, Stu_Class FROM Stu_Table WHERE Stu_Id = (SELECT Stu_Id FROM INSERTED)
UPDATE x
SET x.Stu_Name = i.Stu_Name
FROM dbo.Stu_Table AS x
INNER JOIN inserted AS i ON i.Stu_Id = x.Stu_Id
END
ELSE
BEGIN
INSERT INTO dbo.Stu_Table( Stu_Id, Stu_Name, Stu_Class )
SELECT Stu_Id, Stu_Name, Stu_Class FROM INSERTED
END
END
In a word need help to transfer the old data from student table to archive table. My above trigger syntax can not satisfy me.
If have any query plz ask thanks in advance.
Instead of your current trigger, you should have something like:
Create TRIGGER [dbo].[HistoryKeep]
ON [dbo].[Stu_Table]
INSTEAD OF INSERT
AS
BEGIN
DECLARE #History table (
Action sysname not null,
STU_ID [int] NULL,
[Stu_Name] [varchar] (15) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Stu_Class] [int] NULL
)
;MERGE INTO Stu_Table t
USING INSERTED i ON t.STU_ID = i.STU_ID
WHEN MATCHED THEN UPDATE SET STU_Name = i.STU_Name
WHEN NOT MATCHED THEN INSERT (STU_ID,STU_NAME,STU_CLASS) VALUES (i.STU_ID,i.STU_NAME,i.STU_CLASS)
OUTPUT $Action,deleted.stu_id,deleted.stu_name,deleted.stu_class INTO #History;
INSERT INTO stu_TableHistory (stu_id,stu_name,stu_class)
select stu_id,stu_name,stu_class from #History where Action='UPDATE'
END
Note, also, that you'll need to drop your current PK constraint on STU_TableHistory, since as soon as a row is updated more than once, there'll be two entries containing the same STU_ID.
As per my comment, this treats INSERTED as a table containing multiple rows throughout. So if Stu_Table contains a row for STU_ID 1, the following insert:
INSERT INTO STU_Table (STU_ID,STU_Name,STU_Class) VALUES
(1,'abc',null),
(2,'def',null)
will update the row for STU_ID 1, insert a row for STU_ID 2, and insert one row into stu_tableHistory (for STU_ID 1)