Azure AI Search での OData コレクション フィルターのしくみを理解する

この記事では、複雑なラムダ式を使用して高度なフィルターを記述する開発者向けの背景情報を提供します。 この記事では、Azure AI Search でこれらのフィルターがどのように実行されるかを調べ、コレクション フィルターの規則が存在する理由について説明します。

Azure AI Search のコレクション フィールドに対してフィルターを作成する場合は、ラムダ式共に and all 演算子any使用できます。 ラムダ式は範囲変数を参照するブール式です。 ラムダ式を使用するフィルターでは、 any 演算子と all 演算子はほとんどのプログラミング言語のループに for 似ています。範囲変数はループ変数の役割を担い、ラムダ式はループの本体になります。 範囲変数は、ループの反復処理の間、コレクションの "現在" の値になります。

少なくとも、概念的にはそのように動作します。 実際には、Azure AI Search では、 for ループのしくみとは大きく異なる方法でフィルターが実装されます。 この違いがユーザーには認識されないのが理想ですが、特定の状況ではそうなりません。 最終的な結果として、ラムダ式を作成するときに従う必要がある規則があります。

Note

コレクション フィルターの規則 (例を含む) については、「 Azure AI Searchでの OData コレクション フィルターのトラブルシューティング」を参照してください。

コレクション フィルターに制限がある理由

すべての種類のコレクションでフィルター機能が完全にサポートされない理由は 3 つあります。

  1. 特定のデータ型では、特定の演算子のみがサポートされています。 たとえば、ltgt などを使ってブール値 truefalse を比較しても意味がありません。
  2. Azure AI Search では、関連付けられた検索 Collection(Edm.ComplexType)型のフィールドにサポートされていません。
  3. Azure AI Search では、逆インデックスを使用して、コレクションを含むすべての型のデータに対してフィルターを実行します。

最初の理由は、単に、OData 言語と EDM 型システムの定義方法の結果によるものです。 後の 2 つについては、この記事の残りの部分で詳しく説明します。

複合オブジェクトのコレクションに複数のフィルター条件を適用すると、条件はコレクション内の各オブジェクトに適用されるため、関連付けられます。 たとえば、次のフィルターは、料金が 100 未満のデラックス ルームが 1 つ以上あるホテルを返します。

    Rooms/any(room: room/Type eq 'Deluxe Room' and room/BaseRate lt 100)

フィルター処理が "相関されない" 場合、上記のフィルターでは、1 つの部屋がデラックスで、別の部屋が基本料金 100 未満であるホテルが返される可能性があります。 ラムダ式の両方の句が同じ範囲変数 room に適用されるので、それでは意味がありません。 これが、そのようなフィルターが相関される理由です。

ただし、フルテキスト検索では、特定の範囲変数を参照する方法はありません。 フィールド検索を使って、次のような完全な Lucene クエリを発行するものとします。

    Rooms/Type:deluxe AND Rooms/Description:"city view"

1つの部屋がデラックスで、別の部屋が説明に「街の景色」メンションホテルを取り戻すかもしれません。 たとえば、Id1 である以下のドキュメントは、クエリに一致します。

{
  "value": [
    {
      "Id": "1",
      "Rooms": [
        { "Type": "deluxe", "Description": "Large garden view suite" },
        { "Type": "standard", "Description": "Standard city view room" }
      ]
    },
    {
      "Id": "2",
      "Rooms": [
        { "Type": "deluxe", "Description": "Courtyard motel room" }
      ]
    }
  ]
}

その理由は、次の表に示すように、Rooms/Type ではドキュメント全体の Rooms/Type フィールドの分析されたすべての語句が参照され、Rooms/Description フィールドについても同様であるためです。

フルテキスト検索に対して Rooms/Type が格納される方法:

Rooms/Type での語句 ドキュメント ID
deluxe 1、2
standard 1

フルテキスト検索に対して Rooms/Description が格納される方法:

Rooms/Description での語句 ドキュメント ID
courtyard 2
city 1
garden 1
1
motel 2
ルーム 1、2
standard 1
suite 1
表示 1

したがって、基本的に "部屋の Type が 'Deluxe Room' と等しく、その同じ部屋BaseRate が 100 未満であるドキュメントと一致する" ことを示す上記のフィルターとは異なり、検索クエリでは "Rooms/Type に語句 'deluxe' が含まれ、Rooms/Description に語句 'city view' が含まれるドキュメントと一致する" ことが示されます。 後者の場合、フィールドを相関できる個別の部屋の概念はありません。

