PostgreSQL로 퍼지 검색 구축
Postgres 데이터베이스로 이러한 퍼지 검색을 어떻게 달성할 수 있습니까?
기본적으로 세 가지 접근 방식이 있습니다.
ILIKE '%searchterm%'
와 패턴 일치하여 문자열 내에서 searchterm
를 모두 찾습니다. 이 문서에서는 입력 오류에 민감하지 않고 단어가 완전하지 않은 경우에도 일치해야 하는 퍼지 검색을 원하기 때문에 세 번째 접근 방식에 초점을 맞출 것입니다. 이 두 가지 기준은 처음 두 가지 접근 방식을 기각합니다.
이제 세 번째 접근 방식인 Trigrams에 대해 살펴보겠습니다!
괘
postgres documentation이 트라이그램에 대해 알려주는 내용을 살펴보겠습니다.
A trigram is a group of three consecutive characters taken from a string. We can measure the similarity of two strings by counting the number of trigrams they share. This simple idea turns out to be very effective for measuring the similarity of words in many natural languages.
이것이 무엇을 의미하는지 이해하려면 pg_trgrm 확장의
show_trgrm()
메서드를 확인하십시오.그러나 먼저 trigram 확장을 활성화해야 합니다.
CREATE EXTENSION pg_trgm;
SELECT show_trgm('Hello');
show_trgm
---------------------------------
{" h"," he",ell,hel,llo,"lo "}
SELECT show_trgm('Hello World');
show_trgm
---------------------------------------------------------------
{" h"," w"," he"," wo",ell,hel,"ld ",llo,"lo ",orl,rld,wor}
유사성
이 유사성 메서드는 두 문자열이 얼마나 유사한지를 나타내는 0에서 1까지의 숫자를 반환합니다. 값 1은 문자열이 동일함을 의미합니다.
우리의 경우 첫 번째 문자열에는 총 12개의 괘가 있는 두 번째 문자열과 공통으로 6개의 괘가 있기 때문에 0.5입니다.
SELECT similarity('Hello', 'Hello world');
similarity
-----------------
0.5
하지만 유사성 기능으로 큰 문자열에서 작은 문자열을 검색하면 점수가 급격히 떨어집니다. 두 문자열에 없는 트라이그램이 많이 있기 때문입니다.
SELECT similarity('Hello', 'Hello world, you wonderful planet!');
similarity
-----------------
0.19354838
따라서 Postgres는 문자열의 단어 경계를 존중하는 두 가지 다른 기능을 제공합니다.
단어 유사성
단어 유사성 점수는 단어(strict_word_similarity) 각각의 하위 문자열(word_similarity)의 가장 높은 일치 유사도를 반영합니다.
Note: No matter how long the second string gets, the (strict) word similarity will always be 1, because
Hello
matches exactly a whole word in the example.
SELECT
word_similarity('Hello', 'Hello world, you wonderful planet!'),
strict_word_similarity('Hello', 'Hello world, you wonderful planet!');
word_similarity | strict_word_similarity
----------------------+------------------------
1 | 1
The word similarity score can be understood as the greatest similarity between the first string and any substring of the second string. So said, it is useful for finding the similarity for parts of words.
The strict word similarity score is useful for finding the similarity of whole words.
트라이그램 비교의 멋진 점은 단어의 철자가 틀려도 상대적으로 유사성이 높다는 것입니다.
SELECT similarity('wonderfull', 'wonderful'), strict_word_similarity('wonderfull', 'Hello World, you wonderful planet!'), word_similarity('wonderfull', 'Hello World, you wonderful planet!');
similarity | strict_word_similarity | word_similarity
-----------------+------------------------+-----------------
0.75 | 0.75 | 0.8181818
예시
지금까지는 좋았으니 손을 더럽히자.
서점이 있다고 상상해보십시오. 종종 고객은 제목의 일부만 아는 책에 대해 묻거나 저자의 철자를 정확히 아는 방법을 모릅니다. 퍼지 검색에 딱 맞는 것 같습니다!
단순화를 위해 데이터 세트를 생성해 보겠습니다. 예, 이것이 검색을 위한 가장 아름다운 샘플 데이터가 아니라는 것을 알고 있지만 요점을 이해하시기 바랍니다.
CREATE TABLE books (
id TEXT,
title TEXT,
abstract TEXT,
author TEXT,
PRIMARY KEY(id)
);
INSERT INTO books (
id, title, abstract, author
)
SELECT
left(md5(i::text), 10),
left(md5(random()::text), 15),
md5(random()::text),
concat_ws(' ', left(md5(random()::text), 10), left(md5(random()::text), 10))
FROM generate_series(1, 100000) s(i);
SELECT * FROM books LIMIT 5;
id | title | abstract | author
-----------------+-----------------+----------------------------------+-----------------------
c4ca4238a0 | 06629ea8b04aaa7 | 9f0ad3e3542ecf1efbdddd98cca507a6 | 4dceb23e53 0f01b57e71
c81e728d9d | 0a284b57f95d997 | fdd45a7d9eda64c9deb4882ccbb42296 | f3fbf4ed2a ef99e869d2
eccbc87e4b | b464a24deba866e | 0eda8641e61327719906b493080cd96f | fca64a442c db03ca6331
a87ff679a2 | 6275469f9e41990 | f3771fb3afdb463d302d7849c38d5641 | fbe106a816 b4313ad5c3
e4da3b7fbb | b2e9f3a8bad3aec | a9f4b8432c1b6655ae8775dd5b904498 | 6df1b43a13 87c7a303a4
하지만 기다려. 여러 열을 어떻게 검색할 수 있습니까?
잘 잡았다. 여러 열을 검색하려면
concat_ws
함수로 검색해야 하는 모든 열을 연결해야 합니다.Note: The
<%
operator returnstrue
if the word_similarity is above thepg_trgm.word_similarity_threshold
parameter.The
<%
operator has a commutator%<
.
So 'Hello' <% 'Hello World' = 'Hello World' %< 'Hello'
SHOW pg_trgm.word_similarity_threshold ;
pg_trgm.word_similarity_threshold
----------------------------------------
0.6
SELECT * FROM books WHERE '9c9f6' <% concat_ws(' ', title, abstract, author);
id | title | abstract | author
-----------------+-----------------+----------------------------------+-----------------------
2804d14b1b | 0152e58b57b94ff | 9c9f468fa58fc140427f7354f1a2b88e | b520b29fae ae1297ce25
c764c89e73 | 2f81a4878b09a36 | 667ddc5f403d9c96b43912628e1cb73c | ebab7d5c7c 9c9ffbfd82
5fa8101625 | 30071f6f0e9c9f6 | 5f3250393ac1b214340af81f239e7ca5 | ffec138ddb 3b9060d0c4
7cb394c278 | ce5ad25e38577fc | 805d0e2ca8081c6a8a178b1f0650c9f6 | 9c5332d434 cd79b0e802
9b39d07008 | a58ecb6f4e41c96 | 2df452e5bd0b1102733bbc89dd78e6ee | 9c9fb041fa 980becdc8f
(5 rows)
Time: 1245,955 ms (00:01,246)
아야, 너무 오래 걸렸어. 자, 진의 시간입니다!
GIN으로 검색 성능 향상
Postgres에는 이 사용 사례에 대한 특수 인덱스가 있으며 이를 GIN (General Inverted Index) 이라고 합니다.
"GIN is designed for handling cases where the items to be indexed are composite values, and the queries to be handled by the index need to search for element values that appear within the composite items."
완벽하게 맞는 것 같습니다. 그러나 약간의 문제가 있습니다. concat_ws 함수는 변경할 수 없기 때문에 인덱스를 만들 수 없습니다. 연결된 열에 인덱스를 생성하려면 변경할 수 없는 함수 래퍼를 빌드해야 합니다.
-- create immutable function wrapper for concat_ws
CREATE OR REPLACE FUNCTION f_immutable_concat_ws(t1 text, t2 text, t3 text)
RETURNS text AS
$func$
SELECT concat_ws(' ', t1, t2, t3)
$func$ LANGUAGE sql IMMUTABLE;
-- create a GIN index
CREATE INDEX search_gin_trgm_idx ON books
USING gin (f_immutable_concat_ws(title, abstract, author) gin_trgm_ops);
-- validate performance improvements
EXPLAIN ANALYZE SELECT * FROM books WHERE '9c9f6' <% f_immutable_concat_ws(titale, abstract, author);
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on books (cost=21.68..152.10 rows=100 width=82) (actual time=1.937..2.394 rows=5 loops=1)
Filter: ('9c9f6'::text <% f_immutable_concat_ws(title, abstract, author))
Rows Removed by Filter: 13
Heap Blocks: exact=18
-> Bitmap Index Scan on search_gin_trgm_idx (cost=0.00..21.65 rows=100 width=0) (actual time=1.653..1.663 rows=18 loops=1)
Index Cond: (f_immutable_concat_ws(title, abstract, author) %> '9c9f6'::text)
Planning Time: 4.487 ms
Execution Time: 2.533 ms
(8 rows)
Time: 9,965 ms
예, 이것은 125배 더 빠릅니다! 🏎
여기서 어디로 가야합니까?
시도해 볼 수 있습니다.
strict_word_similarity
<<%
WHERE
조건을 사용하고 AND
로 연결합니다.구문 유사성만으로는 충분하지 않습니까?
Word2vec은 단어의 의미적 유사성을 비교하는 옵션이 될 수 있습니다. 운 좋게도 이미 이에 대한 postgres-word2vec extension이 있습니다.
의견에서 Postgres에 대한 퍼지 검색에 대한 솔루션에 대해 듣고 싶습니다.
건배!
Reference
이 문제에 관하여(PostgreSQL로 퍼지 검색 구축), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/moritzrieger/build-a-fuzzy-search-with-postgresql-2029텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)