SQL Server: czy można wstawić do dwóch tabel jednocześnie?

143

Moja baza danych zawiera trzy tabele o nazwie Object_Table, Data_Tablei Link_Table. Tabela połączeń zawiera tylko dwie kolumny, tożsamość rekordu obiektu i tożsamość rekordu danych.

Chcę skopiować dane, DATA_TABLEskąd są połączone z jedną podaną tożsamością obiektu i wstawić odpowiednie rekordy do Data_Tablei Link_Tabledla innej podanej tożsamości obiektu.

Mogę to zrobić, wybierając zmienną tabeli i przechodząc w pętlę, wykonując dwa wstawienia dla każdej iteracji.

Czy to najlepszy sposób na zrobienie tego?

Edycja : Chcę uniknąć pętli z dwóch powodów, po pierwsze, że jestem leniwy, a tabela pętli / temp wymaga więcej kodu, więcej kodu oznacza więcej miejsc do popełnienia błędu, a drugim powodem jest obawa o wydajność.

Mogę skopiować wszystkie dane w jednej wstawce, ale jak sprawić, by tabela linków łączyła się z nowymi rekordami danych, w których każdy rekord ma nowy identyfikator?

tpower
źródło
Nie interesuje mnie próba zrobienia tego z JEDNĄ wkładką, gdy robię to z 2 wkładkami działa idealnie. Masz na myśli, że chcesz mieć pewność, że obie wstawki są gotowe? Następnie będziesz musiał sprawdzić tę instrukcję zatwierdzenia / wycofania.
Philippe Grondier
2
Byłbym zadowolony z dwóch wstawek, po prostu tożsamości, które muszą zostać wstawione do tabeli linków, są tożsamościami wygenerowanymi w pierwszej wstawce.
tpower

Odpowiedzi:

219

Jednym słowem : Nie.

W jednej transakcji : tak

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

Dobrą wiadomością jest to, że powyższy kod ma również gwarancję, że jest atomowy i może być wysłany na serwer z aplikacji klienckiej z jednym ciągiem sql w pojedynczym wywołaniu funkcji, tak jakby to była jedna instrukcja. Możesz również zastosować wyzwalacz do jednej tabeli, aby uzyskać efekt pojedynczego wstawienia. Jednak ostatecznie nadal są to dwie instrukcje i prawdopodobnie nie chcesz uruchamiać wyzwalacza dla każdej wstawki.

Joel Coehoorn
źródło
2
To jest to, czego szukam od dawna. Dzięki :)
nandu.com
33
@Joel, świetne pytanie. Prawdopodobnie ktoś życzył sobie alternatywnej rzeczywistości, a ty byłeś zwiastunem złych wiadomości. ;)
Kirk Woll,
2
to uratowało mój dzień dzisiaj :) dzięki
Shekhar_Pro
12
To nie rozwiązuje problemu. Chce wstawić dane odczytane z Object_Table. To znaczy insert into ... select ...oświadczenie. W jaki sposób powyższy kod odczytuje lub zapętla dane Object_Table. Nadal musisz użyć zmiennej tabeli, której pytający nie chciał zrobić.
hofnarwillie
8
Jasne, to rozwiązuje problem. Może nie napisałem całego kodu do tego, ale potem OP nie udostępnił również wszystkich kolumn, które chciał skopiować. Funkcje przedstawione w tej odpowiedzi pozwolą OP zrobić to, o co prosi ... uruchomić zapytanie, aby utworzyć rekord, uzyskać identyfikator nowego rekordu i użyć tego identyfikatora jako drugiego rekordu w sposób atomowy. OP wie już, jak wykonać wstawianie / wybieranie. To jest kawałek, którego brakowało.
Joel Coehoorn
35

Nadal potrzebujesz dwóch INSERTinstrukcji, ale wygląda na to, że chcesz pobrać IDENTITYz pierwszej wstawki i użyć jej w drugiej. W takim przypadku możesz zajrzeć do OUTPUTlub OUTPUT INTO: http://msdn.microsoft.com/en- us / library / ms177564.aspx

Cade Roux
źródło
1
Dzięki! Nie wiedziałem o słowie kluczowym OUTPUT, dokładnie tego, czego szukałem. +1
Rex Morgan
czy możliwe jest dwukrotne użycie „OUTPUT INTO” w jednym sql
V.Wu
@ V.Wu nie sądzę, będę musiał ustawić test, aby zobaczyć.
Cade Roux
18

