【问题标题】:SQL Split Function and Ordering Issue?SQL 拆分函数和排序问题?
【发布时间】:2013-10-13 03:17:05
【问题描述】:

我有以下Split函数,

ALTER FUNCTION [dbo].[Split](@String varchar(8000), @Delimiter char(1))     
                returns @temptable TABLE (items varchar(8000))     
            as     
            begin
                set @String = RTRIM(LTRIM(@String))
                declare @idx int     
                declare @slice varchar(8000)     

                select @idx = 1     
                    if len(@String)<1 or @String is null  return     

                while @idx!= 0     
                begin     
                    set @idx = charindex(@Delimiter,@String)     
                    if @idx!=0     
                        set @slice = left(@String,@idx - 1)     
                    else     
                        set @slice = @String     

                    if(len(@slice)>0)
                        insert into @temptable(Items) values(@slice)     

                    set @String = right(@String,len(@String) - @idx)     
                    if len(@String) = 0 break     
                end 
            return     
            end

当我写作时,

SELECT Items 
FROM Split('around the home,clean and protect,soaps and air fresheners,air fresheners',',')

这会给我,

air fresheners
around the home
clean and protect
soaps and air fresheners

我需要维持秩序。

【问题讨论】:

标签: sql tsql sql-server-2008-r2


【解决方案1】:

当您的字符串有 1000 个或更多值要拆分时,这将是一个更快的解决方案。对于表值函数,要进行任何排序,您必须在使用位置应用“ORDER BY”。这是因为没有“ORDER BY”的表中的“SELECT”按照惯例没有任何排序。

CREATE FUNCTION [dbo].[Split]
( 
    @String VARCHAR(max), 
    @Delimiter VARCHAR(max)
) 
RETURNS @Data TABLE 
(
    [Order] INT IDENTITY(1,1), 
    [Value] VARCHAR(max)
)
AS
BEGIN 
    DECLARE @x XML = cast('<i>' + replace(@String, @Delimiter, '</i><i>') + '</i>' AS XML)
    INSERT INTO @Data 
    SELECT v.value('.', 'varchar(max)') FROM @x.nodes('i') AS x(v)
    RETURN 
END
GO

