web-dev-qa-db-fra.com

Comment insérer de nombreuses données dans une table temporaire?

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.

7
Julien Vavasseur

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.

2
Solomon Rutzky