Come sostituire un'espressione regolare in MySQL?


516

Ho una tabella con ~ 500k righe; varchar (255) La colonna UTF8 filenamecontiene un nome file;

Sto cercando di eliminare vari strani caratteri dal nome del file - ho pensato di usare una classe di caratteri: [^a-zA-Z0-9()_ .\-]

Ora, c'è una funzione in MySQL che ti consente di sostituire con un'espressione regolare ? Sto cercando una funzionalità simile alla funzione REPLACE () - l'esempio semplificato segue:

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

Output: "stackoverflow"

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

Output: "-tackover-low"

Conosco REGEXP / RLIKE , ma quelli controllano solo se c'è una partita, non quale sia la partita.

(Ho potuto fare un " SELECT pkey_id,filename FROM foo WHERE filename RLIKE '[^a-zA-Z0-9()_ .\-]'" da uno script PHP, fare preg_replacee poi " UPDATE foo ... WHERE pkey_id=...", ma che assomiglia ad un ultima istanza lento e brutto hack)


8
È una richiesta di funzionalità dal 2007: bugs.mysql.com/bug.php?id=27389 . Se vuoi davvero questa funzione, accedi e fai clic sul pulsante "Mi interessa". Spero che ottenga abbastanza voti.
TMS

4
@Tomas: l'ho fatto ... nel 2009, quando lo stavo cercando. Dato che non ci sono stati progressi su di esso - apparentemente non è una caratteristica così importante. (btw Postgres ha: stackoverflow.com/questions/11722995/... )
Piskvor lasciato l'edificio

1
Correlati, più semplice, versione di questa domanda: stackoverflow.com/questions/6942973/...
Kzqai

2
Ho creato regexp_split(funzione + procedura) e regexp_replace, che sono implementati con l' REGEXPoperatore. Per ricerche semplici, farà il trucco. Puoi trovarlo qui - quindi, questo è il modo con il codice memorizzato MySQL, senza UDF. Se troverai alcuni bug, che non sono coperti da limitazioni conosciute, sentiti libero di aprire il problema.
Alma Do,

1
Ho trovato questa libreria da un altro thread SO: github.com/mysqludf/lib_mysqludf_preg funziona perfettamente.
Kyle,

Risposte:


78

Con MySQL 8.0+ è possibile utilizzare la REGEXP_REPLACEfunzione nativa .

12.5.2 Espressioni regolari :

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

Sostituisce le occorrenze della stringa expr che corrispondono all'espressione regolare specificata dal modello pat con la stringa di sostituzione repl , e restituisce la stringa risultante. Se expr , pat , o repl è NULL, il valore restituito è NULL.

e supporto per le espressioni regolari :

In precedenza, MySQL utilizzava la libreria di espressioni regolari Henry Spencer per supportare gli operatori di espressioni regolari ( REGEXP, RLIKE).

Il supporto per le espressioni regolari è stato reimplementato utilizzando International Components for Unicode (ICU), che fornisce il supporto completo per Unicode ed è sicuro per più byte. La REGEXP_LIKE()funzione esegue la corrispondenza delle espressioni regolari nel modo degli operatori REGEXPe RLIKE, che ora sono sinonimi di quella funzione. Inoltre, la REGEXP_INSTR(), REGEXP_REPLACE()e REGEXP_SUBSTR() funzioni sono disponibili per trovare le posizioni coi ed eseguire sottostringa sostituzione ed estrazione, rispettivamente.

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

DBFiddle Demo


147

MySQL 8.0+ :

È possibile utilizzare la REGEXP_REPLACEfunzione nativa .

Versioni precedenti:

È possibile utilizzare una funzione definita dall'utente ( UDF ) come mysql-udf-regexp .


3
REGEXP_REPLACE come funzione definita dall'utente? Sembra promettente, lo esaminerò. Grazie!
Piskvor lasciò l'edificio il

15
Sfortunatamente mysql-udf-regexp non sembra avere supporto per i caratteri multibyte. regexp_replace ('äöõü', 'ä', '') restituisce una lunga stringa numerica invece del vero testo.
lkraav,

3
MySQL stesso non supporta i caratteri multi-byte con le sue funzionalità RegEx.
Brad

4
Utenti Windows: la libreria UDF collegata qui non sembra avere un buon supporto di Windows. Il metodo di installazione di Windows delineato non ha funzionato bene per me.
Jonathan,

2
@lkraav dovresti provare la libreria lib_mysqludf_preg di seguito in quanto funziona alla grande. Questa è la versione dettagliata in quanto restituisce un BLOB per impostazione predefinita e non so se hai un set di caratteri multibyte come predefinito: seleziona cast (TR come carattere) COLLATE utf8_unicode_ci da (seleziona preg_replace ('/ ä /', '', 'öõüä') R) T
gillyspy,

