web-dev-qa-db-ja.com

一時テーブルに多数のデータを挿入する方法は?

次の形式でT-SQLクエリを生成するプログラムを構築しています。

DECLARE @In TABLE (Col CHAR(20))
INSERT INTO @In VALUES value1, value2... value1000
GO
INSERT INTO @In VALUES value1001, value1002...

しかし、2番目のINSERTステートメントはエラーをスローします。

メッセージ1087、レベル15、状態2、行1
テーブル変数「@In」を宣言する必要があります。

何が悪いのですか?

VALUES (...), (...)を使用できます:

INSERT INTO table(colA, colN, ...) VALUES
    (col1A, col1B, ...)
    , ...
    , (colnA, colnB, ...)

以内に:

DECLARE @In TABLE (Col CHAR(20))
INSERT INTO @In VALUES 
    ('value1')
    , ('value2')
    , ...
    , ('value1000')

X行を一度に挿入します。 GOは必要ありません。 GOより前に宣言された変数は、GOより後には存在しません。

7

簡単に言うと、GOバッチ区切り記号を削除する必要があります(@Julienの回答に記載されています)。

それが機能することを証明するために、以下を試してください:

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

これにより、次のSQLが生成されます。

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

そして、あなたは「メッセージ」タブで見るでしょう:

(1000 row(s) affected)

(1000 row(s) affected)

(1000 row(s) affected)

(523 row(s) affected)

もちろん、VALUESリストを使用する場合、最大値は1000です。 INSERTごとに1001行を実行しようとすると、次のエラーが発生します。

メッセージ10738、レベル15、状態1、行6
INSERTステートメントの行の値式の数が、1000行の値の最大許容数を超えています。

つまり、INSERTステートメントごとに1000行を超える行を挿入する場合は、INSERT INTO ... SELECT構文を使用して、各行を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

これにより、次のSQLが生成されます。

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

そして、あなたは「メッセージ」タブで見るでしょう:

(3523 row(s) affected)

しかし、1つのステートメントで4500行をはるかに超えることはしません。テーブル全体をロックするロックのエスカレーションを回避したい場合(テーブルがパーティション化されていない限りand代わりにパーティションをロックしてエスカレートするオプション)テーブルの有効化)、およびそれは約5000ロックで発生します。

そしてthatと言われますが、これはアプリコードから生成しているため、最初にINSERTステートメントを生成する理由によっては、アプローチを変更して使用することができる場合があります代わりにTable-Valued Paramater(TVP)。 TVPを使用すると、アプリから直接データをクエリまたはストアドプロシージャにテーブル変数としてストリーミングできます。その場合は、次のようにします。

INSERT INTO SchemaName.RealTable (Col)
  SELECT tmp.Col
  FROM   @TVPvariable;

しかし、移植可能な展開スクリプトが必要な場合は、それは実際には選択肢ではありません。

2
Solomon Rutzky