2009-06-12 107 views
405

我有一个约500k行的表格; varchar(255)UTF8列filename包含文件名;如何在MySQL中进行正则表达式替换?

我想带出各种奇怪的字符了名的 - 以为我会使用一个字符类:[^a-zA-Z0-9()_ .\-]

现在,有MySQL中的功能,您可以通过一个正则表达式替换?我在寻找一个类似的功能,以取代()函数 - 简化的示例如下:

SELECT REPLACE('stackowerflow', 'ower', 'over'); 

Output: "stackoverflow" 

/* does something like this exist? */ 
SELECT X_REG_REPLACE('Stackoverflow','/[A-Zf]/','-'); 

Output: "-tackover-low" 

我知道REGEXP/RLIKE,但那些只检查如果有匹配,不与之匹配的是什么

(我可以做一个从PHP脚本“SELECT pkey_id,filename FROM foo WHERE filename RLIKE '[^a-zA-Z0-9()_ .\-]'”,做一个preg_replace,然后“UPDATE foo ... WHERE pkey_id=...”,但是这看起来像一个不得已的慢&丑陋的黑客)

回答

127

但是,如果您有权访问服务器,则可以使用用户定义的函数(UDF),如mysql-udf-regexp

+3

REGEXP_REPLACE作为用户定义的函数?看起来很有希望,将会研究它。谢谢! – Piskvor 2009-06-12 15:34:28

+1

Mysql没有内置此功能。我被告知,甲骨文有这样的(尽管你没有帮助) – Lathan 2010-07-26 14:08:27

+14

不幸的是mysql-udf-regexp似乎不支持多字节字符。 regexp_replace('äöõü','ä','')返回一个长数字字符串,而不是真实文本。 – lkraav 2012-02-20 01:44:19

37

我最近编写了一个MySQL函数来使用正则表达式替换字符串。你可以找到我的帖子在以下位置:

http://techras.wordpress.com/2011/06/02/regex-replace-for-mysql/

下面是函数代码:

DELIMITER $$ 

CREATE FUNCTION `regex_replace`(pattern VARCHAR(1000),replacement VARCHAR(1000),original VARCHAR(1000)) 
RETURNS VARCHAR(1000) 
DETERMINISTIC 
BEGIN 
DECLARE temp VARCHAR(1000); 
DECLARE ch VARCHAR(1); 
DECLARE i INT; 
SET i = 1; 
SET temp = ''; 
IF original REGEXP pattern THEN 
    loop_label: LOOP 
    IF i>CHAR_LENGTH(original) THEN 
    LEAVE loop_label; 
    END IF; 
    SET ch = SUBSTRING(original,i,1); 
    IF NOT ch REGEXP pattern THEN 
    SET temp = CONCAT(temp,ch); 
    ELSE 
    SET temp = CONCAT(temp,replacement); 
    END IF; 
    SET i=i+1; 
    END LOOP; 
ELSE 
    SET temp = original; 
END IF; 
RETURN temp; 
END$$ 

DELIMITER ; 

实例执行:

mysql> select regex_replace('[^a-zA-Z0-9\-]','','2my test3_text-to. check \\ my- sql (regular) ,expressions ._,'); 
+6

它也只适用于单个字符。 – 2012-01-05 21:24:12

+18

我只会强化上述观点:此函数替换*符合单字符表达式的*字符。它上面说它用于“使用正则表达式来重新生成字符串”,这可能有点误导。它完成了它的工作,但这不是要求的工作。 (不是投诉 - 这只是为了挽救领先的人走错路径) – Jason 2012-02-06 23:15:25

93

我的蛮力方法来得到这个工作只是:

  1. 转储表 - mysqldump -u user -p database table > dump.sql
  2. 查找并替换一些模式 - find /path/to/dump.sql -type f -exec sed -i 's/old_string/new_string/g' {} \;,显然还有其他perl表达式可以在文件上执行。
  3. 导入表 - mysqlimport -u user -p database table < dump.sql
+25

好的,这也应该工作;我没有考虑离线替换。在那里不错的思考! – Piskvor 2012-02-27 05:33:13

+10

对我来说似乎很陌生,你会使用这样的find,我会缩短命令sed -i's/old_string/new_string/g'/path/to/dump.sql – speshak 2012-03-23 16:17:00

+2