124

Utilizzare invece MariaDB. Ha una funzione

REGEXP_REPLACE(col, regexp, replace)

Consulta i documenti di MariaDB e i miglioramenti delle espressioni regolari PCRE

Nota che puoi usare anche il raggruppamento di regexp (l'ho trovato molto utile):

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

ritorna

over - stack - flow

12
questo è da MariaBaria 10
Nick,

6
Per la prossima volta che ne ho bisogno, ecco la sintassi per cambiare un'intera colonna: UPDATE table SET Name = REGEXP_REPLACE(Name, "-2$", "\\1")questo rimuove -2 da abcxyz-2 da un'intera colonna contemporaneamente.
Giosia,

27
Cambiare un'intera piattaforma non è certo una soluzione realistica.
David Baucum,

3
@DavidBaucum MariaDB è un sostituto drop-in per MySQL. Quindi non è un "cambio di piattaforma", ma è più come scegliere una compagnia aerea diversa per lo stesso viaggio
Benvorth,


113

Il mio metodo di forza bruta per farlo funzionare era solo:

  1. Dump della tabella - mysqldump -u user -p database table > dump.sql
  2. Trova e sostituisci un paio di schemi - find /path/to/dump.sql -type f -exec sed -i 's/old_string/new_string/g' {} \;, Ovviamente ci sono anche altre espressioni regeular perl che potresti eseguire sul file.
  3. Importa la tabella - mysqlimport -u user -p database table < dump.sql

Se vuoi assicurarti che la stringa non sia altrove nel tuo set di dati, esegui alcune espressioni regolari per assicurarti che si verifichino tutte in un ambiente simile. Inoltre, non è così difficile creare un backup prima di eseguire una sostituzione, nel caso in cui si distrugga accidentalmente qualcosa che perde la profondità delle informazioni.


33
Va bene, dovrebbe funzionare anche quello; Non ho considerato un sostituto offline. Bello fuori dagli schemi pensando lì!
Piskvor lasciò l'edificio il

10
Mi sembra strano che useresti di trovare in quel modo, accorcerei il comando per sed -i 's / old_string / new_string / g' /path/to/dump.sql
speshak

36
Molto rischioso e poco pratico con set di dati di grandi dimensioni o con integrità referenziale in atto: per rimuovere i dati e quindi reinserirli dovrai disattivare l'integrità referenziale, lasciando in pratica anche il tuo database.
Raul Luna,

5
Avendo usato questo metodo in passato, mi associo a Raul, è molto rischioso. Devi anche essere assolutamente certo che la tua stringa non è da nessuna parte nel tuo set di dati.
Eggmatters

1
Anni dopo la risposta a @speshak, ma il motivo per cui ho scelto di accedere al file in questo modo era perché inizialmente ero molto nervoso per gli stessi motivi di cui sopra. All'epoca sembrava che separare la parte "trova il file" dalla parte "sostituisci" avrebbe reso il codice più facile da leggere prima che lo
inviassi

42

risolviamo questo problema senza usare regex, questa query sostituisce solo la stringa di corrispondenza esatta.

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

Esempio:

emp_id employee_firstname

1 luglio

2 luglio ajay

3 luglio

Dopo aver eseguito il risultato della query:

emp_id employee_firstname

1 abc

2 abc ajay

3 abc


@yellowmelon a cosa servono le due coppie di virgolette?
codecowboy

5
Riempie il nome utente con spazi prima e dopo. Questo gli permette di cercare-sostituire (spazio) Employeename (spazio), che evita di prendere il Employeename "jay" se la sua parte di una stringa più grande "ajay". Quindi, al termine, ritaglia gli spazi.
Slam

42

Di recente ho scritto una funzione MySQL per sostituire le stringhe usando espressioni regolari. Puoi trovare il mio post nella seguente posizione:

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

Ecco il codice funzione:

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 ;

Esempio di esecuzione:

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

25
Rinforzerò solo il punto precedente: questa funzione sostituisce i caratteri che corrispondono a un'espressione a carattere singolo. Dice sopra che è usato "per riprogrammare le stringhe usando espressioni regolari", e questo può essere un po 'fuorviante. Fa il suo lavoro, ma non è il lavoro richiesto. (Non è una lamentela - è solo per salvare le persone che portano sulla strada sbagliata)
Jason

2
Sarebbe più utile includere effettivamente il codice nella risposta invece di pubblicare un link nudo.
phobie,

2
Bello - ma sfortunatamente non tratta riferimenti come select regex_replace('.*(abc).*','\1','noabcde')(restituisce 'noabcde', non 'abc').
Izzy,

@phobie lo ha fatto qualcun altro in questa risposta - proprio come riferimento nel caso in cui il collegamento si interrompa;)
Izzy

