pg_bigmとpg_trgmのsimilarity関数
アイエエエエエエ! ドウイツ!? ドウイツナンデ!?
ということで、pg_bigmのsimilarityをちょっと動かしていたらpg_bigmとpg_trgmのsimilarity関数に共通する、一見、ちょっとびっくりする挙動を見つけたのでメモっておく。
結論から言うと、別にバグっているわけじゃない。
similarity関数の暫定サポート
先日のhttp://www.postgresql.jp/wg/shikumi/shikumi26/の講演の中で、「pg_trgmにあるような類似検索用の機能ってサポートしないの?」という質問を澤田さんにしたら「ニーズがあれば作るよ〜」と回答もらったんだけど、なんと早速数日後にはsimilarity関数の、早速pg_bigm用のsimilarity関数のプロトタイプがGithubリポジトリのbranchに上がっていた。
このへんのフットワークの軽さはホント見習わないとな〜。
インストールまで
きちんとテストはしてないバージョンだよ、とは言われたけど、質問(&要望)した身としては、やはり試しておかねばなるまい。
ということでDLして動かしてみようとしたが、いきなりCREATE EXTENSIONで躓く。(´・ω・`)
原因はすぐに解ったのでささっと修正(pg_bigm--1.0.sql内の追加CREATE OPERATORのPROCEDUREに設定した関数をsimilarity_opに修正)して登録しておく。
もちろん澤田さんにもTwitter経由で連絡しておく。連絡するとすぐに修正版をGithub上にアップデートしてくれた。
similarity関数をとりあえず動かす
ということで、とりあえず動かしてみるとした。
手持ちの環境上にあるPostgreSQL 9.2.4上のデータベースにCREATE EXTENSIONで登録。
で、登録されたpg_bigm extensionの確認。
$ psql bigm psql (9.2.4) Type "help" for help. bigm=# \dx List of installed extensions Name | Version | Schema | Description ---------+---------+------------+--------------------------------------- pg_bigm | 1.0 | public | text index searching based on bigrams plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language (2 rows) bigm=# \dx+ pg_bigm Objects in extension "pg_bigm" Object Description -------------------------------------------------------------------------------------------------- function bigmtextcmp(text,text) function gin_bigm_compare_partial(text,text,smallint,internal) function gin_bigm_consistent(internal,smallint,text,integer,internal,internal,internal,internal) function gin_extract_query_bigm(text,internal,smallint,internal,internal,internal,internal) function gin_extract_value_bigm(text,internal) function likequery(text) function pg_gin_pending_stats(regclass) function show_bigm(text) function similarity(text,text) function similarity_op(text,text) operator %(text,text) operator class gin_bigm_ops for access method gin operator class gin_trgm_ops for access method gin operator family gin_bigm_ops for access method gin operator family gin_trgm_ops for access method gin (15 rows)
functionとしてsimilarity()が入ってますね。関数形式はpg_trgmともちろん同じです。
ということで、まずは簡単にテスト。
bigm=# SELECT similarity('ABCDE','ABCDE'); similarity ------------ 1 (1 row) bigm=# SELECT similarity('ABCDE','FGHIJ'); similarity ------------ 0 (1 row)
うむ。完全一致なら1、全く類似度がなければ0。これはわかり易い例。
じゃあ日本語ならどうなのか。
bigm=# SELECT similarity('あいうえお','あいうえお'); similarity ------------ 1 (1 row)
うむ。完全に同一だからOK。
bigm=# SELECT similarity('あいうえお','かきくけこ'); similarity ------------ 0.0909091 (1 row)
おや・・・この2つの文字列間には全く類似度がないと思うのだが、0にはならないのか。これはマルチバイト文字固有の挙動なのか?
bigm=# SELECT similarity('PostgreSQL','Oracle'); similarity ------------ 0 (1 row) bigm=# SELECT similarity('PostgreSQL','Oracle'); similarity ------------ 0.1875 (1 row)
算出された値は十分に小さいので、よほど閾値を小さくしなければsimilarity_opで変な判定をすることはないのかもしれないが、ちょっと気になる。
で、これはpg_bigm固有かと思いきやpg_trgmでも類似の挙動になっていた。実際コードを見てもpg_bigmとpg_trgmのsimilarityや、そこから呼ばれるcnt_smlもほぼ同一だからなあ・・・。
連続した同一文字の扱い
で、もっと気になったのは連続した同一文字が続いた文字列同士をsimilarityにかけたときの挙動。
bigm=# SELECT similarity('アイエエエエエエ!','アイエエ!'); similarity ------------ 1 (1 row)
アイエエエエエエ! ドウイツ!? ドウイツナンデ!?
同一文字が(2文字以上?)連続した場合、何文字あっても2文字だけ連続した文字と解釈しているのかな。
ちなみにこれもpg_bigm/pg_trgmともに同じような挙動を示す(pg_trgmの場合3文字以上、pg_bigmの場合は2文字以上みたい。語を3文字区切り/2文字区切りで処理する違いからくるのだろう)。
そして、この挙動は日本語固有というわけでもなく英字でも同様である。
trgm=# SELECT similarity('Yahooooooooo!','Yahooo!'); similarity ------------ 1 (1 row)
show_bigmの挙動
で、なんで上記のような挙動になるのかというと、それはshow_bigm()を動かしてみると理解できる。show_bigmで上記の文字列を指定すると
bigm=# SELECT show_bigm('アイエエエエエエ!'); show_bigm ----------------------------------- {アイ,イエ,エエ,エ!,"! "," ア"} (1 row) bigm=# SELECT show_bigm('アイエエ!'); show_bigm ----------------------------------- {アイ,イエ,エエ,エ!,"! "," ア"} (1 row)
全く同じ結果になる。つまりsimilarity関数では、このbigm結果を比較しているので全く同一と見なすという挙動なのだろう。
これはpg_trgmでも同様(こっちはハッシュ値が同じ。末端の"cf"がよく分からんが)だった。
trgm=# CREATE EXTENSION pg_trgm ; CREATE EXTENSION trgm=# SELECT show_trgm('アイエエエエエエ!'); show_trgm ------------------------------------------------------------- {0xb6ea15,0xcadd17,0xcfb0ce,0xeaa498,0x08dcbf,0x2ebd32,cf;} (1 row) trgm=# SELECT show_trgm('アイエエ!'); show_trgm --------------------------------------------------------- {0xb6ea15,0xcadd17,0xcfb0ce,0xeaa498,0x08dcbf,0x2ebd32} (1 row)
おまけ
で、この検証やってるときに気づいたんだけど、pg_bigmでsimilarity()/similarity_op()をサポートするなら合わせてshow_limit()/set_limit()も必要かも・・・。