可以工作,如果替换不能破坏了SQL本身。 – 2012-05-04 09:28:26

7

你“可以”做...但它不是非常明智......这大约是大胆的,我会努力......只要充分支持正则表达式你用perl或类似的东西好得多。

UPDATE db.tbl 
SET column = 
CASE 
WHEN column REGEXP '[[:<:]]WORD_TO_REPLACE[[:>:]]' 
THEN REPLACE(column,'WORD_TO_REPLACE','REPLACEMENT') 
END 
WHERE column REGEXP '[[:<:]]WORD_TO_REPLACE[[:>:]]' 
+1

不,那不行。假设你的列包含'asdfWORD_TO_REPLACE WORD_TO_REPLACE'。你的方法会导致'asdfREPLACEMENT REPLACEMENT',正确的答案是“asdfWORD_TO_REPLACE REPLACEMENT”。 – 2012-10-03 17:14:00

104

改为使用MariaDB。它有一个功能

REGEXP_REPLACE(col, regexp, replace) 

MariaDB docsPCRE Regular expression enhancements

注意,您可以使用正则表达式的分组以及(我发现非常有用):

SELECT REGEXP_REPLACE("stackoverflow", "(stack)(over)(flow)", '\\2 - \\1 - \\3') 

回报

over - stack - flow 
4

我们可以在SELECT查询中使用IF条件如下:

假设对于任何具有“ABC”,“ABC1”,“ABC2”,“ABC3”,...的我们想用“ABC”代替,然后在SELECT查询中使用REGEXP和IF()我们可以做到这一点。

语法:

SELECT IF(column_name REGEXP 'ABC[0-9]$','ABC',column_name) 
FROM table1 
WHERE column_name LIKE 'ABC%'; 

例子:

SELECT IF('ABC1' REGEXP 'ABC[0-9]$','ABC','ABC1'); 
26

我们解决这个问题,而无需使用正则表达式 此查询仅更换精确匹配的字符串。

update employee set 
employee_firstname = 
trim(REPLACE(concat(" ",employee_firstname," "),' jay ',' abc ')) 

实施例:

EMP_ID employee_firstname

1周杰伦

2周杰伦阿贾伊

3周杰伦

执行查询结果后:

EMP_ID employee_firstname

1 ABC

2 ABC阿贾伊

3 ABC

5

UPDATE:现在使这个成为一个博客帖子:http://stevettt.blogspot.co.uk/2018/02/a-mysql-regular-expression-replace.html

通过所有必要的子于 function provided by Rasika Godawatte但拖网点

下展开,而不是只是测试单个字符:

-- ------------------------------------------------------------------------------------ 
-- USAGE 
-- ------------------------------------------------------------------------------------ 
-- SELECT reg_replace(<subject>, 
--     <pattern>, 
--     <replacement>, 
--     <greedy>, 
--     <minMatchLen>, 
--     <maxMatchLen>); 
-- where: 
-- <subject> is the string to look in for doing the replacements 
-- <pattern> is the regular expression to match against 
-- <replacement> is the replacement string 
-- <greedy> is TRUE for greedy matching or FALSE for non-greedy matching 
-- <minMatchLen> specifies the minimum match length 
-- <maxMatchLen> specifies the maximum match length 
-- (minMatchLen and maxMatchLen are used to improve efficiency but are 
-- optional and can be set to 0 or NULL if not known/required) 
-- Example: 
-- SELECT reg_replace(txt, '^[Tt][^ ]* ', 'a', TRUE, 2, 0) FROM tbl; 
DROP FUNCTION IF EXISTS reg_replace; 
DELIMITER // 
CREATE FUNCTION reg_replace(subject VARCHAR(21845), pattern VARCHAR(21845), 
    replacement VARCHAR(21845), greedy BOOLEAN, minMatchLen INT, maxMatchLen INT) 
