【问题标题】:Concatenating nvarchar(max) values doesn't seem to work (+= working as =)连接 nvarchar(max) 值似乎不起作用(+= 工作为 =)
【发布时间】:2018-07-12 23:44:31
【问题描述】:

使用 SQL Server 2012,我发现尝试基于表中的 nvarchar(max) 列构建字符串似乎无法正常工作。它似乎覆盖,而不是附加。任意示例:

    DECLARE @sql nvarchar(max);
    SELECT @sql = N'';
    SELECT @sql += [definition] + N'
GO
'
    FROM sys.sql_modules 
    WHERE OBJECT_NAME(object_id) LIKE 'dt%'
    ORDER BY OBJECT_NAME(object_id);

    PRINT @sql;

这应该打印出数据库中所有各种 dt_ 表的所有 SQL 模块定义,由 GO 分隔,作为可以运行的脚本。但是...这仅打印出 LAST 模块定义,而不是所有模块的总和。它的行为就好像“+=”只是一个“=”。

如果您稍微更改它...例如将 [definition] 转换为 nvarchar(4000),它会突然按预期工作。此外,如果您选择任何其他非 nvarchar(max) 或 varchar(max) 类型的列,它会按预期工作。示例:

DECLARE @sql nvarchar(max);
SELECT @sql = N'';
SELECT @sql += CAST([definition] AS nvarchar(4000)) + '
GO
'
FROM sys.sql_modules 
WHERE OBJECT_NAME(object_id) LIKE 'dt%'
ORDER BY OBJECT_NAME(object_id);

PRINT @sql;

这是一个已知的错误吗?或者这是否按预期工作?难道我做错了什么?有什么办法可以让我正常工作吗?我尝试了十几种不同的方法,包括确保连接中的每个表达式都是相同的 nvarchar(max) 类型,包括字符串文字。

注意:这个例子只是一个说明问题的例子,而不是我在现实生活中想要做的。如果您的数据库没有定义“dt*”表,您可以更改 WHERE 子句以指定您想要的任何数据库中的任何表组或存储过程,您将得到相同的结果......只有最后一个出现在@sql 字符串中,就好像您只是使用“=”而不是“+=”。此外,明确声明“@sql = @sql +”的行为方式相同...适用于除 nvarchar(max) 或 varchar(max) 之外的所有字符串类型。

我已经验证了 [definition] 值都不是 NULL,因此没有 NULL 恶作剧发生。

【问题讨论】:

  • 旁白:如here 所述,使用for xml 连接值是可靠的。
  • 添加到@HABO 的评论中,聚合字符串连接的行为是未定义的,结果取决于计划。您必须在更高版本的 SQL Server 中使用 XML PATH 方法或 STRING_AGG()。
  • 在 SSMS 之外尝试这个,像 LinqPad 或任何其他外部工具一样尝试
  • 不推荐这样做。来自SET @local_variable:“不要在 SELECT 语句中使用变量来连接值(即计算聚合值)。可能会出现意外的查询结果。这是因为 SELECT 列表中的所有表达式(包括赋值)都不能保证每个输出行只执行一次"
  • 有趣...当我使用普通字符串类型时,它对我来说总是可靠地工作,从 2005 年到 2017 年的每个 SQL Server 版本。我只遇到过 varchar(最大)类型。

标签: sql-server tsql sql-server-2012


【解决方案1】:

添加到@HABO 的评论中,聚合字符串连接的行为是未定义的,结果取决于计划。使用 FOR XML 提供确定性结果并遵守 ORDER BY 子句:

DECLARE @sql nvarchar(max);

SET @sql = 
(SELECT [definition] + N'
GO
'
FROM sys.sql_modules 
WHERE OBJECT_NAME(object_id) LIKE 'dt%'
ORDER BY OBJECT_NAME(object_id)
FOR XML PATH(''), TYPE).value('(./text())[1]', 'nvarchar(MAX)');

PRINT @sql; --SSMS will truncate long strings

