在 SQL 中仅选择特定字符之后的数字字符

Posted

技术标签:

【中文标题】在 SQL 中仅选择特定字符之后的数字字符【英文标题】:Select only numeric characters after a specific character in TSQL 【发布时间】: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即可。 不一定一样。 非数字字符是否总是在字符串的末尾? 是的,总是在最后,但它可以是一个或多个非数字字符。 【参考方案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() 找不到任何非数字字符的情况。

【讨论】:

以上是关于在 SQL 中仅选择特定字符之后的数字字符的主要内容,如果未能解决你的问题,请参考以下文章

我想编写一个 sql (Oracle SQL) 查询来从特定字符之后的列中选择字符串的一部分

返回包含特定位置数字的字符串

SQL - 在特定条件下从多行中仅选择一个值

在sql server中仅反转字符串的数字部分

如何使用 REGEXP sql 从字符串中仅提取 5 位数字

匹配两个特定子字符串之一之前或之后的数字子字符串