フィルター

Qdrantを使用すると、ベクトルの類似検索に加えて属性によるフィルタリングが可能です。これは、SQLのwhere条件の設定と同様であり、点のペイロードやidに対して条件を設定できます。

オブジェクトのすべての特徴が埋め込みに表現されない場合、追加の条件を設定することが重要です。たとえば、在庫の利用可能性、ユーザーの場所、または予想価格範囲など、さまざまなビジネス要件があります。

フィルター条件

Qdrantでは、条件を節で組み合わせることができます。節は、ORANDNOTなどの異なる論理演算であり、再帰的に入れ子にすることができます。そのため、任意のブール式を再現することができます。

Qdrantで実装されている節を見てみましょう。

ペイロードを持つ一連の点があると仮定しましょう:

[
  { "id": 1, "city": "London", "color": "green" },
  { "id": 2, "city": "London", "color": "red" },
  { "id": 3, "city": "London", "color": "blue" },
  { "id": 4, "city": "Berlin", "color": "red" },
  { "id": 5, "city": "Moscow", "color": "green" },
  { "id": 6, "city": "Moscow", "color": "blue" }
]

必須条件

例:

POST /collections/{collection_name}/points/scroll

{
    "filter": {
        "must": [
            { "key": "city", "match": { "value": "London" } },
            { "key": "color", "match": { "value": "red" } }
        ]
    }
  ...
}

フィルタリングされた点は次のとおりです:

[{ "id": 2, "city": "London", "color": "red" }]

mustを使用すると、mustにリストされている各条件が満たされている場合にのみ節がtrueになります。この意味で、mustAND演算子に相当します。

任意条件

shouldは、SQLのOR演算子に類似しています。

例:

POST /collections/{collection_name}/points/scroll

{
    "filter": {
        "should": [
            { "key": "city", "match": { "value": "London" } },
            { "key": "color", "match": { "value": "red" } }
        ]
    }
  ...
}

フィルタリングされた点は次のとおりです:

[
  { "id": 1, "city": "London", "color": "green" },
  { "id": 2, "city": "London", "color": "red" },
  { "id": 3, "city": "London", "color": "blue" },
  { "id": 4, "city": "Berlin", "color": "red" }
]

shouldを使用すると、shouldにリストされている条件の少なくとも1つが満たされている限り節がtrueになります。この意味で、shouldOR演算子に相当します。

must_not

例:

POST /collections/{collection_name}/points/scroll

{
    "filter": {
        "must_not": [
            { "key": "city", "match": { "value": "London" } },
            { "key": "color", "match": { "value": "red" } }
        ]
    }
  ...
}

フィルタリングされた点は次のとおりです:

[
  { "id": 5, "city": "Moscow", "color": "green" },
  { "id": 6, "city": "Moscow", "color": "blue" }
]

must_notを使用すると、must_notにリストされている条件のいずれも満たされていない場合にのみ、サブ節がtrueになります。この意味で、must_notは表現(NOT A) AND (NOT B) AND (NOT C)に相当します。

条件の組み合わせ

複数の条件を同時に使用することが可能です:

POST /collections/{collection_name}/points/scroll

{
    "filter": {
        "must": [
            { "key": "city", "match": { "value": "ロンドン" } }
        ],
        "must_not": [
            { "key": "color", "match": { "value" : "赤" } }
        ]
    }
  ...
}

フィルターされたポイントは以下の通りです:

[
  { "id": 1, "city": "ロンドン", "color": "緑" },
  { "id": 3, "city": "ロンドン", "color": "青" }
]

この場合、条件は AND を使用して組み合わせられています。

さらに、条件は再帰的にネストされることがあります。たとえば:

POST /collections/{collection_name}/points/scroll

{
    "filter": {
        "must_not": [
            {
                "must": [
                    { "key": "city", "match": { "value": "ロンドン" } },
                    { "key": "color", "match": { "value": "赤" } }
                ]
            }
        ]
    }
  ...
}

