Django+Tritonnで日本語全文検索〜重み付けもあるよ〜
DBにTritonnを使ってDjangoから全文検索をする という話
Tritonnってなに?どうやって入れんの
かつあい
モデル構造
class Book(models.Model): """本""" title = models.CharField(max_length=255) desc = models.TextField() pub_date = models.DateTimeField(auto_now_add=True) class BookSearch(models.Model): """本の検索テーブル""" book = models.OneToOneField(Book) title = models.CharField(max_length=255) desc = models.TextField()
全文検索に使いたいカラムを持った〜〜Searchモデルみたいなのを定義。
OneToOneとして元オブジェクトにリレーションを張る。
下準備
当然このままだとsyncdbしても(多くは)InnoDBだわFulltext index張られてないわなので、BookSearchモデルを定義したアプリのフォルダにsql/BookSearch.sqlを書いてテーブル定義をむりくり書き換える。
DROP TABLE `book_search`; CREATE TABLE `book_search` ( にゃんにゃん, FULLTEXT KEY `search` USING MECAB, NORMALIZE, SECTIONALIZE, 256 (`title`, `desc`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
manage.py sqlで出したテーブル定義をまるコピしつつ既存テーブルをDROPしてfulltext keyとEngine=MyISAMを付けて再定義する感じ。
シグナル定義
BookのsaveにフックしてBookSearchも作らないと検索出来ないですよね。
なのでシグナル書く。
def book_search_signal(sender, instance, **kwargs): try: bs = BookSearch.objects.get(book=instance) except: bs = BookSearch() bs.title = instance.title bs.desc = instance.desc bs.save() models.signals.post_save.connect(book_search_signal, sender=Book)
検索メソッド
パフォーマンスを気にするのであればBookSearchからQuerySetを作るべきだけど、そうすると最終的に出てくるのがBookSearchオブジェクトのリストになってしまうのでとりまわしが面倒。
取り回しを重視するのであればBookから作ってBookオブジェクトのリストを得ると楽でよいです。
今回は後者で。
class BookManager(models.Manager): にゃんにゃん def fulltext(self, keywords): match = u"""MATCH ( `book_search`.`title`, `book_search`.`desc`, ) AGAINST ( %s IN BOOLEAN MODE )""" param = "*W1:3,2:1 %s" % u" ".join([u"+" + x for x in keywords]) query = self.be()\ .extra( select=SortedDict([ ("fulltext_score", match), ]), select_params=( param, ), tables=[ "`book_search`" ], where=[ match, """ `book_search`.`book_id` = `book`.`id` """ ], params=[ param, ], ).order_by("-fulltext_score") return query
extraでがんばって最終的に重み付け検索+fulltext_scoreでソートしたquerysetを得る。
SELECTカラムにfulltext_scoreなるカラムをつける辺りが結構複雑なので、MySQL側のクエリログを見ながら試行錯誤する必要あり。*1
使う
こんな感じに使える。
Book.objects.fulltext(["ぬる", "ぽ"]).filter(pub_date__gte=datetime.now()-timedelta(days=7))
過去一週間以内に発売された、"ぬる"か"ぽ"をタイトルか紹介文に含むBookオブジェクト。
おわり
わりと富豪的プログラミングだと思うので、お使いの際はデータ量等々とご相談の上どうぞ。