しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

Azure Search を使って検索結果のファセットを取得してみた

前回の Azure Search を使ってツイートログ検索を実装してみた - しばやん雑記 では基本的な使い方しかしませんでしたが、今回はちょっと応用的な使い方としてファセットを取得してみたいと思います。

ファセットについてはこちらの記事がとてもわかりやすいのでおススメです。

Amazon CloudSearchで全件対象のファセットを表示する。新着(123件)みたいなやつ。 | Developers.IO

Azure Search でもファセットに対応しているので簡単に実装出来ます。とりあえずインデックスとデータの投入までは簡単に終わらせておきましょう。

var client = new IndexManagementClient(connection);

await client.CreateIndexAsync(new Index("items")
    .WithStringField("id", p => p.IsKey().IsRetrievable())
    .WithStringField("name", p => p.IsSearchable().IsRetrievable())
    .WithIntegerField("rating", p => p.IsRetrievable().IsSortable().IsFacetable().IsFilterable())
);

今回は EC サイトっぽいケースを想定して、評価をファセットとして扱うようにします。あとフィルタリングも同時に行いたいので、必要な属性を追加しておきます。

インデックスはこんなところなので、次はデータの投入を行います。

var client = new IndexManagementClient(connection);

var operations = new[]
{
    new IndexOperation(IndexOperationType.Upload, "id", "1")
        .WithProperty("name", "@kamebuchi")
        .WithProperty("rating", 5),
    new IndexOperation(IndexOperationType.Upload, "id", "2")
        .WithProperty("name", "@statemachine")
        .WithProperty("rating", 1),
    new IndexOperation(IndexOperationType.Upload, "id", "3")
        .WithProperty("name", "@Masayuki_Ozawa")
        .WithProperty("rating", 5),
    new IndexOperation(IndexOperationType.Upload, "id", "4")
        .WithProperty("name", "@k1hash")
        .WithProperty("rating", 3),
    new IndexOperation(IndexOperationType.Upload, "id", "5")
        .WithProperty("name", "@daruyanagi")
        .WithProperty("rating", 1)
    new IndexOperation(IndexOperationType.Upload, "id", "6")
        .WithProperty("name", "@sleepy_taka")
        .WithProperty("rating", 5)
};

await client.PopulateAsync("items", operations);

データの内容には特に意味はなく、お義父さんとだるやなぎ仕事しろという感じです。本来なら DB に持っている商品情報をインデックスに追加していく形になると思います。

これでインデックスの準備が出来たので、ファセットを同時に取得する検索を行ってみます。

var client = new IndexQueryClient(connection);

var result = await client.SearchAsync("items", new SearchQuery()
    .Facet("rating")
);

foreach (var facet in result.Body.Facets)
{
    foreach (var facetResult in facet.Value)
    {
        Console.WriteLine("{0} - {1} ({2})", facet.Key, facetResult.Value, facetResult.Count);
    }
}

Console.WriteLine("=======================");

foreach (var item in result.Body.Records)
{
    Console.WriteLine("{0} - {1},{2}", item.Score, item.Properties["name"], item.Properties["rating"]);
}

ファセットとして使用するフィールドは Facet 拡張メソッドで指定します。そして結果は Facets プロパティに配列で返ってくるので、これを foreach で取り出すようなコードです。

f:id:shiba-yan:20140822214611p:plain

実行した結果はこんな感じです。考え方的には rating の値で group by を行い、その件数を取得していると思えばわかりやすいでしょうか。

そして折角なので rating の値でフィルタリングを行ってみます。

// rating が 1 のアイテムだけを取り出す
var result = await client.SearchAsync("items", new SearchQuery()
    .Facet("rating")
    .Filter("rating eq 1")
);

ちょっと条件式の指定がいけてないですが、filter には OData 的な式を書けるようになっているっぽいです。

というか、REST API が OData そのままだった気がします。

f:id:shiba-yan:20140822220028p:plain

実行すると @statemachine と @daruyanagi だけがフィルタリングされた結果を取得できました。