2012年11月29日木曜日

sqliteを利用した日本語での全文検索実験


■概要

ひょんな事から、sqliteでの全文検索について調べました。
すると全文検索用に、fts3/4指定ができる事を発見したのですが、予想通り"スペース"での、単語区切りしかできないようです。
日本語を処理したかったので、自分でn-gramしたデータを登録してテストしてみました。
確かに高速に検索できます。

※ MeCabを利用したtokenizerとかはandroidでの利用を考え考察していません。

■テストデータ

郵便番号データを下記からダウンロードしました。
http://www.post.japanpost.jp/zipcode/dl/kogaki.html

全国版データを利用しました。
ちなみにcentos6.xではlhaコマンドを入手できなかったので、http://d.hatena.ne.jp/pcmaster/20100211/p3 を参考にインストールしました。

$ lha e ken_all.lzh
$ ruby gensql.rb ken_all.csv > ken_all.sql # 下記スクリプトで分割済みのフィールドを準備

■実験

$ sqlite3 a.sqlite
> create virtual table zip_codes using fts3
(
  code text
,  address text
,  search_field text
);

> begin;
> .read ken_all.sql
> commit;

> .timer ON;
> select code, address from zip_codes where address like ('%東成区%');
CPU Time: user 0.272959 sys 0.024997

> select count(*) from zip_codes where address like ('%東成区%');
CPU Time: user 0.262960 sys 0.032995

> select code, address from zip_codes where search_field match '東成 成区';
CPU Time: user 0.001000 sys 0.000000

> select count(*) from zip_codes where search_field match '東成 成区';
CPU Time: user 0.001000 sys 0.000000

なんと数値上は300倍近く高速化されましたw!
ちなみに検索文字列もn-gram分割する必要があります。

■(参考)データ加工用スクリプト

require 'csv'

# なんちゃって2-gram。英数字は特別扱いするとか正規化するとか工夫してください。
# javaで組めば幸せになるはず(?)
def gen_2_gram(org)
  result = []
  (org.length - 1).times { |i| result << org[i, 2] }
  result.join(" ")
end

# *.csvファイルを処理する。
ARGV.each do |file|
  CSV.foreach(file, "r:sjis") do |row|
    zip     = row[0]
    address = (row[6] + row[7] + row[8]).encode("utf-8")
    puts "insert into zip_codes (code, address, search_field) values (#{zip}, '#{address}', '#{gen_2_gram(address)}');"
  end
end

※"以下に掲載がない場合"とかは実験目的なので特に処理していません。