sql将字符串拆分为始终相同数量的片段

Posted

技术标签:

【中文标题】sql将字符串拆分为始终相同数量的片段【英文标题】:sql split string into always same amount of pieces 【发布时间】:2018-08-16 14:40:51 【问题描述】:

我需要使用分隔符(例如,)将字符串拆分为多个部分

在任何情况下我都需要相同数量的块,如果文本太短,函数应该返回空字符串。

我想将其存储为标量函数,因为与表值函数相比,它更适合在需要的地方使用。

我正在寻找类似 myFunction (inputstring, chunknumber) 的函数

例如:输入字符串 = abc, def, ghi

预期的样本结果:

myFunction (inputstring, 1) --> 'abc'

myFunction (inputstring, 3) --> 'ghi'

myFunction (inputstring, 5) --> ''

【问题讨论】:

您使用的是哪个 dbms? MS SQL Server 2014 标量函数比表值函数更好吗?假设您的表函数是内联的(单个语句),它将比标量函数快得多。 【参考方案1】:

在 SQL Server 中,您可以使用以下用户定义函数:

CREATE FUNCTION [dbo].[myFunction]
(   
    @String         VARCHAR(MAX), 
    @ChunkNumber    INT
)
RETURNS VARCHAR (100)  
AS
BEGIN     
    DECLARE @Incrementer AS INT = 1;   
    DECLARE @SliceValue VARCHAR(MAX);
    DECLARE @Delimiter AS CHAR(1) = ',';
    DECLARE @ReturnObject AS VARCHAR (100) = '';
    DECLARE @SplitedValues TABLE (Id INT IDENTITY NOT NULL, Item VARCHAR (100));

    IF LEN(@String) < 1 OR @String IS NULL  
        RETURN @ReturnObject   

    WHILE @Incrementer != 0     
    BEGIN     
        SET @Incrementer = CHARINDEX(@Delimiter, @String);

        IF @Incrementer != 0     
            SET @SliceValue = LEFT(@String, @Incrementer - 1)     
        ELSE     
            SET @SliceValue = @String     

        IF(LEN(@SliceValue) > 0)
            INSERT INTO @SplitedValues(Item) VALUES (@SliceValue)     

        SET @String = RIGHT(@String, LEN(@String) - @Incrementer)     
        IF LEN(@String) = 0 BREAK     
    END 

    SELECT @ReturnObject = Item FROM @SplitedValues WHERE Id = @ChunkNumber;

RETURN @ReturnObject  

END

示例执行:

SELECT [dbo].[myFunction] ('abc, def, ghi', 5) -- returns empty
SELECT [dbo].[myFunction] ('abc, def, ghi', 3) -- returns ghi
SELECT [dbo].[myFunction] ('abc, def, ghi', 1) -- returns abc

【讨论】:

对分离器使用循环可能是最糟糕的方法。有这么多基于集合的拆分器将把这扇门吹走。【参考方案2】:

这里是返回位置编号的基于集合的拆分器至关重要的地方。谢天谢地 Jeff Moden 有这样一个分离器。 http://www.sqlservercentral.com/articles/Tally+Table/72993/

他的分离器的代码是:

CREATE FUNCTION [dbo].[DelimitedSplit8K]
--===== Define I/O parameters
        (@pString VARCHAR(8000), @pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE!  IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
     -- enough to cover VARCHAR(8000)
  WITH E1(N) AS (
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
                ),                          --10E+1 or 10 rows
       E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
       E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
 cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
                     -- for both a performance gain and prevention of accidental "overruns"
                 SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
                ),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
                 SELECT 1 UNION ALL
                 SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
                ),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
                 SELECT s.N1,
                        ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
                   FROM cteStart s
                )
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
 SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
        Item       = SUBSTRING(@pString, l.N1, l.L1)
   FROM cteLen l
;

现在对于您的情况,您需要另一个功能。没问题,我们可以利用他的分离器来解决这个问题。但是我们还需要一个计数表。希望您拥有其中之一,但如果没有,这里就是我使用的那个。我将其保留为视图,因为它非常有用,而且速度快如闪电。我最初也是从 Jeff Moden 那里了解到的这项技术。

create View [dbo].[cteTally] as

WITH
    E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
    E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
    E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
    cteTally(N) AS 
    (
        SELECT  ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
    )
select N from cteTally
GO

现在我们可以开始创建新函数了。有了这两个其他组件就非常简单了。

create function GetChunkValue
(
    @Inputstring varchar(100)
    , @ChunkNumber int
) returns table as
    return
    select ChunkValue = isnull(s.Item, '')
    from cteTally t
    left join dbo.DelimitedSplit8K(@Inputstring, ',') s on s.ItemNumber = t.N
    where t.N = @ChunkNumber

最后是一些使用它的示例代码。

select *
from GetChunkValue('abc, def, ghi', 2)

【讨论】:

以上是关于sql将字符串拆分为始终相同数量的片段的主要内容,如果未能解决你的问题,请参考以下文章

将 JavaScript 字符串拆分为固定长度的片段

将逗号分隔的字符串拆分为 R 中定义的数量

如何在一定数量的字符后将 NSString 拆分为多个字符串

当内容在单引号中时,使用 PowerShell 将 SQL 文件/字符串拆分为批处理排除拆分

将 DataFrame 转换为 RDD 并将 RDD 动态拆分为与 DataFrame 相同数量的 Columns

用空格(或任何字符)将文本单元格拆分为任意数量的单词,重复单词