Comment puis-je insérer des données dans deux tables simultanément dans SQL Server?
Disons que ma structure de table ressemble à ceci:
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)
)
Le Champ [id]
de la première table correspond au champ [table1_id]
de la seconde. Ce que je voudrais faire est d'insérer des données dans les deux tables dans une seule transaction. Maintenant, je sais déjà comment faire cela en faisant INSERT-SELECT-INSERT, comme ceci:
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;
C'est tout bon et bien pour les petits cas comme celui où vous n'insérez que peut-être une poignée de lignes. Mais ce que je dois faire est d'insérer quelques centaines mille lignes, ou peut-être même un million de lignes, tout à la fois. Les données proviennent d'une autre table, donc si Je ne l'insérais que dans une seule table, ce serait facile, je devrais juste faire ceci:
INSERT INTO [table] ([data])
SELECT [data] FROM [external_table];
Mais comment ferais-je cela et diviser les données en [table1]
et [table2]
, et encore mettre à jour [table2]
avec le [table1_id]
approprié comme je le fais? Est-il même possible?
6 réponses
Essayez ceci:
insert into [table] ([data])
output inserted.id, inserted.data into table2
select [data] from [external_table]
Mise à jour: Re:
Denis-cela semble très proche de ce que je veux faire, mais peut-être pourriez-vous corriger L'instruction SQL suivante pour moi? Fondamentalement, les [data] dans [table1] et les [data] dans [table2] représentent deux colonnes différentes/distinctes de [external_table]. L'instruction que vous avez publiée ci-dessus ne fonctionne que lorsque vous voulez que les colonnes [data] soient les mêmes.
INSERT INTO [table1] ([data])
OUTPUT [inserted].[id], [external_table].[col2]
INTO [table2] SELECT [col1]
FROM [external_table]
Il est impossible de générer des colonnes externes dans une instruction insert
, donc, je pense que vous pourriez faire quelque chose comme ça
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]
;
J'étais aussi aux prises avec ce problème, et je trouve que le meilleur moyen est d'utiliser un curseur .
J'ai essayé la solution Denis avec OUTPUT, mais comme il l'a mentionné, il est impossible de sortir des colonnes externes dans une instruction insert, et la fusion ne peut pas fonctionner lorsque vous insérez plusieurs lignes par select.
Donc, j'ai utilisé un curseur, pour chaque ligne de la table externe, j'ai fait un INSERT, puis utilisez l'identité @@pour un autre 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
Gardez un œil sur SQL Server pour prendre en charge L'instruction 'INSERT ALL'. Oracle l'a déjà, il ressemble à ceci (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;
Une autre option consiste à exécuter les deux insertions séparément, en laissant la colonne FK null, puis en exécutant une mise à jour pour la poulater correctement.
S'il n'y a rien de naturel stocké dans les deux tables qui correspondent d'un enregistrement à un autre (probable), créez une colonne GUID temporaire et remplissez-la dans vos données et insérez-la dans les deux champs. Ensuite, vous pouvez mettre à jour avec le FK approprié et annuler les GUID.
Par exemple:
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
Vous pouvez écrire une procédure stockée qui itère sur la transaction que vous avez proposée. L'itérateur serait le curseur de la table qui contient les données source.