Как сделать регулярное выражение заменить в MySQL?

516

У меня есть таблица с ~ 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 , но те только проверить , если есть совпадение, а не то , что матч есть.

мог бы сделать " SELECT pkey_id,filename FROM foo WHERE filename RLIKE '[^a-zA-Z0-9()_ .\-]'" из скрипта PHP, сделать " preg_replaceа потом" UPDATE foo ... WHERE pkey_id=...", но это похоже на последний медленный и уродливый хак)

Писквор покинул здание
источник
8
Это запрос функции с 2007 года: bugs.mysql.com/bug.php?id=27389 . Если вы действительно хотите эту функцию, войдите в систему и нажмите кнопку «Влияет на меня». Надеюсь, он получит достаточно голосов.
TMS
4
@ Томас: Я сделал это ... в 2009 году, когда я искал это. Поскольку прогресс в этом деле был нулевым - очевидно, это не такая важная особенность. (между прочим, у Postgres это есть: stackoverflow.com/questions/11722995/… )
Писквор покинул здание
1
Связанная, более простая версия этого вопроса: stackoverflow.com/questions/6942973/…
Kzqai
2
Я создал regexp_split(функция + процедура) & regexp_replace, которые реализованы с помощью REGEXPоператора. Для простых поисков это сделает свое дело. Вы можете найти это здесь - так, это путь с хранимым кодом MySQL, без UDF. Если вы обнаружите некоторые ошибки, на которые не распространяются известные ограничения - не стесняйтесь открывать проблему.
Алма До
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]]])

Заменяет вхождения в строке expr, которые соответствуют регулярному выражению, указанному в шаблоне pat, на строку замены repl и возвращает полученную строку. Если выражение , погладить , или РЕПЛ есть NULL, возвращаемое значение NULL.

и поддержка регулярных выражений :

Ранее MySQL использовал библиотеку регулярных выражений Генри Спенсера для поддержки операторов регулярных выражений ( REGEXP, RLIKE).

Поддержка регулярных выражений была переопределена с использованием International Components for Unicode (ICU), которая обеспечивает полную поддержку Unicode и является многобайтовой безопасностью. REGEXP_LIKE()Функция выполняет регулярные выражения в манере REGEXPи RLIKEоператоров, которые в настоящее время являются синонимами для этой функции. Кроме того, REGEXP_INSTR(), REGEXP_REPLACE()и REGEXP_SUBSTR() функции доступны для поиска соответствия позиции и выполнить замену подстроки и экстракции, соответственно.

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

DBFiddle Demo

Лукаш Шозда
источник
147

MySQL 8.0+ :

Вы можете использовать встроенную REGEXP_REPLACEфункцию.

Старые версии:

Вы можете использовать пользовательскую функцию ( UDF ), например mysql-udf-regexp .

