Как я могу вставить данные в две таблицы одновременно в SQL Server? - PullRequest
50 голосов
/ 15 сентября 2010

Допустим, моя структура таблицы выглядит примерно так:

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)
)

Поле [id] первой таблицы соответствует полю [table1_id] второй. То, что я хотел бы сделать, это вставить данные в обе таблицы в одной транзакции. Теперь я уже знаю, как сделать это, выполнив INSERT-SELECT-INSERT, например:

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;

Это хорошо для небольших случаев, например, когда вы вставляете только несколько строк. Но мне нужно вставить пару сотен тысяч строк или, может быть, даже миллион строк одновременно. Данные поступают из другой таблицы, поэтому, если бы я только вставлял их в одну таблицу, это было бы легко, мне просто нужно было бы сделать это:

INSERT INTO [table] ([data])
SELECT [data] FROM [external_table];

Но как бы мне это сделать и разбить данные на [table1] и [table2], и все же обновить [table2] с помощью соответствующего [table1_id], как я это делаю? Это вообще возможно?

Ответы [ 6 ]

30 голосов
/ 15 сентября 2010

Попробуйте это:

insert into [table] ([data])
output inserted.id, inserted.data into table2
select [data] from [external_table]

ОБНОВЛЕНИЕ: Re:

Денис - кажется, это очень близко к тому, что я хочу сделать, но, возможно, вы могли бы исправить для меня следующую инструкцию SQL? В основном [данные] в [таблица1] и [данные] в [таблица2] представляют два разных / отличных столбца от [external_table]. Вышеупомянутое утверждение работает только тогда, когда вы хотите, чтобы столбцы [data] были одинаковыми.

INSERT INTO [table1] ([data]) 
OUTPUT [inserted].[id], [external_table].[col2] 
INTO [table2] SELECT [col1] 
FROM [external_table] 

Невозможно вывести внешние столбцы в операторе insert, поэтому я думаю, что вы могли бы сделать что-то подобное

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]
;
4 голосов
/ 18 января 2016

Я также боролся с этой проблемой и обнаружил, что лучший способ - это использовать CURSOR .

Я пробовал решение Дениса с OUTPUT, но, как он упомянул, это невозможнодля вывода внешних столбцов в операторе вставки, и MERGE не может работать при вставке нескольких строк путем выбора.

Итак, я использовал CURSOR, для каждой строки во внешней таблице я сделалВСТАВКА, затем используйте @@ IDENTITY для другой ВСТАВКИ.

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
1 голос
/ 15 сентября 2010

Следите за тем, чтобы SQL Server поддерживал оператор INSERT ALL. У Oracle это уже есть, это выглядит так ( 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
0 голосов
/ 15 сентября 2010

Другой вариант - запустить две вставки отдельно, оставив столбец FK пустым, а затем запустить обновление для правильной разметки.

Если в двух таблицах нет ничего естественного, что соответствует одной записи другой (вероятно), то создайте временный столбец GUID и заполните его в своих данных и вставьте в оба поля. Затем вы можете обновить с правильным FK и обнулить GUID.

Например:

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
0 голосов
/ 15 сентября 2010
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;
0 голосов
/ 15 сентября 2010

Вы можете написать хранимую процедуру, которая повторяет транзакцию, которую вы предложили. Итератор будет курсором для таблицы, содержащей исходные данные.

...