フィルターされたポイントは以下の通りです:

[
  { "id": 1, "city": "ロンドン", "color": "緑" },
  { "id": 3, "city": "ロンドン", "color": "青" },
  { "id": 4, "city": "ベルリン", "color": "赤" },
  { "id": 5, "city": "モスクワ", "color": "緑" },
  { "id": 6, "city": "モスクワ", "color": "青" }
]

条件フィルタリング

ペイロードには、異なる種類の値に対応する異なるタイプのクエリが適用されます。既存の条件のバリエーションとそれらが適用されるデータ型を見てみましょう。

マッチング

{
  "key": "color",
  "match": {
    "value": "赤"
  }
}

他のタイプに対しても、マッチング条件はまったく同じように見えますが、使用されるタイプが異なります:

{
  "key": "count",
  "match": {
    "value": 0
  }
}

最も簡単な条件は、保存された値が与えられた値と等しいかどうかをチェックするものです。複数の値が保存されている場合、少なくとも1つの値が条件を満たす必要があります。これはキーワード、整数、およびブール型のペイロードに適用することができます。

任意のマッチ

v1.1.0 以降

複数の値のいずれかであるかをチェックしたい場合は、任意のマッチ条件を使用できます。任意のマッチは与えられた値を論理 OR 操作として扱います。これは IN 演算子としても記述することができます。

これはキーワードと整数のペイロードに適用することができます。

例:

{
  "key": "color",
  "match": {
    "any": ["黒", "黄"]
  }
}

この例では、保存された値が または である場合、条件が満たされます。

保存された値が配列である場合、与えられた値のいずれかに一致する値が少なくとも1つ含まれている必要があります。例えば、保存された値が ["黒", "緑"] の場合、条件が満たされます。なぜならば "黒"["黒", "黄"] の中に含まれているからです。

除外マッチ

v1.2.0 以降

複数の値のいずれでもないかをチェックしたい場合は、除外マッチ条件を使用できます。除外マッチは与えられた値を論理 NOR 操作として扱います。これは NOT IN 演算子としても記述することができます。

これはキーワードと整数のペイロードに適用することができます。

例:

{
  "key": "color",
  "match": {
    "except": ["黒", "黄"]
  }
}

この例では、保存された値が でも でもない場合、条件が満たされます。

保存された値が配列である場合、与えられた値のいずれとも一致しない値が少なくとも1つ含まれている必要があります。例えば、保存された値が ["黒", "緑"] の場合、条件が満たされます。なぜならば "緑""黒" または "黄" に一致しないからです。

ネストされたキー

v1.1.0以降で利用可能

ペイロードが任意のJSONオブジェクトであるため、ネストされたフィールドをフィルタリングする必要があります。

便宜上、Jq プロジェクトに類似した構文を使用しています。

以下のペイロードを持つポイントのセットがあるとします。

[
  {
    "id": 1,
    "country": {
      "name": "Germany",
      "cities": [
        {
          "name": "Berlin",
          "population": 3.7,
          "sightseeing": ["Brandenburg Gate", "Reichstag"]
        },
        {
          "name": "Munich",
          "population": 1.5,
          "sightseeing": ["Marienplatz", "Olympiapark"]
        }
      ]
    }
  },
  {
    "id": 2,
    "country": {
      "name": "Japan",
      "cities": [
        {
          "name": "Tokyo",
          "population": 9.3,
          "sightseeing": ["Tokyo Tower", "Tokyo Skytree"]
        },
        {
          "name": "Osaka",
          "population": 2.7,
          "sightseeing": ["Osaka Castle", "Universal Studios Japan"]
        }
      ]
    }
  }
]

ネストされたフィールドを検索するためにドット表記を使用できます。

POST /collections/{collection_name}/points/scroll

{
    "filter": {
        "should": [
            {
                "key": "country.name",
                "match": {
                    "value": "Germany"
                }
            }
        ]
    }
}

また、[ ]構文を使用して配列を検索し、内部の値をプロジェクト化することもできます。

