【问题标题】:Split a string and retain character position拆分字符串并保留字符位置
【发布时间】:2025-06-12 20:10:01
【问题描述】:

我有一个大字符串 (nvarchar(MAX)),我试图拆分 2 并确定它们是原始字符串的哪一部分。

例子

字符串:

5;718;0;1071;1.23|0;750;0;997;1.25|0;750;0;997;1.25|0;750;0;997;1.25|0;750;0;997;1.25|0;750;0;997;1.25

我首先需要根据'|' 字符拆分字符串,如下所示:

5;718;0;1071;1.23

0;750;0;997;1.25

0;750;0;997;1.25

0;750;0;997;1.25

0;750;0;997;1.25

0;750;0;997;1.25

然后我会根据';' 字符拆分每个:

所以5;718;0;1071;1.23 会分裂成:

5
718
0
1071
1.23

我知道我可以在 '|' 上执行 string_split,然后在 ';' 上执行另一个 string_split但这不会保持顺序或识别结果是从字符串的哪个部分拆分出来的,不幸的是,我在尝试使用 OPENJSON() 时无法获得我正在寻找的结果:

根据上面的示例,我需要一个可以识别718 来自第一组和所述组中的第二项的结果。

【问题讨论】:

    标签: sql-server sql-server-2016


    【解决方案1】:

    这是一个可以解析字符串并保持序列的选项

    示例

    Declare @S varchar(max) = '5;718;0;1071;1.23|0;750;0;997;1.25|0;750;0;997;1.25|0;750;0;997;1.25|0;750;0;997;1.25|0;750;0;997;1.25'
    
    Select Seq1 = A.RetSeq
          ,Val1 = A.RetVal
          ,Seq2 = B.RetSeq
          ,Val2 = B.RetVal
     From  [dbo].[tvf-Str-Parse](@S,'|') A
     Cross Apply [dbo].[tvf-Str-Parse](A.RetVal,';') B
    

    退货

    感兴趣的功能

    CREATE FUNCTION [dbo].[tvf-Str-Parse] (@String varchar(max),@Delimiter varchar(10))
    Returns Table 
    As
    Return (  
        Select RetSeq = row_number() over (order by 1/0)
              ,RetVal = ltrim(rtrim(B.i.value('(./text())[1]', 'varchar(max)')))
        From  (Select x = Cast('<x>' + replace((Select replace(@String,@Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A 
        Cross Apply x.nodes('x') AS B(i)
    );
    

    编辑 - 表更新

    Declare @YourTable table (ID int,SomeCol varchar(max))
    Insert Into @YourTable values
    (1,'5;718;0;1071;1.23|0;750;0;997;1.25|0;750;0;997;1.25|0;750;0;997;1.25|0;750;0;997;1.25|0;750;0;997;1.25')
    
    
    Select A.ID
          ,Seq1 = B.RetSeq
          ,Val1 = B.RetVal
          ,Seq2 = C.RetSeq
          ,Val2 = C.RetVal
     From  @YourTable A
     Cross Apply [dbo].[tvf-Str-Parse](SomeCol,'|') B
     Cross Apply [dbo].[tvf-Str-Parse](B.RetVal,';') C
    

    【讨论】:

    • 假设我可以通过该函数传递一列来代替指定每个单独的字符串,这是否正确?
    • @Tom 绝对。它将作为交叉应用。等一下,我会更新我的答案
    • @Tom See EDIT - 更新表格
    【解决方案2】:

    这是我的老学校:

    declare @v nvarchar(max) = N'5;718;0;1071;1.23|0;750;0;997;1.25|0;750;0;997;1.25|0;750;0;997;1.25|0;750;0;997;1.25|0;750;0;997;1.25'
    
    
    select a.value('.', 'nvarchar(max)') [value], v2.col, rnInternal,ROW_NUMBER() over(partition by rnInternal order by rnInternal, Split.a) rnExternal
    from 
    (
        select cast('<M>' + REPLACE(v.[value], ';', '</M><M>') + '</M>' AS XML) as col, rnInternal
        from 
        (
            select 
                a.value('.', 'nvarchar(max)') [value],
                ROW_NUMBER() over(order by Split.a) rnInternal
            from
                (select cast('<M>' + REPLACE(@v, '|', '</M><M>') + '</M>' AS XML) as col) as A
                CROSS APPLY A.col.nodes ('/M') AS Split(a)
            where
                a.value('.', 'nvarchar(max)') <> ''
        ) v
    ) v2
    CROSS APPLY v2.col.nodes ('/M') AS Split(a)
    order by rnInternal, rnExternal
    

    【讨论】:

      【解决方案3】:

      您对STRING_SPLIT() 的看法是正确的(正如documentation 中提到的那样输出行可能是任何顺序),但您可以使用基于 JSON 的方法来获得预期的结果。

      您需要将输入文本转换为有效的嵌套 JSON 数组(5;718;0;1071;1.23|0;750;0;997;1.25| 转换为 [[5,718,0,1071,1.23],[0,750,0,997,1.25]])并使用默认架构通过两个 OPENJSON() 调用来解析该数组。 OPENJSON() 调用的结果是一个包含 keyvaluetype 列的表,如果是 JSON 数组,key 列返回指定数组中元素的索引:

      DECLARE @text nvarchar(max) = N'5;718;0;1071;1.23|0;750;0;997;1.25|0;750;0;997;1.25|0;750;0;997;1.25|0;750;0;997;1.25|0;750;0;997;1.25'
      
      SELECT 
         CONVERT(int, j1.[key]) + 1 AS id1,
         CONVERT(int, j2.[key]) + 1 AS id2,
         j2.[value]
      FROM OPENJSON(CONCAT('[[', REPLACE(REPLACE(@text, '|', '],['), ';', ','), ']]')) j1
      CROSS APPLY OPENJSON(j1.[value]) j2
      ORDER BY CONVERT(int, j1.[key]), CONVERT(int, j2.[key])
      

      结果:

      id1 id2 value
      1   1   5
      1   2   718
      1   3   0
      1   4   1071
      1   5   1.23
      2   1   0
      2   2   750
      ...
      6   1   0
      6   2   750
      6   3   0
      6   4   997
      6   5   1.25
      

      如果要识别每个组和子组的 id,则需要添加适当的WHERE 子句:

      SELECT 
         CONVERT(int, j1.[key]) + 1 AS id1,
         CONVERT(int, j2.[key]) + 1 AS id2
      FROM OPENJSON(CONCAT('[[', REPLACE(REPLACE(@text, '|', '],['), ';', ','), ']]')) j1
      CROSS APPLY OPENJSON(j1.[value]) j2
      WHERE j2.[value] = '718'
      ORDER BY CONVERT(int, j1.[key]), CONVERT(int, j2.[key])
      

      结果:

      id1 id2
      1   2
      

      【讨论】: