web-dev-qa-db-ja.com

MySQLで正規表現を置き換える方法

〜500k行のテーブルがあります。 varchar(255)UTF8列filenameにはファイル名が入ります。

私はファイル名からさまざまな奇妙な文字を取り除こうとしています - 私は文字クラスを使用すると思いました:[^a-zA-Z0-9()_ .\-]

MySQLには、正規表現 を使って置き換えることができる関数がありますか。私はREPLACE()関数に似た機能を探しています - 簡単な例を以下に示します。

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

Output: "stackoverflow"

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

Output: "-tackover-low"

私は/について知っています REGEXP/RLIKE しかし、それらだけチェックします もし 一致がある、ない 一致はそうです。

(I できます PHPスクリプトから "SELECT pkey_id,filename FROM foo WHERE filename RLIKE '[^a-zA-Z0-9()_ .\-]'"を実行し、次にpreg_replaceを実行してから "UPDATE foo ... WHERE pkey_id=..."を実行しますが、これは最後の手段として遅くて醜いハックのようです)

465
Piskvor

MySQL 8.0以降ではREGEXP_REPLACEをネイティブに使用できます。

12.5.2正規表現

REGEXP_REPLACE(expr、pat、repl [、pos [、occurrence [、match_type]]]])

パターンpatで指定された正規表現に一致する文字列expr内の出現箇所を置換文字列replで置き換え、結果の文字列を返します。 expr、pat、またはreplがNULLの場合、戻り値はNULLです。

and 正規表現のサポート

以前は、MySQLはHenry Spencer正規表現ライブラリを使用して正規表現演算子をサポートしていました(REGEXP、RLIKE)。

正規表現のサポートは、完全なUnicodeサポートを提供し、マルチバイトセーフであるInternational Components for Unicode(ICU)を使用して再実装されました。 REGEXP_LIKE()関数は、REGEXPおよびRLIKE演算子の方法で正規表現マッチングを実行します。これらの演算子は、その関数の同義語です。 さらに、REGEXP_INSTR()、REGEXP_REPLACE()、およびREGEXP_SUBSTR()関数を使用して、一致位置を見つけ、部分文字列の置換と抽出をそれぞれ実行できます。

SELECT REGEXP_REPLACE('Stackoverflow','[A-Zf]','-',1,0,'c'); 
-- Output:
-tackover-low

DBFiddleデモ

40
Lukasz Szozda

いいえ.

しかし、サーバーにアクセスできる場合は、 mysql-udf-regexp のようなユーザー定義関数(UDF)を使用できます。

編集: MySQL 8.0以降では、ネイティブにREGEXP_REPLACEを使用できます。上記の回答の詳細

138
Jeremy Stein

代わりにMariaDBを使用してください。機能あり

REGEXP_REPLACE(col, regexp, replace)

MariaDBのドキュメント および PCREの正規表現の機能強化 を参照してください。

正規表現のグループ化も使えることに注意してください(私はそれが非常に役に立つことがわかりました)。

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

戻る

over - stack - flow
118
Benvorth

これを機能させるための私のブルートフォース方法はちょうど:

  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

文字列がデータセット内の他の場所にないことを確認したい場合は、いくつかの正規表現を実行して、それらがすべて同じ環境で発生するようにします。また、誤って情報の深さを失ったものを破壊してしまった場合に備えて、置き換えを実行する前にバックアップを作成するのはそれほど難しいことではありません。

105
Ryan Ward

私は最近、正規表現を使って文字列を置き換える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 ._,');
42

正規表現を使わずにこの問題を解決します。

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

例:

emp_id従業員名

1ジェイ

2ジェイアジェイ

3ジェイ

クエリ結果実行後

emp_id従業員名

1 abc

2 abc ajay

3 abc

33
Jay Patel

この質問がされて以来、満足のいく答えがあります。この素晴らしいパッケージを見てください。

https://github.com/mysqludf/lib_mysqludf_preg

サンプルSQL

SELECT PREG_REPLACE('/(.*?)(fox)/' , 'dog' , 'the quick brown fox' ) AS demo;

このブログ投稿 からリンクされているパッケージを見つけました この質問

14
dotancohen

PDATE 2:REGEXP_REPLACE を含む便利な正規表現関数のセットがMySQL 8.0で提供されています。これにより、以前のバージョンを使用するように制限されていない限り、不要な読み取りが行われます。


PDATE 1:これをブログ投稿にしました: http://stevettt.blogspot.co.uk/2018/02/a-mysql-regular-expression-replace.html


以下は 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デモ