POST /collections/{collection_name}/points/scroll

{
    "filter": {
        "should": [
            {
                "key": "country.cities[].population",
                "range": {
                    "gte": 9.0,
                }
            }
        ]
    }
}

このクエリでは、人口が9.0を超える都市を持つ国のみを出力します。そのため、id 2のポイントのみが出力されます。

ネストされたフィールドは配列であることもあります。

POST /collections/{collection_name}/points/scroll

{
    "filter": {
        "should": [
            {
                "key": "country.cities[].sightseeing",
                "match": {
                    "value": "Osaka Castle"
                }
            }
        ]
    }
}

このクエリでは、"Osaka Castle"を観光スポットとして持つ都市を持つ国のみを出力します。そのため、id 2のポイントのみが出力されます。

ネストされたオブジェクトのフィルタリング

バージョン1.2.0から利用可能

デフォルトでは、条件はポイントのペイロード全体を考慮します。

以下は、ペイロード内の2つのポイントが与えられた場合の例です。

[
  {
    "id": 1,
    "dinosaur": "t-rex",
    "diet": [
      { "food": "leaves", "likes": false},
      { "food": "meat", "likes": true}
    ]
  },
  {
    "id": 2,
    "dinosaur": "diplodocus",
    "diet": [
      { "food": "leaves", "likes": true},
      { "food": "meat", "likes": false}
    ]
  }
]

次のクエリは、これらの2つのポイントにマッチします。

POST /collections/{collection_name}/points/scroll

{
    "filter": {
        "must": [
            {
                "key": "diet[].food",
                  "match": {
                    "value": "meat"
                }
            },
            {
                "key": "diet[].likes",
                  "match": {
                    "value": true
                }
            }
        ]
    }
}

上記の2つのポイントがマッチする理由は、それらが以下の2つの条件を満たすためです。

  • "t-rex" が diet[1].food で食べ物が肉であり、 diet[1].likes で好きなものが真であることを満たす
  • "diplodocus" が diet[1].food で食べ物が肉であり、 diet[0].likes で好きなものが真であることを満たす

例えば、この例でidが1のポイントに条件に合致するポイントのみを取得するには、ネストされたオブジェクトフィルターを使用する必要があります。

ネストされたオブジェクトフィルターを使用すると、オブジェクト配列を独立してクエリできます。

これは、ペイロードキーの興味のあるオブジェクト配列と適用するフィルターで構成される nested 条件タイプを使用して達成できます。

キーはオブジェクト配列を指し、必要に応じて角かっこ表記("data"または"data[]")を使用できます。

POST /collections/{collection_name}/points/scroll

{
    "filter": {
        "must": [
            "nested": {
                {
                    "key": "diet",
                    "filter": {
                        "must": [
                            {
                                "key": "food",
                                "match": {
                                    "value": "meat"
                                }
                            },
                            {
                                "key": "likes",
                                "match": {
                                    "value": true
                                }
                            }
                        ]
                    }
                }
            }
        ]
    }
}

マッチングロジックは、ペイロード内の配列要素レベルで適用されるように修正されます。

ネストされたフィルターは、配列の単一の要素にネストされたフィルターを適用する場合と同じように機能します。配列の少なくとも1つの要素がネストされたフィルターにマッチする限り、親ドキュメントは条件に一致すると見なされます。

制限

ネストされたオブジェクトフィルターは has_id 条件をサポートしていません。使用する必要がある場合は、隣接する must 節に配置してください。

POST /collections/{collection_name}/points/scroll

{
    "filter": {
        "must": [
            "nested": {
                {
                    "key": "diet",
                    "filter": {
                        "must": [
                            {
                                "key": "food",
                                "match": {
                                    "value": "meat"
                                }
                            },
                            {
                                "key": "likes",
                                "match": {
                                    "value": true
                                }
                            }
                        ]
                    }
                }
            },
            { "has_id": [1] }
        ]
    }
}

正確なテキストのマッチング

バージョン0.10.0から利用可能

