PostgreSQL에서 LIKE, SIMILAR TO 또는 정규식과 패턴 일치


94

B 또는 D로 시작하는 사람들의 이름을 찾는 간단한 쿼리를 작성해야했습니다.

SELECT s.name 
FROM spelers s 
WHERE s.name LIKE 'B%' OR s.name LIKE 'D%'
ORDER BY 1

더 성능이 좋도록 이것을 다시 작성할 수있는 방법이 있는지 궁금합니다. 그래서 피할 수 or및 / 또는 like?


왜 다시 쓰려고합니까? 공연? 깔끔함? 되어 s.name색인?
Martin Smith

성능을 위해 쓰고 싶습니다 .s.name이 색인화되지 않았습니다.
루카스 카우프만

8
와일드 카드를 사용하지 않고 검색하고 추가 열을 선택하지 않으면 name성능에 관심이있는 경우 색인 이 유용 할 수 있습니다.
Martin Smith

답변:


161

귀하의 쿼리는 거의 최적입니다. 구문은 훨씬 짧아지지 않고 쿼리는 훨씬 빠르지 않습니다.

SELECT name
FROM   spelers
WHERE  name LIKE 'B%' OR name LIKE 'D%'
ORDER  BY 1;

구문 을 정말로 줄이려면 분기 와 함께 정규 표현식을 사용하십시오 .

...
WHERE  name ~ '^(B|D).*'

또는 문자 클래스를 사용하면 약간 더 빠릅니다 .

...
WHERE  name ~ '^[BD].*'

색인이없는 빠른 테스트 SIMILAR TO는 어느 경우 보다 나에게 더 빠른 결과 를 제공합니다.
적절한 B-Tree 지수가 제자리에 LIKE오면이 레이스 에서 승리합니다.

매뉴얼에서 패턴 일치에 대한 기본 사항을 읽으십시오 .

우수한 성능을위한 색인

성능이 염려되면 더 큰 테이블에 대해 다음과 같은 색인을 작성하십시오.

CREATE INDEX spelers_name_special_idx ON spelers (name text_pattern_ops);

이러한 종류의 쿼리를 수십 배 더 빠르게 만듭니다. 로캘 별 정렬 순서에는 특별한 고려 사항이 적용됩니다. 매뉴얼에서 연산자 클래스에 대해 자세히 알아보십시오 . 표준 "C"로케일을 사용하는 경우 (대부분의 사람들은 그렇지 않음) 일반 색인 (기본 연산자 클래스 포함)이 수행합니다.

이러한 색인은 왼쪽 고정 패턴에만 적합합니다 (문자열의 시작 부분과 일치).

SIMILAR TO또는 기본 왼쪽 고정식이있는 정규식도이 색인을 사용할 수 있습니다. 그러나 분기 또는 문자 클래스는 아닙니다 (적어도 PostgreSQL 9.0에 대한 테스트에서는).(B|D)[BD]

Trigram 일치 또는 텍스트 검색은 특수 GIN 또는 GiST 인덱스를 사용합니다.

패턴 매칭 연산자 개요

  • LIKE( ~~)은 단순하고 빠르지 만 기능이 제한적입니다.
    ILIKE( ~~*) 대소 문자를 구분하지 않는 변형입니다.
    pg_trgm은 두 가지 모두에 대한 인덱스 지원을 확장합니다.

  • ~ (정규 표현식 일치)는 강력하지만 더 복잡하며 기본 표현식보다 더 느릴 수 있습니다.

  • SIMILAR TO그냥 무의미 . 특이한 반종 LIKE및 정규 표현. 나는 그것을 사용하지 않습니다. 아래를 참조하십시오.

  • % 는 추가 모듈에서 제공하는 "유사성"연산자pg_trgm입니다. 아래를 참조하십시오.

  • @@텍스트 검색 연산자입니다. 아래를 참조하십시오.

pg_trgm-트라이 그램 일치

부터 PostgreSQL의 9.1 당신은 확장을 용이하게 할 수 pg_trgm에 대한 인덱스 지원을 제공하기 위해 모든 LIKE / ILIKE패턴 (그리고 간단한 정규 표현식 패턴 ~GIN 또는 GIST 인덱스를 사용하여).

세부 사항, 예제 및 링크 :

pg_trgm또한 다음 연산자를 제공합니다 .

  • % - "유사성"연산자
  • <%(정류자 : %>)-Postgres 9.6 이상의 "word_similarity"연산자
  • <<%(정류자 : %>>)-Postgres 11 이상의 "strict_word_similarity"연산자

텍스트 검색

별도의 인프라 및 인덱스 유형과 일치하는 특수한 유형의 패턴입니다. 사전과 형태소 분석을 사용하며 특히 자연 언어의 문서에서 단어를 찾을 수있는 훌륭한 도구입니다.

