【问题标题】:Select only numeric characters after a specific character in TSQL在 SQL 中仅选择特定字符之后的数字字符
【发布时间】:2022-01-08 09:47:54
【问题描述】:

我有一个 VARCHAR 数据库字段,其数据以下列方式存储:

DatabaseField
1185731-2148838B
1185731-2148838S
1185731-2148838W
1185731-2148839B

我想将此字段分成两个单独的列 - 包含“-”两侧的数字。

要获取'-'之前和之后的值,我可以使用以下

SUBSTRING(DatabaseField,0,CHARINDEX('-',DatabaseField))
SUBSTRING(DatabaseField,CHARINDEX('-',DatabaseField)+1,LEN(DatabaseField))

这给了我

NewColumnA   NewColumnB
1185731      2148838B
1185731      2148838S
1185731      2148838W
1185731      2148839B

我怎样才能从新列 B 中删除任何非数字字符 - 它可能不是列中的一个字母 - 它可能是 2 或 3,所以我不能只从新列中删除最后一个数字。

【问题讨论】:

  • 如果数字字符的个数相同,则直接使用LEFT即可。
  • 不一定一样。
  • 非数字字符是否总是在字符串的末尾?
  • 是的,总是在最后,但它可以是一个或多个非数字字符。

标签: tsql sql-server-2016


【解决方案1】:

填充它们!

declare @test table (DatabaseField varchar(30));

insert into @test values
  ('1185731-2148838B') 
, ('1234567-1234567?') 
, ('1185731-214883612WAH') 
, ('1185731-2148839BLAH') 
, ('AYE-CARAMBA')
, ('Stufffffff') 
;

select DatabaseField
, [NewColumnA] = NULLIF(STUFF(DatabaseField,PATINDEX('%[0-9]-%',Databasefield),42,''),'') 
, [NewColumnB] = PARSENAME(REPLACE(STUFF(DatabaseField,PATINDEX('%[^0-9-]%',DatabaseField),42,''),'-','.'),1)
from @test;
DatabaseField NewColumnA NewColumnB
1185731-2148838B 118573 2148838
1234567-1234567? 123456 1234567
1185731-214883612WAH 118573 214883612
1185731-2148839BLAH 118573 2148839
AYE-CARAMBA null null
Stufffffff null null

测试 dbfiddle here

【讨论】:

  • 我身边还有一个。出于口味问题,我不喜欢 PARSENAME() 拆分字符串,但是 - 好吧 - 它在这种情况下有效:-)
  • 谢谢。好吧,如果使用不当, parsename 可能会带来风险,因为它并不是真正为此目的而设计的。但对于少于 5 个分隔符的小字符串,它相当方便。
  • 顺便说一句:就像艾伦的回答一样,这将与没有任何非数字字符的值打破......请记住:-)
  • @Shnugo 很好发现。实际上,问题不在于 NewColumB,它应该给 null。但第一个是 CHARINDEX()-1。所以我也塞了那个。
【解决方案2】:

就像(现已删除)的答案一样,我会使用 PARSENAME 来拆分 2 个部分。然后您可以使用PATINDEX 查找第一个非数字字符并从中获取LEFT 字符:

SELECT LEFT(PN.Field1,ISNULL(NULLIF(PATINDEX('%[^0-9]%',PN.Field1),0),LEN(Field1))) AS Field1,
       LEFT(PN.Field2,ISNULL(NULLIF(PATINDEX('%[^0-9]%',PN.Field2),0)-1,LEN(Field2))) AS Field2
FROM (VALUES('1185731-2148838B'),
            ('1185731-2148838S'),
            ('1185731-2148838W'),
            ('1185731-2148839B'))V(YourString)
      CROSS APPLY (VALUES(PARSENAME(REPLACE(V.YourString,'-','.'),2),PARSENAME(REPLACE(V.YourString,'-','.'),1)))PN(Field1,Field2);