match 条件の特殊なケースは、text マッチング条件です。これにより、テキストフィールド内の特定の部分文字列、トークン、またはフレーズを検索できます。

この条件を満たす正確なテキストは、フルテキストインデックスの構成によって異なります。構成はインデックスが作成されるときに定義され、フルテキストインデックス内で記述されます。

フィールドにフルテキストインデックスがない場合、この条件は正確な部分文字列のマッチングに基づいて動作します。

{
  "key": "description",
  "match": {
    "text": "good and cheap"
  }
}

クエリに複数の単語がある場合、この条件はすべての単語がテキストに現れる場合にのみ満たされます。

範囲

{
  "key": "price",
  "range": {
    "gt": null,
    "gte": 100.0,
    "lt": null,
    "lte": 450.0
  }
}

range 条件は、格納されたペイロードの可能な値の範囲を設定します。複数の値が格納されている場合、少なくとも1つの値が条件に一致する必要があります。

利用可能な比較演算には次が含まれます:

  • gt - より大きい
  • gte - 以上
  • lt - 未満
  • lte - 以下

この条件は浮動小数点数と整数のペイロードに適用できます。

地理的境界ボックス

{
  "key": "location",
  "geo_bounding_box": {
    "bottom_right": {
      "lat": 52.495862,
      "lon": 13.455868
    },
    "top_left": {
      "lat": 52.520711,
      "lon": 13.403683
    }
  }
}

これは、locationbottom_right が右下の座標、top_left が左上の座標の長方形内に一致させます。

地理的半径

{
  "key": "location",
  "geo_radius": {
    "center": {
      "lat": 52.520711,
      "lon": 13.403683
    },
    "radius": 1000.0
  }
}

これは、location を中心を center、半径を radius メートルの円内に一致させます。

複数の値が格納されている場合、少なくとも1つの値が条件に一致する必要があります。これらの条件は地理データ形式に一致するペイロードにのみ適用できます。

値の数

直接値の比較に加えて、フィルタリングは値の数にも基づくことができます。

例えば、次のデータが与えられると:

[
  { "id": 1, "name": "商品A", "comments": ["とても良い!", "優秀"] },
  { "id": 2, "name": "商品B", "comments": ["普通", "もう少し期待していた", "良い"] }
]

2つ以上のコメントを持つ商品のみを検索できます:

{
  "key": "comments",
  "values_count": {
    "gt": 2
  }
}

その結果は次のとおりです:

[{ "id": 2, "name": "商品B", "comments": ["普通", "もう少し期待していた", "良い"] }]

値が配列でない場合、値の数は1と見なされます。

空である

時には、特定の値が欠落しているレコードをフィルタリングすることが役立ちます。IsEmpty 条件はこのような場合に役立ちます:

{
  "is_empty": {
    "key": "reports"
  }
}

この条件は、reports フィールドが存在しないか、null または [] の値を持つレコードに一致します。

IsEmpty は、論理的否定 must_not と組み合わせて使用する際に非常に役立ちます。この場合、すべての空でない値を選択します。

NULL である

match 条件では NULL 値をテストすることができません。IsNull 条件を使用する必要があります:

{
    "is_null": {
        "key": "reports"
    }
}

この条件は、reports フィールドが存在し、値が NULL のレコードに一致します。

ID を持つ

この種のクエリはペイロードとは関係ありませんが、特定の状況では非常に役立ちます。例えば、ユーザーが特定の検索結果を無関係としてタグ付けしたい場合や、特定のポイント間でのみ検索したい場合があります。

POST /collections/{collection_name}/points/scroll

{
    "filter": {
        "must": [
            { "has_id": [1,3,5,7,9,11] }
        ]
    }
  ...
}

フィルタリングされたポイントは次のとおりです:

[
  { "id": 1, "city": "ロンドン", "color": "緑" },
  { "id": 3, "city": "ロンドン", "color": "青" },
  { "id": 5, "city": "モスクワ", "color": "緑" }
]