![](/img/trans.png)
[英]Why NVarchar(MAX) cannot execute complete dynamic query using SP_ExecuteSQL?
[英]Does `sp_executesql` really accepts the `nvarchar(max)` argument?
摘要: EXEC sp_executesql @code
未能在內容比4000更長@code
,但@code
不截斷為4000個Unicode字符。
我正在觀察 SQL Server 2014 Developer Edition 上的問題。
更多細節:我的 SQL 安裝腳本動態定義了一些代碼,因為它應該修改代碼以反映環境(只有一次,在安裝過程中)。 讓以下@datasource
變量捕獲特定環境的結果:
DECLARE @datasource nvarchar(100) = N'testdb.dbo.source_table'
@code
變量聲明為nvarchar(max)
類型, REPLACE
函數用於根據需要修改字符串(即用@datasource
內容替換占位符)——請參閱下面的代碼片段。
在Management Studio中用@code
執行sp_executesql
時,顯示如下錯誤:
消息 156,級別 15,狀態 1,過程 my_sp,第 86 行
關鍵字“AS”附近的語法不正確。
消息 102,級別 15,狀態 1,過程 my_sp,第 88 行
“WHERE”附近的語法不正確。
下面的代碼片段是上述方式失敗的代碼的精確副本(將被復制)。 功能可能並不重要——可能只有代碼的長度很重要。 @code
內容顯然被sp_executesql
截斷了; 然而,它不應該是(見下文):
-- ... repeated from above
DECLARE @datasource nvarchar(100) = N'testdb.dbo.source_table'
DECLARE @code nvarchar(MAX) = REPLACE(N'
-- Comment comment comment comment comment comment comment comment comment.
-- Comment comment comment comment comment comment comment comment comment.
-- Comment comment comment comment comment comment comment comment comment.
CREATE PROCEDURE dbo.my_sp
AS
BEGIN
SET NOCOUNT ON
DECLARE @result int = -555 -- Comment comment comment comment comment.
-- Comment comment comment comment comment comment comment comment comment.
-- Comment comment comment comment comment comment comment comment comment.
DECLARE @info_table TABLE (
action nvarchar(10), -- Comment comment comment comment comment
firmaID int, -- Comment comment comment comment comment
kod numeric(8, 0), -- Comment comment comment comment comment
oz1 nvarchar(40), -- Comment comment comment comment comment
oz2 nvarchar(40), -- Comment comment comment comment comment
oz3 nvarchar(40),
oz4 nvarchar(40)
)
-- Comment comment comment comment comment comment comment comment comment.
BEGIN TRANSACTION tran_firmy
BEGIN TRY
MERGE dbo.firmy AS target
USING (SELECT kod, ico, dic, nazev,
oz1, oz2, oz3, oz4,
jeaktivni,
ulice, mesto, psc
FROM @datasource) AS source
ON target.kod = source.kod
WHEN MATCHED AND (COALESCE(target.ico, '''') != COALESCE(source.ico, '''')
OR COALESCE(target.dic, '''') != COALESCE(source.dic, '''')
OR COALESCE(target.nazev, '''') != COALESCE(source.nazev, '''')
OR COALESCE(target.nepouzivat_oz1, '''') != COALESCE(source.oz1, '''')
OR COALESCE(target.nepouzivat_oz2, '''') != COALESCE(source.oz2, '''')
OR COALESCE(target.nepouzivat_oz3, '''') != COALESCE(source.oz3, '''')
OR COALESCE(target.nepouzivat_oz4, '''') != COALESCE(source.oz4, '''')
OR COALESCE(target.jeaktivni, 0) != COALESCE(source.jeaktivni, 0)
OR COALESCE(target.ulice, '''') != COALESCE(source.ulice, '''')
OR COALESCE(target.mesto, '''') != COALESCE(source.mesto, '''')
OR COALESCE(target.psc, '''') != COALESCE(source.psc, '''')
) THEN
UPDATE
SET target.ico = source.ico,
target.dic = source.dic,
target.nazev = source.nazev,
target.nepouzivat_oz1 = source.oz1,
target.nepouzivat_oz2 = source.oz2,
target.nepouzivat_oz3 = source.oz3,
target.nepouzivat_oz4 = source.oz4,
target.jeaktivni = source.jeaktivni,
target.ulice = source.ulice,
target.mesto = source.mesto,
target.psc = source.psc,
target.changed = GETDATE(),
target.changedby = ''dialog''
WHEN NOT MATCHED THEN
INSERT (kod, ico, dic, nazev,
nepouzivat_oz1, nepouzivat_oz2, nepouzivat_oz3, nepouzivat_oz4,
jeaktivni,
ulice, mesto, psc,
created, createdby)
VALUES (source.kod, source.ico, source.dic, source.nazev,
source.oz1, source.oz2, source.oz3, source.oz4,
source.jeaktivni,
source.ulice, source.mesto, source.psc,
GETDATE(), ''dialog'')
OUTPUT
$action AS action, -- INSERT or UPDATE
inserted.ID AS firmaID,
inserted.kod AS kod,
inserted.nepouzivat_oz1 AS oz1,
inserted.nepouzivat_oz2 AS oz2,
inserted.nepouzivat_oz3 AS oz3,
inserted.nepouzivat_oz4 AS oz4
INTO @info_table;
-- Comment comment comment comment comment comment comment comment comment.
-- Comment comment comment comment comment comment comment comment comment.
SET @result = @@ROWCOUNT
-- Comment comment comment comment comment comment comment comment comment.
-- Comment comment comment comment comment comment comment comment comment.
DELETE FROM obchodni_zastupci AS ozt
WHERE ozt.kod IN (
SELECT kod FROM @info_table AS it WHER it.action = ''UPDATE''
)
-- Comment comment comment comment comment comment comment comment comment.
-- Comment comment comment comment comment comment comment comment comment.
UPDATE dodaci_adresy
SET custID = f.ID
FROM firmy AS f, dodaci_adresy AS da
WHERE da.custID IS NULL AND f.kod = da.kod_firmy
COMMIT TRANSACTION tran_firmy
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION tran_firmy
SET @result = -1 -- Comment comment comment comment comment comment comment comment comment.
END CATCH
RETURN @result -- Comment comment comment comment comment comment comment comment comment.
END', N'@datasource', N'testdb.dbo.source_table')
-- The following prints only show that the full-length string is there
PRINT SUBSTRING(@code, 0, 4000)
PRINT '-----------------------------------------------------------'
PRINT SUBSTRING(@code, 4000, 10000)
EXEC sp_executesql @code
-- The following command also does not work (uncomment it).
-- EXEC(@code)
-- Even splitting to two variables and passing the concatenation
-- does not work.
-- DECLARE @code1 nvarchar(MAX) = SUBSTRING(@code, 0, 4000)
-- DECLARE @code2 nvarchar(MAX) = SUBSTRING(@code, 4000, 10000)
-- EXEC(@code1 + @code2)
注意兩個PRINT
命令。 第一個打印前 4000 個字符,第二個打印其余的字符。 它在行的中間被截斷,但它僅用於表明@code
確實包含完整的字符串。
sp_executesql (Transact-SQL)的文檔說:
[@stmt=] 語句
[...] 字符串的大小僅受可用數據庫服務器內存的限制。 在 64 位服務器上,字符串的大小限制為 2 GB,即 nvarchar(max) 的最大大小。
我在別處找到了使用EXEC(@code)
的提示,它沒有sp_executesql
的限制。 但是,它與上述文檔中引用的部分相矛盾。 此外, EXEC(@code)
也不起作用。
當替換后的相同內容復制/粘貼到SQL控制台時,它起作用(即創建過程)。
如何破案?
sp_executesql 確實接受NVARCHAR(MAX)
。 問題是查詢模板中的以下語句有錯誤:
DELETE FROM obchodni_zastupci AS ozt
WHERE ozt.kod IN (
SELECT kod FROM @info_table AS it WHER it.action = ''UPDATE''
)
它應該是:如下:
DELETE FROM obchodni_zastupci
WHERE obchodni_zastupci.kod IN (
SELECT kod FROM @info_table AS it WHERE it.action = ''UPDATE''
)
完整查詢應如下所示:
DECLARE @datasource nvarchar(100) = N'testdb.dbo.source_table'
DECLARE @template NVARCHAR(MAX) = N'
-- Comment comment comment comment comment comment comment comment comment.
-- Comment comment comment comment comment comment comment comment comment.
-- Comment comment comment comment comment comment comment comment comment.
CREATE PROCEDURE dbo.my_sp
AS
BEGIN
SET NOCOUNT ON
DECLARE @result int = -555 -- Comment comment comment comment comment.
-- Comment comment comment comment comment comment comment comment comment.
-- Comment comment comment comment comment comment comment comment comment.
DECLARE @info_table TABLE (
action nvarchar(10), -- Comment comment comment comment comment
firmaID int, -- Comment comment comment comment comment
kod numeric(8, 0), -- Comment comment comment comment comment
oz1 nvarchar(40), -- Comment comment comment comment comment
oz2 nvarchar(40), -- Comment comment comment comment comment
oz3 nvarchar(40),
oz4 nvarchar(40)
)
-- Comment comment comment comment comment comment comment comment comment.
BEGIN TRANSACTION tran_firmy
BEGIN TRY
MERGE dbo.firmy AS target
USING (SELECT kod, ico, dic, nazev,
oz1, oz2, oz3, oz4,
jeaktivni,
ulice, mesto, psc
FROM @datasource) AS source
ON target.kod = source.kod
WHEN MATCHED AND (COALESCE(target.ico, '''') != COALESCE(source.ico, '''')
OR COALESCE(target.dic, '''') != COALESCE(source.dic, '''')
OR COALESCE(target.nazev, '''') != COALESCE(source.nazev, '''')
OR COALESCE(target.nepouzivat_oz1, '''') != COALESCE(source.oz1, '''')
OR COALESCE(target.nepouzivat_oz2, '''') != COALESCE(source.oz2, '''')
OR COALESCE(target.nepouzivat_oz3, '''') != COALESCE(source.oz3, '''')
OR COALESCE(target.nepouzivat_oz4, '''') != COALESCE(source.oz4, '''')
OR COALESCE(target.jeaktivni, 0) != COALESCE(source.jeaktivni, 0)
OR COALESCE(target.ulice, '''') != COALESCE(source.ulice, '''')
OR COALESCE(target.mesto, '''') != COALESCE(source.mesto, '''')
OR COALESCE(target.psc, '''') != COALESCE(source.psc, '''')
) THEN
UPDATE
SET target.ico = source.ico,
target.dic = source.dic,
target.nazev = source.nazev,
target.nepouzivat_oz1 = source.oz1,
target.nepouzivat_oz2 = source.oz2,
target.nepouzivat_oz3 = source.oz3,
target.nepouzivat_oz4 = source.oz4,
target.jeaktivni = source.jeaktivni,
target.ulice = source.ulice,
target.mesto = source.mesto,
target.psc = source.psc,
target.changed = GETDATE(),
target.changedby = ''dialog''
WHEN NOT MATCHED THEN
INSERT (kod, ico, dic, nazev,
nepouzivat_oz1, nepouzivat_oz2, nepouzivat_oz3, nepouzivat_oz4,
jeaktivni,
ulice, mesto, psc,
created, createdby)
VALUES (source.kod, source.ico, source.dic, source.nazev,
source.oz1, source.oz2, source.oz3, source.oz4,
source.jeaktivni,
source.ulice, source.mesto, source.psc,
GETDATE(), ''dialog'')
OUTPUT
$action AS action, -- INSERT or UPDATE
inserted.ID AS firmaID,
inserted.kod AS kod,
inserted.nepouzivat_oz1 AS oz1,
inserted.nepouzivat_oz2 AS oz2,
inserted.nepouzivat_oz3 AS oz3,
inserted.nepouzivat_oz4 AS oz4
INTO @info_table;
-- Comment comment comment comment comment comment comment comment comment.
-- Comment comment comment comment comment comment comment comment comment.
SET @result = @@ROWCOUNT
-- Comment comment comment comment comment comment comment comment comment.
-- Comment comment comment comment comment comment comment comment comment.
DELETE FROM obchodni_zastupci
WHERE obchodni_zastupci.kod IN (
SELECT kod FROM @info_table AS it WHERE it.action = ''UPDATE''
)
-- Comment comment comment comment comment comment comment comment comment.
-- Comment comment comment comment comment comment comment comment comment.
UPDATE dodaci_adresy
SET custID = f.ID
FROM firmy AS f, dodaci_adresy AS da
WHERE da.custID IS NULL AND f.kod = da.kod_firmy
COMMIT TRANSACTION tran_firmy
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION tran_firmy
SET @result = -1 -- Comment comment comment comment comment comment comment comment comment.
END CATCH
RETURN @result -- Comment comment comment comment comment comment comment comment comment.
END'
DECLARE @code nvarchar(MAX) = REPLACE(@template, N'@datasource', N'testdb.dbo.source_table');
exec (@code);
您的查詢看起來超過了 nvarchar 4000 的最大限制,在這種情況下,您必須將動態查詢分成兩部分。
Declare @QueryA NVARCHAR(MAX),@QueryB NVARCHAR(MAX)
SET @QueryA='SELECT * FROM'
SET @QueryB=' Employee'
EXEC (@QueryA+@QueryB)
注意:如果仍然出現相同的錯誤,請嘗試拆分更多部分
我不知道為什么會出現錯誤:
Msg 156, Level 15, State 1, Procedure my_sp, Line 86
Incorrect syntax near the keyword 'AS'.
Msg 102, Level 15, State 1, Procedure my_sp, Line 88
Incorrect syntax near 'WHERE'.
被解釋為可能是您的字符串長度太長。 這顯然是一個語法錯誤。 正如埃德蒙指出的那樣,你有兩個錯誤。
無論如何,我發布此答案是為了消除由另一個答案和您在問題中提出的建議所創造的神話,即長度是一個問題,因為您的陳述超過4,000
字符。 這是一個腳本,用於創建100,000
字符長度的NVARCHAR
SQL 語句並將其作為EXEC (@SQL)
和sp_executeSQ
執行。在 SQL 2008 SP4-OD 10.0.6547.0 (x64) 上執行都沒有問題,在 2014 SP2 上也沒有問題.
因此,似乎沒有問題,自 2008 年版本以來,至少不需要任何解決方法。
DECLARE @CharacterLength INT = 100000
DECLARE @SQL NVARCHAR(MAX) = 'SELECT ' + CHAR(39)
DECLARE @i INT = 1
WHILE (LEN(@SQL) <= @CharacterLength - 2)
BEGIN
SET @SQL = @SQL + 'A'
END
SET @SQL = @SQL + CHAR(39)
PRINT 'Total Length: ' + CAST(LEN(@SQL) AS VARCHAR(100))
EXECUTE sp_executesql @sql
PRINT 'No Problem with sp_executesql'
BEGIN TRY
PRINT 'Total Length: ' + CAST(LEN(@SQL) AS VARCHAR(100))
EXEC (@SQL)
PRINT 'No Problem with EXEC (@SQL)'
END TRY
BEGIN CATCH
PRINT 'Yep never got here because there was no problem with over this character limit'
END CATCH
同樣的情況也困擾了我一段時間。 對我來說,解決方案不是聲明一個以上的 NVARCHAR(MAX) 變量。
在構建動態 SQL 時,您可能將 NVARCHAR(MAX) 用於組合成傳遞給 sp_executesql 的最終 SQL 查詢變量的子字符串。
NVARCHAR(MAX) 的 MAX 內存分配為 2GB。 您的服務器可能正在划分聲明的完整 2GB PER NVARCHAR(MAX)。 如果您聲明了三個 NVARCHAR(MAX) 變量,則您的服務器可能會分配 6GB 來執行您的腳本。 這可能足以使您的 RAM 過載,具體取決於運行時正在執行的其他內容。
如果您知道子字符串都將輕松地少於 8,000 個字符,請對子字符串使用 VARCHAR(8000) 而不是 NVARCHAR(MAX)。 只需將 NVARCHAR(MAX) 用於傳遞到 sp_executesql 的最終字符串變量(其中組合了所有子字符串變量)。
這就是為我解決這個問題的原因。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.