text
관련 열에 대한 데이터 유형 을 가정하고 있습니다.
CREATE TABLE prefix (code text, name text, price int);
CREATE TABLE num (number text, time int);
"간단한"솔루션
SELECT DISTINCT ON (1)
n.number, p.code
FROM num n
JOIN prefix p ON right(n.number, -1) LIKE (p.code || '%')
ORDER BY n.number, p.code DESC;
중요 요소들:
DISTINCT ON
SQL 표준의 Postgres 확장입니다 DISTINCT
. SO에 대한 이 관련 답변 에서 사용 된 쿼리 기술에 대한 자세한 설명을 찾으십시오 .
ORDER BY p.code DESC
가장 긴 일치를 선택합니다 때문에 '1234'
종류 후'123'
오름차순으로 .
간단한 SQL 바이올린 .
인덱스가 없으면 쿼리가 매우 오랫동안 실행 됩니다 (완료되기를 기다리지 않았습니다). 이를 빠르게하려면 인덱스 지원이 필요합니다. 추가 모듈에서 제공 한 언급 한 트라이 그램 인덱스가 적합합니다 pg_trgm
. GIN과 GiST 지수 중에서 선택해야합니다. 숫자의 첫 번째 문자는 노이즈 일 뿐이며 인덱스에서 제외되어 기능 인덱스가됩니다.
필자의 테스트에서 기능적인 트라이 그램 GIN 지수 는 예상대로 Trigram GiST 지수보다 경쟁에서 승리했습니다.
CREATE INDEX num_trgm_gin_idx ON num USING gin (right(number, -1) gin_trgm_ops);
고급 dbfiddle 여기에 .
모든 테스트 결과는 17k 숫자 및 2k 코드 설정이 축소 된 로컬 Postgres 9.1 테스트 설치 결과입니다.
- 총 런타임 : 1719.552ms (trigram GiST)
- 총 런타임 : 912.329ms (트리 그램 GIN)
훨씬 더 빠른
시도 실패 text_pattern_ops
주의가 산만해진 첫 번째 노이즈 특성을 무시하면 기본 왼쪽 고정 패턴 일치가 발생합니다. 따라서 연산자 클래스text_pattern_ops
(열 유형 가정 text
) 로 기능적인 B- 트리 인덱스를 시도했습니다 .
CREATE INDEX num_text_pattern_idx ON num(right(number, -1) text_pattern_ops);
이것은 하나의 검색어 로 직접 쿼리 를 수행하는 데 매우 효과적 이며 trigram 인덱스를 비교하면 나쁘게 보입니다.
SELECT * FROM num WHERE right(number, -1) LIKE '2345%'
- 총 런타임 : 3.816ms (trgm_gin_idx)
- 총 런타임 : 0.147 MS (text_pattern_idx)
그러나 쿼리 플래너는 두 테이블을 조인하는 데이 인덱스를 고려하지 않습니다. 나는 전에이 제한을 보았습니다. 아직 이것에 대한 의미있는 설명이 없습니다.
부분 / 기능적 B- 트리 인덱스
대안으로 부분 인덱스가있는 부분 문자열에서 동등 검사를 사용합니다. 이 수 A의 사용 JOIN
.
우리는 일반적으로 제한된 수의 different lengths
접두사 만 가지고 있기 때문에 부분 색인으로 여기 에 제시된 것과 유사한 솔루션을 작성할 수 있습니다 .
예를 들어 1 ~ 5 자의 접두사가 있습니다 . 고유 한 접두사 길이마다 하나씩 여러 개의 부분 기능 색인을 작성하십시오.
CREATE INDEX prefix_code_idx5 ON prefix(code) WHERE length(code) = 5;
CREATE INDEX prefix_code_idx4 ON prefix(code) WHERE length(code) = 4;
CREATE INDEX prefix_code_idx3 ON prefix(code) WHERE length(code) = 3;
CREATE INDEX prefix_code_idx2 ON prefix(code) WHERE length(code) = 2;
CREATE INDEX prefix_code_idx1 ON prefix(code) WHERE length(code) = 1;
이들은 부분 인덱스이므로 모두 하나의 완전한 인덱스보다 거의 크지 않습니다.
선행 노이즈 문자를 고려하여 숫자에 일치하는 색인을 추가하십시오.
CREATE INDEX num_number_idx5 ON num(substring(number, 2, 5)) WHERE length(number) >= 6;
CREATE INDEX num_number_idx4 ON num(substring(number, 2, 4)) WHERE length(number) >= 5;
CREATE INDEX num_number_idx3 ON num(substring(number, 2, 3)) WHERE length(number) >= 4;
CREATE INDEX num_number_idx2 ON num(substring(number, 2, 2)) WHERE length(number) >= 3;
CREATE INDEX num_number_idx1 ON num(substring(number, 2, 1)) WHERE length(number) >= 2;
이러한 인덱스는 각각 부분 문자열 만 포함하고 부분적인 반면 각각은 대부분 또는 모든 테이블을 포함합니다. 따라서 긴 숫자를 제외하고 단일 총 인덱스보다 훨씬 더 큽니다. 또한 쓰기 작업에 더 많은 작업을 수행합니다. 이것이 놀라운 속도 의 비용 입니다.
해당 비용이 너무 비싸면 (쓰기 성능이 중요하거나 쓰기 작업이 너무 많거나 디스크 공간 문제)이 인덱스를 건너 뛸 수 있습니다. 나머지는 훨씬 빠르지 만 여전히 빠릅니다 ...
숫자가 n
문자 보다 짧지 않으면 WHERE
일부 또는 전체에서 중복 절을 삭제하고 WHERE
다음 모든 쿼리에서 해당 절을 삭제하십시오 .
재귀 CTE
지금까지 모든 설정을 통해 재귀 CTE 가있는 매우 우아한 솔루션을 원했습니다 .
WITH RECURSIVE cte AS (
SELECT n.number, p.code, 4 AS len
FROM num n
LEFT JOIN prefix p
ON substring(number, 2, 5) = p.code
AND length(n.number) >= 6 -- incl. noise character
AND length(p.code) = 5
UNION ALL
SELECT c.number, p.code, len - 1
FROM cte c
LEFT JOIN prefix p
ON substring(number, 2, c.len) = p.code
AND length(c.number) >= c.len+1 -- incl. noise character
AND length(p.code) = c.len
WHERE c.len > 0
AND c.code IS NULL
)
SELECT number, code
FROM cte
WHERE code IS NOT NULL;
그러나이 쿼리는 나쁘지 않습니다-그것은 trigram GIN 인덱스가있는 간단한 버전만큼 성능이 뛰어납니다. 그러나 내가 목표로 한 것을 제공하지는 않습니다. 재귀 용어는 한 번만 계획되므로 최상의 인덱스를 사용할 수 없습니다. 비 재귀 항만 가능합니다.
UNION ALL
우리는 적은 수의 재귀를 다루기 때문에 반복적으로 철자를 쓸 수 있습니다. 이를 통해 각 계획에 최적화 된 계획이 가능합니다. (그러나 우리는 이미 성공적인 숫자의 재귀 적 제외를 잃습니다. 따라서 여전히 더 넓은 범위의 접두사 길이에 대한 개선의 여지가 여전히 남아 있습니다)) :
SELECT DISTINCT ON (1) number, code
FROM (
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 5) = p.code
AND length(n.number) >= 6 -- incl. noise character
AND length(p.code) = 5
UNION ALL
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 4) = p.code
AND length(n.number) >= 5
AND length(p.code) = 4
UNION ALL
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 3) = p.code
AND length(n.number) >= 4
AND length(p.code) = 3
UNION ALL
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 2) = p.code
AND length(n.number) >= 3
AND length(p.code) = 2
UNION ALL
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 1) = p.code
AND length(n.number) >= 2
AND length(p.code) = 1
) x
ORDER BY number, code DESC;
마침내 획기적인!
SQL 함수
이것을 SQL 함수로 랩핑하면 반복 사용을위한 쿼리 계획 오버 헤드가 제거됩니다.
CREATE OR REPLACE FUNCTION f_longest_prefix()
RETURNS TABLE (number text, code text) LANGUAGE sql AS
$func$
SELECT DISTINCT ON (1) number, code
FROM (
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 5) = p.code
AND length(n.number) >= 6 -- incl. noise character
AND length(p.code) = 5
UNION ALL
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 4) = p.code
AND length(n.number) >= 5
AND length(p.code) = 4
UNION ALL
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 3) = p.code
AND length(n.number) >= 4
AND length(p.code) = 3
UNION ALL
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 2) = p.code
AND length(n.number) >= 3
AND length(p.code) = 2
UNION ALL
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 1) = p.code
AND length(n.number) >= 2
AND length(p.code) = 1
) x
ORDER BY number, code DESC
$func$;
요구:
SELECT * FROM f_longest_prefix_sql();
동적 SQL을 사용한 PL / pgSQL 함수
이 plpgsql 함수는 위의 재귀 CTE와 매우 유사하지만 동적 SQL을 사용 EXECUTE
하면 모든 반복에 대해 쿼리를 다시 계획해야합니다. 이제 모든 맞춤 색인을 사용합니다.
또한 이것은 모든 접두사 길이 범위 에서 작동합니다 . 이 함수는 범위에 대해 두 가지 매개 변수를 사용하지만 DEFAULT
값으로 준비 했으므로 명시 적 매개 변수없이 작동합니다.
CREATE OR REPLACE FUNCTION f_longest_prefix2(_min int = 1, _max int = 5)
RETURNS TABLE (number text, code text) LANGUAGE plpgsql AS
$func$
BEGIN
FOR i IN REVERSE _max .. _min LOOP -- longer matches first
RETURN QUERY EXECUTE '
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(n.number, 2, $1) = p.code
AND length(n.number) >= $1+1 -- incl. noise character
AND length(p.code) = $1'
USING i;
END LOOP;
END
$func$;
마지막 단계는 하나의 기능으로 쉽게 감쌀 수 없습니다.
어느 단지 다음과 같이 호출 :
SELECT DISTINCT ON (1)
number, code
FROM f_longest_prefix_prefix2() x
ORDER BY number, code DESC;
또는 다른 SQL 함수를 랩퍼로 사용하십시오.
CREATE OR REPLACE FUNCTION f_longest_prefix3(_min int = 1, _max int = 5)
RETURNS TABLE (number text, code text) LANGUAGE sql AS
$func$
SELECT DISTINCT ON (1)
number, code
FROM f_longest_prefix_prefix2($1, $2) x
ORDER BY number, code DESC
$func$;
요구:
SELECT * FROM f_longest_prefix3();
필요한 계획 오버 헤드로 인해 조금 느려집니다. 그러나 SQL보다 다재다능하고 접두어가 길수록 짧습니다.
code
첫 번째 테이블에서 나중에 접두사와 같은지 확실하지 않습니다 . 명확히 해 주시겠습니까? 그리고 예제 데이터의 일부 수정과 원하는 출력 (문제를 쉽게 따라갈 수 있도록)도 환영합니다.