【讨论】:

    【解决方案3】:

    我会这样做:

    DECLARE @test TABLE (DatabaseField VARCHAR(30));
    INSERT @test VALUES('1185731-2148838B'), ('1234567-1234567?'),
                       ('1185731-214883612WAH'), ('1185731-2148839BLAH');
    
    SELECT
      t.DatabaseField,
      NewA = SUBSTRING(clean.S,1,f1.Pos),
      NewB = SUBSTRING(clean.S, f1.Pos+2, 30)
    FROM        @test AS t
    CROSS APPLY (VALUES(CHARINDEX('-', t.DatabaseField)-1))        AS f1(Pos)
    CROSS APPLY (VALUES(PATINDEX('%[^0-9-]%', t.DatabaseField)-1)) AS f2(Pos)
    CROSS APPLY (VALUES(SUBSTRING(t.DatabaseField,1,f2.Pos)))      AS clean(S);
    

    返回:

    DatabaseField                  NewA                           NewB
    ------------------------------ ------------------------------ ------------------------------
    1185731-2148838B               1185731                        2148838
    1234567-1234567?               1234567                        1234567
    1185731-214883612WAH           1185731                        214883612
    1185731-2148839BLAH            1185731                        2148839
    

    【讨论】:

    • 很好的答案,加上我身边的一个。当根本没有非数字字符时,这将中断...不知道,这是否重要,但值得牢记...
    【解决方案4】:

    您可以定义一个函数来根据 REGEX 检查列并删除字符串中的任何非数字字符。并在您的查询中使用它。

    更新了答案以容纳更多长度并避免第 2 列的字符索引陷阱。拆分基于找到的第一个“-”。也添加了一些示例

    CREATE OR ALTER FUNCTION dbo.fnGetNumbersOnly
    (
        @StringToCheck NVARCHAR(MAX)
    )
    RETURNS @Results TABLE
    (
        NumbericValue BIGINT
    )
    /*****************************************************************************************************************************************************
    SELECT dbo.fnGetNumbers('21488-d*39B') AS Result
    *****************************************************************************************************************************************************/
    AS
    BEGIN
        DECLARE @Start INT = 1,
                @End   INT = ISNULL (LEN (@StringToCheck), 0)
        DECLARE @Result NVARCHAR(MAX) = N''
    
        WHILE @Start <= @End
        BEGIN
            DECLARE @Char CHAR(1) = SUBSTRING (@StringToCheck, @Start, 1)
    
            IF (@Char NOT LIKE '[0-9]')
            BEGIN
                SET @Start += 1;
    
                CONTINUE
            END
    
            SET @Result += @Char
            SET @Start += 1;
        END
    
        INSERT INTO @Results
        (
            NumbericValue
        )
        SELECT @Result
    
        RETURN
    END
    GO
    
    SELECT      c.stringtosplit AS GivenData,
                a.NumbericValue AS NewColumnA,
                b.NumbericValue AS NewColumnB
    FROM
                (
                    SELECT '1185731-2148838B' AS stringtosplit
                    UNION ALL
                    SELECT '1185731-2148838S'
                    UNION ALL
                    SELECT '1185731-2148838W'
                    UNION ALL
                    SELECT '1185731-2148839B'
                    UNION ALL
                    SELECT '1185-731-21484-#839B'
                    UNION ALL
                    SELECT '1185731-2148yt839B'
                ) AS c
    CROSS APPLY dbo.fnGetNumbersOnly (LEFT(c.stringtosplit, CHARINDEX ('-', c.stringtosplit))) AS a
    CROSS APPLY dbo.fnGetNumbersOnly (REPLACE (c.stringtosplit, LEFT(c.stringtosplit, CHARINDEX ('-', c.stringtosplit)), '')) AS b
    GO
    

    【讨论】:

    • 这会非常慢。循环非常慢,标量 UDF 也是如此。如果性能很重要,则放弃循环并将标量函数更改为内联函数。
    • 我同意,但这取决于我们打算查询的数据库和数据集。我只是给出了与所问内容相关的答案。如果需要,我们可以将其制成一个更快的表值函数,并将其添加为交叉应用并显示结果集。我们可以将数据移动到临时表中并循环遍历它。这完全取决于我们
    • 进行查询以使用表值函数。
    • @Nav,这并没有让它变得更好......WHILE-loop 与 multi-statement TVF 的组合是众所周知的性能杀手。 .. inline TVF 要好得多,但这不允许 procedural 方法(这就是它实际上更好的原因)
    • 同意。我从来没有说过它是性能最好的版本。我说这完全取决于我们想要实现的目标以及我们拥有的信息。
    【解决方案5】:

    还有一个建议:

    SELECT t.YourField
          ,B.firstPart
          ,CASE WHEN posNonNum < 1 THEN B.secondPart ELSE LEFT(B.secondPart,posNonNum) END AS secondPart
    FROM YourTable t
    CROSS APPLY(VALUES(CHARINDEX('-',t.YourField))) A(posHyphen)
    CROSS APPLY(VALUES(LEFT(t.YourField,posHyphen-1),RIGHT(t.YourField,LEN(t.YourField)-posHyphen))) B(firstPart,secondPart)
    CROSS APPLY(VALUES(PATINDEX('%[^0-9]%',B.secondPart))) C(posNonNum);
    

    简而言之:

    • 第一个APPLY将分隔连字符的位置添加到结果集中
    • 第二个APPLY使用LEFT()获取第一部分,RIGHT()返回第二部分。
    • 第三个APPLY 将在第二部分中查找第一个非数字字符。
    • 最后的SELECT 添加CASE 来处理PATINDEX() 找不到任何非数字字符的情况。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-12-30
      • 2015-06-22
      • 1970-01-01
      • 2021-12-13
      • 2016-07-08
      • 2021-09-13
      • 2020-04-26
      • 2019-11-25
      相关资源
      最近更新 更多