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)
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
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
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
Ma méthode de force brute pour que cela fonctionne est la suivante:
mysqldump -u user -p database table > dump.sql
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.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.
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 ._,');
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
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 .
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
Limitations
\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).^
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.a.*?b.*
) n'est pas prise en charge.Exemples d'utilisation
La fonction a été utilisée pour répondre aux questions StackOverflow suivantes:
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[[:>:]]'
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');
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/
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 ;