使用+ =创建动态SQL在某些情况下会截断以前的内容

时间:2016-07-07 14:50:20

标签: sql-server sql-server-2012 concatenation

在某些情况下,当我尝试使用自串联生成字符串时,我在SQL Server 2012(最新更新)上遇到了奇怪的行为

@Str += ... 

@Str = @Str + ...

它正在截断查询中变量的先前内容,这是连接NULL值时的预期行为,除非我不是......

这是一个简化版本的代码,减少到最小,以重现我的实例上的错误。很难再现,因为只是将函数结果复制到临时表(在我的情况下是不可能的)修复它,所以我怀疑在查询规划或优化方面。

DECLARE @CTESQL VARCHAR(MAX)= '';

SELECT 
    --TOP 4096--Workaround for SQL SERVER bug dropping previous text in some cases (4096 = max statement in a select clause)
    @CTESQL+= CASE WHEN 1 = ROW_NUMBER() OVER (ORDER BY PvtColumnName) THEN '1'
              ELSE CASE WHEN LAG(PvtColumnName) OVER (ORDER BY PvtColumnName) <> ISNULL(PvtColumnName,
                                                              ColumnName)
                        THEN '2'
                   ELSE '3'
                   END
              END + CASE WHEN PvtColumnName IS NULL THEN '4'
                    ELSE (CASE WHEN 1 = ROW_NUMBER() OVER (ORDER BY t.PvtColumnName DESC)
                               THEN '5'
                          ELSE '6'
                          END)
                    END
FROM
    dbo.ImportDefinition('stgPopulation') t
ORDER BY
    PvtColumnName
  , ColumnId

PRINT (@CTESQL);

表函数'ImportDefinition'返回以下数据:

PvtColumnName       ColumnId    ColumnName
------------------- ----------- --------------------
NULL                3           Country
NULL                2           GMPSubRegion
NULL                4           ISO_Ctry
NULL                9           Source
AgeGroupCode        6           Total
AreaTypeCode        6           Total
AgeGroupCode        7           Under5
AreaTypeCode        7           Under5
AgeGroupCode        8           Urban
AreaTypeCode        8           Urban
NULL                1           RegionFullName
NULL                5           Year

预期结果是:

343414343434363636363625

SQL Server的实际结果是:

25

一个简单的解决方法是使用'TOP n'来修复它,但我不知道为什么而且它很脏。

我有一些希望,强迫MAXDOP 1会有所帮助,但没有运气。

这是我第二次遇到这个问题,所以尽管有多种解决方法我真的很想了解发生了什么,或者某处是否有错误。

感谢您的专业知识。

修改 这是一个允许重现相同行为的脚本:

IF OBJECT_ID('dbo.MyTable', 'U') IS NOT NULL
    DROP TABLE dbo.MyTable;
CREATE TABLE dbo.MyTable
    (
     F1 VARCHAR(255) NOT NULL
   , F2 NVARCHAR(4000) NULL
    )
ON  [PRIMARY];

GO
INSERT  INTO dbo.MyTable
        (F1, F2)
VALUES
        ('foo', 'a')
,       ('faa', 'b')
,       ('fuu', 'a');


DECLARE @CTESQL VARCHAR(MAX)= '';

SELECT
    @CTESQL+= CASE WHEN 1 = ROW_NUMBER() OVER (ORDER BY F2)
                   THEN '1'
                   ELSE CASE WHEN LAG(F2) OVER (ORDER BY F2) <> ISNULL(F2,
                                                              F1)
                             THEN '2'
                             ELSE '3'
                        END
              END + CASE WHEN F2 IS NULL THEN '4'
                         ELSE (CASE WHEN 1 = ROW_NUMBER() OVER (ORDER BY F2 DESC)
                                    THEN '5'
                                    ELSE '6'
                               END)
                    END
FROM
    MyTable
ORDER BY
    F2;

PRINT (@CTESQL);

2 个答案:

答案 0 :(得分:1)

很抱歉只是重新阅读并注意到将该功能的结果复制到表格可以防止再现该问题。

除非您想为基表和表值函数创建允许其他人重现问题的脚本,否则任何人都可以做到最好的猜测。

我的第一个猜测是你的功能没有返回你想到的结果,但如果是,那么TVF和你用来构建的未记录的技术之间的相互作用就会有所体现来自查询结果的字符串。

我强调无证件提醒您,这正是您正在使用的技术,并且无法说未记录的行为中存在“错误”。 SQL从来没有打算以你使用它的方式工作,并且它恰好在大多数时间以这种方式工作,但不能保证它将一直以这种方式工作,或者在将来的版本中都是如此。即使用TOP n修复它也没有记录,可能无法在SQL Server的未来版本中使用。

更好的解决方案是开始使用STUFF()进行字符串连接。已经有很多关于如何在这个网站和互联网上做到这一点的例子。

关于“为什么这不起作用?”这个问题,我怀疑你得到的最佳答案是:“这是无证件的行为。谁知道?”

