MySQL에서 정규 표현식을 바꾸는 방법은 무엇입니까?


515

~ 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"

내가 알고 정규 표현식 / 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
@Tomas : 나는 2009 년에 그것을 둘러 보았을 때 그렇게했습니다. 진전이 없었으므로 분명히 중요한 기능은 아닙니다. (btw Postgres에 있음 : stackoverflow.com/questions/11722995/… )
Piskvor는 건물을

1
이 질문의 관련 있고 간단한 버전 : stackoverflow.com/questions/6942973/…
Kzqai

2
연산자 로 구현 된 regexp_split(function + procedure) &을 만들었습니다 . 간단한 조회의 경우 트릭을 수행합니다. 여기에서 찾을 수 있습니다 -UDF가 아닌 MySQL 저장 코드를 사용하는 방법입니다. 알려진 제한 사항에 포함되지 않은 버그를 발견 한 경우 언제든지 문제를여십시오. regexp_replaceREGEXP
앨마도

1
다른 SO 스레드 에서이 라이브러리를 찾았습니다 : github.com/mysqludf/lib_mysqludf_preg 가 완벽하게 작동합니다.
Kyle

답변:


77

MySQL을 8.0 이상 당신은 기본적으로 사용할 수있는 REGEXP_REPLACE기능.

12.5.2 정규식 :

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

패턴 pat에 의해 지정된 정규 표현식과 일치 하는 문자열 expr 에서 발생하는 문자열을 대체 문자열 repl로 바꾸고 결과 문자열을 리턴합니다. 경우 EXPR , , 또는 REPL이 있다 , 반환 값입니다 .NULLNULL

그리고 정규 표현식 지원 :

이전에는 MySQL 이 Henry Spencer 정규식 라이브러리를 사용하여 정규식 연산자 ( REGEXP, RLIKE) 를 지원했습니다 .

ICU (International Components for Unicode)를 사용하여 정규식 지원이 다시 구현되어 완전한 유니 코드 지원을 제공하며 멀티 바이트 안전합니다. 이 REGEXP_LIKE()함수는 REGEXPand RLIKE연산자 의 방식으로 정규 표현식 일치를 수행 하며 이제는 해당 함수의 동의어입니다. 또한 REGEXP_INSTR(), REGEXP_REPLACE() REGEXP_SUBSTR() 기능 매치 위치를 찾아 사용할 수 있으며, 각각 교체 추출 서브 스트링을 수행한다.

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

DBFiddle 데모


146

MySQL 8.0 이상 :

기본 REGEXP_REPLACE기능을 사용할 수 있습니다 .

이전 버전 :

mysql-udf-regexp 와 같은 UDF (사용자 정의 함수)를 사용할 수 있습니다 .


3
사용자 정의 함수로서 REGEXP_REPLACE? 유망 해 보이며 그것을 조사 할 것입니다. 감사!
Piskvor는

15
불행히도 mysql-udf-regexp는 멀티 바이트 문자를 지원하지 않는 것 같습니다. regexp_replace ( 'äöõü', 'ä', '')는 실제 텍스트 대신 긴 숫자 문자열을 반환합니다.
lkraav

3
MySQL 자체는 RegEx 기능을 가진 멀티 바이트 문자를 지원하지 않습니다.
Brad

4
Windows 사용자 : 여기에 링크 된 UDF 라이브러리가 Windows를 제대로 지원하지 않는 것 같습니다. 설명 된 Windows 설치 방법이 나에게 적합하지 않았습니다.
Jonathan