접두사 일치도 지원됩니다.

뿐만 아니라 구문 검색 포스트 그레스 9.6 가입일 :

매뉴얼소개운영자와 기능개요를 고려하십시오 .

퍼지 문자열 일치를위한 추가 도구

추가 모듈 fuzzystrmatch 는 더 많은 옵션을 제공하지만 성능은 일반적으로 위의 모든 것보다 열등합니다.

특히, levenshtein()기능 의 다양한 구현은 도구적일 수있다.

왜 정규 표현식 ( ~)이 항상 더 빠릅 SIMILAR TO니까?

대답은 간단합니다. SIMILAR TO표현식은 내부적으로 정규 표현식으로 다시 작성됩니다. 따라서 모든 SIMILAR TO표현식 에 대해 하나 이상의 빠른 정규 표현식이 있습니다 (표현을 다시 작성하는 오버 헤드를 절약 함). SIMILAR TO 이제까지 사용하면 성능이 향상되지 않습니다 .

어쨌든 LIKE( ~~) 으로 수행 할 수있는 간단한 표현 이 더 빠릅니다 LIKE.

SIMILAR TOPostgreSQL에서는 SQL 표준 초안으로 작성 되었기 때문에 지원됩니다. 그들은 여전히 ​​그것을 제거하지 못했습니다. 그러나 그것을 제거하고 대신 정규 표현식 일치를 포함시킬 계획이 있습니다.

EXPLAIN ANALYZE그것을 공개합니다. 어떤 테이블로든 직접 시도하십시오!

EXPLAIN ANALYZE SELECT * FROM spelers WHERE name SIMILAR TO 'B%';

공개 :

