2013-10-18 70 views
5

我需要創建一個函數,它將返回分隔字符串的第n個元素。使用T-SQL,返回字符串中的第n個分隔元素

對於數據遷移項目,我使用SQL腳本將存儲在SQL Server數據庫中的JSON審計記錄轉換爲結構化報告。目標是在沒有任何代碼的情況下提供腳本使用的sql腳本和sql函數。

(這是一個短期的修補程序將同時使用一個新的審覈功能被添加的ASP.NET/MVC應用程序)

有分隔字符串可用表例子不乏其人。 我選擇的公共表表達式例如http://www.sqlperformance.com/2012/07/t-sql-queries/split-strings

例子:我想從返回67「1,222,2,67,888,1111」

回答

5

這是我最初的解決方案... 它是基於工作通過Aaron Bertrand http://www.sqlperformance.com/2012/07/t-sql-queries/split-strings

我只是改變了返回類型,使其成爲一個標量函數。

例子: SELECT dbo.GetSplitString_CTE( '1,222,2,67,888,1111', '',4)

CREATE FUNCTION dbo.GetSplitString_CTE 
(
    @List  VARCHAR(MAX), 
    @Delimiter VARCHAR(255), 
    @ElementNumber int 
) 
RETURNS VARCHAR(4000) 
AS 
BEGIN 

    DECLARE @result varchar(4000)  
    DECLARE @Items TABLE (position int IDENTITY PRIMARY KEY, 
          Item VARCHAR(4000) 
         ) 

    DECLARE @ll INT = LEN(@List) + 1, @ld INT = LEN(@Delimiter); 

    WITH a AS 
    (
     SELECT 
      [start] = 1, 
      [end] = COALESCE(NULLIF(CHARINDEX(@Delimiter, 
         @List, @ld), 0), @ll), 
      [value] = SUBSTRING(@List, 1, 
        COALESCE(NULLIF(CHARINDEX(@Delimiter, 
         @List, @ld), 0), @ll) - 1) 
     UNION ALL 
     SELECT 
      [start] = CONVERT(INT, [end]) + @ld, 
      [end] = COALESCE(NULLIF(CHARINDEX(@Delimiter, 
         @List, [end] + @ld), 0), @ll), 
      [value] = SUBSTRING(@List, [end] + @ld, 
        COALESCE(NULLIF(CHARINDEX(@Delimiter, 
         @List, [end] + @ld), 0), @ll)-[end][email protected]) 
     FROM a 
     WHERE [end] < @ll 
    ) 
    INSERT @Items SELECT [value] 
    FROM a 
    WHERE LEN([value]) > 0 
    OPTION (MAXRECURSION 0); 

    SELECT @result=Item 
    FROM @Items 
    WHERE [email protected] 

    RETURN @result; 
END 
GO 
+3

這是相當的開銷先用沉重的遞歸CTE來分割你的字符串,只是挑out *第n個元素*。這可以做得更容易... – Shnugo

1

在精神失常的罕見一刻,我只是想,分裂是,如果要容易得多我們使用XML解析出來給我們:

(使用@Gary金德爾的答案變量)

declare @xml xml 
set @xml = '<split><el>' + replace(@list,@Delimiter,'</el><el>') + '</el></split>' 

select 
    el = split.el.value('.','varchar(max)') 
from @xml.nodes('/split/el') split(el)) 

此列出該字符串的所有元素,由指定的茶分裂racter。

我們可以使用XPath測試過濾掉空值,以及進一步的XPath測試來限制這對我們感興趣的元素在全加里的功能變爲:

alter FUNCTION dbo.GetSplitString_CTE 
(
    @List  VARCHAR(MAX), 
    @Delimiter VARCHAR(255), 
    @ElementNumber int 
) 
RETURNS VARCHAR(max) 
AS 
BEGIN 

     declare @xml xml 
     set @xml = '<split><el>' + replace(@list,@Delimiter,'</el><el>') + '</el></split>' 

     declare @ret varchar(max) 
     set @ret = (select 
       el = split.el.value('.','varchar(max)') 
     from @xml.nodes('/split/el[string-length(.)>0][position() = sql:variable("@elementnumber")]') split(el)) 

     return @ret 

END 
+0

偉大的解決方案。我想知道什麼是更昂貴的表變量與身份或XML。 Jon,我將不得不創建你的函數,並在一個大的結果集上與CTE解決方案並行運行,並查看哪些使用了更多的資源。 –

+0

