【问题标题】:Edit string column in SQL - remove sections between separators在 SQL 中编辑字符串列 - 删除分隔符之间的部分
【发布时间】:2019-01-29 16:18:37
【问题描述】:

我的表中有一个字符串列,其中包含“字符分隔”数据,例如:

“值|数据|4|Z|11/06/2012”

这些数据被输入“解析器”并反序列化为特定对象。 (这个细节不相关,不能更改)

我的对象的结构已经改变,现在我想摆脱一些数据的“部分”

所以我想让之前的值变成这个

“价值|数据|2012 年 11 月 6 日”

我希望我能获得一些关于如何在 T-SQL 中执行此操作的帮助。

数据总是有相同数量的部分,'n',我想删除所有行的相同部分,'n-x 和 'n-y'

到目前为止,我知道我需要一个更新语句来更新我的列值。 我找到了各种拆分字符串的方法,但我很难将它应用到我的场景中。

在 C# 中我会这样做

string RemoveSecitons(string value)
{
    string[] bits = string.split(value,'|');

    List<string> wantedBits = new List<string>();


    for(var i = 0; i < bits.Length; i++)
    {
        if ( i==2 || i==3) // position of sections I no longer want
        {
            continue;
        }
        wantedBits.Add(bits[i]);
    }

    return string.Join(wantedBits,'|');

}

但是我将如何在 SQL 中执行此操作我不知道从哪里开始。任何帮助将不胜感激

谢谢

附言。我需要在 SQL Server 2012 上运行这个 SQL

编辑:看起来以某种方式解析为 xml 可能是一个流行的答案,但是我不能保证我的字符串不会包含诸如“

【问题讨论】:

  • 您正在删除值 4Z。是否有任何类型的模式可以识别您要删除的数据?
  • 在我的实际场景中它是一个整数值和一个日期,但是数据包含其他整数值
  • @Dave 。 . .哦,这是一个很好的例子,说明了为什么要将数据存储在列中而不是字符串中。我怀疑有一个有点优雅的 XML 解决方案。

标签: sql tsql sql-server-2012


【解决方案1】:

使用NGrams8K,您可以轻松编写令人讨厌的快速自定义拆分器。这里的逻辑基于DelimitedSplit8K。这甚至可能胜过您发布的 C# 代码。

DECLARE @string VARCHAR(8000) = '"Value|Data|4|Z|11/06/2012"',
        @delim  CHAR(1)       = '|';

SELECT newString = 
(
  SELECT SUBSTRING(
           @string, split.pos+1,
           ISNULL(NULLIF(CHARINDEX(@delim,@string,split.pos+1),0),8000)-split.pos)
  FROM
  (
    SELECT ROW_NUMBER() OVER (ORDER BY d.Pos), d.Pos
    FROM
    (
      SELECT 0 UNION ALL
      SELECT ng.position 
      FROM   samd.ngrams8k(@string,1) AS ng
      WHERE  ng.token = @delim
    ) AS d(Pos)
  ) AS split(ItemNumber,Pos)
  WHERE split.ItemNumber IN (1,2,5)
  ORDER BY split.ItemNumber
  FOR XML PATH('')
);

返回:

newString
----------------------------
"Value|Data|11/06/2012"