【讨论】:

    【解决方案2】:

    += 运算符仅适用于 SQL Server 中的数值数据类型。 Microsoft documentation here

    对于字符串拼接,需要分别写赋值和拼接。

    DECLARE @sql nvarchar(max);
    SELECT @sql = N'';
    SELECT @sql = @sql + [definition] + N'
    GO
    '
    FROM sys.sql_modules 
    WHERE OBJECT_NAME(object_id) LIKE 'dt%'
    ORDER BY OBJECT_NAME(object_id);
    
    PRINT @sql;
    

    此外,如果您在 Management Studio 中运行此查询,请记住,它将返回的数据大小是有限制的(包括在打印语句中)。因此,如果您的模块定义超过此限制,它们将在输出中被截断。

    【讨论】:

    • += 不仅适用于数字数据类型。在您在页面底部错过的链接中,还有另一个字符串 concat docs.microsoft.com/en-us/sql/t-sql/language-elements/… 的链接
    • 好收获。这实际上可能只是关于 += 字符串连接操作如何在 select 语句中工作的奇怪之处。看起来 SQL Server 可能只处理一次分配。
    • 但正如我在问题中所说,将其更改为“@sql = @sql +”并不能修复或更改它。而“+=”也适用于所有其他字符串类型……只是不是 varchar(max) 或 nvarchar(max)。如果您使用 varchar(max) 列创建自己的表并在其中放入短字符串,您会得到相同的行为。这显然不是“数据量”问题。
    【解决方案3】:

    它按预期工作:

    DECLARE @sql nvarchar(max);
    SELECT @sql =COALESCE(@sql + N' GO ','')+[definition]
    FROM sys.sql_modules 
    
    
    Print @sql;
    

    【讨论】:

    • 不,我得到了完全相同的行为...在 SELECT 之后,@sql 变量仅包含最后一行的 [定义]...而不是所有定义的串联根据需要和预期返回的行。
    • 这是因为 ORDER BY。我复制/粘贴了您的原始代码,当我注释掉 ORDER BY 时,它按预期工作。而如果 ORDER BY 处于活动状态,则它只显示最后一行。
    • 不,它甚至不是 ORDER BY。如果您 ORDER BY object_id 一切正常,那么问题是 OBJECT_NAME 函数。由于排序对您很重要,因此要解决此问题,您可以尝试以下方法,它对我有用:select [definition], OBJECT_NAME(object_id) as obj_name into #tmp FROM sys.sql_modules DECLARE @sql nvarchar(max); SELECT @sql = N''; SELECT @sql += [definition] + N' GO ' FROM #tmp ORDER BY obj_name PRINT @sql;
    【解决方案4】:

    以下代码演示了从一组有序行中获取列值,并使用 for xml 在值之间创建带有分隔符的聚合字符串,包括换行符。

    -- Carriage return and linefeed characters.
    declare @CRLF as NVarChar(16) = NChar( 13 ) + NChar( 10 );
    -- Carriage return and linefeed characters XML encoded.
    declare @EncodedCRLF as NVarChar(16) = N'
';
    -- Separator to insert between source rows in the accumulated string.
    declare @Separator as NVarChar(64) = ';' + @EncodedCRLF + 'go;' + @EncodedCRLF + 'do something with ';
    -- Characters in   @Separator   to remove from the first element in   @Result .
    declare @SkipCount as Int = 4 + 2 * Len( @EncodedCRLF );
    -- The result.
    declare @Result as NVarChar( max ) = '';
    
    -- Do it.
    select @Result =
      Replace(
        Stuff(
          -- Specify the sorting order of the XML elements in the following   select   statement.
          ( select @Separator + name from sys.tables order by name desc for XML path(''), type).value('.[1]', 'VarChar(max)' ),
          1, @SkipCount, '' ),
        @EncodedCRLF, @CRLF );
    
    -- Output the result.
    select @Result as [Result];
    -- Output the result with line breaks shown.
    print @Result; -- Switch to   Messages   tab in SSMS to see result.
    

    【讨论】:

      【解决方案5】:

      我很确定 PRINT 命令是造成问题的原因。

      当我尝试时:

      DECLARE @SQL NVARCHAR(MAX) = N'';
      SELECT @sql += SUBSTRING( [definition], 1, 100 ) + CHAR(13) + CHAR(10) + 'GO' + CHAR(13) + CHAR(10)
      FROM SYS.SQL_MODULES 
      PRINT @sql;
      

      我得到了我所有的程序(前 100 个字符......)。所以这似乎是对 PRINT 命令的限制。

      第二次尝试,另一个数据库,没有“子字符串”:

      DECLARE @SQL NVARCHAR(MAX) = N'';
      SELECT @sql += [definition] + CHAR(13) + CHAR(10) + 'GO' + CHAR(13) + CHAR(10)
      FROM SYS.SQL_MODULES 
      PRINT @sql;
      

      现在我得到了 1,5 个程序:第一个完成,第二个在 proc 的在线中间停止。 PRINT有限制

      第三次尝试,证明我的假设:

      DECLARE @SQL NVARCHAR(MAX) = N'';
      SELECT @sql += [definition] + 'GO' FROM SYS.SQL_MODULES 
      PRINT @sql;
      

      现在我多了一行代码!所以对我来说,这就是 PRINT 有某种限制的证明。

      最后一次尝试:

      DECLARE @SQL NVARCHAR(MAX) = N'';
      SELECT @sql += [definition] + N'GO' FROM SYS.SQL_MODULES 
      PRINT SUBSTRING(@SQL,1,4000)
      PRINT SUBSTRING(@SQL,4001,4000)
      

      结果:现在我得到了超过2个程序,虽然现在4K后有一个换行符,但这可能发生在......不好的地方。

      【讨论】:

      • 我真的不认为是这样。我还使用了 WHILE 循环一次只打印一行。它只得到最后一个,而不是全部。我认为在您的示例中,SUBSTRING 将值转换为 nvarchar(100) 或其他内容,因此它“有效”。如果将其保留在 varchar(max),则不会。您可以使用 varchar(max) 创建自己的表,并将短字符串也放入其中以进行复制。 += 连接似乎不适用于 varchar(max) 列。
      • @pmbAustin:也许您对“转换”的想法与此有关。我编辑了问题以在另一个数据库上对其进行测试。结果是:1得到了1,5个程序:第一个没问题,第二个坏了(停在中间)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-08
      相关资源
      最近更新 更多