F.17. fuzzystrmatch â вÑÑиÑление ÑÑ Ð¾Ð¶ÐµÑÑи и ÑаÑÑÑоÑÐ½Ð¸Ñ Ð¼ÐµÐ¶Ð´Ñ ÑÑÑоками #
ÐодÑÐ»Ñ fuzzystrmatch ÑодеÑÐ¶Ð¸Ñ Ð½ÐµÑколÑко ÑÑнкÑий Ð´Ð»Ñ Ð²ÑÑиÑÐ»ÐµÐ½Ð¸Ñ ÑÑ
ожеÑÑи и ÑаÑÑÑоÑÐ½Ð¸Ñ Ð¼ÐµÐ¶Ð´Ñ ÑÑÑоками.
Ðнимание
РнаÑÑоÑÑее вÑÐµÐ¼Ñ ÑÑнкÑии soundex, metaphone, dmetaphone и dmetaphone_alt плоÑ
о ÑабоÑаÑÑ Ñ Ð¼Ð½Ð¾Ð³Ð¾Ð±Ð°Ð¹ÑнÑми кодиÑовками (в ÑаÑÑноÑÑи, Ñ UTF-8). ÐÑполÑзÑйÑе ÑÑнкÑии daitch_mokotoff или levenshtein в ÑабоÑе Ñ Ñакими даннÑми.
ÐаннÑй модÑÐ»Ñ ÑÑиÑаеÑÑÑ Â«Ð´Ð¾Ð²ÐµÑеннÑм», Ñо еÑÑÑ ÐµÐ³Ð¾ могÑÑ ÑÑÑанавливаÑÑ Ð¾Ð±ÑÑнÑе полÑзоваÑели, имеÑÑие пÑаво CREATE в ÑекÑÑей базе даннÑÑ
.
F.17.1. Soundex #
СиÑÑема Soundex позволÑÐµÑ Ð²ÑÑиÑлиÑÑ Ð¿Ð¾Ñ Ð¾Ð¶Ð¸Ðµ по звÑÑÐ°Ð½Ð¸Ñ Ð¸Ð¼ÐµÐ½Ð°, пÑÐ¸Ð²Ð¾Ð´Ñ Ð¸Ñ Ðº одинаковÑм кодам. ÐзнаÑалÑно она иÑполÑзовалаÑÑ Ð´Ð»Ñ Ð¾Ð±ÑабоÑки даннÑÑ Ð¿ÐµÑепиÑи наÑÐµÐ»ÐµÐ½Ð¸Ñ Ð¡Ð¨Ð Ð² 1880, 1900 и 1910 г. ÐамеÑÑÑе, ÑÑо ÑÑа ÑиÑÑема не оÑÐµÐ½Ñ Ð¿Ð¾Ð»ÐµÐ·Ð½Ð° Ð´Ð»Ñ Ð½ÐµÐ°Ð½Ð³Ð»Ð¾ÑзÑÑнÑÑ Ð¸Ð¼Ñн.
ÐодÑÐ»Ñ fuzzystrmatch пÑедоÑÑавлÑÐµÑ Ð´Ð²Ðµ ÑÑнкÑии Ð´Ð»Ñ ÑабоÑÑ Ñ ÐºÐ¾Ð´Ð°Ð¼Ð¸ Soundex:
soundex(text) returns text difference(text, text) returns int
ФÑнкÑÐ¸Ñ soundex пÑеобÑазÑÐµÑ ÑÑÑÐ¾ÐºÑ Ð² код Soundex. ФÑнкÑÐ¸Ñ difference пÑеобÑазÑÐµÑ Ð´Ð²Ðµ ÑÑÑоки в иÑ
ÐºÐ¾Ð´Ñ Soundex и заÑем ÑообÑÐ°ÐµÑ ÐºÐ¾Ð»Ð¸ÑеÑÑво ÑовпадаÑÑиÑ
позиÑий в ÑÑиÑ
кодаÑ
. Так как ÐºÐ¾Ð´Ñ Soundex ÑоÑÑоÑÑ Ð¸Ð· ÑеÑÑÑÑÑ
Ñимволов, ÑезÑлÑÑаÑом Ð¼Ð¾Ð¶ÐµÑ Ð±ÑÑÑ ÑиÑло Ð¾Ñ Ð½ÑÐ»Ñ Ð´Ð¾ ÑеÑÑÑÑÑ
(0 обознаÑÐ°ÐµÑ Ð¿Ð¾Ð»Ð½Ð¾Ðµ неÑооÑвеÑÑÑвие, а 4 â ÑоÑное Ñовпадение). (Таким обÑазом, Ð¸Ð¼Ñ ÑÑой ÑÑнкÑии не вполне коÑÑекÑное â лÑÑÑим именем Ð´Ð»Ñ Ð½ÐµÑ Ð±Ñло Ð±Ñ similarity.)
ÐеÑколÑко пÑимеÑов иÑполÑзованиÑ:
SELECT soundex('hello world!');
SELECT soundex('Anne'), soundex('Ann'), difference('Anne', 'Ann');
SELECT soundex('Anne'), soundex('Andrew'), difference('Anne', 'Andrew');
SELECT soundex('Anne'), soundex('Margaret'), difference('Anne', 'Margaret');
CREATE TABLE s (nm text);
INSERT INTO s VALUES ('john');
INSERT INTO s VALUES ('joan');
INSERT INTO s VALUES ('wobbly');
INSERT INTO s VALUES ('jack');
SELECT * FROM s WHERE soundex(nm) = soundex('john');
SELECT * FROM s WHERE difference(s.nm, 'john') > 2;F.17.2. Daitch-Mokotoff Soundex #
Ðак и иÑÑ Ð¾Ð´Ð½Ð°Ñ ÑиÑÑема Soundex, ÑиÑÑема Daitch-Mokotoff Soundex ÑопоÑÑавлÑÐµÑ Ð¿Ð¾Ñ Ð¾Ð¶Ð¸Ðµ по звÑÑÐ°Ð½Ð¸Ñ Ð¸Ð¼ÐµÐ½Ð° и пÑеобÑазÑÐµÑ Ð¸Ñ Ð² один и ÑÐ¾Ñ Ð¶Ðµ код. Ðднако Daitch-Mokotoff Soundex знаÑиÑелÑно полезнее Ð´Ð»Ñ Ð½ÐµÐ°Ð½Ð³Ð»Ð¸Ð¹ÑÐºÐ¸Ñ Ð¸Ð¼Ñн, Ñем иÑÑ Ð¾Ð´Ð½Ð°Ñ ÑиÑÑема. ÐÑновнÑе ÑлÑÑÑÐµÐ½Ð¸Ñ Ð¿Ð¾ ÑÑÐ°Ð²Ð½ÐµÐ½Ð¸Ñ Ñ Ð¸ÑÑ Ð¾Ð´Ð½Ð¾Ð¹ ÑиÑÑемой пеÑеÑиÑÐ»ÐµÐ½Ñ Ð½Ð¸Ð¶Ðµ:
Ðод оÑнован на пеÑвÑÑ ÑеÑÑи знаÑимÑÑ Ð±ÑÐºÐ²Ð°Ñ , а не на ÑеÑÑÑÑÑ .
ÐÑква или комбинаÑÐ¸Ñ Ð±Ñкв ÑопоÑÑавлÑеÑÑÑ Ñ Ð´ÐµÑÑÑÑÑ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ñми кодами, а не Ñ ÑемÑÑ.
ÐÑли две поÑледоваÑелÑнÑе бÑÐºÐ²Ñ Ð¸Ð¼ÐµÑÑ Ð¾Ð´Ð¸Ð½ звÑк, они заменÑÑÑÑÑ ÑиÑÑой.
Ðогда бÑква или комбинаÑÐ¸Ñ Ð±Ñкв могÑÑ Ð¸Ð¼ÐµÑÑ ÑазнÑе звÑки, генеÑиÑÑеÑÑÑ Ð½ÐµÑколÑко кодов, ÑÑÐ¾Ð±Ñ Ð¾Ñ Ð²Ð°ÑиÑÑ Ð²Ñе возможнÑе ваÑианÑÑ.
ÐÑа ÑÑнкÑÐ¸Ñ Ð³ÐµÐ½ÐµÑиÑÑÐµÑ ÐºÐ¾Ð´Ñ Daitch-Mokotoff Soundex Ð´Ð»Ñ Ð²Ñ Ð¾Ð´Ð½ÑÑ Ð´Ð°Ð½Ð½ÑÑ :
daitch_mokotoff(source text) returns text[]РезÑлÑÑÐ°Ñ Ð¼Ð¾Ð¶ÐµÑ ÑодеÑжаÑÑ Ð¾Ð´Ð¸Ð½ или неÑколÑко кодов в завиÑимоÑÑи Ð¾Ñ ÐºÐ¾Ð»Ð¸ÑеÑÑва возможнÑÑ Ð²Ð°ÑианÑов пÑоизноÑениÑ, поÑÑÐ¾Ð¼Ñ Ð¾Ð½ пÑедÑÑавлÑеÑÑÑ Ð² виде маÑÑива.
ÐоÑколÑÐºÑ ÐºÐ¾Ð´ Daitch-Mokotoff Soundex ÑоÑÑÐ¾Ð¸Ñ Ð²Ñего из 6 ÑиÑÑ, ÑекомендÑеÑÑÑ Ð¿ÐµÑедаваÑÑ Ð² source одно Ñлово или имÑ.
ÐеÑколÑко пÑимеÑов:
SELECT daitch_mokotoff('George');
daitch_mokotoff
-----------------
{595000}
SELECT daitch_mokotoff('John');
daitch_mokotoff
-----------------
{160000,460000}
SELECT daitch_mokotoff('Bierschbach');
daitch_mokotoff
-----------------------------------------------------------
{794575,794574,794750,794740,745750,745740,747500,747400}
SELECT daitch_mokotoff('Schwartzenegger');
daitch_mokotoff
-----------------
{479465}ÐÐ»Ñ ÑопоÑÑÐ°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¾ÑделÑнÑÑ
имÑн возвÑаÑаемÑе ÑекÑÑовÑе маÑÑÐ¸Ð²Ñ Ð¼Ð¾Ð³ÑÑ ÑÑавниваÑÑÑÑ Ð½Ð°Ð¿ÑÑмÑÑ Ñ Ð¸ÑполÑзованием опеÑаÑоÑа &&: лÑбое пеÑеÑеÑение можно ÑÑиÑаÑÑ Ñовпадением. ÐÐ»Ñ Ð¿Ð¾Ð²ÑÑÐµÐ½Ð¸Ñ ÑÑÑекÑивноÑÑи можно иÑполÑзоваÑÑ Ð¸Ð½Ð´ÐµÐºÑ GIN, Ñм. ÐлавÑ 70 и ÑледÑÑÑий пÑимеÑ:
CREATE TABLE s (nm text);
CREATE INDEX ix_s_dm ON s USING gin (daitch_mokotoff(nm)) WITH (fastupdate = off);
INSERT INTO s (nm) VALUES
('Schwartzenegger'),
('John'),
('James'),
('Steinman'),
('Steinmetz');
SELECT * FROM s WHERE daitch_mokotoff(nm) && daitch_mokotoff('Swartzenegger');
SELECT * FROM s WHERE daitch_mokotoff(nm) && daitch_mokotoff('Jane');
SELECT * FROM s WHERE daitch_mokotoff(nm) && daitch_mokotoff('Jens');ÐÐ»Ñ Ð¸Ð½Ð´ÐµÐºÑаÑии и ÑопоÑÑÐ°Ð²Ð»ÐµÐ½Ð¸Ñ Ð»Ñбого колиÑеÑÑва имÑн в лÑбом поÑÑдке можно иÑполÑзоваÑÑ ÑÑнкÑии полноÑекÑÑового поиÑка. См. ÐлавÑ 12 и ÑледÑÑÑий пÑимеÑ:
CREATE FUNCTION soundex_tsvector(v_name text) RETURNS tsvector
BEGIN ATOMIC
SELECT to_tsvector('simple',
string_agg(array_to_string(daitch_mokotoff(n), ' '), ' '))
FROM regexp_split_to_table(v_name, '\s+') AS n;
END;
CREATE FUNCTION soundex_tsquery(v_name text) RETURNS tsquery
BEGIN ATOMIC
SELECT string_agg('(' || array_to_string(daitch_mokotoff(n), '|') || ')', '&')::tsquery
FROM regexp_split_to_table(v_name, '\s+') AS n;
END;
CREATE TABLE s (nm text);
CREATE INDEX ix_s_txt ON s USING gin (soundex_tsvector(nm)) WITH (fastupdate = off);
INSERT INTO s (nm) VALUES
('John Doe'),
('Jane Roe'),
('Public John Q.'),
('George Best'),
('John Yamson');
SELECT * FROM s WHERE soundex_tsvector(nm) @@ soundex_tsquery('john');
SELECT * FROM s WHERE soundex_tsvector(nm) @@ soundex_tsquery('jane doe');
SELECT * FROM s WHERE soundex_tsvector(nm) @@ soundex_tsquery('john public');
SELECT * FROM s WHERE soundex_tsvector(nm) @@ soundex_tsquery('besst, giorgio');
SELECT * FROM s WHERE soundex_tsvector(nm) @@ soundex_tsquery('Jameson John');ÐÑли желаÑелÑно избежаÑÑ Ð¿ÐµÑеÑÑÑÑа кодов Soundex пÑи пеÑепÑовеÑке индекÑа, вмеÑÑо индекÑа по вÑÑÐ°Ð¶ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð¶Ð½Ð¾ иÑполÑзоваÑÑ Ð¸Ð½Ð´ÐµÐºÑ Ð¿Ð¾ оÑделÑÐ½Ð¾Ð¼Ñ ÑÑолбÑÑ. ÐÐ»Ñ ÑÑого можно иÑполÑзоваÑÑ Ñ ÑанимÑй генеÑиÑÑемÑй ÑÑолбеÑ; Ñм. Раздел 5.3.
F.17.3. ÐевенÑÑейн #
ÐÑа ÑÑнкÑÐ¸Ñ Ð²ÑÑиÑлÑÐµÑ ÑаÑÑÑоÑние ÐевенÑÑейна Ð¼ÐµÐ¶Ð´Ñ Ð´Ð²ÑÐ¼Ñ ÑÑÑоками:
levenshtein(source text, target text, ins_cost int, del_cost int, sub_cost int) returns int levenshtein(source text, target text) returns int levenshtein_less_equal(source text, target text, ins_cost int, del_cost int, sub_cost int, max_d int) returns int levenshtein_less_equal(source text, target text, max_d int) returns int
Рв source, и в target Ð¼Ð¾Ð¶ÐµÑ Ð±ÑÑÑ Ð¿ÐµÑедана лÑÐ±Ð°Ñ ÑÑÑока, оÑлиÑÐ½Ð°Ñ Ð¾Ñ NULL, не длиннее 255 Ñимволов. ÐаÑамеÑÑÑ ÑÑоимоÑÑи (ins_cost, del_cost, sub_cost) опÑеделÑÑÑ ÑÐµÐ½Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ, ÑÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð¸Ð»Ð¸ Ð·Ð°Ð¼ÐµÐ½Ñ Ñимволов, ÑооÑвеÑÑÑвенно. ÐÑи паÑамеÑÑÑ Ð¼Ð¾Ð¶Ð½Ð¾ опÑÑÑиÑÑ, как во вÑоÑой веÑÑии ÑÑнкÑии; в ÑÑом ÑлÑÑае вÑе они по ÑмолÑÐ°Ð½Ð¸Ñ ÑÐ°Ð²Ð½Ñ 1.
ФÑнкÑÐ¸Ñ levenshtein_less_equal ÑвлÑеÑÑÑ ÑÑкоÑенной веÑÑией ÑÑнкÑии ÐевенÑÑейна, пÑедназнаÑенной Ð´Ð»Ñ Ð¸ÑполÑзованиÑ, ÑолÑко когда инÑеÑÐµÑ Ð¿ÑедÑÑавлÑÑÑ Ð½ÐµÐ±Ð¾Ð»ÑÑие ÑаÑÑÑоÑниÑ. ÐÑли ÑакÑиÑеÑкое ÑаÑÑÑоÑние менÑÑе или Ñавно max_d, Ñо levenshtein_less_equal возвÑаÑÐ°ÐµÑ ÑоÑное его знаÑение; в пÑоÑивном ÑлÑÑае она возвÑаÑÐ°ÐµÑ Ð·Ð½Ð°Ñение, болÑÑее Ñем max_d. ÐÑли знаÑение max_d оÑÑиÑаÑелÑное, она ÑабоÑÐ°ÐµÑ Ñак же, как ÑÑнкÑÐ¸Ñ levenshtein.
ÐÑимеÑÑ:
test=# SELECT levenshtein('GUMBO', 'GAMBOL');
levenshtein
-------------
2
(1 row)
test=# SELECT levenshtein('GUMBO', 'GAMBOL', 2, 1, 1);
levenshtein
-------------
3
(1 row)
test=# SELECT levenshtein_less_equal('extensive', 'exhaustive', 2);
levenshtein_less_equal
------------------------
3
(1 row)
test=# SELECT levenshtein_less_equal('extensive', 'exhaustive', 4);
levenshtein_less_equal
------------------------
4
(1 row)F.17.4. Metaphone #
Metaphone, как и Soundex, поÑÑÑоен на идее ÑоÑÑÐ°Ð²Ð»ÐµÐ½Ð¸Ñ ÐºÐ¾Ð´Ð°, пÑедÑÑавлÑÑÑего Ð²Ñ Ð¾Ð´Ð½ÑÑ ÑÑÑокÑ. Ðве ÑÑÑоки пÑизнаÑÑÑÑ Ð¿Ð¾Ñ Ð¾Ð¶Ð¸Ð¼Ð¸, еÑли Ð¸Ñ ÐºÐ¾Ð´Ñ ÑовпадаÑÑ.
ÐÑа ÑÑнкÑÐ¸Ñ Ð²ÑÑиÑлÑÐµÑ ÐºÐ¾Ð´ меÑаÑона Ð²Ñ Ð¾Ð´Ð½Ð¾Ð¹ ÑÑÑоки:
metaphone(source text, max_output_length int) returns text
РкаÑеÑÑве source должна пеÑедаваÑÑÑÑ ÑÑÑока, оÑлиÑÐ½Ð°Ñ Ð¾Ñ NULL, не длиннее 255 Ñимволов. ÐаÑамеÑÑ max_output_length задаÑÑ Ð¼Ð°ÐºÑималÑнÑÑ Ð´Ð»Ð¸Ð½Ñ Ð²ÑÑ
одного кода меÑаÑона; еÑли код оказÑваеÑÑÑ Ð´Ð»Ð¸Ð½Ð½ÐµÐµ, он обÑезаеÑÑÑ Ð´Ð¾ ÑÑой длинÑ.
ÐÑимеÑ:
test=# SELECT metaphone('GUMBO', 4);
metaphone
-----------
KM
(1 row)F.17.5. Double Metaphone #
ÐлгоÑиÑм Double Metaphone (Ðвойной меÑаÑон) вÑÑиÑлÑÐµÑ Ð´Ð²Ðµ ÑÑÑоки Â«Ð¿Ð¾Ñ Ð¾Ð¶ÐµÐ³Ð¾ звÑÑаниÑ» Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ð½Ð½Ð¾Ð¹ ÑÑÑоки â «пеÑвиÑнÑÑ» и «алÑÑеÑнаÑивнÑÑ». РболÑÑинÑÑве ÑлÑÑаев они ÑовпадаÑÑ, но Ð´Ð»Ñ Ð½ÐµÐ°Ð½Ð³Ð»Ð¾ÑзÑÑнÑÑ Ð¸Ð¼Ñн в оÑобенноÑÑи они могÑÑ Ð±ÑÑÑ Ð²ÐµÑÑма ÑазлиÑнÑми, в завиÑимоÑÑи Ð¾Ñ Ð¿ÑоизноÑениÑ. ÐÑи ÑÑнкÑии вÑÑиÑлÑÑÑ Ð¿ÐµÑвиÑнÑй и алÑÑеÑнаÑивнÑй кодÑ:
dmetaphone(source text) returns text dmetaphone_alt(source text) returns text
Ðлина Ð²Ñ Ð¾Ð´Ð½ÑÑ ÑÑÑок Ð¼Ð¾Ð¶ÐµÑ Ð±ÑÑÑ Ð»Ñбой.
ÐÑимеÑ:
test=# SELECT dmetaphone('gumbo');
dmetaphone
------------
KMP
(1 row)