Ho modificato questo metodo per tentare di affrontare alcune delle limitazioni sopra menzionate e altro. Si prega di vedere questa risposta .
Steve Chambers,


13

AGGIORNAMENTO 2: un utile set di funzioni regex tra cui REGEXP_REPLACE è stato ora fornito in MySQL 8.0. Questo rende la lettura inutile a meno che tu non sia costretto a utilizzare una versione precedente.


AGGIORNAMENTO 1: Ora l'ho trasformato in un post sul blog: http://stevettt.blogspot.co.uk/2018/02/a-mysql-regular-expression-replace.html


Quanto segue si espande sulla funzione fornita da Rasika Godawatte ma scorre attraverso tutte le sottostringhe necessarie invece di testare singoli caratteri:

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

dimostrazione

Demo di Rextester

limitazioni

  1. Questo metodo richiederà ovviamente un po 'di tempo quando la stringa del soggetto è grande. Aggiornamento: ora sono stati aggiunti i parametri di lunghezza minima e massima della corrispondenza per migliorare l'efficienza quando sono noti (zero = sconosciuto / illimitato).
  2. Essa non consentono la sostituzione di backreference (ad esempio \1, \2 ecc) per sostituire gruppi di cattura. Se è necessaria questa funzionalità, vedere questa risposta che tenta di fornire una soluzione alternativa aggiornando la funzione per consentire una ricerca e sostituzione secondarie all'interno di ogni corrispondenza trovata (a spese di una maggiore complessità).
  3. Se ^e / o $viene utilizzato nel pattern, devono essere rispettivamente all'inizio e alla fine, ad esempio pattern come (^start|end$)non supportati.
  4. C'è un flag "goloso" per specificare se la corrispondenza complessiva deve essere golosa o non golosa. La combinazione di corrispondenza avida e pigra all'interno di una singola espressione regolare (ad esempio a.*?b.*) non è supportata.

Esempi di utilizzo

La funzione è stata utilizzata per rispondere alle seguenti domande StackOverflow:


7

Puoi 'farlo' ... ma non è molto saggio ... questo è audace come proverò ... per quanto riguarda il supporto completo di RegEx, farai molto meglio a usare perl o simili.

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
No, non funzionerà. Immagina che la tua colonna contenga "asdfWORD_TO_REPLACE WORD_TO_REPLACE". Il tuo metodo risulterebbe in "asdfREPLACEMENT REPLACEMENT" dove la risposta corretta sarebbe "asdfWORD_TO_REPLACE REPLACEMENT".
Ryan Shillington,

1
@Ryan ... questo è esattamente il motivo per cui ho affermato che non era molto saggio ... nel caso in cui tu fornissi questo sicuramente fallirebbe. In breve, è una cattiva idea usare la struttura "regex-like". Ancora peggio ... se lasci cadere la clausola where tutti i tuoi valori saranno NULL ...
Eddie B,

1
In realtà Ryan in questo caso non sei corretto poiché gli indicatori troveranno solo corrispondenze per la parola di "zero" di lunghezza zero, quindi solo le parole con limiti prima e dopo la parola corrisponderebbero ... È comunque una cattiva idea ...
Eddie B,

6

Possiamo usare la condizione IF nella query SELECT come di seguito:

Supponiamo che per qualsiasi cosa con "ABC", "ABC1", "ABC2", "ABC3", ..., vogliamo sostituire con "ABC" quindi usando la condizione REGEXP e IF () nella query SELECT, possiamo ottenere questo .

Sintassi:

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

Esempio:

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

Ciao, grazie per il suggerimento. Ho provato qualcosa di simile, ma le prestazioni sui miei set di dati sono state insoddisfacenti. Per i set più piccoli, questo può essere praticabile.
Piskvor lasciò l'edificio il

3

Quello sotto trova fondamentalmente la prima corrispondenza da sinistra e quindi ne sostituisce tutte le occorrenze (testato in ).

Uso:

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

Implementazione:

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

Penso che ci sia un modo semplice per raggiungere questo obiettivo e sta funzionando bene per me.

Per selezionare le righe usando REGEX

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

AGGIORNARE le righe usando 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'

Riferimento REGEXP: https://www.geeksforgeeks.org/mysql-regular-expressions-regexp/


Grazie :) È possibile farlo facilmente dalla versione 8.
Piskvor ha lasciato l'edificio il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.