【问题标题】:How to extract two consecutive digits from a text field in MySQL?如何从 MySQL 中的文本字段中提取两个连续的数字?
【发布时间】:2011-03-19 10:14:05
【问题描述】:

我有一个 MySQL 数据库,我有一个查询:

SELECT `id`, `originaltext` FROM `source` WHERE `originaltext` regexp '[0-9][0-9]'

这会检测所有包含 2 位数字的原始文本。

我需要 MySQL 将这些数字作为字段返回,以便我可以进一步操作它们。

理想情况下,如果我可以添加应大于 20 的其他标准,那就太好了,但我也可以单独执行。

【问题讨论】:

    标签: mysql regex text-manipulation


    【解决方案1】:

    如果您希望数据库中的正则表达式功能更强大,可以考虑使用LIB_MYSQLUDF_PREG。这是一个导入 PCRE 库的 MySQL 用户函数的开源库。 LIB_MYSQLUDF_PREG 仅以源代码形式提供。要使用它,您需要能够编译它并将其安装到您的 MySQL 服务器中。安装这个库不会以任何方式改变 MySQL 的内置正则表达式支持。它只是提供了以下附加功能:

    PREG_CAPTURE 从字符串中提取正则表达式匹配。 PREG_POSITION 返回正则表达式匹配字符串的位置。 PREG_REPLACE 对字符串执行搜索和替换。 PREG_RLIKE 测试一个正则表达式是否匹配一个字符串。

    所有这些函数都将正则表达式作为它们的第一个参数。此正则表达式必须像 Perl 正则表达式运算符一样格式化。例如。要测试正则表达式是否不区分主题大小写,您将使用 MySQL 代码 PREG_RLIKE('/regex/i', subject)。这类似于 PHP 的 preg 函数,它也需要额外的 // 分隔符用于 PHP 字符串中的正则表达式。

    如果您想要更简单的东西,您可以更改此功能以更好地满足您的需求。

    CREATE FUNCTION REGEXP_EXTRACT(string TEXT, exp TEXT)
    -- Extract the first longest string that matches the regular expression
    -- If the string is 'ABCD', check all strings and see what matches: 'ABCD', 'ABC', 'AB', 'A', 'BCD', 'BC', 'B', 'CD', 'C', 'D'
    -- It's not smart enough to handle things like (A)|(BCD) correctly in that it will return the whole string, not just the matching token.
    
    RETURNS TEXT
    DETERMINISTIC
    BEGIN
      DECLARE s INT DEFAULT 1;
      DECLARE e INT;
      DECLARE adjustStart TINYINT DEFAULT 1;
      DECLARE adjustEnd TINYINT DEFAULT 1;
    
      -- Because REGEXP matches anywhere in the string, and we only want the part that matches, adjust the expression to add '^' and '$'
      -- Of course, if those are already there, don't add them, but change the method of extraction accordingly.
    
      IF LEFT(exp, 1) = '^' THEN 
        SET adjustStart = 0;
      ELSE
        SET exp = CONCAT('^', exp);
      END IF;
    
      IF RIGHT(exp, 1) = '$' THEN
        SET adjustEnd = 0;
      ELSE
        SET exp = CONCAT(exp, '$');
      END IF;
    
      -- Loop through the string, moving the end pointer back towards the start pointer, then advance the start pointer and repeat
      -- Bail out of the loops early if the original expression started with '^' or ended with '$', since that means the pointers can't move
      WHILE (s <= LENGTH(string)) DO
        SET e = LENGTH(string);
        WHILE (e >= s) DO
          IF SUBSTRING(string, s, e) REGEXP exp THEN
            RETURN SUBSTRING(string, s, e);
          END IF;
          IF adjustEnd THEN
            SET e = e - 1;
          ELSE
            SET e = s - 1; -- ugh, such a hack to end it early
          END IF;
        END WHILE;
        IF adjustStart THEN
          SET s = s + 1;
        ELSE
          SET s = LENGTH(string) + 1; -- ugh, such a hack to end it early
        END IF;
      END WHILE;
    
      RETURN NULL;
    
    END
    

    【讨论】:

    • 嗯 - 我认为在我的原始帖子中不是那么清楚,但原始文本中有很多文本“绒毛”围绕数字......我需要“提取”数字其中。而且一篇原文中可能有超过1个数字...
    【解决方案2】:

    MySQL 中没有使用正则表达式提取文本的任何语法。您可以使用 REGEXP 来识别包含两个连续数字的行,但是要提取它们,您必须使用普通的字符串操作函数,这在这种情况下非常困难。

    替代方案:

    • 从数据库中选择整个值,然后在客户端上使用正则表达式。
    • 使用对 SQL 标准有更好支持的不同数据库(我知道,这可能不是一个选项)。然后你可以使用这个:SUBSTRING(originaltext from '%#[0-9]{2}#%' for '#')

    【讨论】:

    • 我同意。我的直觉是从数据库中获取整个字段,然后使用脚本将其与正则表达式进行比较并提取匹配项。
    【解决方案3】:

    我遇到了同样的问题,这是我找到的解决方案(但并非在所有情况下都有效):

    • 使用LOCATE() 查找您不想匹配的字符串的开头和结尾
    • 使用MID() 提取中间的子字符串...
    • 保留正则表达式以仅匹配您确定会找到匹配项的行。

    【讨论】:

    • 此处的示例可能会有所帮助
    【解决方案4】:

    我将我的代码用作存储过程(函数),应该可以从单个块中的数字中提取任何数字。这是我更广泛的图书馆的一部分。

    DELIMITER $$
    
    --  2013.04 michal@glebowski.pl
    --  FindNumberInText("ab 234 95 cd", TRUE) => 234  
    --  FindNumberInText("ab 234 95 cd", FALSE) => 95
    
    DROP FUNCTION IF EXISTS FindNumberInText$$
    CREATE FUNCTION FindNumberInText(_input VARCHAR(64), _fromLeft BOOLEAN) RETURNS VARCHAR(32)
    BEGIN
      DECLARE _r              VARCHAR(32) DEFAULT '';
      DECLARE _i              INTEGER DEFAULT 1;
      DECLARE _start          INTEGER DEFAULT 0;
      DECLARE _IsCharNumeric  BOOLEAN;
    
      IF NOT _fromLeft THEN SET _input = REVERSE(_input); END IF;
      _loop: REPEAT
        SET _IsCharNumeric = LOCATE(MID(_input, _i, 1), "0123456789") > 0;
        IF _IsCharNumeric THEN
          IF _start = 0 THEN SET _start  = _i;  END IF;
        ELSE
          IF _start > 0 THEN LEAVE _loop;       END IF;
        END IF;
        SET _i = _i + 1;
      UNTIL _i > length(_input) END REPEAT;
    
      IF _start > 0 THEN
        SET _r = MID(_input, _start, _i - _start);
        IF NOT _fromLeft THEN SET _r = REVERSE(_r);  END IF;
      END IF;
      RETURN _r;
    END$$
    

    【讨论】:

      【解决方案5】:

      我认为更简洁的方法是使用REGEXP_SUBSTR():

      这会提取两个任意数字:

      SELECT REGEXP_SUBSTR(`originalText`,'[0-9]{2}') AS `twoDigits` FROM `source`;
      

      这会精确提取两位数字,但从 20 到 99(例如:1112 返回 null;1521 返回 52):

      SELECT REGEXP_SUBSTR(`originalText`,'[2-9][0-9]') AS `twoDigits` FROM `source`;
      

      我在 v8.0 中都进行了测试,它们都可以工作。就是这样,祝你好运!

      【讨论】:

      • 不错。我用它将完整地址中的西班牙邮政编码/邮政编码复制到一个新列中...... UPDATE members SET postcode = (SELECT REGEXP_SUBSTR(address,'[0-9]{5}') AS fiveDigits);
      【解决方案6】:

      如果要返回字符串的一部分:

      SELECT id , substring(columnName,(locate('partOfString',columnName)),10) from tableName;
      

      Locate() 将返回匹配字符串的起始位置,它成为Function Substring() 的起始位置

      【讨论】:

        【解决方案7】:

        我知道这个问题被问到已经有一段时间了,但我遇到了这个问题,并认为这对我的自定义正则表达式替换器来说是一个很好的挑战 - 请参阅 this blog post

        ...好消息是它可以,尽管它需要调用很多次。请参阅this online rextester demo,它显示了以下 SQL 的工作原理。

        SELECT reg_replace(
                 reg_replace(
                   reg_replace(
                     reg_replace(
                       reg_replace(
                         reg_replace(
                           reg_replace(txt,
                                       '[^0-9]+',
                                       ',',
                                       TRUE,
                                       1, -- Min match length
                                       0 -- No max match length
                                       ),
                                     '([0-9]{3,}|,[0-9],)',
                                     '',
                                     TRUE,
                                     1, -- Min match length
                                     0 -- No max match length
                                     ),
                                   '^[0-9],',
                                   '',
                                   TRUE,
                                   1, -- Min match length
                                   0 -- No max match length
                                   ),
                                 ',[0-9]$',
                                 '',
                                 TRUE,
                                 1, -- Min match length
                                 0 -- No max match length
                                 ),
                               ',{2,}',
                               ',',
                               TRUE,
                               1, -- Min match length
                               0 -- No max match length
                               ),
                             '^,',
                             '',
                             TRUE,
                             1, -- Min match length
                             0 -- No max match length
                             ),
                           ',$',
                           '',
                           TRUE,
                           1, -- Min match length
                           0 -- No max match length
                           ) AS `csv`
        FROM tbl;
        

        【讨论】:

          猜你喜欢
          • 2015-11-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-09-21
          相关资源
          最近更新 更多