将索引添加到临时 table 会导致单行结果集

Adding index to temporary table results in single row result set

在 sp_executesql 中,select 语句用于将变量本身和临时 table.

中的列的值连接起来

当我将索引应用于此临时 table 并在上述 select 语句中使用排序时,这会产生奇怪的结果。

只有当临时 table 的行数大于 50 行时才会发生这种情况。

我不喜欢粘贴大代码示例,但我无法进一步减少它。

如果@maxjob >= 8,则生成的@htmllog 包含 67 个字符。这是意想不到的结果。

如果@maxjob < 8,则生成的@htmllog 始终包含超过 67 个字符。这是预期的结果。

此外,

当我从 #acl_log table 中删除索引 idx_key 时,当 @maxjob >= 8 时问题就消失了。 要么 当我从@executesql_sql 中删除 'order by [key] asc' 时,问题也消失了。

为什么?

declare @logtable as varchar(max)
set @logtable = '#acl_log'

if (OBJECT_ID('[tempdb]..#acl_log')) is not null
    drop table #acl_log

create table #acl_log(
    [key]       int             identity,
    [message]   nvarchar(max)   not null,
    index idx_key ([key])
)

declare @job as int
declare @maxjob as int

set @job = 0
set @maxjob = 8

while (@job < @maxjob + 1)
begin
    insert into #acl_log([message])
    values
        ('Internet Explorer is currently running without add-ons')
        ,('All Internet Explorer add-ons, such as ActiveX controls or toolbars, are turned off. Some webpages might not display correctly.')
        ,('To continue to your home page, click the Home button.')
        ,('To browse using add-ons, close Internet Explorer and then start it again.')
        ,('Forward Arrow  Check for the latest Windows updates. ')
        ,('Question Icon How do browser add-ons affect my browsing experience? ')

    set @job = @job + 1
end

declare @executesql_sql as nvarchar(max)
declare @executesql_param as nvarchar(max)

declare @htmllog as varchar(max)
set @executesql_sql = '
    set @htmllog = ''''
    select @htmllog = @htmllog + [message]
    from ' + @logtable + '
    order by [key] asc'
set @executesql_param = '@htmllog varchar(max) output'

exec master..sp_executesql @executesql_sql, @executesql_param, @htmllog = @htmllog output

select len(@htmllog), @htmllog

聚合字符串连接的行为未定义,因为根据 Microsoft 在 this connect item:

中的评论,它依赖于计划

Even without ORDER BY, we do not guarantee that @var = @var + will produce the concatenated value for any statement that affects multiple rows. The right-hand side of the expression can be evaluated either once or multiple times during query execution and the behavior as I said is plan dependent.

一种解决方法是 FOR XML 子句:

DECLARE @logtable AS varchar(MAX);
SET @logtable = '#acl_log';

IF (OBJECT_ID('[tempdb]..#acl_log')) IS NOT NULL
    DROP TABLE #acl_log;

CREATE TABLE #acl_log(
    [key]       int             IDENTITY,
    [message]   nvarchar(max)   not null,
    INDEX idx_key ([key])
);

DECLARE @job as int;
DECLARE @maxjob as int;

SET @job = 0;
SET @maxjob = 8;

WHILE (@job < @maxjob + 1)
BEGIN
    INSERT INTO #acl_log([message])
    VALUES
        ('Internet Explorer is currently running without add-ons')
        ,('All Internet Explorer add-ons, such as ActiveX controls or toolbars, are turned off. Some webpages might not display correctly.')
        ,('To continue to your home page, click the Home button.')
        ,('To browse using add-ons, close Internet Explorer and then start it again.')
        ,('Forward Arrow  Check for the latest Windows updates. ')
        ,('Question Icon How do browser add-ons affect my browsing experience? ');

    SET @job = @job + 1;
END

DECLARE @executesql_sql AS nvarchar(MAX);
DECLARE @executesql_param AS nvarchar(MAX);

DECLARE @htmllog AS varchar(max);
SET @executesql_sql = '
    SET @htmllog = (SELECT [message]
    FROM ' + @logtable + '
    ORDER BY [key]
    FOR XML PATH(''''), TYPE).value(''.'', ''varchar(MAX)'');';

set @executesql_param = '@htmllog varchar(max) OUTPUT';

EXEC master..sp_executesql @executesql_sql, @executesql_param, @htmllog = @htmllog output;

SELECT LEN(@htmllog), @htmllog;
GO