【讨论】:

    【解决方案2】:

    不是最优雅的方式,但有效:

    SELECT SUBSTRING(@str,1, CHARINDEX('|',@str,CHARINDEX('|',@str,1)+1)-1)
       + SUBSTRING(@str, CHARINDEX('|',@str,CHARINDEX('|',@str,CHARINDEX('|',@str,CHARINDEX('|',@str,1)+1)+1)+1), LEN(@str))
    
    
    ----------------------
    Value|Data|11/06/2012
    

    【讨论】:

      【解决方案3】:

      你可以试试XQuery:

      DECLARE @s VARCHAR(100)='Value|Data|4|Z|11/06/2012';
      
      SELECT CAST('<x>' + REPLACE(@s,'|','</x><x>') + '</x>' AS XML)
             .value('concat(/x[1],"|",/x[2],"|",/x[5])','nvarchar(max)');
      

      简而言之:通过一些字符串替换将值转换为 XML。然后我们再次使用XQuery-concat 将第一个、第二个和第五个元素绑定在一起。

      这个版本效率稍低,但带有禁止字符是安全的:

      SELECT CAST('<x>' + REPLACE((SELECT @s AS [*] FOR XML PATH('')),'|','</x><x>') + '</x>' AS XML)
             .value('concat(/x[1],"|",/x[2],"|",/x[5])','nvarchar(max)')
      

      【讨论】:

        【解决方案4】:

        只是为了好玩添加一个非xml选项:

        编辑和警告 - 万一有人尝试使用不同的解决方案却没有阅读 cmets...

        HABO 正确地指出,如果任何列中有句点(“.”),这很容易被破坏。 PARSENAME 依赖于 4 部分命名结构,如果超出,将返回 NULL。如果任何值包含另一个管道(“|”)或添加另一个分隔列,此解决方案也会中断 - 我的答案中的子字符串专门作为对 4 部分命名的依赖的解决方法。如果您尝试将此解决方案用于具有 7 个分隔列的变量,则需要对其进行重新设计或废弃,以支持此处的其他答案之一。

        DECLARE 
            @a VARCHAR(100)= 'Value|Data|4|Z|11/06/2012'
        
        
        SELECT 
            PARSENAME(REPLACE(SUBSTRING(@a,0,LEN(@a)-CHARINDEX('|',REVERSE(@a))+1),'|','.'),4)+'|'+
            PARSENAME(REPLACE(SUBSTRING(@a,0,LEN(@a)-CHARINDEX('|',REVERSE(@a))+1),'|','.'),3)+'|'+
            SUBSTRING(@a,LEN(@a)-CHARINDEX('|',REVERSE(@a))+2,LEN(@a))
        

        【讨论】:

        • 干得好! +1 喜欢这个!
        • 值得注意的是,ValueData 中出现的句点(“.”)并不友好。
        • @HABO,这是真的。如果任何值包含另一个管道或添加另一个分隔列,它也会中断。
        【解决方案5】:

        这是一个快速的方法。

        CREATE FUNCTION [dbo].StringSplitXML
        (
            @String  VARCHAR(MAX), @Separator CHAR(1)
        )
        RETURNS @RESULT TABLE(id int identity(1,1),Value VARCHAR(MAX))
        AS
        BEGIN    
         DECLARE @XML XML
         SET @XML = CAST(
            ('<i>' + REPLACE(@String, @Separator, '</i><i>') + '</i>')
            AS XML)
        
         INSERT INTO @RESULT
         SELECT t.i.value('.', 'VARCHAR(MAX)') 
         FROM @XML.nodes('i') AS t(i)
         WHERE t.i.value('.', 'VARCHAR(MAX)') <> ''
        
         RETURN
        END
        GO
        SELECT * FROM dbo.StringSplitXML( 'Value|Data|4|Z|11/06/2012','|')
        WHERE id not in (3,4)
        

        请注意,使用 UDF 会减慢速度,因此仅当您有相当小的数据集可供使用时才应考虑使用此解决方案。

        【讨论】:

        • 您好,感谢您抽出时间提供帮助。我应该(并且我将编辑我的问题以反映这一点)请注意,我不能保证字符串不会包含无效的 xml 字符,如 '
        • 如果是这种情况,有很多不使用 XML 的字符串拆分函数。只要您使用的函数返回一个数字位置 id 和值,您应该能够使用上述概念,返回表并使用 WHERE 子句对其进行过滤。检查我刚刚看到的评论链接,我确信那里有一些很好的字符串拆分功能。
        • Note that using a UDF will slow things down, so this solution should be considered only if you have a reasonably small data set to work with. 正确写入 UDF 不会减慢速度!包含 BEGIN / END 逻辑的 T-SQL UDF 很糟糕,因为它们不是内联的;内联 UDF 的速度非常快。为了改进您的拆分器功能,我建议: 1. 将其重写为内联表值函数 2. 将返回类型更改为 VARCHAR(8000); MAX 数据类型只应在绝对必要时使用。 PS - 即使是内联的 XML 拆分器也很慢。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-02-23
        • 1970-01-01
        • 2018-11-25
        • 2016-03-13
        • 1970-01-01
        相关资源
        最近更新 更多