制限

  1. このメソッドは、件名の文字列が大きい場合はもちろん時間がかかります。 Update:最小および最大一致長パラメーターが追加され、既知の場合の効率を改善(ゼロ=不明/無制限) )。
  2. それwo n't後方参照の置換(たとえば、\1\2など)を使用して、キャプチャグループを置き換えます。この機能が必要な場合は、 this answer を参照してください。関数を更新することで回避策を提供し、見つかった各一致内で2次検索および置換を許可します(複雑さが増します)。
  3. ^および/または$がパターンで使用される場合、それらはそれぞれ最初と最後になければなりません。 (^start|end$)などのパターンはサポートされていません。
  4. 全体のマッチングを貪欲にするか、貪欲でないかを指定する「貪欲な」フラグがあります。単一の正規表現(たとえば、a.*?b.*)内での欲張りマッチングと遅延マッチングの組み合わせはサポートされていません。

使用例

この関数は、次のStackOverflowの質問に答えるために使用されています。

11
Steve Chambers

あなたはそれを「する」ことができます...しかしそれほど賢明ではありません...これは私がやろうとしているのと同じくらい大胆なことです...

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[[:>:]]'
7
Eddie B

以下のように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');
5
user3796869

これを達成する簡単な方法があり、私にとってはうまく機能していると思います。

REGEXを使用して行を選択するには

SELECT * FROM `table_name` WHERE `column_name_to_find` REGEXP 'string-to-find'

REGEXを使用して行を更新するには

UPDATE `table_name` SET column_name_to_find=REGEXP_REPLACE(column_name_to_find, 'string-to-find', 'string-to-replace') WHERE column_name_to_find REGEXP 'string-to-find'

REGEXPリファレンス: https://www.geeksforgeeks.org/mysql-regular-expressions-regexp/

1

以下のものは基本的に左から最初の一致を見つけて、それから全ての出現を置き換えます( mysql-5.6 でテストされています)。

使用法:

SELECT REGEX_REPLACE('dis ambiguity', 'dis[[:space:]]*ambiguity', 'disambiguity');

Implementation:

DELIMITER $$
CREATE FUNCTION REGEX_REPLACE(
  var_original VARCHAR(1000),
  var_pattern VARCHAR(1000),
  var_replacement VARCHAR(1000)
  ) RETURNS
    VARCHAR(1000)
  COMMENT 'Based on https://techras.wordpress.com/2011/06/02/regex-replace-for-mysql/'
BEGIN
  DECLARE var_replaced VARCHAR(1000) DEFAULT var_original;
  DECLARE var_leftmost_match VARCHAR(1000) DEFAULT
    REGEX_CAPTURE_LEFTMOST(var_original, var_pattern);
    WHILE var_leftmost_match IS NOT NULL DO
      IF var_replacement <> var_leftmost_match THEN
        SET var_replaced = REPLACE(var_replaced, var_leftmost_match, var_replacement);
        SET var_leftmost_match = REGEX_CAPTURE_LEFTMOST(var_replaced, var_pattern);
        ELSE
          SET var_leftmost_match = NULL;
        END IF;
      END WHILE;
  RETURN var_replaced;
END $$
DELIMITER ;

DELIMITER $$
CREATE FUNCTION REGEX_CAPTURE_LEFTMOST(
  var_original VARCHAR(1000),
  var_pattern VARCHAR(1000)
  ) RETURNS
    VARCHAR(1000)
  COMMENT '
  Captures the leftmost substring that matches the [var_pattern]
  IN [var_original], OR NULL if no match.
  '
BEGIN
  DECLARE var_temp_l VARCHAR(1000);
  DECLARE var_temp_r VARCHAR(1000);
  DECLARE var_left_trim_index INT;
  DECLARE var_right_trim_index INT;
  SET var_left_trim_index = 1;
  SET var_right_trim_index = 1;
  SET var_temp_l = '';
  SET var_temp_r = '';
  WHILE (CHAR_LENGTH(var_original) >= var_left_trim_index) DO
    SET var_temp_l = LEFT(var_original, var_left_trim_index);
    IF var_temp_l REGEXP var_pattern THEN
      WHILE (CHAR_LENGTH(var_temp_l) >= var_right_trim_index) DO
        SET var_temp_r = RIGHT(var_temp_l, var_right_trim_index);
        IF var_temp_r REGEXP var_pattern THEN
          RETURN var_temp_r;
          END IF;
        SET var_right_trim_index = var_right_trim_index + 1;
        END WHILE;
      END IF;
    SET var_left_trim_index = var_left_trim_index + 1;
    END WHILE;
  RETURN NULL;
END $$
DELIMITER ;
1
Nae