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

472

У меня есть таблица с ~ 500 тыс. строк; 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 script, сделать preg_replace, а затем "UPDATE foo ... WHERE pkey_id=...", но это выглядит как медленный и уродливый взломать последний курорт)

  • 7
    Это запрос функции с 2007 года: bugs.mysql.com/bug.php?id=27389 . Если вы действительно хотите эту функцию, войдите в систему и нажмите кнопку «Влияет на меня». Надеюсь, он получит достаточно голосов.
  • 4
    @ Томас: Я сделал это ... в 2009 году, когда я искал это. Поскольку прогресс в этом направлении был нулевым - очевидно, это не такая важная особенность. (кстати, у Postgres это есть: stackoverflow.com/questions/11722995/… )
Показать ещё 5 комментариев
Теги:
mysql-udf

11 ответов

29
Лучший ответ

MySQL 8. 0+ вы можете использовать изначально REGEXP_REPLACE.

12.5.2 Регулярные выражения:

REGEXP_REPLACE (expr, pat, repl [, pos [, появление [, match_type]]])

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

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

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

Поддержка регулярных выражений была повторно реализована с использованием международных компонентов для 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

137

Нет.

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

РЕДАКТИРОВАТЬ: MySQL 8. 0+ вы можете использовать изначально REGEXP_REPLACE. Больше в ответе выше

  • 3
    REGEXP_REPLACE как пользовательская функция? Выглядит многообещающе, посмотрю на это. Спасибо!
  • 1
    Mysql не имеет этой встроенной функции. Мне сказали, что у Oracle это есть (но вам не поможет)
Показать ещё 6 комментариев
118

Вместо этого используйте MariaDB. Он имеет функцию

REGEXP_REPLACE(col, regexp, replace)

См. Документы MariaDB и Улучшения регулярного выражения PCRE

Обратите внимание, что вы также можете использовать группировку regexp (я нашел это очень полезным):

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

возвращает

over - stack - flow
  • 4
    Niiiice! Тем более, что мы уже перешли на него по несвязанным причинам. Спасибо за чаевые :)
  • 11
    это из Мариадб 10
Показать ещё 5 комментариев
100

Мой метод грубой силы, чтобы заставить это работать, было просто:

  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 regeular, которые вы могли бы выполнить и в файле.
  3. Импортировать таблицу - mysqlimport -u user -p database table < dump.sql

Если вы хотите убедиться, что строка не находится в другом месте вашего набора данных, запустите несколько регулярных выражений, чтобы убедиться, что все они встречаются в подобной среде. Также не сложно создать резервную копию, прежде чем запускать замену, если вы случайно уничтожили что-то, что теряет глубину информации.

  • 28
    Хорошо, это тоже должно сработать; Я не рассматривал автономную замену. Хорошие нестандартные мысли там!
  • 10
    Мне кажется странным, что вы использовали бы поиск таким образом, я бы сократил команду до sed -i 's / old_string / new_string / g' /path/to/dump.sql
Показать ещё 5 комментариев
39

Недавно я написал функцию 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 ._,');
  • 7
    Это также работает только на отдельных персонажей ..
  • 20
    Я только подчеркну вышеупомянутый пункт: эта функция заменяет символы, которые соответствуют однозначному выражению. Выше сказано, что он используется «для переотображения строк с помощью регулярных выражений», и это может быть немного обманчивым. Это делает свою работу, но это не та работа, о которой просят. (Не жалоба - это просто чтобы спасти людей на неверный путь)
Показать ещё 4 комментария
33

мы решаем эту проблему без использования регулярного выражения этот запрос заменяет только точную строку соответствия.

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

Пример:

emp_id employee_firstname

1 jay

2 jay ajay

3 jay

После выполнения результата запроса:

emp_id employee_firstname

1 abc

2 abc ajay

3 abc

  • 4
    Я понятия не имею, почему у этого ответа не было голосов, но это работает отлично.
  • 0
    @yellowmelon для чего нужны две пары двойных кавычек?
Показать ещё 2 комментария
14

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

https://github.com/mysqludf/lib_mysqludf_preg

Пример SQL:

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

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

  • 1
    Как бы вы обновили значение в таблице?
  • 0
    @dotancohen, это тоже мой вопрос!
8

ОБНОВЛЕНИЕ 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:

  • 1
    Вот Это Да! Идеально!
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 ...
Показать ещё 6 комментариев
4

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

Нижеследующее в основном находит первое совпадение слева, а затем заменяет все его совпадения (проверено в ).

Использование:

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 ;

Ещё вопросы

Сообщество Overcoder
Наверх
Меню