【问题标题】:Query to get only numbers from a string查询以仅从字符串中获取数字
【发布时间】:2013-05-16 01:23:08
【问题描述】:

我有这样的数据:

string 1: 003Preliminary Examination Plan   
string 2: Coordination005  
string 3: Balance1000sheet

我期望的输出是

string 1: 003
string 2: 005
string 3: 1000

我想在 SQL 中实现它。

【问题讨论】:

  • 所以你想进行选择并从 ":" 之后的值中提取数字?

标签: sql sql-server


【解决方案1】:

这是最简单和最容易的一种。这也适用于多次出现的整个字符串。

CREATE FUNCTION dbo.fn_GetNumbers(@strInput NVARCHAR(500))
RETURNS NVARCHAR(500)
AS
BEGIN
DECLARE @strOut NVARCHAR(500) = '', @intCounter INT = 1
WHILE @intCounter <= LEN(@strInput) 
BEGIN
    SELECT @strOut = @strOut + CASE WHEN SUBSTRING(@strInput, @intCounter, 1) LIKE '[0-9]' THEN SUBSTRING(@strInput, @intCounter, 1) ELSE '' END    
    SET @intCounter = @intCounter + 1 

END
RETURN @strOut
END 

【讨论】:

    【解决方案2】:

    我发现这种方法的运行速度比投票最多的答案快 3 倍。创建以下函数 dbo.GetNumbers:

    CREATE FUNCTION dbo.GetNumbers(@String VARCHAR(8000))
    RETURNS VARCHAR(8000)
    AS
    BEGIN;
        WITH
            Numbers
        AS (
            --Step 1.
            --Get a column of numbers to represent
            --every character position in the @String.
            SELECT 1 AS Number
            UNION ALL
            SELECT Number + 1
            FROM Numbers
            WHERE Number < LEN(@String)
            )
            ,Characters
        AS (
            SELECT Character
            FROM Numbers
            CROSS APPLY (
                    --Step 2.
                    --Use the column of numbers generated above
                    --to tell substring which character to extract.
                    SELECT SUBSTRING(@String, Number, 1) AS Character
                ) AS c
            )
        --Step 3.
        --Pattern match to return only numbers from the CTE
        --and use STRING_AGG to rebuild it into a single string.
        SELECT @String = STRING_AGG(Character,'')
        FROM Characters
        WHERE Character LIKE '[0-9]'
    
        --allows going past the default maximum of 100 loops in the CTE
        OPTION (MAXRECURSION 8000) 
    
        RETURN @String
    END
    GO
    

    测试

    测试目的:

    SELECT dbo.GetNumbers(InputString) AS Numbers
    FROM ( VALUES
             ('003Preliminary Examination Plan') --output: 003
            ,('Coordination005')                 --output: 005
            ,('Balance1000sheet')                --output: 1000
            ,('(111) 222-3333')                  --output: 1112223333
            ,('1.38hello@f00.b4r#\-6')           --output: 1380046
        ) testData(InputString)
    

    性能测试: 开始设置测试数据...

    --Add table to hold test data
    CREATE TABLE dbo.NumTest (String VARCHAR(8000)) 
    
    --Make an 8000 character string with mix of numbers and letters
    DECLARE @Num VARCHAR(8000) = REPLICATE('12tf56se',800)
    
    --Add this to the test table 500 times
    DECLARE @n INT = 0
    WHILE @n < 500
    BEGIN
        INSERT INTO dbo.NumTest VALUES (@Num)
        SET @n = @n +1
    END
    

    现在测试 dbo.GetNumbers 函数:

    SELECT dbo.GetNumbers(NumTest.String) AS Numbers
    FROM dbo.NumTest -- Time to complete: 1 min 7s
    

    然后在同一数据上测试来自top voted answer 的 UDF。

    SELECT dbo.udf_GetNumeric(NumTest.String)
    FROM dbo.NumTest -- Time to complete: 3 mins 12s
    

    Inspiration for dbo.GetNumbers

    小数

    如果你需要它来处理小数,你可以使用以下任何一种方法,我发现它们之间没有明显的性能差异。

    • '[0-9]' 更改为'[0-9.]'
    • Character LIKE '[0-9]' 更改为ISNUMERIC(Character) = 1(SQL 将单个小数视为数字)

    奖金

    您可以通过使用以下选项替换 WHERE Character LIKE '[0-9]' 来轻松适应不同的要求:

    • WHERE Letter LIKE '[a-zA-Z]' --Get only letters
    • WHERE Letter LIKE '[0-9a-zA-Z]' --Remove non-alphanumeric
    • WHERE Letter LIKE '[^0-9a-zA-Z]' --Get only non-alphanumeric

    【讨论】:

      【解决方案3】:

      仅从字符串中获取数字可以在单行中完成。 试试这个:

      SUBSTRING('your-string-here', PATINDEX('%[0-9]%', 'your-string-here'), LEN('your-string-here'))
      

      注意:仅适用于字符串中的第一个 int,例如:abc123vfg34 返回 123。

      【讨论】:

      • 如果将整个字符串的长度作为第三个参数传递,它将从找到的第一个数字开始获取字符串的其余部分。你用你的例子试过吗?它实际上会返回123vfg34
      【解决方案4】:

      首先找出数字的起始长度,然后反转字符串以再次找出第一个位置(这将为您从末尾给出数字的结束位置)。现在,如果你从两个数字中减去 1 并从字符串全长中减去它,你将只得到数字长度。现在使用 SUBSTRING 获取号码

      declare @fieldName nvarchar(100)='AAAA1221.121BBBB'
      
      declare @lenSt int=(select PATINDEX('%[0-9]%', @fieldName)-1)
      declare @lenEnd int=(select PATINDEX('%[0-9]%', REVERSE(@fieldName))-1)
      
      select SUBSTRING(@fieldName, PATINDEX('%[0-9]%', @fieldName), (LEN(@fieldName) - @lenSt -@lenEnd))
      

      【讨论】:

        【解决方案5】:

        为了它……

        此解决方案不同于所有早期的解决方案,即:

        • 无需创建函数
        • 无需使用模式匹配
        • 不需要临时表
        • 此解决方案使用递归公用表表达式 (CTE)

        但首先 - 请注意,问题并未指定此类字符串的存储位置。在下面的解决方案中,我创建了一个 CTE 作为将这些字符串放入某种“源表”的快速而肮脏的方式。

        还要注意 - 此解决方案使用 recursive common table expression (CTE) - 所以不要对这里使用两个 CTE 感到困惑。第一个只是使数据可用于解决方案 - 但解决此问题只需要第二个 CTE。您可以调整代码以使第二个 CTE 查询您现有的表、视图等。

        最后 - 我的编码很冗长,尝试使用列和 CTE 名称来解释正在发生的事情,您也许可以稍微简化这个解决方案。我添加了一些伪电话号码,并带有一些(预期的和非典型的,视情况而定)格式,以获取乐趣。

        with SOURCE_TABLE as (
            select '003Preliminary Examination Plan' as numberString
            union all select 'Coordination005' as numberString
            union all select 'Balance1000sheet' as numberString
            union all select '1300 456 678' as numberString
            union all select '(012) 995 8322  ' as numberString
            union all select '073263 6122,' as numberString
        ),
        FIRST_CHAR_PROCESSED as (
            select
                len(numberString) as currentStringLength,
                isNull(cast(try_cast(replace(left(numberString, 1),' ','z') as tinyint) as nvarchar),'') as firstCharAsNumeric,
                cast(isNull(cast(try_cast(nullIf(left(numberString, 1),'') as tinyint) as nvarchar),'') as nvarchar(4000)) as newString,
                cast(substring(numberString,2,len(numberString)) as nvarchar) as remainingString
            from SOURCE_TABLE
            union all
            select
                len(remainingString) as currentStringLength,
                cast(try_cast(replace(left(remainingString, 1),' ','z') as tinyint) as nvarchar) as firstCharAsNumeric,
                cast(isNull(newString,'') as nvarchar(3999)) + isNull(cast(try_cast(nullIf(left(remainingString, 1),'') as tinyint) as nvarchar(1)),'') as newString,
                substring(remainingString,2,len(remainingString)) as remainingString
            from FIRST_CHAR_PROCESSED fcp2
            where fcp2.currentStringLength > 1
        )
        select 
            newString
            ,* -- comment this out when required
        from FIRST_CHAR_PROCESSED 
        where currentStringLength = 1
        

        那么这里发生了什么?

        基本上,在我们的 CTE 中,我们选择第一个字符并使用 try_cast (see docs) 将其转换为 tinyint(对于一位数字来说,这是一个足够大的数据类型)。请注意,SQL Server 中的类型转换规则说空字符串(或空格,就此而言)将解析为零,因此添加 nullif 以强制空格和空字符串解析为 null (see discussion )(否则,只要在源数据中遇到空格,我们的结果就会包含一个零字符)。

        CTE 还返回第一个字符之后的所有内容 - 这成为我们对 CTE 的递归调用的输入;换句话说:现在让我们处理下一个字符。

        最后,CTE 中的字段newString 是通过连接生成的(在第二个SELECT 中)。在任何给定列的两个 SELECT 语句之间使用递归 CTE the data type must match - 包括列大小。因为我们知道我们正在添加(最多)一个字符,所以我们将该字符转换为 nvarchar(1),并将newString(到目前为止)转换为 nvarchar(3999)。连接起来,结果将是 nvarchar(4000) - 匹配我们在第一个 SELECT 中执行的类型转换。

        如果您运行此查询并排除 WHERE 子句,您将了解发生了什么 - 但行的顺序可能很奇怪。 (您不一定会看到与单个输入值相关的所有行组合在一起 - 但您仍然应该能够关注)。

        希望这是一个有趣的选项,可以帮助一些想要严格基于表达式的解决方案的人。

        【讨论】:

        • 附言。 FWIW 我认为我的要点清楚地表明了这种解决方案的新颖性。值得注意的是,它使用集合逻辑而不是过程代码,我想知道它是否也会因此提高性能。
        【解决方案6】:

        T-SQL 函数从文本中读取所有整数并返回指定索引处的整数,从左或右开始,也使用起始搜索词(可选):

        create or alter function dbo.udf_number_from_text(
            @text nvarchar(max),
            @search_term nvarchar(1000) = N'',
            @number_position tinyint = 1,
            @rtl bit = 0
        ) returns int
        as
            begin
                declare @result int = 0;
                declare @search_term_index int = 0;
        
                if @text is null or len(@text) = 0 goto exit_label;
                set @text = trim(@text);
                if len(@text) = len(@search_term) goto exit_label;
        
                if len(@search_term) > 0
                    begin
                        set @search_term_index = charindex(@search_term, @text);
                        if @search_term_index = 0 goto exit_label;
                    end;
        
                if @search_term_index > 0
                    if @rtl = 0
                        set @text = trim(right(@text, len(@text) - @search_term_index - len(@search_term) + 1));
                    else
                        set @text = trim(left(@text, @search_term_index - 1));
                if len(@text) = 0 goto exit_label;
        
                declare @patt_number nvarchar(10) = '%[0-9]%';
                declare @patt_not_number nvarchar(10) = '%[^0-9]%';
                declare @number_start int = 1;
                declare @number_end int;
                declare @found_numbers table (id int identity(1,1), val int);
        
                while @number_start > 0
                begin
                    set @number_start = patindex(@patt_number, @text);
                    if @number_start > 0
                        begin
                            if @number_start = len(@text)
                                begin
                                    insert into @found_numbers(val)
                                    select cast(substring(@text, @number_start, 1) as int);
        
                                    break;
                                end;
                            else
                                begin
                                    set @text = right(@text, len(@text) - @number_start + 1);
                                    set @number_end = patindex(@patt_not_number, @text);
        
                                    if @number_end = 0
                                        begin
                                            insert into @found_numbers(val)
                                            select cast(@text as int);
        
                                            break;
                                        end;
                                    else
                                        begin
                                            insert into @found_numbers(val)
                                            select cast(left(@text, @number_end - 1) as int);
        
                                            if @number_end = len(@text)
                                                break;
                                            else
                                                begin
                                                    set @text = trim(right(@text, len(@text) - @number_end));
                                                    if len(@text) = 0 break;
                                                end;
                                        end;
                                end;
                        end;
                end;
        
                if @rtl = 0
                    select @result = coalesce(a.val, 0)
                    from (select row_number() over (order by m.id asc) as c_row, m.val
                            from @found_numbers as m) as a
                    where a.c_row = @number_position;
                else
                    select @result = coalesce(a.val, 0)
                    from (select row_number() over (order by m.id desc) as c_row, m.val
                            from @found_numbers as m) as a
                    where a.c_row = @number_position;
        
        
                exit_label:
                    return @result;
            end;
        

        例子:

        select dbo.udf_number_from text(N'Text text 10 text, 25 term', N'term',2,1);
        

        返回 10;

        【讨论】:

          【解决方案7】:

          此 UDF 适用于所有类型的字符串:

          CREATE FUNCTION udf_getNumbersFromString (@string varchar(max))
          RETURNS varchar(max)
          AS
          BEGIN
              WHILE  @String like '%[^0-9]%'
              SET    @String = REPLACE(@String, SUBSTRING(@String, PATINDEX('%[^0-9]%', @String), 1), '')
              RETURN @String
          END
          

          【讨论】:

            【解决方案8】:

            首先创建这个UDF

            CREATE FUNCTION dbo.udf_GetNumeric
            (
              @strAlphaNumeric VARCHAR(256)
            )
            RETURNS VARCHAR(256)
            AS
            BEGIN
              DECLARE @intAlpha INT
              SET @intAlpha = PATINDEX('%[^0-9]%', @strAlphaNumeric)
              BEGIN
                WHILE @intAlpha > 0
                BEGIN
                  SET @strAlphaNumeric = STUFF(@strAlphaNumeric, @intAlpha, 1, '' )
                  SET @intAlpha = PATINDEX('%[^0-9]%', @strAlphaNumeric )
                END
              END
              RETURN ISNULL(@strAlphaNumeric,0)
            END
            GO
            

            现在使用 function 作为

            SELECT dbo.udf_GetNumeric(column_name) 
            from table_name
            

            SQL FIDDLE

            希望这能解决你的问题。

            Reference

            【讨论】:

            • 这行得通,尽管它从字符串中提取并连接所有数字,例如/p-1544937/apartment-flat-6th-october.html 将返回 15449376,这并不总是您可能正在寻找的
            • 它不处理十进制数。例如输入“10.95”将返回“1095”
            • 它应该真正返回一个 INT。 RETURN CAST(ISNULL(@strAlphaNumeric, 0) AS INT)
            • @stomy 在给定一个全数字的 255 个字符的字符串的情况下会爆发一个很好的溢出错误(实际上也小于这个值)。
            【解决方案9】:

            如果您使用的是 Postgres 并且您有像“2000 - 一些示例文本”这样的数据,请尝试子字符串和位置组合,否则如果在您的场景中没有分隔符,则需要编写正则表达式:

            SUBSTRING(Column_name from 0 for POSITION('-' in column_name) - 1) as 
            number_column_name
            

            【讨论】:

              【解决方案10】:

              在甲骨文中

              你可以用这个得到你想要的:

              SUBSTR('ABCD1234EFGH',REGEXP_INSTR ('ABCD1234EFGH', '[[:digit:]]'),REGEXP_COUNT ('ABCD1234EFGH', '[[:digit:]]'))
              

              示例查询:

              SELECT SUBSTR('003Preliminary Examination Plan  ',REGEXP_INSTR ('003Preliminary Examination Plan  ', '[[:digit:]]'),REGEXP_COUNT ('003Preliminary Examination Plan  ', '[[:digit:]]')) SAMPLE1,
              SUBSTR('Coordination005',REGEXP_INSTR ('Coordination005', '[[:digit:]]'),REGEXP_COUNT ('Coordination005', '[[:digit:]]')) SAMPLE2,
              SUBSTR('Balance1000sheet',REGEXP_INSTR ('Balance1000sheet', '[[:digit:]]'),REGEXP_COUNT ('Balance1000sheet', '[[:digit:]]')) SAMPLE3 FROM DUAL
              

              【讨论】:

                【解决方案11】:

                虽然这是谷歌搜索中的第一个旧线程,但我想出了一个与以前不同的答案。这将允许您通过您的标准来保存字符串中的内容,无论该标准可能是什么。如果你愿意,你可以把它放在一个函数中一遍又一遍地调用。

                declare @String VARCHAR(MAX) = '-123.  a    456-78(90)'
                declare @MatchExpression VARCHAR(255) = '%[0-9]%'
                declare @return varchar(max)
                
                WHILE PatIndex(@MatchExpression, @String) > 0
                    begin
                    set @return = CONCAT(@return, SUBSTRING(@string,patindex(@matchexpression, @string),1))
                    SET @String = Stuff(@String, PatIndex(@MatchExpression, @String), 1, '')
                    end
                select (@return)
                

                【讨论】:

                  【解决方案12】:

                  对@Epsicron 的回答稍作修改

                  SELECT SUBSTRING(string, PATINDEX('%[0-9]%', string), PATINDEX('%[0-9][^0-9]%', string + 't') - PATINDEX('%[0-9]%', 
                                      string) + 1) AS Number
                  FROM (values ('003Preliminary Examination Plan'),
                      ('Coordination005'),
                      ('Balance1000sheet')) as a(string)
                  

                  不需要临时变量

                  【讨论】:

                    【解决方案13】:

                    我没有创建函数的权限,但有类似

                    的文本
                    ["blahblah012345679"]
                    

                    并且需要从中间提取数字

                    请注意,这是假设数字组合在一起,而不是在字符串的开头和结尾。

                    select substring(column_name,patindex('%[0-9]%', column_name),patindex('%[0-9][^0-9]%', column_name)-patindex('%[0-9]%', column_name)+1)
                    from table name
                    

                    【讨论】:

                    • 不幸的是,此查询不适用于 +49 1522 662-44-33 这样的电话号码,它只是将字符串截断为 49,而我希望 4915226624433
                    • 这对我有用,但我可以看到如果数字被分解,可能会出现问题。
                    【解决方案14】:
                    declare @puvodni nvarchar(20)
                    set @puvodni = N'abc1d8e8ttr987avc'
                    
                    WHILE PATINDEX('%[^0-9]%', @puvodni) > 0 SET @puvodni = REPLACE(@puvodni, SUBSTRING(@puvodni, PATINDEX('%[^0-9]%', @puvodni), 1), '' ) 
                    
                    SELECT @puvodni
                    

                    【讨论】:

                      【解决方案15】:

                      通过之前的查询,我得到了以下结果:

                      'AAAA1234BBBB3333' >>>> 输出:1234

                      '-çã+0!\aº1234' >>>> 输出:0

                      下面的代码返回所有数字字符:

                      第一个输出:12343333

                      第二个输出:01234

                      declare @StringAlphaNum varchar(255)
                      declare @Character varchar
                      declare @SizeStringAlfaNumerica int
                      declare @CountCharacter int
                      
                      set @StringAlphaNum = 'AAAA1234BBBB3333'
                      set @SizeStringAlfaNumerica = len(@StringAlphaNum)
                      set @CountCharacter = 1
                      
                      while isnumeric(@StringAlphaNum) = 0
                      begin
                          while @CountCharacter < @SizeStringAlfaNumerica
                              begin
                                  if substring(@StringAlphaNum,@CountCharacter,1) not like '[0-9]%'
                                  begin
                                      set @Character = substring(@StringAlphaNum,@CountCharacter,1)
                                      set @StringAlphaNum = replace(@StringAlphaNum, @Character, '')
                                  end
                          set @CountCharacter = @CountCharacter + 1
                          end
                          set @CountCharacter = 0
                      end
                      select @StringAlphaNum
                      

                      【讨论】:

                      • 注意: 遍历每个字符的解决方案通常在 T-SQL 中性能较差。一旦我用内置的REPLACE() 函数替换了类似的循环,它的性能提高了 5000%(处理速度提高了 50 倍)。换句话说,这会使您的查询速度比实际速度慢 50 倍。使用内置的文本处理函数避免循环。在最坏的情况下,在 .NET 中创建自定义文本处理函数并将其链接到 SQL 服务器。
                      • 努诺,这不太行。在您的 WHILE 循环中,您测试字符是否为数字,如果不是则缩短字符串;但是,您忽略了更新@SizeStringAlfaNumerica。否则,谢谢! :) 不过,我会让你更新你的代码。
                      【解决方案16】:

                      查询:

                      DECLARE @temp TABLE
                      (
                          string NVARCHAR(50)
                      )
                      
                      INSERT INTO @temp (string)
                      VALUES 
                          ('003Preliminary Examination Plan'),
                          ('Coordination005'),
                          ('Balance1000sheet')
                      
                      SELECT SUBSTRING(string, PATINDEX('%[0-9]%', string), PATINDEX('%[0-9][^0-9]%', string + 't') - PATINDEX('%[0-9]%', 
                                          string) + 1) AS Number
                      FROM @temp
                      

                      【讨论】:

                      • 这不适用于空格或分隔数字,但可以回答问题。
                      • "string + 't'" 是什么意思?
                      • @Vnge 我想确保字符串以字母结尾
                      【解决方案17】:

                      试试这个 -

                      查询:

                      DECLARE @temp TABLE
                      (
                            string NVARCHAR(50)
                      )
                      
                      INSERT INTO @temp (string)
                      VALUES 
                          ('003Preliminary Examination Plan'),
                          ('Coordination005'),
                          ('Balance1000sheet')
                      
                      SELECT LEFT(subsrt, PATINDEX('%[^0-9]%', subsrt + 't') - 1) 
                      FROM (
                          SELECT subsrt = SUBSTRING(string, pos, LEN(string))
                          FROM (
                              SELECT string, pos = PATINDEX('%[0-9]%', string)
                              FROM @temp
                          ) d
                      ) t
                      

                      输出:

                      ----------
                      003
                      005
                      1000
                      

                      【讨论】:

                      • 虽然我对这个答案印象深刻并且它完美地解决了 OP 的问题,但应该注意的是,这个解决方案只适用于连续的数字系列。对于像Coor60nation005 这样的字符串,它将返回60 而不是结尾005
                      • +1 对此答案和宝达的评论,因为这正是我正在寻找的行为,但接受的答案不是。
                      • 它不适用于 (111) 222-3333。我要1112223333
                      • 如果字符串以数字开头,然后是字符,然后是数字怎么办?例如:“123abc$%10xyz9”。如何使用 select 语句获得“123109”的结果?
                      【解决方案18】:

                      请尝试:

                      declare @var nvarchar(max)='Balance1000sheet'
                      
                      SELECT LEFT(Val,PATINDEX('%[^0-9]%', Val+'a')-1) from(
                          SELECT SUBSTRING(@var, PATINDEX('%[0-9]%', @var), LEN(@var)) Val
                      )x
                      

                      【讨论】:

                      • declare @var nvarchar(max)='Balance1000sheet123' SELECT LEFT(Val,PATINDEX('%[^0-9]%', Val+'a')-1) from( SELECT SUBSTRING(@var, PATINDEX('%[0-9]%', @var), LEN(@var)) Val )x。什么是数字不是连续的。
                      • 它将给出字符串中的第一个数字。即 1000
                      • 是的,情况也可能是 Balance1000sheet123
                      猜你喜欢
                      • 2012-11-01
                      • 2017-12-09
                      • 1970-01-01
                      • 2015-11-02
                      • 1970-01-01
                      • 2017-08-22
                      • 1970-01-01
                      • 2011-07-23
                      • 2018-04-14
                      相关资源
                      最近更新 更多