【问题标题】:Split string into words in columns将字符串拆分为列中的单词
【发布时间】:2019-03-12 18:15:48
【问题描述】:

我希望在 SQL Server 2014 中将字符串拆分为列中的单词。我找到了一些解决方案,但所有解决方案都以行的形式给出结果。如何将下面的字符串分成列?

“一二三四五”

【问题讨论】:

  • 亲爱的杰西卡。对于您未来的问题,请记住,这个社区的成员在这里自愿利用他们的空闲时间帮助其他开发人员。我们不会也不应该关心问题对提出问题的人来说是否紧急。此外,如果您想快速获得良好的帮助,请提出好的问题。您可以从阅读 How to Ask 开始,特别是对于 SQL 相关问题,请阅读 meta.dba.stackexchange 上名为 Help me write this query in SQL 的优秀帖子。感谢您阅读
  • 反馈:在您的帖子历史记录中,人们曾多次要求查看您的初始尝试,或要求澄清,但这些通常不会出现。请养成在被问到时更改问题的习惯。

标签: sql sql-server tsql sql-server-2014


【解决方案1】:

您可以使用 XML 并按位置抓取元素:

DECLARE @YourString VARCHAR(100)='First Second Third Fourth Fifth';

WITH StringAsXML AS
(
    SELECT CAST('<x>' + REPLACE((SELECT @YourString AS [*] FOR XML PATH('')),' ','</x><x>') + '</x>' AS XML) TheXml
)
SELECT TheXml.value('x[1]/text()[1]','nvarchar(max)') AS FirstElement
      ,TheXml.value('x[2]/text()[1]','nvarchar(max)') AS SecondElement
      ,TheXml.value('x[3]/text()[1]','nvarchar(max)') AS ThirdElement
      ,TheXml.value('x[4]/text()[1]','nvarchar(max)') AS FourthElement
      ,TheXml.value('x[5]/text()[1]','nvarchar(max)') AS FifthElement
FROM StringAsXML;

备注

您可以使用PIVOT条件聚合FROM(VALUES()) 或以上。但是这些方法中的任何一种都需要一组已知的列(已知的元素数量或至少最大数量的元素)。

如果你不能依赖这样的知识,你可以使用动态创建的SQL。这意味着在字符串基础上创建一个工作语句并使用EXEC 进行动态执行。

更新:动态方法

这种方法将处理可变数量的元素

DECLARE @YourString VARCHAR(100)='First Second Third Fourth Fifth';
DECLARE @Delimiter CHAR(1)=' ';
DECLARE @countElements INT = LEN(@YourString)-LEN(REPLACE(@YourString,@Delimiter,''));