Poniżej przedstawiono sytuację, którą miałem, używając zmiennych tabeli.

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

Dzięki kolejnej odpowiedzi, która wskazała mi klauzulę OUTPUT, mogę zademonstrować rozwiązanie:

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

Okazuje się jednak, że w rzeczywistości nie jest to takie proste z powodu następującego błędu

klauzula OUTPUT INTO nie może znajdować się po żadnej ze stron relacji (klucz podstawowy, klucz obcy)

Nadal mogę zrobić OUTPUT INTOstół tymczasowy, a następnie skończyć z normalną wkładką. Więc mogę uniknąć pętli, ale nie mogę uniknąć tabeli tymczasowej.

tpower
źródło
6

Wygląda na to, że tabela Link zawiera wiele: wiele relacji między tabelą Object i tabelą danych.

Sugeruję użycie procedury składowanej do zarządzania transakcjami. Jeśli chcesz wstawić dane do tabeli obiektów lub danych, wykonaj wstawki, pobierz nowe identyfikatory i wstaw je do tabeli łączy.

Dzięki temu cała Twoja logika pozostaje zamknięta w jednym, łatwym do wywołania sproc.

Bob Probst
źródło
Dlaczego nikt inny cię nie poparł? Procedura składowana jest oczywistym i najlepszym sposobem. Połącz swoją odpowiedź z odpowiedzią Joela Coehoorna, a otrzymasz najlepszą odpowiedź!
Rhyous,
4

Jeśli chcesz, aby działania były mniej lub bardziej atomowe, owinąłbym je w transakcję. W ten sposób możesz mieć pewność, że oba się wydarzyły lub oba nie wydarzyły się w razie potrzeby.

Craig
źródło
2
Akcje są atomowe, jeśli są opakowane w transakcję, a nie „mniej więcej” atomowe. To, co niekoniecznie jest gwarantowane, to poziom izolacji, chyba że tak określisz.
Dave Markle
4

Możesz utworzyć widok, wybierając nazwy kolumn wymagane przez instrukcję wstawiania, dodać wyzwalacz INSTEAD OF INSERT i wstawić do tego widoku.

devio
źródło
4

Chcę podkreślić używanie

SET XACT_ABORT ON;

dla transakcji MSSQL z wieloma instrukcjami sql.

Zobacz: https://msdn.microsoft.com/en-us/library/ms188792.aspx Zapewniają bardzo dobry przykład.

Tak więc ostateczny kod powinien wyglądać następująco:

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
Siergiej Zinowjew
źródło
2

Wstaw może działać tylko na jednym stole naraz. Wiele wstawek musi mieć wiele instrukcji.

Nie wiem, czy musisz wykonać pętlę przez zmienną tabeli - czy nie możesz po prostu użyć wstawienia masy do jednej tabeli, a następnie wstawienia masy do drugiej?

Przy okazji - domyślam się, że masz na myśli skopiowanie danych z Object_Table; w przeciwnym razie pytanie nie ma sensu.

Carlton Jenke
źródło
2

Zanim będzie można wykonać wstawianie z wieloma tabelami w Oracle, można użyć sztuczki polegającej na wstawianiu do widoku, w którym zdefiniowano wyzwalacz INSTEAD OF, aby wykonać wstawianie. Czy można to zrobić w programie SQL Server?

David Aldridge
źródło
-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
źródło
Czy mógłbyś dodać jakieś wyjaśnienia?
Kyll
-2

// jeśli chcesz wstawić to samo co pierwsza tabela

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

// lub jeśli chcesz wstawić określone części pierwszej tabeli

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

// Wiem, że wygląda zbyt dobrze, żeby mieć rację, ale działa i możesz kontynuować dodawanie zapytania, po prostu zmień

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

Mam 17 tabel, w których to zadziałało.

Brion
źródło
jeśli coś pójdzie nie tak w środku wkładek? Twoje wkładki będą niekompletne. dobrze? Jeśli to robisz… czy masz do tego funkcję wycofywania zmian? Jeśli nie ... masz problem z integralnością danych.
deepcell
7
-1. Wydaje się, że ta odpowiedź używa metod MySQL w PHP. Pytanie jest oznaczone tagami sql i sql-server , bez wzmianki o MySQL lub PHP.
mskfisher,