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 ONSQL 표준의 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첫 번째 테이블에서 나중에 접두사와 같은지 확실하지 않습니다 . 명확히 해 주시겠습니까? 그리고 예제 데이터의 일부 수정과 원하는 출력 (문제를 쉽게 따라갈 수 있도록)도 환영합니다.