Джереми Стейн
источник
3
REGEXP_REPLACE как пользовательская функция? Выглядит многообещающе, посмотрю на это. Спасибо!
Писквор покинул здание
15
К сожалению, mysql-udf-regexp не поддерживает многобайтовые символы. regexp_replace ('äöõü', 'ä', '') возвращает длинную числовую строку вместо реального текста.
Lkraav
3
Сам MySQL не поддерживает многобайтовые символы с его функциями RegEx.
Брэд
4
Пользователи Windows: библиотека UDF, связанная здесь, похоже, не имеет хорошей поддержки Windows. Описанный метод установки Windows не работает для меня.
Джонатан
2
@lkraav, попробуйте библиотеку lib_mysqludf_preg ниже, так как она прекрасно работает. Это подробная версия, поскольку она возвращает BLOB-объект по умолчанию, и я не знаю, есть ли у вас многобайтовая кодировка по умолчанию: выберите cast (TR as char) COLLATE utf8_unicode_ci from (выберите 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
Benvorth
источник
12
это из Мариадб 10
Ник
6
В следующий раз, когда мне это понадобится, вот синтаксис для изменения целого столбца: он UPDATE table SET Name = REGEXP_REPLACE(Name, "-2$", "\\1")удаляет -2 из abcxyz-2 из целого столбца сразу.
Иосия
27
Изменение всей платформы вряд ли является реалистичным решением.
Дэвид Баукум
3
@DavidBaucum MariaDB является заменой MySQL. Таким образом, это не «смена платформы», а скорее выбор другой авиакомпании для той же поездки
Benvorth
3
@ Benvorth MySQL 8.0 также поддерживает это .
Лукаш Шозда
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
Хорошо, это тоже должно сработать; Я не рассматривал автономную замену. Хорошие нестандартные мысли там!
Писквор покинул здание
10
Мне кажется странным, что вы использовали бы поиск таким образом, я бы сократил команду до sed -i 's / old_string / new_string / g' /path/to/dump.sql
speshak
36
Очень рискованно и непрактично с большими наборами данных или с сохранением ссылочной целостности: для удаления данных и их последующей вставки вам придется отключить ссылочную целостность, на практике также отключив базу данных.
Рауль Луна
5
Воспользовавшись этим методом в прошлом, я согласен с Раулем, это очень рискованно. Вы также должны быть абсолютно уверены, что ваша строка не находится в вашем наборе данных.
eggmatters
1
Несколько лет спустя до ответа @speshak, но причина, по которой я выбрал такой файл, была в том, что я изначально очень нервничал по тем же причинам, что были упомянуты выше. В то время казалось, что отделение части «найти файл» от части «замена» облегчит чтение кода до того, как я его
Райан Уорд
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 азбука

2 abc ajay

3 азбуки

Джей Патель
источник
@yellowmelon для чего нужны две пары двойных кавычек?
codecowboy
5
Он дополняет имя сотрудника пробелами до и после. Это позволяет ему искать и заменять (пробел) имя_служащего (пробел), что позволяет избежать перехвата имя_служащего "jay", если оно входит в большую строку "ajay". Затем он обрезает пространство, когда закончите.
Шлем
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
Было бы более полезно включить код в ответ, а не размещать голую ссылку.
Фоби
2
select regex_replace('.*(abc).*','\1','noabcde')Хорошо, но, к сожалению, не работает с такими ссылками, как (возвращает 'noabcde', а не 'abc').
Иззи
@phobie кто-то другой сделал это в этом ответе - просто как ссылка на случай, если ссылка умрет;)
Иззи
Я изменил этот метод, чтобы попытаться устранить некоторые из упомянутых выше ограничений и многое другое. Пожалуйста, посмотрите этот ответ .
Стив Чемберс
14

Я рад сообщить, что, поскольку этот вопрос был задан, теперь есть удовлетворительный ответ! Взгляните на этот потрясающий пакет:

https://github.com/mysqludf/lib_mysqludf_preg

Пример SQL:

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

Я нашел пакет из этого поста в блоге как связанный по этому вопросу .

dotancohen
источник
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 Demo

Ограничения

  1. Этот метод, конечно, займет некоторое время, когда строка темы велика. Обновление: теперь добавлены параметры минимальной и максимальной длины совпадения для повышения эффективности, когда они известны (ноль = неизвестно / не ограничено).
  2. Это не позволит заменить обратные ссылки (например \1, \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».
Райан Шиллингтон
1
@ Райан ... именно поэтому я заявил, что это было не очень мудро ... в случае использования, который вы предоставляете, это определенно потерпит неудачу. Короче говоря, это плохая идея использовать структуру, похожую на регулярные выражения. Еще хуже ... если вы отбросите предложение where, все ваши значения будут равны NULL ...
Eddie B
1
На самом деле Райан в этом случае ошибается, так как маркеры найдут совпадения только для слова «границы» нулевой длины, поэтому будут совпадать только слова с границами до и после слова ... Хотя это все еще плохая идея ...
Эдди Б
6

Мы можем использовать условие IF в запросе SELECT, как показано ниже:

Предположим, что для чего-либо с «ABC», «ABC1», «ABC2», «ABC3», ... мы хотим заменить на «ABC», а затем с помощью условий REGEXP и IF () в запросе SELECT, мы можем достичь этого ,

Синтаксис:

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');
user3796869
источник
Здравствуйте, спасибо за предложение. Я пробовал что-то подобное, но производительность моих наборов данных была неудовлетворительной. Для небольших наборов это может быть жизнеспособным.
Писквор покинул здание
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 ;
Nae
источник
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.
Писквор покинул здание