【讨论】:

    【解决方案2】:
    declare @Version nvarchar(3000)
    declare @Delimiter char(1) = ','
    declare @result nvarchar(3000)
    set @Version = 'Terça-feira, Quarta-feira, Sexta-feira, Segunda-feira';
    
    with V as (select value v, Row_Number() over (order by (select 0)) n 
        from String_Split(@Version, @Delimiter)
    )
        SELECT @result = STUFF((SELECT ', ' + RTRIM(LTRIM(v))
          FROM V
          ORDER BY CASE RTRIM(LTRIM(v))
                WHEN 'Segunda-feira' then 1 
                WHEN 'Terça-feira' then 2
                WHEN 'Quarta-feira' then 3 
                WHEN 'Quinta-feira' then 4 
                WHEN 'Sexta-feira' then 5
               END FOR XML PATH('')), 1, LEN(@Delimiter), '')
    PRINT @result
    

    【讨论】:

    • 你好威廉。感谢您发布此答案。如果您添加更多详细信息,您可能会获得更多赞成票。例如,您的代码的示例输出和/或解决方案的简要说明。干杯!
    【解决方案3】:

    如果您可以遵守 SQL Server 的兼容级别 130,则可以使用 String_Split() 函数。

    使用此函数和 Row_Number() 函数,您可以返回包含原始序列的表。例如:

    declare @Version nvarchar(128)
    set @Version = '1.2.3';
    
    with V as (select value v, Row_Number() over (order by (select 0)) n 
        from String_Split(@Version, '.')
    )
        select
            (select v from V where n = 1) Major,
            (select v from V where n = 2) Minor,
            (select v from V where n = 3) Revision
    

    请注意,Row_Number 需要排序,但如果您传递文字值,则结果将按解析后的顺序排列。不保证将来的 SQL Server 版本会出现这种情况,因为根据 String_Split 文档,没有官方订购。我怀疑微软会打破这一点,至少在引入一个返回命令的函数版本之前,但同时,在编写决定是否发射导弹的代码时,最好不要依赖这个命令。

    返回:

    Major Minor Revision
    ----- ----- --------
    1     2     3
    

    【讨论】:

    • 您的查询不保证订单,用户在执行时可能会得到不同的订单! (order by (select 0)) 说明你没有订单,函数 STRING_SPLIT 不保证返回相同的订单!
    • 添加使用 ROW_NUMBER() 确实 not 克服了 STRING_SPLIT() 不保证其结果顺序的事实(如在在线手册)。如果排序很重要,请不要使用此模式。
    • 我猜你们没有阅读我的免责声明?我明确表示,如果他们改变实现,这在理论上可能会中断;目前它可以工作,并且与其他解决方案不同,它简单且富有表现力。
    【解决方案4】:

    一个更简单的函数:

    CREATE FUNCTION dbo.SplitStrings_Ordered
    (
        @List       nvarchar(MAX),
        @Delimiter  nvarchar(255)
    )
    RETURNS TABLE
    AS
    RETURN 
    (
      SELECT [Index] = CHARINDEX(@Delimiter, @List + @Delimiter, Number),
             Item = SUBSTRING(@List, Number, CHARINDEX(@Delimiter, 
                    @List + @Delimiter, Number) - Number)
        FROM 
        (
          SELECT ROW_NUMBER() OVER (ORDER BY [object_id]) FROM sys.all_objects
        ) AS n(Number)
        WHERE Number <= CONVERT(INT, LEN(@List))
        AND SUBSTRING(@Delimiter + @List, Number, LEN(@Delimiter)) = @Delimiter
    );
    GO
    

    示例用法:

    DECLARE @s nvarchar(MAX) = N',around the home,clean and protect,soaps and air'
      + ' fresheners,air fresheners';
    
    SELECT Item FROM dbo.SplitStrings_Ordered(@s, N',') ORDER BY [Index];
    

    或者从按输入排序的表中返回订单:

    SELECT o.OrderID
      FROM dbo.Orders AS o
      INNER JOIN dbo.SplitStrings_Ordered('123,789,456') AS f
      ON o.OrderID = CONVERT(int, f.Item)
      ORDER BY f.[Index];
    

    【讨论】:

    • @user960567 请注意次要更新,以防您使用 > 1 个字符的分隔符。
    • 谢谢。你的功能是不是更好(在性能方面)比上面的更好?
    • @user960567 我想是这样,因为它不依赖于昂贵的循环和一堆变量赋值。也就是说,我的车比你的快吗?取决于很多因素。请使用您的数据和使用模式在您的硬件上进行测试。
    【解决方案5】:

    您的函数将需要设置一个订单列(本示例中的 seq):

    ALTER FUNCTION [dbo].[Split](@String varchar(8000), @Delimiter char(1))     
                returns @temptable TABLE (seq int, items varchar(8000))     
            as     
            begin
                set @String = RTRIM(LTRIM(@String))
                declare @idx int     
                declare @seq int
                declare @slice varchar(8000)     
    
                set @seq=1
    
                select @idx = 1     
                    if len(@String)<1 or @String is null  return     
    
                while @idx!= 0     
                begin     
                    set @idx = charindex(@Delimiter,@String)     
                    if @idx!=0     
                        set @slice = left(@String,@idx - 1)     
                    else     
                        set @slice = @String     
    
                    if(len(@slice)>0)
                    begin
                        set @seq = @seq + 1
                        insert into @temptable(seq, Items) values(@seq,@slice)     
                    end
    
                    set @String = right(@String,len(@String) - @idx)     
                    if len(@String) = 0 break     
                end 
            return     
            end
    GO
    SELECT * FROM Split('around the home,clean and protect,soaps and air fresheners,air fresheners',',') order by seq 
    

    【讨论】:

      猜你喜欢
      • 2020-06-12
      • 1970-01-01
      • 1970-01-01
      • 2012-06-14
      • 2012-11-20
      • 1970-01-01
      • 2010-11-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多