web-dev-qa-db-fra.com

Comment faire pour remplacer une expression régulière dans MySQL?

J'ai une table avec ~ 500k lignes; varchar (255) UTF8 column filename contient un nom de fichier;

J'essaie d'éliminer divers noms étranges du nom du fichier. Je pensais utiliser une classe de caractères: [^a-zA-Z0-9()_ .\-].

Maintenant, existe-t-il une fonction dans MySQL qui vous permet de remplacer par une expression régulière ? Je recherche une fonctionnalité similaire à la fonction REPLACE () - voici un exemple simplifié:

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

Output: "stackoverflow"

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

Output: "-tackover-low"

Je sais à propos de REGEXP/RLIKE , mais ceux-ci ne font que vérifier si il y a une correspondance, pas quoi le match est.

(I pourrait faire un "SELECT pkey_id,filename FROM foo WHERE filename RLIKE '[^a-zA-Z0-9()_ .\-]'" à partir d'un script PHP, faire un preg_replace puis "UPDATE foo ... WHERE pkey_id=...", mais ressemble à un hack lent et laid de dernier recours)

471
Piskvor

Vous pouvez utiliser MySQL 8.0+ de manière native REGEXP_REPLACE.

12.5.2 Expressions régulières :

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

Remplace les occurrences dans la chaîne expr qui correspondent à l'expression régulière spécifiée par le modèle de motif par la chaîne de remplacement repl et renvoie la chaîne résultante. Si expr, pat ou repl est NULL, la valeur de retour est NULL.

et support des expressions régulières :

Auparavant, MySQL utilisait la bibliothèque d’expression régulière Henry Spencer pour prendre en charge les opérateurs d’expression régulière (REGEXP, RLIKE).

La prise en charge des expressions régulières a été réimplémentée à l'aide de ICU (International Components for Unicode), qui fournit une prise en charge Unicode complète et est sécurisé pour plusieurs octets. La fonction REGEXP_LIKE () effectue une correspondance d'expression régulière à la manière des opérateurs REGEXP et RLIKE, qui sont désormais des synonymes de cette fonction. De plus, les fonctions REGEXP_INSTR (), REGEXP_REPLACE () et REGEXP_SUBSTR () sont disponibles pour rechercher des positions de correspondance et effectuer des substitutions et des extractions de sous-chaînes, respectivement.

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

Démo DBFiddle

45
Lukasz Szozda

Non.

Mais si vous avez accès à votre serveur, vous pouvez utiliser une fonction définie par l'utilisateur (UDF) telle que mysql-udf-regexp .

EDIT: MySQL 8.0+, vous pouvez utiliser nativement REGEXP_REPLACE. Plus en réponse ci-dessus

140
Jeremy Stein

Utilisez plutôt MariaDB. Il a une fonction

REGEXP_REPLACE(col, regexp, replace)

Voir docs MariaDB et améliorations de l’expression régulière PCRE

Notez que vous pouvez aussi utiliser le groupement d'expressions rationnelles (j'ai trouvé cela très utile):

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

résultats

over - stack - flow
120
Benvorth

Ma méthode de force brute pour que cela fonctionne est la suivante:

  1. Vider la table - mysqldump -u user -p database table > dump.sql
  2. Recherchez et remplacez quelques modèles - find /path/to/dump.sql -type f -exec sed -i 's/old_string/new_string/g' {} \;, Il est évident que vous pouvez également utiliser d'autres expressions régulières Perl sur le fichier.
  3. Importer la table - mysqlimport -u user -p database table < dump.sql

Si vous voulez vous assurer que la chaîne n'est pas ailleurs dans votre jeu de données, exécutez quelques expressions régulières pour vous assurer qu'elles se produisent toutes dans un environnement similaire. Il n’est pas difficile non plus de créer une sauvegarde avant de remplacer, au cas où vous détruisez accidentellement quelque chose qui perd de la profondeur de l’information.

107
Ryan Ward