DECLARE @Statement VARCHAR(MAX)=
'WITH StringAsXML AS
(
    SELECT CAST(''<x>'' + REPLACE((SELECT ''ReplaceYourString'' AS [*] FOR XML PATH('''')),'' '',''</x><x>'') + ''</x>'' AS XML) TheXml
)
SELECT ReplaceColumnList
FROM StringAsXML;';

DECLARE @columnList VARCHAR(MAX);

WITH cte AS
(
    SELECT 1 AS ElementCounter
          ,CAST('TheXml.value(''x[1]/text()[1]'',''nvarchar(max)'') AS Element_01' AS VARCHAR(MAX)) AS ColStatement
    UNION ALL 
    SELECT cte.ElementCounter+1
          ,cte.ColStatement + CAST(',TheXml.value(''x[' + CAST(cte.ElementCounter+1 AS VARCHAR(10)) + ']/text()[1]'',''nvarchar(max)'') AS Element_' + REPLACE(STR(cte.ElementCounter + 1,2),' ','0') AS VARCHAR(MAX))
    FROM cte
    WHERE cte.ElementCounter <= @countElements
)
SELECT @columnList=(SELECT TOP 1 cte.ColStatement FROM cte ORDER BY cte.ElementCounter DESC)

--replace the string you want to split
SET @Statement = REPLACE(@Statement,'ReplaceYourString',@YourString);
--replace the columnList
SET @Statement = REPLACE(@Statement,'ReplaceColumnList',@columnList);

EXEC(@Statement);

更新 2:我所知道的最小的完全内联和 位置安全 拆分器

试试这个:

DECLARE @inp VARCHAR(200) = 'First Second Third Fourth Fifth';
DECLARE @dlmt VARCHAR(100)=' ';

;WITH
a AS (SELECT n=0, i=-1, j=0 UNION ALL SELECT n+1, j, CHARINDEX(@dlmt, @inp, j+1) FROM a WHERE j > i),
b AS (SELECT n, SUBSTRING(@inp, i+1, IIF(j>0, j, LEN(@inp)+1)-i-1) s FROM a WHERE i >= 0)
SELECT * FROM b;

为了完成它:上面的小分离器结合PIVOT

;WITH
a AS (SELECT n=0, i=-1, j=0 UNION ALL SELECT n+1, j, CHARINDEX(@dlmt, @inp, j+1) FROM a WHERE j > i),
b AS (SELECT n, SUBSTRING(@inp, i+1, IIF(j>0, j, LEN(@inp)+1)-i-1) s FROM a WHERE i >= 0)

SELECT p.* 
FROM b
PIVOT(MAX(s) FOR n IN([1],[2],[3],[4],[5])) p;

【讨论】:

    【解决方案2】:

    您可以使用SQL split string function 将字符串分隔为单词,并使用原始字符串中单词的顺序,您可以使用 PIVOT 查询等 CASE 语句并显示为列

    这是一个示例

    declare @string varchar(max) = 'First Second Third Fourth Fifth'
    
    ;with cte as (
    select
        case when id = 1 then val end as Col1,
        case when id = 2 then val end as Col2,
        case when id = 3 then val end as Col3,
        case when id = 4 then val end as Col4,
        case when id = 5 then val end as Col5
    from dbo.split( @string,' ')
    )
    select
        max(Col1) as Col1,
        max(Col2) as Col2,
        max(Col3) as Col3,
        max(Col4) as Col4,
        max(Col5) as Col5
    from cte
    

    如果无法创建 UDF,可以使用 SQL 代码中的逻辑如下

    请注意,如果您的数据在数据库表列中,您可以简单地替换第一个SQL CTE expression中的列内容

    declare @string varchar(max) = 'First Second Third Fourth Fifth'
    
    ;with cte1 as (
        select convert(xml, N'<root><r>' + replace(@string,' ','</r><r>') + '</r></root>') as rawdata
    ), cte2 as (
      select
        ROW_NUMBER() over (order by getdate()) as id,
        r.value('.','varchar(max)') as val
      from cte1
      cross apply rawdata.nodes('//root/r') as records(r)
    )
    select
        max(Col1) as Col1,
        max(Col2) as Col2,
        max(Col3) as Col3,
        max(Col4) as Col4,
        max(Col5) as Col5
    from (
        select
            case when id = 1 then val end as Col1,
            case when id = 2 then val end as Col2,
            case when id = 3 then val end as Col3,
            case when id = 4 then val end as Col4,
            case when id = 5 then val end as Col5
        from cte2
    ) t
    

    【讨论】:

    • 您好,Eralper,感谢您的友好回复。我无权在 DB 中创建函数。请问可以修改查询吗?
    • 我添加了第二个查询 Jessica
    • 嗨,Eralper,您更新后的查询会起作用,但要提一下:如果字符串包含禁止字符(如&amp;),则会中断。这可能会通过所有内部测试并中断生产......此外,它似乎是可靠的,但没有关于 ROW_NUMBER 的文档,这可以保证元素按预期顺序返回......你可能有一个查看我回答中的最后一个示例,了解 position-safe 内联拆分器。
    • 谢谢Shnugo,使用“SELECT @string AS [*] FOR XML PATH('')”消除和类似字符的技巧非常有趣!
    【解决方案3】:

    您可以将parsename 函数用作:

    create table tab ( str varchar(100));
    
    insert into tab values('First Second Third Fourth Fifth');
    
    with t as
    (
    select replace(str,' ','.') as str
      from tab
    )    
    Select substring(str,1,charindex('.',str)-1) as col_first,
           parsename(substring(str,charindex('.',str)+1,len(str)),4) as col_second,
           parsename(substring(str,charindex('.',str)+1,len(str)),3) as col_third,
           parsename(substring(str,charindex('.',str)+1,len(str)),2) as col_fourth,
           parsename(substring(str,charindex('.',str)+1,len(str)),1) as col_fifth
      from t;
    
    col_first   col_second  col_third   col_fourth  col_fifth
    ---------   ----------  ---------   ----------  ---------
    First       Second      Third       Fourth      Fifth
    

    附注首先,需要将主字符串分成最多三个三个点(.)字符的部分(否则该功能不起作用)。这是对parsename 的限制。

    Rextester Demo

    【讨论】:

    • 给定的例子清楚地表明,有四个以上的部分。 PARSENAME 是——当然! - 这里的错误选择......(在我看来,几乎在任何情况下,字符串拆分都是错误的选择......)
    • @Shnugo 出于这个原因,我将字符串分成两部分,每部分最多包含三个点。
    • @Shnugo 拆分一次就可以了。
    • 我不知道你所说的 拆分一次 是什么意思...对我来说这是错误的工具,因为它是特定的且完全不直观的反向编号.
    猜你喜欢
    • 2011-11-03
    • 2011-06-12
    • 2017-08-16
    • 1970-01-01
    • 2014-06-09
    • 2018-04-06
    • 2023-01-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多