Je construis un programme qui génère une requête T-SQL sous la forme suivante:
DECLARE @In TABLE (Col CHAR(20))
INSERT INTO @In VALUES value1, value2... value1000
GO
INSERT INTO @In VALUES value1001, value1002...
mais la deuxième instruction INSERT
génère une erreur:
Msg 1087, niveau 15, état 2, ligne 1
Doit déclarer la variable de table "@In".
Qu'est-ce que je fais mal?
Vous pouvez utiliser VALUES (...), (...)
:
INSERT INTO table(colA, colN, ...) VALUES
(col1A, col1B, ...)
, ...
, (colnA, colnB, ...)
Avec @In:
DECLARE @In TABLE (Col CHAR(20))
INSERT INTO @In VALUES
('value1')
, ('value2')
, ...
, ('value1000')
Il insérera X lignes en une seule fois. GO n'est pas nécessaire. Les variables déclarées avant GO
n'existent plus après GO
.
Simplement, le séparateur de lots GO
devrait être supprimé (comme indiqué dans la réponse de @ Julien).
Prouvez simplement que cela fonctionne, essayez ce qui suit:
DECLARE @ValuesPerInsert INT = 1000; -- 1000 works, 1001 fails
DECLARE @SQL NVARCHAR(MAX) = '
DECLARE @In TABLE (Col CHAR(20))';
;WITH cte AS
(
SELECT TOP (3523)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS [Num],
(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) % @ValuesPerInsert) AS [Position]
FROM [master].[sys].[all_columns]
) -- select * from cte
SELECT @SQL += CASE cte.[Position]
WHEN 1 THEN N';' + NCHAR(0x0D) + NCHAR(0x0A)
+ N'INSERT INTO @In (Col) VALUES (''Val-'
+ CONVERT(NVARCHAR(10), cte.[Num]) + N''')'
ELSE ', (''Val-' + CONVERT(NVARCHAR(10), cte.[Num]) + N''')'
END
FROM cte;
SET @SQL += ';
';
SELECT CONVERT(XML, N'<sql>' + @SQL + N'</sql>') AS [@SQL];
EXEC (@SQL);
GO
Cela produit le SQL suivant:
DECLARE @In TABLE (Col CHAR(20));
INSERT INTO @In (Col) VALUES ('Val-1'), ('Val-2'), ('Val-3'), ('Val-4'), ..., ('Val-1000');
INSERT INTO @In (Col) VALUES ('Val-1001'), ('Val-1002'), ('Val-1003'), ..., ('Val-2000');
INSERT INTO @In (Col) VALUES ('Val-2001'), ('Val-2002'), ('Val-2003'), ..., ('Val-3000');
INSERT INTO @In (Col) VALUES ('Val-3001'), ('Val-3002'), ('Val-3003'), ..., ('Val-3523');
Et vous verrez dans l'onglet "Messages":
(1000 row(s) affected)
(1000 row(s) affected)
(1000 row(s) affected)
(523 row(s) affected)
Bien sûr, il existe une valeur maximale de 1000 lors de l'utilisation de la liste VALUES
. Si vous essayez de faire 1001 lignes par INSERT
, vous obtiendrez l'erreur suivante:
Msg 10738, niveau 15, état 1, ligne 6
Le nombre d'expressions de valeur de ligne dans l'instruction INSERT dépasse le nombre maximal autorisé de 1 000 valeurs de ligne.
Cela dit, si vous souhaitez insérer plus de 1 000 lignes par instruction INSERT
, vous pouvez utiliser le INSERT INTO ... SELECT
construire et combiner chaque ligne avec un UNION ALL
:
DECLARE @SQL NVARCHAR(MAX) = '
DECLARE @In TABLE (Col CHAR(20));
';
;WITH cte AS
(
SELECT TOP (3523)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS [Num]
FROM [master].[sys].[all_columns]
) -- select * from cte
SELECT @SQL += CASE cte.[Num]
WHEN 1 THEN N'INSERT INTO @In (Col)' + NCHAR(0x0D) + NCHAR(0x0A)
+ N' SELECT ''Val-1'''
ELSE NCHAR(0x0D) + NCHAR(0x0A) + N' UNION ALL'
+ NCHAR(0x0D) + NCHAR(0x0A) + N' SELECT ''Val-'
+ CONVERT(NVARCHAR(10), cte.[Num]) + N''''
END
FROM cte;
SET @SQL += ';
';
SELECT CONVERT(XML, N'<sql>' + @SQL + N'</sql>') AS [@SQL];
EXEC (@SQL);
GO
Cela produit le SQL suivant:
DECLARE @In TABLE (Col CHAR(20));
INSERT INTO @In (Col)
SELECT 'Val-1'
UNION ALL
SELECT 'Val-2'
UNION ALL
SELECT 'Val-3'
UNION ALL
SELECT 'Val-4'
UNION ALL
SELECT 'Val-5'
...
UNION ALL
SELECT 'Val-3522'
UNION ALL
SELECT 'Val-3523';
Et vous verrez dans l'onglet "Messages":
(3523 row(s) affected)
Mais je n'irais pas bien au-dessus de 4500 lignes dans une seule déclaration: vous voulez éviter l'escalade de verrous qui verrouille la table entière (enfin, à moins que la table ne soit partitionnée et l'option pour escalader en verrouillant la partition à la place de la table est activée), et cela se produit à environ 5000 verrous.
Et que étant dit, puisque vous générez cela à partir du code d'application, selon la raison pour laquelle vous générez des instructions INSERT
en premier lieu, vous pourrez peut-être changer votre approche et utiliser un Paramètre table (TVP) à la place. L'utilisation d'un TVP vous permettrait de diffuser les données de l'application directement dans une requête ou une procédure stockée en tant que variable de table, auquel cas vous feriez simplement:
INSERT INTO SchemaName.RealTable (Col)
SELECT tmp.Col
FROM @TVPvariable;
Mais si vous avez besoin d'un script de déploiement portable, ce n'est vraiment pas une option.