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


516

50万行までのテーブルがあります。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が、それらはチェックしている場合ではなく、一致するものが試合があります。

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


8
これは2007年以降の機能リクエストです:bugs.mysql.com/bug.php?id=27389。この機能が本当に必要な場合は、ログインして「影響を受ける」ボタンをクリックしてください。うまくいけば、それは十分な票を獲得するでしょう。
TMS

4
@Tomas:私はそれをやった... 2009年に探していたとき。進捗状況はゼロのため、明らかに重要な機能ではありません。(ところでPostgresはそれを持っています:stackoverflow.com/questions/11722995/…
Piskvorは建物を去る

1
関連、この質問のシンプルなバージョン:stackoverflow.com/questions/6942973/...
Kzqai

2
演算子で実装されるregexp_split(関数+プロシージャ)&を作成しました。単純な検索の場合は、これでうまくいきます。あなたはここでそれを見つけるかもしれません-したがって、これはMySQLに保存されたコードでの方法であり、UDFではありません。既知の制限でカバーされていないバグが見つかった場合は、自由に問題を開いてください。regexp_replaceREGEXP
Alma Do

1
別のSOスレッドからこのライブラリを見つけました:github.com/mysqludf/lib_mysqludf_pregは完全に機能します。
カイル

回答:


78

MySQL 8.0以降、あなたはネイティブに使用できるREGEXP_REPLACE機能を。

12.5.2正規表現

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

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

正規表現のサポート

以前は、MySQLはヘンリースペンサー正規表現ライブラリを使用して正規表現演算子(REGEXPRLIKE)をサポートしていました。

正規表現のサポートは、Unicode対応の国際コンポーネント(ICU)を使用して再実装され、完全なUnicodeサポートを提供し、マルチバイトに対して安全です。REGEXP_LIKE()関数は、のように正規表現マッチング行うREGEXPRLIKE、今、その関数の同義語であるオペレータを、。また、 REGEXP_INSTR() REGEXP_REPLACE()、および REGEXP_SUBSTR() 機能それぞれ、マッチ位置を見つけて、サブストリング置換および抽出を実行するために利用可能です。

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

DBFiddleデモ


147

MySQL 8.0以降

ネイティブを使用できます REGEXP_REPLACE関数。

古いバージョン:

mysql-udf-regexpのようなユーザー定義関数(UDF)を使用できます。


3
ユーザー定義関数としてのREGEXP_REPLACE?有望に見える、それを調べます。ありがとう!
Piskvorが建物を去った

15
残念ながら、mysql-udf-regexpはマルチバイト文字をサポートしていないようです。regexp_replace( 'äöõü'、 'ä'、 '')は、実際のテキストではなく長い数値文字列を返します。
lkraav、2012

3
MySQL自体は、RegEx機能でマルチバイト文字をサポートしていません。
ブラッド

4
Windowsユーザー:ここにリンクされているUDFライブラリは、Windowsを適切にサポートしていないようです。概説されたウィンドウズのインストール方法は私にはうまくいきませんでした。
ジョナサン

2
@lkraavはうまく機能するため、以下のlib_mysqludf_pregライブラリを試す必要があります。これはデフォルトでblobを返すため、この冗長バージョンです。デフォルトとしてマルチバイト文字セットがあるかどうかはわかりません。selectcast(TR as char)COLLATE utf8_unicode_ci from(select preg_replace( '/ä/'、 ''、 'öõüä')R)T
Gillyspy

124

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

REGEXP_REPLACE(col, regexp, replace)

MariaDBのドキュメントPCREの正規表現の機能強化をご覧ください

正規表現のグループ化も使用できることに注意してください(非常に便利だと思いました)。

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

戻り値

over - stack - flow

12
これは、mariadb 10から
Nick

6
次に必要になるときは、列全体を変更するための構文を次に示します。UPDATE table SET Name = REGEXP_REPLACE(Name, "-2$", "\\1")これにより、abcxyz-2から-2が列全体から一度に削除されます。
ジョサイア

27
プラットフォーム全体を変更することは、現実的な解決策とは言えません。
David Baucum

3
@DavidBaucum MariaDBは、MySQLのドロップイン置換です。つまり、「プラットフォームの変更」ではなく、同じ旅行に別の航空会社を選択するようなものです
Benvorth '30


113

これを機能させるための私の力ずくの方法はただ次のとおりです。

  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

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


33
さて、それもうまくいくはずです。オフラインでの交換は考えていませんでした。すぐに使える素晴らしい考え!
Piskvorが建物を去る

10
そのようにfindを使用するのは奇妙に思えますが、コマンドをsed -i 's / old_string / new_string / g' /path/to/dump.sqlに短縮します
speshak

36
非常にリスクが高く、ビッグデータセットや参照整合性が適切でない場合は非実用的です。データを削除してから再度挿入するには、参照整合性をオフにし、実際にはデータベースもオフにする必要があります。
Raul Luna 2014年

5
過去にこの方法を使用したことがあるので、私はラウルに腹を立てますが、これは非常に危険です。また、文字列がデータセット内のどこにもないことを完全に確認する必要もあります。
eggmatters

1
回答@speshakには何年も遅れますが、このようにファイルにアクセスすることを選択したのは、上記と同じ理由で元々緊張していたからです。当時、「ファイルを見つける」部分を「置換」部分から分離すると、コードを送信する前にコードが読みやすくなります
Ryan Ward

42

正規表現を使用せずにこの問題を解決します。このクエリは完全一致文字列のみを置き換えます。

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 ajay

3 ABC


@yellowmelon 2組の二重引用符は何のためのものですか?
codecowboy 2016年

5
彼は従業員名の前後にスペースを埋め込みます。これにより、(space)employeename(space)の検索置換が可能になり、より大きな文字列 "ajay"の一部である場合に従業員名 "jay"をキャッチすることが回避されます。それから、彼は終わったらスペースを取り除きます。
スラム

42

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

25
上記の点を強調しておきます。この関数は、単一文字式に一致する文字を置き換えます。上記では「正規表現を使用して文字列を置き換える」ために使用されていると述べており、誤解を招く可能性があります。それはその仕事をしますが、それは求められている仕事ではありません。(不満ではありません-それは単に主要な人々を間違った道に
ジェイソン

2
ネイキッドリンクを投稿するのではなく、コードを実際に回答に含める方が便利です。
phobie 2015年

2
いいですが、残念ながらselect regex_replace('.*(abc).*','\1','noabcde')(「abc」ではなく「noabcde」を返す)などの参照は扱いません。
イジー

@phobie他の誰かがこの回答でそれを行いました–リンクが切れた場合の参考として;)
Izzy

このメソッドを変更して、上記の制限のいくつかに対処しようとしました。この回答をご覧ください。
Steve Chambers


13

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


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

使用例

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


7

あなたはそれを「することができます」...しかしそれはあまり賢明ではありません...これは私が試す限り大胆です...完全なRegExが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」になります。
Ryan Shillington

1
@ライアン...それがまさに私がそれがあまり賢明ではないと述べた理由です...あなたが提供するユースケースでは、これは間違いなく失敗するでしょう。つまり、「正規表現のような」構造を使用することは悪い考えです。さらに悪いことに... where句を削除すると、すべての値がNULLになります...
Eddie B

1
単語が一致し前と後の実際のマーカーとして、あなたがしている間違ったこのケースでライアンは長さゼロの単語「境界」のマッチ境界線とこれだけの単語を検索します...それはしかしまだ悪いアイデアです...
エディB

6

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

こんにちは、提案をありがとう。同様のことを試みていますが、データセットのパフォーマンスは満足のいくものではありませんでした。小さめのセットの場合、これは実行可能な場合があります。
Piskvorが建物を去る14

3

以下のものは、基本的に左から最初の一致を見つけて、それをすべて置き換えます(テスト済み )。

使用法:

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

実装:

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 ;

3

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

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/


ありがとう:)バージョン8以降簡単に行うことが可能です
Piskvorは、建物の左側の
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.