有興趣知道 - CTE做了很多字符串操作。對於你的例子(數字只在元素中),我沒有打擾解析出非法並用xml實體替換(例如''=> @apos;')。對於更復雜的字符串,您可能需要(但是它通常不是分割操作的情況) –

+0

只需重新閱讀您的評論。如果你想存儲的東西,你仍然可以很容易地將輸出保存到表變量。比較是在兩個解析機制之間進行的。 –

1

,你可以把這個選擇UFN。如果你需要,你可以自定義它來指定分隔符。在這種情況下,你的ufn將有兩個輸入。數字N和分隔符使用。

DECLARE @tlist varchar(max)='10,20,30,40,50,60,70,80,90,100' 
    DECLARE @i INT=1, @nth INT=3 
    While len(@tlist) <> 0 
    BEGIN 
      IF @[email protected] 
      BEGIN 
       select Case when charindex(',',@tlist) <> 0 Then LEFT(@tlist,charindex(',',@tlist)-1) 
          Else @tlist 
        END 
      END 

       Select @tlist = Case when charindex(',',@tlist) <> 0 Then substring(@tlist,charindex(',',@tlist)+1,len(@tlist)) 
          Else '' 
          END 

      SELECT @[email protected]+1 
    END 
-1

我沒有足夠的評論聲望,所以我添加了一個答案。請適當調整。

我跟加里·金德爾的答案的情況下的一個問題那裏是兩個分隔符

如果你 SELECT * FROM dbo.GetSplitString_CTE(「ABC ^高清^^ GHI」,「^」之間沒有任何東西, 3) 你 GHI的 而不是一個空字符串

如果您註釋掉 WHERE LEN([值])> 0 線,你會得到期望的結果

0

我無法對Gary的溶膠評論因爲我的信譽低下

我知道Gary引用了另一個鏈接。

我一直在努力瞭解爲什麼我們需要這個變量

@ld INT = LEN(@Delimiter) 

我也弄不明白,爲什麼CHARINDEX具有分隔符的長度的位置開始,@ld

我與許多測試單個字符分隔符的例子,他們的工作。大多數時候,分隔符是單個字符。但是,由於顯影劑包含在LD作爲分隔符的長度,代碼必須對具有一個以上的字符

在這種情況下定界符工作,下列情況下將會失敗

11 ,,, 22 ,, ,33 ,,, 44 ,,, 55 ,,,

我從這個鏈接的代碼克隆。 http://codebetter.com/raymondlewallen/2005/10/26/quick-t-sql-to-parse-a-delimited-string/

我已經嘗試了各種方案,包括有多個字符分隔符

alter FUNCTION [dbo].[split1] 
(
    @string1 VARCHAR(8000) -- List of delimited items 
    , @Delimiter VARCHAR(40) = ',' -- delimiter that separates items 
    , @ElementNumber int 
) 
RETURNS varchar(8000) 
AS 
BEGIN 
    declare @position int 
    declare @piece varchar(8000)='' 
    declare @returnVal varchar(8000)='' 
    declare @Pattern varchar(50) = '%' + @Delimiter + '%' 
    declare @counter int =0 
    declare @ld int = len(@Delimiter) 
    declare @ls1 int = len (@string1) 
    declare @foundit int = 0 

    if patindex(@Pattern , @string1) = 0 
     return '' 

    if right(rtrim(@string1),1) <> @Delimiter 
     set @string1 = @string1 + @Delimiter 

    set @position = patindex(@Pattern , @string1) + @ld -1 
    while @position > 0 
    begin 
     set @counter = @counter +1 
     set @ls1 = len (@string1) 
     if (@ls1 >= @ld) 
      set @piece = left(@string1, @position - @ld) 
     else 
      break 
     if (@counter = @ElementNumber) 
     begin 
      set @foundit = 1 
       break 
     end 
     if len(@string1) > 0 
     begin 
      set @string1 = stuff(@string1, 1, @position, '') 
      set @position = patindex(@Pattern , @string1) + @ld -1 
     end 
     else 
     set @position = -1 
    end 


    if @foundit =1 
     set @returnVal = @piece 
    else 
     set @returnVal = '' 
    return @returnVal 
+1

看起來你在這裏問一個問題。你是?如果沒有,請刪除你問的部分。 –

11

這是最簡單的答案rerieve 67(類型安全!):

SELECT CAST('<x>' + REPLACE('1,222,2,67,888,1111',',','</x><x>') + '</x>' AS XML).value('/x[4]','int') 

這個問題是不是關於字符串拆分的方法,而是關於如何獲得第n個元素。最簡單的,完全inlineable方式將IMO是這樣的:

這是一個真正的一行得到第2部分用空格分隔:

DECLARE @input NVARCHAR(100)=N'part1 part2 part3'; 
SELECT CAST(N'<x>' + REPLACE(@input,N' ',N'</x><x>') + N'</x>' AS XML).value('/x[2]','nvarchar(max)') 

當然,你可以使用變量定界符和位置(使用sql:column檢索直接從查詢的值的位置):

DECLARE @dlmt NVARCHAR(10)=N' '; 
DECLARE @pos INT = 2; 
SELECT CAST(N'<x>' + REPLACE(@input,@dlmt,N'</x><x>') + N'</x>' AS XML).value('/x[sql:variable("@pos")][1]','nvarchar(max)') 

如果字符串可能包括禁止的字符,你仍然可以這樣做。首先在您的字符串上首先使用FOR XML PATH,以隱式地使用擬合轉義序列替換所有禁止的字符。

這是一個非常特殊的情況,如果 - 另外 - 您的分隔符是分號。在這種情況下,我第一次更換分隔符爲「#DLMT#」,並替換這最後的XML標籤:

SET @input=N'Some <, > and &;Other äöü@€;One more'; 
SET @dlmt=N';'; 
SELECT CAST(N'<x>' + REPLACE((SELECT REPLACE(@input,@dlmt,'#DLMT#') AS [*] FOR XML PATH('')),N'#DLMT#',N'</x><x>') + N'</x>' AS XML).value('/x[sql:variable("@pos")][1]','nvarchar(max)'); 
+1

這是一些嚴重的事情!驚人 –

相關問題