RETURNS VARCHAR(21845) DETERMINISTIC BEGIN 
    DECLARE result, subStr, usePattern VARCHAR(21845); 
    DECLARE startPos, prevStartPos, startInc, len, lenInc INT; 
    IF subject REGEXP pattern THEN 
    SET result = ''; 
    -- Sanitize input parameter values 
    SET minMatchLen = IF(minMatchLen < 1, 1, minMatchLen); 
    SET maxMatchLen = IF(maxMatchLen < 1 OR maxMatchLen > CHAR_LENGTH(subject), 
         CHAR_LENGTH(subject), maxMatchLen); 
    -- Set the pattern to use to match an entire string rather than part of a string 
    SET usePattern = IF (LEFT(pattern, 1) = '^', pattern, CONCAT('^', pattern)); 
    SET usePattern = IF (RIGHT(pattern, 1) = '$', usePattern, CONCAT(usePattern, '$')); 
    -- Set start position to 1 if pattern starts with^or doesn't end with $. 
    IF LEFT(pattern, 1) = '^' OR RIGHT(pattern, 1) <> '$' THEN 
     SET startPos = 1, startInc = 1; 
    -- Otherwise (i.e. pattern ends with $ but doesn't start with ^): Set start pos 
    -- to the min or max match length from the end (depending on "greedy" flag). 
    ELSEIF greedy THEN 
     SET startPos = CHAR_LENGTH(subject) - maxMatchLen + 1, startInc = 1; 
    ELSE 
     SET startPos = CHAR_LENGTH(subject) - minMatchLen + 1, startInc = -1; 
    END IF; 
    WHILE startPos >= 1 AND startPos <= CHAR_LENGTH(subject) 
     AND startPos + minMatchLen - 1 <= CHAR_LENGTH(subject) 
     AND !(LEFT(pattern, 1) = '^' AND startPos <> 1) 
     AND !(RIGHT(pattern, 1) = '$' 
      AND startPos + maxMatchLen - 1 < CHAR_LENGTH(subject)) DO 
     -- Set start length to maximum if matching greedily or pattern ends with $. 
     -- Otherwise set starting length to the minimum match length. 
     IF greedy OR RIGHT(pattern, 1) = '$' THEN 
     SET len = LEAST(CHAR_LENGTH(subject) - startPos + 1, maxMatchLen), lenInc = -1; 
     ELSE 
     SET len = minMatchLen, lenInc = 1; 
     END IF; 
     SET prevStartPos = startPos; 
     lenLoop: WHILE len >= 1 AND len <= maxMatchLen 
       AND startPos + len - 1 <= CHAR_LENGTH(subject) 
       AND !(RIGHT(pattern, 1) = '$' 
         AND startPos + len - 1 <> CHAR_LENGTH(subject)) DO 
     SET subStr = SUBSTRING(subject, startPos, len); 
     IF subStr REGEXP usePattern THEN 
      SET result = IF(startInc = 1, 
          CONCAT(result, replacement), CONCAT(replacement, result)); 
      SET startPos = startPos + startInc * len; 
      LEAVE lenLoop; 
     END IF; 
     SET len = len + lenInc; 
     END WHILE; 
     IF (startPos = prevStartPos) THEN 
     SET result = IF(startInc = 1, CONCAT(result, SUBSTRING(subject, startPos, 1)), 
         CONCAT(SUBSTRING(subject, startPos, 1), result)); 
     SET startPos = startPos + startInc; 
     END IF; 
    END WHILE; 
    IF startInc = 1 AND startPos <= CHAR_LENGTH(subject) THEN 
     SET result = CONCAT(result, RIGHT(subject, CHAR_LENGTH(subject) + 1 - startPos)); 
    ELSEIF startInc = -1 AND startPos >= 1 THEN 
     SET result = CONCAT(LEFT(subject, startPos), result); 
    END IF; 
    ELSE 
    SET result = subject; 
    END IF; 
    RETURN result; 
END// 
DELIMITER ; 

演示

Rextester Demo

限制

  1. 当主题字符串很大时,此方法当然需要一段时间。 更新:现在已经添加了最小和最大匹配长度参数以提高已知效率(零=未知/无限制)的效率。
  2. 不会允许反向引用的取代(例如\1\2 等)来代替捕获组。如果需要此功能,请参阅this answer,它试图通过更新该功能来提供解决方法,以允许在每个找到的匹配中进行二级查找和替换(以增加复杂性为代价)。
  3. 如果^和/或$在该模式中使用,它们必须分别处于最开始和最末端 - 例如,不支持(^start|end$)等模式。
  4. 有一个“贪婪”标志来指定整体匹配应该是贪婪还是非贪婪。不支持在单个正则表达式中结合贪婪和惰性匹配(例如a.*?b.*)。