次の形式で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
より後には存在しません。
簡単に言うと、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;
しかし、移植可能な展開スクリプトが必要な場合は、それは実際には選択肢ではありません。