逆インデックスとコレクション

複雑なコレクションに対するラムダ式の制限は、単純なコレクション (... などCollection(Edm.Int32)Collection(Edm.GeographyPoint)) よりもはるかに少ないことに気付いたかもしれません。 これは、Azure AI Search では複雑なコレクションがサブドキュメントの実際のコレクションとして格納されますが、単純なコレクションはコレクションとしてまったく格納されないためです。

たとえば、オンライン小売店用のインデックスでの seasons のようなフィルター可能な文字列コレクション フィールドについて考えます。 このインデックスには、次のようなドキュメントがアップロードされる可能性があります。

{
  "value": [
    {
      "id": "1",
      "name": "Hiking boots",
      "seasons": ["spring", "summer", "fall"]
    },
    {
      "id": "2",
      "name": "Rain jacket",
      "seasons": ["spring", "fall", "winter"]
    },
    {
      "id": "3",
      "name": "Parka",
      "seasons": ["winter"]
    }
  ]
}

seasons フィールドの値は逆インデックスと呼ばれる構造に格納され、次のようになります。

任期 ドキュメント ID
spring 1、2
summer 1
fall 1、2
winter 2、3

このデータ構造は、特定の語句はどのドキュメントに含まれているか、という 1 つの質問に対して非常に高速に回答するように設計されています。 この質問に答える処理は、コレクションのループというより、単純な等値チェックに近いものです。 実際、これが文字列コレクションの場合、Azure AI Search では、eqのみをanyのラムダ式内の比較演算子として許可する理由です。

次に、同じ範囲変数orで複数の等値チェックを結合する方法を見てみましょう。 それは、代数と量指定子の分配則に従って動作します。 次の式

    seasons/any(s: s eq 'winter' or s eq 'fall')

は以下に匹敵します。

    seasons/any(s: s eq 'winter') or seasons/any(s: s eq 'fall')

そして、2 つの各 any サブ式を、逆インデックスを使って効率的に実行できます。 また、量指定子の否定法則により、次の式

    seasons/all(s: s ne 'winter' and s ne 'fall')

は以下に匹敵します。

    not seasons/any(s: s eq 'winter' or s eq 'fall')

これが、ne および andall を使用できる理由です。

Note

詳細はこのドキュメントの範囲を超えますが、これらの同じ原則が、地理空間ポイントのコレクションに対する距離と交差のテストに拡張されます。 これが、any で次のようになる理由です。

  • geo.intersects を否定することはできません
  • geo.distance は、lt または le を使って比較する必要があります
  • 式を結合するには、and ではなく or を使う必要があります

all には逆の規則が適用されます。

ltgtlege の各演算子がサポートされるデータ型のコレクションをフィルター処理するときは、さまざまな式が許可されます (Collection(Edm.Int32) など)。 具体的には、基になる比較式が and を使って範囲比較に結合されている限り、any においてand だけでなく or を使うことができ、それがさらに or を使って結合されます。 ブール式のこの構造は選言標準形 (DNF) と呼ばれ、"ORs of ANDs" としても知られています。 逆に、これらのデータ型に対する all のラムダ式は、連言標準形 (CNF) ("ANDs of ORs" としても知られます) になっている必要があります。 Azure AI Search では、このような範囲の比較は、文字列の高速な用語検索を実行できるのと同様に、逆インデックスを使用して効率的に実行できるためです。

まとめると、ラムダ式で許可されることの経験則は次のようになります。

  • any の内部では、等値、範囲比較、geo.intersectslt または le での geo.distance の比較など、"ポジティブ チェック" が常に許可されます (距離のチェックでは "近さ" は等価性と同じように考えます)。
  • any の内部では、or が常に許可されます。 and は、範囲チェックを表現できるデータ型に対してだけ、ORs of ANDs (DNF) を使用する場合にのみ使用できます。
  • 内部では all、ルールが逆になります。 負のチェックのみが許可され、常に使用andでき、範囲チェックに対してのみ使用できますor。これは、ANDs of OR (CNF) として表されます。

実際には、これらはいずれにしても使用する可能性が最も高いフィルターの種類です。 それでも、可能なことの境界を理解しておくと役に立ちます。

許可されるフィルターと許可されないフィルターの種類の具体的な例については、「有効なコレクション フィルターを記述する方法」をご覧ください。

次のステップ