スティルハウスの書庫の書庫

はてなダイアリーで書いてた「スティルハウスの書庫」を移転してきました。

複雑なクエリのためのプロパティを仕込んでおく方法

以前、コンポジットインデックスを自作するというエントリを書きましたが、そのためにわざわざ新しいエンティティを作らずに、普通のエンティティにコンポジットインデックス代わりのプロパティを持たせる方法をちょこちょこ使い始めました。

例えば、以下のようなクエリを実行したいとき、

select * from Person where name == 'foo' & age >= 20 & age < 60 order by age asc

このままではコンポジットインデックスを作らないとクエリ実行時にエラーが発生してしまいます。そこで、このエンティティPersonにプロパティsortKeyを追加します。このsortKeyには、name+ageの文字列を入れておきます。

bar:10
bar:29
foo:05
foo:25
foo:31
...

って感じです。数値を含める場合は「0」でパディングするのがポイントです。あとは、以下のクエリを実行します。

select * from Person where sortKey >= 'foo:20' & sortKey < 'foo:60' order by sortKey asc

また、例えば「name == 'foo' order by sortKey asc」ならば、

select * from Person where sortKey >= 'foo:00' & sortKey <= 'foo:99' order by sortKey asc

になります。これなら、インデックスを個別に構築したり管理する必要はありません。ただもちろん、nameやageの更新時にあわせてsortKeyも更新するフックは入れておく必要があります。

そういうわけで、最近はエンティティを設計するときに「だが待ってほしい。あらかじめsortKey入れといた方がいいのではないか」と考えるようになりました。SQLでは後からいくらでも条件検索したりjoinできるから、正規化しておけばクエリ要件を事前に考えてスキーマ設計する必要は(パフォチューを別にして)あまりありません。一方、Datastoreでは「クエリ要件をシングルプロパティのスキャンに落せるエンティティ設計」を事前にしておくことがコツと思いました(ここで紹介した例以外でも、List Propertyの活用も同様ですね)。