...  
Seq Scan on spelers  (cost= ...  
  Filter: (name ~ '^(?:B.*)$'::text)

SIMILAR TO정규 표현식 ( ~) 으로 다시 작성되었습니다 .

이 특별한 경우를위한 최고의 성능

그러나 EXPLAIN ANALYZE더 많은 것을 보여줍니다. 위에서 언급 한 색인을 사용하여 시도하십시오.

EXPLAIN ANALYZE SELECT * FROM spelers WHERE name ~ '^B.*;

공개 :

...
 ->  Bitmap Heap Scan on spelers  (cost= ...
       Filter: (name ~ '^B.*'::text)
        ->  Bitmap Index Scan on spelers_name_text_pattern_ops_idx (cost= ...
              Index Cond: ((prod ~>=~ 'B'::text) AND (prod ~<~ 'C'::text))

내부적으로 로케일 인식되지 지수 (과 text_pattern_ops또는 사용하여 로케일 C:) 간단한 왼쪽 고정 표현은 이러한 텍스트 패턴 사업자로 재 작성되어 ~>=~, ~<=~, ~>~, ~<~. 이것은을위한 경우이다 ~, ~~또는 SIMILAR TO모두.

동일의 인덱스 마찬가지입니다 varchar가지는 형태 varchar_pattern_opscharbpchar_pattern_ops.

따라서 원래 질문에 적용하면 가능한 가장 빠른 방법입니다 .

SELECT name
FROM   spelers  
WHERE  name ~>=~ 'B' AND name ~<~ 'C'
    OR name ~>=~ 'D' AND name ~<~ 'E'
ORDER  BY 1;

물론 인접한 이니셜 을 검색해야하는 경우 다음을 더 단순화 할 수 있습니다.

WHERE  name ~>=~ 'B' AND name ~<~ 'D'   -- strings starting with B or C

의 일반 사용에 이득 ~또는 ~~작은입니다. 성능이 가장 중요한 요구 사항이 아닌 경우 표준 연산자를 고수해야합니다. 질문에 이미있는 내용에 도달해야합니다.


OP에는 이름에 대한 색인이 없지만 원래 쿼리에 2 개의 범위 탐색과 similar스캔이 관련되어 있음을 알고 있습니까?
Martin Smith

2
@MartinSmith : EXPLAIN ANALYZE2 개의 비트 맵 인덱스 스캔 을 보여주는 빠른 테스트 . 여러 비트 맵 인덱스 스캔을보다 빠르게 결합 할 수 있습니다.
Erwin Brandstetter

감사. 그래서 대체 어떤 사용량이있을 것 OR으로 UNION ALL또는 교체 name LIKE 'B%'name >= 'B' AND name <'C'포스트 그레스에를?
Martin Smith

1
@MartinSmith : UNION범위를 하나의 WHERE절로 결합 하면 쿼리 속도가 빨라집니다. 내 답변에 더 많이 추가했습니다. 물론 로케일을 고려해야합니다. 로케일 인식 검색은 항상 느립니다.
Erwin Brandstetter

2
@ a_horse_with_no_name : 나는 기대하지 않습니다. GIN 인덱스가있는 pg_tgrm의 새로운 기능은 일반적인 텍스트 검색을위한 것입니다. 처음에 고정 된 검색은 이미 그보다 빠릅니다.
Erwin Brandstetter

11

테이블에 열을 추가하는 것은 어떻습니까. 실제 요구 사항에 따라

person_name_start_with_B_or_D (Boolean)

person_name_start_with_char CHAR(1)

person_name_start_with VARCHAR(30)

PostgreSQL은 SQL Server의 기본 테이블에서 계산 열을 지원하지 않지만 트리거를 통해 새 열을 유지 관리 할 수 ​​있습니다. 분명히이 새 열은 색인화됩니다.

또는 표현식인덱스 가 동일하고 저렴합니다. 예 :

CREATE INDEX spelers_name_initial_idx ON spelers (left(name, 1)); 

조건에서 표현식과 일치하는 쿼리는이 인덱스를 활용할 수 있습니다.

이러한 방식으로, 데이터가 작성되거나 수정 될 때 성능 저하가 발생하므로 활동이 적은 환경 (즉, 읽기보다 쓰기 수가 훨씬 적음)에만 적합 할 수 있습니다.


8

당신은 시도 할 수 있습니다

SELECT s.name
FROM   spelers s
WHERE  s.name SIMILAR TO '(B|D)%' 
ORDER  BY s.name

위의 표현이나 원래 표현이 Postgres에서 Sargable인지 여부는 알 수 없습니다.

제안 된 색인을 작성하면 이것이 다른 옵션과 비교되는 방식에 관심이 있습니다.

SELECT name
FROM   spelers
WHERE  name >= 'B' AND name < 'C'
UNION ALL
SELECT name
FROM   spelers
WHERE  name >= 'D' AND name < 'E'
ORDER  BY name

1
그것은 효과가 있었고 1.25의 비용이 1.25였습니다. 감사 !
루카스 카우프만

2

비슷한 성능 문제에 직면하여 과거에 한 일은 마지막 문자의 ASCII 문자를 늘리고 BETWEEN을하는 것입니다. 그런 다음 LIKE 기능의 하위 세트에 대해 최상의 성능을 얻습니다. 물론 특정 상황에서만 작동하지만 예를 들어 이름을 검색하는 초대형 데이터 세트의 경우 성능이 무시 무시한 수준에서 수용 가능한 수준으로 변경됩니다.


2

아주 오래된 질문이지만이 문제에 대한 또 다른 빠른 해결책을 찾았습니다.

SELECT s.name 
FROM spelers s 
WHERE ascii(s.name) in (ascii('B'),ascii('D'))
ORDER BY 1

ascii () 함수는 문자열의 첫 문자 만 찾습니다.


1
이것에 인덱스를 사용합니까 (name)?
ypercubeᵀᴹ

2

이니셜을 확인하기 위해 종종 "char"큰 따옴표로 캐스팅을 사용 합니다. 휴대용이 아니지만 매우 빠릅니다. 내부적으로는 단순히 텍스트를 분해하고 첫 번째 문자를 반환하며 "char"비교 작업은 유형이 1 바이트 고정 길이이므로 매우 빠릅니다.

SELECT s.name 
FROM spelers s 
WHERE s.name::"char" =ANY( ARRAY[ "char" 'B', 'D' ] )
ORDER BY 1

캐스트 "char"ascii()@ Sole021 의 slution 보다 빠르지 만 UTF8 호환 (또는 그 문제에 대한 다른 인코딩)은 아니며 첫 번째 바이트 만 반환하므로 비교가 평범한 이전 7과 비교되는 경우에만 사용해야합니다 비트 ASCII 문자


1

이러한 경우를 처리하기 위해 아직 언급되지 않은 두 가지 방법이 있습니다.

  1. 부분 (또는 분할-전체 범위 수동으로 작성된 경우) 색인-데이터의 서브 세트 만 필요한 경우 (예 : 일부 유지 보수 중 또는 일부보고를 위해 임시) 가장 유용합니다.

    CREATE INDEX ON spelers WHERE name LIKE 'B%'
  2. 테이블 자체 분할 (첫 번째 문자를 분할 키로 사용)-이 기술은 특히 PostgreSQL 10+ (더 적은 고통스러운 분할) 및 11+ (쿼리 실행 중 분할 정리)에서 고려할 가치가 있습니다.

또한 테이블의 데이터가 정렬되면 BRIN 인덱스 를 사용 하여 첫 번째 문자를 통해 이점을 얻을 수 있습니다 .


-4

단일 문자 비교를 수행하는 것이 아마도 더 빠를 것입니다.

SUBSTR(s.name,1,1)='B' OR SUBSTR(s.name,1,1)='D'

1
실제로는 아닙니다. column LIKE 'B%'열에서 하위 문자열 함수를 사용하는 것보다 효율적입니다.
ypercubeᵀᴹ
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.