`sp_executesql`真的接受`nvarchar(max)`参数吗?

时间:2016-09-29 13:16:42

标签: sql sql-server sql-server-2014 nvarchar limits

摘要 EXEC sp_executesql @code的{​​{1}}内容超过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函数用于根据需要修改字符串(即用{{1替换占位符)内容) - 请参阅下面的代码段。

在Management Studio中使用@datasource执行sp_executesql时,会显示以下错误:

  

Msg 156,Level 15,State 1,Procedure my_sp,Line 86
  关键字' AS'附近的语法不正确   Msg 102,Level 15,State 1,Procedure my_sp,Line 88
  ' WHERE'附近的语法不正确。

下面的代码段是上述方式失败的代码的完整副本(待再现)。功能可能并不重要 - 可能只有代码的长度。 @code内容明显被@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) 命令。第一个打印前4000个字符,第二个打印其余字符。它在行的中间被剪切,但它仅用于表示PRINT确实包含完整的字符串。

sp_executesql (Transact-SQL)的文档说:

  

[@ stmt =]声明

     

[...]字符串的大小仅受可用数据库服务器的限制   记忆。在64位服务器上,字符串的大小限制为2 GB,   nvarchar(max)的最大大小。

我在其他地方找到了使用@code的提示,但没有EXEC(@code)的限制。但是,它与上面引用的文档部分相矛盾。此外,sp_executesql也不起作用。

将替换后的相同内容复制/粘贴到SQL控制台时,它可以正常工作(即创建过程)。

如何解决此案?

4 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:0)

您的查询看起来像超过nvarchar 4000 的最大限制,在这种情况下,您必须将动态查询拆分为两部分

Declare @QueryA NVARCHAR(MAX),@QueryB NVARCHAR(MAX)

SET @QueryA='SELECT * FROM'
SET @QueryB=' Employee'

EXEC (@QueryA+@QueryB)

注意:如果仍有相同错误,请尝试拆分更多部分

答案 2 :(得分:0)

我不知道为什么会出错:

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

被解释为字符串的长度可能太长。这显然是语法错误。 Edmon指出,你有两个错误。

无论如何,我发布这个答案是为了消除由另一个答案创建的神话,以及你在问题中提出的长度是一个问题的建议,因为你的陈述超过4,000个字符。这是一个脚本,用于生成100,000字符长度NVARCHAR SQL语句,并将其作为EXEC (@SQL)sp_executeSQ L执行。在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

答案 3 :(得分:-2)

这种情况使我有些困扰。对于我来说,解决方案是不声明多个NVARCHAR(MAX)变量。

在构建动态SQL时,您可能会对子字符串使用NVARCHAR(MAX),这些子字符串将合并到传递给sp_executesql的最终SQL查询变量中。

NVARCHAR(MAX)的最大内存分配为2GB。您的服务器可能正在划定声明的完整2GB PER NVARCHAR(MAX)。例如,如果您声明了三个NVARCHAR(MAX)var,则您的服务器可能分配了6GB的空间来执行脚本。这可能足以使您的RAM过载,具体取决于运行时正在执行的操作。

如果您知道所有子字符串都将少于8,000个字符,请为子字符串使用VARCHAR(8000)而不是NVARCHAR(MAX)。只需对传递给sp_executesql的最终字符串变量(合并了所有子字符串var)使用NVARCHAR(MAX)。

这就是为我解决此问题的原因。