J'ai récemment écrit une fonction MySQL pour remplacer les chaînes de caractères à l'aide d'expressions régulières. Vous pouvez trouver mon message à l'adresse suivante:

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

Voici le code de fonction:

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 ;

Exemple d'exécution:

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

nous résolvons ce problème sans utiliser regex. Cette requête remplace uniquement la chaîne de correspondance exacte.

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

Exemple:

emp_id employee_firstname

1 geai

2 jay ajay

3 geais

Après avoir exécuté le résultat de la requête:

emp_id employee_firstname

1 abc

2 abc ajay

3 abc

37
Jay Patel

Je suis heureux d'annoncer que, depuis que cette question a été posée, il existe maintenant une réponse satisfaisante! Jetez un coup d'oeil à ce forfait génial:

https://github.com/mysqludf/lib_mysqludf_preg

Exemple de code SQL:

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

J'ai trouvé le paquet de cet article de blog comme lié sur cette question .

14
dotancohen

PDATE 2: Un ensemble utile de fonctions regex, y compris REGEXP_REPLACE , a maintenant été fourni dans MySQL 8.0. Cela rend la lecture inutile si vous n'êtes pas obligé d'utiliser une version antérieure.


PDATE 1: J'ai maintenant transformé cela en un article de blog: http://stevettt.blogspot.co.uk/2018/02/a-mysql-normal-expression-replace.html


Les éléments suivants développent le fonction fournie par Rasika Godawatte mais parcourent toutes les sous-chaînes nécessaires au lieu de simplement tester des caractères uniques:

-- ------------------------------------------------------------------------------------
-- 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 ;

démo

Démo Rextester

Limitations

  1. Cette méthode va bien sûr prendre un certain temps lorsque la chaîne de sujet est longue. Mise à jour: Ont maintenant ajouté des paramètres de longueur de correspondance minimum et maximum pour une efficacité améliorée lorsque ceux-ci sont connus (zéro = inconnu/illimité ).
  2. Cela not ​​permet la substitution de références arrières (par exemple \1, \2 etc.) pour remplacer les groupes capturés. Si cette fonctionnalité est nécessaire, veuillez consulter cette réponse qui tente de fournir une solution de contournement en mettant à jour la fonction pour permettre une recherche secondaire et une substitution dans chaque correspondance trouvée (au prix d'une complexité accrue).
  3. Si ^et/ou $ sont utilisés dans le modèle, ils doivent figurer respectivement au tout début et à la fin - par ex. les motifs tels que (^start|end$) ne sont pas pris en charge.
  4. Un indicateur "glouton" permet de spécifier si la correspondance globale doit être gloutonne ou non gloutonne. La combinaison de correspondances gourmandes et paresseuses dans une seule expression régulière (par exemple, a.*?b.*) n'est pas prise en charge.

Exemples d'utilisation

La fonction a été utilisée pour répondre aux questions StackOverflow suivantes:

11
Steve Chambers

Vous 'pouvez' le faire ... mais ce n'est pas très sage ... c'est à peu près aussi audacieux que je vais essayer ... dans la mesure où tout le support RegEx vous aide beaucoup mieux en utilisant Perl ou similaire.

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

Nous pouvons utiliser la condition IF dans la requête SELECT comme ci-dessous:

Supposons que pour quoi que ce soit avec "ABC", "ABC1", "ABC2", "ABC3", ..., nous voulons remplacer par "ABC", puis en utilisant les conditions REGEXP et IF () dans la requête SELECT, nous pouvons y parvenir. .

Syntaxe:

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

Exemple:

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

Je pense qu’il existe un moyen facile d’y parvenir et que cela fonctionne très bien pour moi.

Pour sélectionner des lignes à l'aide de REGEX

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

Pour mettre à jour des lignes à l'aide de 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'

Référence REGEXP: https://www.geeksforgeeks.org/mysql-regular-expressions-regexp/

1
silambarasan R.D

Celui ci-dessous trouve la première correspondance à gauche, puis en remplace toutes les occurrences (testé dans mysql-5.6 ).

Utilisation:

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

Mise en oeuvre:

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