2016-07-07 64 views
2

在某些情況下創建動態SQL,我得到了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); 
+0

如果更改了基礎數據,使最後的「25」將不會發生,你得到「36」或者你得到一個空字符串或NULL ? –

回答

1

對不起只是重新閱讀,發現複製功能表的結果可以防止問題的再現。

除非您想爲基表和表值函數創建腳本,以允許其他人重現該問題,否則任何人都可以做的就是猜測。

我的第一個猜測是,你的函數沒有返回你想的結果,但如果是的話,也有一些是關於TVF和您使用的是修建從查詢字符串的無證技術之間的相互作用結果。

我強調無證提醒你,正是這種技術所使用的,這是不可能的說,存在無證行爲「錯誤」。 SQL從來沒有打算按照您使用它的方式工作,並且這只是偶然的,大多數情況下這種方式正常工作,但不能保證它會一直以這種方式工作,或者根本不會在將來的版本中工作。即使使用TOP n修復它也沒有記錄,並且可能無法在未來版本的SQL Server中使用。

更好的解決方案是開始使用STUFF()來做你的字符串連接。在這個網站和互聯網上的其他地方已經有很多例子了。

關於「爲什麼這不起作用?」這個問題,我懷疑你會得到的最好答案是:「這是無證的行爲,誰知道?

EDIT響應於評論:

  1. 的無證技術我指的是一個字符串變量的使用+ =建築物。請參閱this article,然後向下滾動到名爲「不可靠方法」的部分。您正在使用的方法是列出的第二個方法,即「在SELECT中具有可變級聯的標量UDF」,儘管您沒有在UDF中使用它。但是,SELECT @var = @var + SomeData...的技術是無證的,因此不可靠的部分。

  2. 我使用的「使用STUFF()的解決方案」與SqlZim在他的回答中提出的相同。同一解決方案同時採用了STUFF()FOR XML。作爲簡寫,我將其稱爲使用STUFF(),因爲我知道對該關鍵字的搜索會導致該解決方案。

+0

尊敬的Tab Alleman, 您認爲哪種技術是無證的? 我已經能夠得到使用表而不是TVF轉載的問題。 我同意'Top n'不是解決方案。 – Kilren

+0

我測試過沒有確定結果的STUFF(),CONCAT()返回相同的結果。 – Kilren

+0

請參閱上面對我的回答的編輯。如果您可以使用Table(無TVF)重現問題,那麼您是否可以編輯您的問題並添加一個腳本來創建一個表格,以重新發布您發佈的查詢時的問題? –

1

我能夠重現您的問題的唯一方法是刪除@ 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 
+0

謝謝SqlZim, 使用FOR XML接口來處理簡化版本。 我會盡力實現明天的全部功能並回復您。 即便如此我還是好奇;) – Kilren

+0

FYI 我@@版本是: '的Microsoft SQL Server 2012(SP3-CU2)(KB3137746) - 11.0.6523.0(X64) \t 2016年3月2日21:29:在Windows NT 6.1上的企業版(64位)(內部版本7601:Service Pack 1)'版本(c)Microsoft Corporation \t Windows NT 6.1上的企業版 – Kilren

相關問題