编辑回应评论:

  1. 我所指的未记载的技术是使用+ =构建字符串变量。请参阅this article,然后向下滚动到“不可靠的方法”部分。您正在使用的方法是列出的第二个,“SELECT中带有变量串联的标量UDF”,尽管您没有在UDF中使用它。尽管如此,SELECT @var = @var + SomeData...的技术仍然是未记录的部分,因此不可靠。

  2. 我所指的“使用STUFF()的解决方案与SqlZim在他的回答中提出的解决方案相同”。相同的解决方案同时使用STUFF()FOR XML。作为简写,我将其称为使用STUFF(),因为我知道搜索该关键字会导致该解决方案。

答案 1 :(得分:1)

我能够重现您的问题的唯一方法是删除&#39; +&#39;在@ctesql + =。

您可以试试下面的stuff()版本,看看是否有同样的问题。

use TempDb
go
set nocount on;
--if exists (select * from tempdb.sys.objects where name like '#ImportDefinition%') begin; drop table #ImportDefinition; end;
--/*
if not exists (select * from tempdb.sys.objects where name like '#ImportDefinition%')
begin;
create table #ImportDefinition (PvtColumnName nvarchar(16) ,ColumnId smallint ,ColumnName nvarchar(16) ) 
insert into #ImportDefinition values  (null ,'3' ,'Country') ,(null ,'2' ,'GMPSubRegion') ,(null ,'4' ,'ISO_Ctry') ,(null ,'9' ,'Source') ,('AgeGroupCode' ,'6' ,'Total') ,('AreaTypeCode' ,'6' ,'Total') ,('AgeGroupCode' ,'7' ,'Under5') ,('AreaTypeCode' ,'7' ,'Under5') ,('AgeGroupCode' ,'8' ,'Urban') ,('AreaTypeCode' ,'8' ,'Urban') ,(null ,'1' ,'RegionFullName') ,(null ,'5' ,'Year');
end;
-- select * from #ImportDefinition
--*/
declare @ctesql varchar(max)= '';
--/*
select 
 @ctesql+=(case when 1 = row_number() over (order by pvtcolumnname) then '1'
                when lag(pvtcolumnname) over (order by pvtcolumnname) <> isnull(pvtcolumnname, columnname) then '2'
                    else '3'
              end) 
        + (case when pvtcolumnname is null then '4'
                when 1 = row_number() over (order by t.pvtcolumnname desc) then '5'
                  else '6'
              end)
  from #importdefinition t
  order by pvtcolumnname, columnid;
print (@ctesql);

declare @ForXmlPath varchar(max)
select @ForXmlPath = stuff((select 
          (case when 1 = row_number() over (order by pvtcolumnname) then '1'
                when lag(pvtcolumnname) over (order by pvtcolumnname) <> isnull(pvtcolumnname, columnname) then '2'
                    else '3'
              end) 
        + (case when pvtcolumnname is null then '4'
                when 1 = row_number() over (order by t.pvtcolumnname desc) then '5'
                  else '6'
              end)
    from #importdefinition t
    order by pvtcolumnname, columnid
  for xml path (''), type).value('.','varchar(max)'),1,0,'');
print @ForXmlPath;
--*/
print char(10);
print @@version;
declare @options int = @@options;
print 'disable_def_cnst_chk'   + case when 1 & @options = 1         then ' on' else ' off' end;
print 'implicit_transactions'  + case when 2 & @options = 2         then ' on' else ' off' end;
print 'cursor_close_on_commit' + case when 4 & @options = 4         then ' on' else ' off' end;
print 'ansi_warnings'          + case when 8 & @options = 8         then ' on' else ' off' end;
print 'ansi_padding'           + case when 16 & @options = 16       then ' on' else ' off' end;
print 'ansi_nulls'             + case when 32 & @options = 32       then ' on' else ' off' end;
print 'arithabort'             + case when 64 & @options = 64       then ' on' else ' off' end;
print 'arithignore'            + case when 128 & @options = 128     then ' on' else ' off' end;
print 'quoted_identifier'      + case when 256 & @options = 256     then ' on' else ' off' end;
print 'nocount'                + case when 512 & @options = 512     then ' on' else ' off' end;
print 'ansi_null_dflt_on'      + case when 1024 & @options = 1024   then ' on' else ' off' end;
print 'ansi_null_dflt_off'     + case when 2048 & @options = 2048   then ' on' else ' off' end;
print 'concat_null_yields_null'+ case when 4096 & @options = 4096   then ' on' else ' off' end;
print 'numeric_roundabort'     + case when 8192 & @options = 8192   then ' on' else ' off' end;
print 'xact_abort'             + case when 16384 & @options = 16384 then ' on' else ' off' end;
go

结果如下:

343414343434363636363625
343414343434363636363625


Microsoft SQL Server 2012 - 11.0.5058.0 (X64) 
    May 14 2014 18:34:29 
    Copyright (c) Microsoft Corporation
    Developer Edition (64-bit) on Windows NT 6.1 <X64> (Build 7601: Service Pack 1) (Hypervisor)

disable_def_cnst_chk off
implicit_transactions off
cursor_close_on_commit off
ansi_warnings on
ansi_padding on
ansi_nulls on
arithabort on
arithignore off
quoted_identifier on
nocount on
ansi_null_dflt_on on
ansi_null_dflt_off off
concat_null_yields_null on
numeric_roundabort off
xact_abort off