2
@lkraav 아래의 lib_mysqludf_preg 라이브러리를 사용해보십시오. 이 장황한 버전은 기본적으로 얼룩을 반환하므로 기본값으로 멀티 바이트 문자 집합이 있는지 모르겠습니다. select cast (TR as char) COLLATE utf8_unicode_ci from (select 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

12
이것은 mariadb 10에서 온 것입니다
Nick

6
다음에 필요한 경우 전체 열을 변경하는 구문은 다음과 같습니다. UPDATE table SET Name = REGEXP_REPLACE(Name, "-2$", "\\1")이렇게하면 abcxyz-2에서 -2가 전체 열에서 한 번에 제거됩니다.
Josiah

27
전체 플랫폼을 변경하는 것은 현실적인 해결책이 아닙니다.
David Baucum

3
@DavidBaucum MariaDB는 MySQL의 드롭 인 대체품입니다. 따라서 "플랫폼 변경"이 아니라 같은 여행을 위해 다른 항공사를 선택하는 것과 비슷합니다
Benvorth


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 regeular 표현식이 있습니다.
  3. 테이블 가져 오기- mysqlimport -u user -p database table < dump.sql

문자열이 데이터 세트의 다른 곳에 있지 않도록하려면 몇 가지 정규식을 실행하여 모두 비슷한 환경에서 발생하는지 확인하십시오. 또한 심도있는 정보를 잃어버린 것을 실수로 파괴 할 경우를 대비하여 교체를 실행하기 전에 백업을 만드는 것이 어렵지 않습니다.


33
좋아, 그것도 효과가있다. 오프라인 교체는 고려하지 않았습니다. 거기에 좋은 즉시 생각!
Piskvor는

10
당신이 그런 식으로 찾기를 사용하는 것이 이상하다고 생각됩니다-sed -i 's / old_string / new_string / g'/path/to/dump.sql
speshak

36
빅 데이터 세트 또는 참조 무결성이있는 경우 매우 위험하고 실용적이지 않습니다. 데이터를 제거한 다음 다시 삽입하려면 참조 무결성을 해제해야합니다. 실제로 데이터베이스도 그대로 둡니다.
Raul Luna

5
과거 에이 방법을 사용했지만 Raul과 동의합니다. 이는 매우 위험합니다. 문자열이 데이터 세트의 다른 곳에 있지 않다는 것을 확실히 확신해야합니다.
eggmatters

1
대답 @speshak에 늦게 늦었지만이 파일에 액세스하기로 선택한 이유는 원래 위에서 언급 한 것과 같은 이유로 매우 긴장했기 때문입니다. 당시 "파일 찾기"부분을 "바꾸기"부분에서 분리하면 코드를 제출하기 전에 코드를보다 쉽게 ​​읽을 수있을 것입니다.
Ryan Ward

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 ABC

2 abc ajay

3 ABC


@yellowmelon 두 쌍의 큰 따옴표는 무엇입니까?
codecowboy

5
직원 이름 앞뒤에 공백을 채 웁니다. 이를 통해 (space) 직원 이름 (space)을 검색 대치 할 수 있으므로 더 큰 문자열 "ajay"의 일부인 경우 "jay"직원 이름을 포착하지 않아도됩니다. 그런 다음 그는 완료되면 공간을 잘라냅니다.
Slam

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
위의 요점을 강화하겠습니다.이 함수는 단일 문자 표현식과 일치하는 문자 를 대체합니다 . 그것은 "정규 표현식을 사용하여 문자열을 리 팔링하는 데 사용된다"고 말하며 약간 오도 할 수 있습니다. 그것은 그 일을하지만, 요구되는 일이 아닙니다. (불만은 아닙니다-그것은 단지 잘못된 길을 선도하는 사람들을 구하는 것입니다)
Jason

2
알몸 링크를 게시하는 대신 답변에 실제로 코드를 포함시키는 것이 더 도움이됩니다.
phobie

2
멋지지만 불행히도 select regex_replace('.*(abc).*','\1','noabcde')( 'abc'가 아닌 'noabcde'를 반환) 와 같은 참조를 처리하지 않습니다 .
Izzy

@ pobie 누군가 가이 답변 에서 링크를 죽일 경우를 대비하여 참조로 다른 사람이 그렇게 했습니다 .)
Izzy

위에서 언급 한 몇 가지 제한 사항을 해결하기 위해이 방법을 수정했습니다. 이 답변을 참조하십시오 .
Steve Chambers


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 데모

한계

  1. 물론이 방법은 제목 문자열이 클 때 시간이 걸립니다. 업데이트 : 최소 및 최대 일치 길이 매개 변수를 추가하여 효율성을 높이도록했습니다 (제로 = 알 수 없음 / 제한 없음).
  2. 되지 역 참조 (예 치환 수 \1, \2 촬상기를 대체 등). 이 기능이 필요한 경우, 참조하십시오 이 답변 에서 보조 찾기 및 바꾸기를 허용하도록 기능을 업데이트하여 (복잡성이 증가함에 따라) 해결 방법을 제공하는 을 .
  3. ^및 / 또는 $패턴에서 사용되는 경우 , 각각 시작과 끝에 있어야합니다 (예 : (^start|end$)지원되지 않는 패턴) .
  4. 전체 일치가 욕심인지 아닌지 욕설인지를 지정하는 "greedy"플래그가 있습니다. 단일 정규 표현식 (예 :) 내에서 욕심과 게으른 일치를 결합하는 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"가됩니다.
Ryan Shillington

1
@Ryan ... 그것이 정확히 현명하지 않다고 말한 이유입니다 ... 유스 케이스에서 이것이 가장 확실하게 실패 할 것입니다. 간단히 말해서 'regex-like'구조를 사용하는 것은 좋지 않습니다. 더 나쁜 ... where 절을 삭제하면 모든 값이 NULL이됩니다 ...
Eddie B

1
실제로이 경우 Ryan은 길이가 0 인 단어 'boundaries'와 일치하는 단어 만 찾을 수 있으므로 단어의 앞뒤에 경계가있는 단어 만 일치하므로 잘못되었습니다. 그래도 좋지 않은 생각입니다 ...
Eddie B

6

다음과 같이 SELECT 쿼리에서 IF 조건을 사용할 수 있습니다.

"ABC", "ABC1", "ABC2", "ABC3"등이있는 모든 항목에 대해 "ABC"로 바꾸고 SELECT 쿼리에서 REGEXP 및 IF () 조건을 사용하려는 경우이를 달성 할 수 있습니다. .

통사론:

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

안녕하세요, 제안 해 주셔서 감사합니다. 비슷한 것을 시도했지만 데이터 세트의 성능이 만족스럽지 않았습니다. 작은 세트의 경우 이것이 가능할 수 있습니다.
Piskvor는

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 ;

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부터 쉽게 할 수 있습니다
Piskvor 건물 왼쪽
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.