SQL Server: возможно ли вставить в две таблицы одновременно?

143

Моя база данных содержит три таблицы с именами Object_Table, Data_Tableи Link_Table. Таблица ссылок содержит только два столбца: идентификатор записи объекта и идентификатор записи данных.

Я хочу скопировать данные из того места, DATA_TABLEгде они связаны с одной данной идентификацией объекта, и вставить соответствующие записи в Data_Tableи Link_Tableдля другой данной идентификации объекта.

Я могу сделать это, выбрав в табличную переменную и циклически делая две вставки для каждой итерации.

Это лучший способ сделать это?

Редактировать : я хочу избежать цикла по двум причинам: во-первых, я ленив, а таблица цикла / температуры требует больше кода, больше кода означает больше мест для ошибок, а вторая причина - это проблема производительности.

Я могу скопировать все данные за одну вставку, но как получить таблицу ссылок для ссылки на новые записи данных, где каждая запись имеет новый идентификатор?

мин
источник
Меня не интересует попытка сделать это с ОДНОЙ вставкой, когда работа с 2 вставками работает на отлично. Вы хотите убедиться, что оба вкладыша завершены? Затем вам нужно будет проверить эту инструкцию коммита / отката.
Филипп Грондиер
2
Я был бы счастлив с двумя вставками, это просто, что идентичности, которые должны быть вставлены в таблицу ссылок, являются идентичностями, сгенерированными в первой вставке.
tpower

Ответы:

219

В одном заявлении : Нет.

В одной транзакции : да

BEGIN TRANSACTION
   DECLARE @DataID int;
   INSERT INTO DataTable (Column1 ...) VALUES (....);
   SELECT @DataID = scope_identity();
   INSERT INTO LinkTable VALUES (@ObjectID, @DataID);
COMMIT

Хорошей новостью является то, что приведенный выше код также гарантированно является атомарным и может быть отправлен на сервер из клиентского приложения с одной строкой sql в одном вызове функции, как если бы это был один оператор. Вы также можете применить триггер к одной таблице, чтобы получить эффект одной вставки. Однако в конечном итоге это все еще два оператора, и вы, вероятно, не хотите запускать триггер для каждой вставки.

Джоэл Коухорн
источник
2
Это то, что я ищу долго. Спасибо :)
nandu.com
33
@ Джоэл, отличный вопрос. Предположительно кто-то хотел альтернативной реальности, а вы были носителями плохих новостей. ;)
Кирк Уолл
2
это спасло мой день сегодня :) спасибо
Shekhar_Pro
12
Это не решает проблему. Он хочет вставить данные, прочитанные из Object_Table. Т.е. insert into ... select ...заявление. Как приведенный выше код читает или перебирает данные Object_Table. Затем вам все еще нужно использовать табличную переменную, которую запрашивающий не хотел делать.
hofnarwillie
8
Конечно, это решает проблему. Возможно, я написал не весь код для этого, но затем ОП не разделял все столбцы, которые он хотел скопировать. Функции, продемонстрированные в этом ответе, позволят ОП делать то, что он просит ... выполнить запрос, чтобы создать запись, получить идентификатор новой записи и использовать этот идентификатор для второй записи атомарным способом. ОП уже знает, как сделать вставку / выбор. Это часть, которую он пропустил.
Джоэл Коухорн
35

Вам по-прежнему нужны два INSERTоператора, но, похоже, вы хотите получить IDENTITYиз первой вставки и использовать ее во второй, и в этом случае вы можете посмотреть OUTPUTили OUTPUT INTO: http://msdn.microsoft.com/en- нас / библиотека / ms177564.aspx

Кейд Ру
источник
1
Спасибо! Я не знал о ключевом слове OUTPUT, именно то, что я искал. +1
Рекс Морган
Можно ли использовать «OUTPUT INTO» дважды в одном sql
V.Wu
@ V. Не думаю, мне нужно будет провести тест, чтобы увидеть.
Cade Roux
18

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

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

Благодаря другому ответу, который указал мне на предложение OUTPUT, я могу продемонстрировать решение:

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

Оказывается, однако, что это не так просто в реальной жизни из-за следующей ошибки

предложение OUTPUT INTO не может быть ни по одной из сторон отношения (первичный ключ, внешний ключ)

Я могу еще OUTPUT INTOвременную таблицу, а затем закончить с обычной вставкой. Так что я могу избежать цикла, но не могу избежать временной таблицы.

мин
источник
6

Похоже, что таблица ссылок фиксирует взаимосвязь «многие: многие» между таблицей объектов и таблицей данных.

Я предлагаю использовать хранимую процедуру для управления транзакциями. Если вы хотите вставить в таблицу объектов или данных, выполните вставки, получите новые идентификаторы и вставьте их в таблицу ссылок.

Это позволяет сохранить всю логику в одном простом для вызова sproc.

Боб Пробст
источник
Почему никто не проголосовал против тебя? Хранимая процедура является очевидным и лучшим способом. Объедините свой ответ с ответом Джоэла Кохорна, и вы получите лучший ответ!
Rhyous
4

Если вы хотите, чтобы действия были более или менее атомарными, я бы обязательно включил их в транзакцию. Таким образом, вы можете быть уверены, что оба произошли, или оба не произошли по мере необходимости.

Craig
источник
2
Действия являются атомарными, если они заключены в транзакцию, а не «более или менее» атомарными. То, что не обязательно гарантировано, является уровнем изоляции, если вы не укажете это.
Дейв Маркл
4

Вы можете создать представление, выбрав имена столбцов, требуемые оператором вставки, добавить триггер INSTEAD OF INSERT и вставить в это представление.

devio
источник
4

Я хочу подчеркнуть использование

SET XACT_ABORT ON;

для транзакции MSSQL с несколькими операторами SQL.

См .: https://msdn.microsoft.com/en-us/library/ms188792.aspx. Они представляют собой очень хороший пример.

Итак, окончательный код должен выглядеть следующим образом:

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
Сергей Зиновьев
источник
2

Вставка может работать только на одном столе одновременно. Несколько вставок должны иметь несколько операторов.

Я не знаю, что вам нужно выполнить циклическое преобразование табличной переменной - вы не можете просто использовать массовую вставку в одну таблицу, а затем массовую вставку в другую?

Кстати, я предполагаю, что вы имеете в виду скопировать данные из Object_Table; в противном случае вопрос не имеет смысла.

Карлтон Дженке
источник
2

Перед тем, как выполнить множественную вставку в Oracle, вы могли бы использовать прием, включающий вставку в представление, для которого был определен триггер INSTEAD OF для выполнения вставок. Можно ли это сделать в SQL Server?

Дэвид Олдридж
источник
-1
-- ================================================
-- 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
FakirPori
источник
Не могли бы вы добавить некоторые объяснения?
Кайлл
-2

// если вы хотите вставить то же самое, что и первая таблица

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

// или если вы хотите вставить определенные части таблицы один

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

// я знаю, что это выглядит слишком хорошо, чтобы быть правым, но это работает, и вы можете продолжать добавлять запрос, просто изменив

    "$qry"-number and number in @mysql_query($qry"")

У меня есть 17 таблиц, в которых это работало.

Brion
источник
если что-то пойдет не так в середине вставок? Ваши вкладыши будут неполными. право? Если это так .. у вас есть функция отката для его лечения? Если нет .. у вас есть проблемы с целостностью ваших данных.
Deepcell
7
-1. Этот ответ, кажется, использует методы MySQL в PHP. Вопрос помечен как sql и sql-server , без упоминания о MySQL или PHP.
mskfisher