Descripción del funcionamiento de los filtros de colección de OData en Azure AI Search

En este artículo se proporciona información general para los desarrolladores que escriben filtros avanzados con expresiones lambda complejas. En el artículo se explica por qué existen reglas para los filtros de recopilación mediante la exploración de cómo Azure AI Search ejecuta estos filtros.

Al crear un filtro en campos de colección en Azure AI Search, puede usar losanyoperadores y all junto con expresiones lambda. Las expresiones lambda son expresiones booleanas que hacen referencia a una variable de rango. En los filtros que usan una expresión lambda, los any operadores y all son análogos a un for bucle en la mayoría de los lenguajes de programación, con la variable de intervalo que toma el rol de variable de bucle y la expresión lambda como cuerpo del bucle. La variable de rango toma el valor "actual" de la colección durante la iteración del bucle.

Al menos, así es cómo funciona conceptualmente. En la realidad, Azure AI Search implementa los filtros de forma muy diferente a cómo funcionan los bucles de for. Idealmente, esta diferencia tendría que ser invisible para usted, pero en ciertas situaciones no lo es. El resultado final es que hay reglas que se deben seguir al escribir las expresiones lambda.

Nota:

Para obtener información sobre qué son las reglas para los filtros de colección, con ejemplos incluidos, vea Solución de problemas de filtros de colección de OData en Azure AI Search.

Por qué los filtros de colección son limitados

Hay tres motivos subyacentes por los que las características de filtro no son totalmente compatibles con todos los tipos de colecciones:

  1. Solo se admiten determinados operadores para determinados tipos de datos. Por ejemplo, no tiene sentido comparar los valores booleanos true y false con lt, gt y así sucesivamente.
  2. Azure AI Search no admite la búsqueda correlacionada en campos del tipo Collection(Edm.ComplexType).
  3. Azure AI Search usa índices invertidos para ejecutar los filtros en todos los tipos de datos, incluidas las colecciones.

El primer motivo es simplemente una consecuencia de cómo se definen el lenguaje OData y el sistema de tipos EDM. Los dos últimos se explican con más detalle en el resto de este artículo.

Cuando se aplican varios criterios de filtro sobre una colección de objetos complejos, los criterios se correlacionan porque se aplican a cada objeto de la colección. Por ejemplo, el siguiente filtro devuelve hoteles que tienen al menos una habitación deluxe con una tarifa inferior a 100:

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

Si el filtrado fuera no correlacionado, el filtro anterior podría devolver hoteles en los que una habitación es deluxe y otra diferente tiene una tarifa base inferior a 100. Eso no tendría sentido, ya que las dos cláusulas de la expresión lambda se aplican a la misma variable de rango, es decir, room. Por este motivo estos filtros se ponen en correlación.

Pero para la búsqueda de texto completo, no hay ninguna manera para hacer referencia a una variable de rango específica. Si usa la búsqueda clasificada por campos para emitir una consulta completa de Lucene como esta:

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

es posible que obtenga hoteles en los que una habitación es de lujo y otra habitación menciona "vista a la ciudad" en la descripción. Por ejemplo, el documento siguiente con el Id de 1 coincidiría con la consulta:

{
  "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" }
      ]
    }
  ]
}

El motivo es que Rooms/Type hace referencia a todos los términos analizados del campo Rooms/Type en todo el documento y lo mismo sucede con Rooms/Description, como se muestra en las tablas siguientes.

Cómo se almacena Rooms/Type para la búsqueda de texto completo:

Término en Rooms/Type Id. de documento
deluxe 1, 2
Estándar 1

Cómo se almacena Rooms/Description para la búsqueda de texto completo:

Término en Rooms/Description Id. de documento
courtyard 2
city 1
garden 1
large 1
motel 2
room 1, 2
Estándar 1
suite 1
view 1

Por tanto, a diferencia del filtro anterior, que básicamente dice "comparar los documentos donde para una habitación Type es igual a "Deluxe Room" (habitación deluxe) y esa misma habitación tiene un valor BaseRate menor que 100", la consulta de búsqueda dice "comparar documentos donde Rooms/Type tiene el término "deluxe" y Rooms/Description tiene la frase "city view". En el último caso no hay ningún concepto de habitación individual cuyos campos se puedan poner en correlación.

Colecciones e índices invertidos

Es posible que haya observado que hay muchas menos restricciones en las expresiones lambda en colecciones complejas que para colecciones simples como Collection(Edm.Int32), Collection(Edm.GeographyPoint), etc. Esto se debe a que Azure AI Search almacena colecciones complejas como colecciones reales de subdocumentos, mientras que las colecciones simples no se almacenan como colecciones en absoluto.

Por ejemplo, considere un campo de la colección de cadena que se puede filtrar como seasons en un índice para un comerciante en línea. Algunos documentos cargados en este índice podrían tener este aspecto:

{
  "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"]
    }
  ]
}

Los valores del campo seasons se almacenan en una estructura denominada índice invertido, similar a lo siguiente:

Término Id. de documento
spring 1, 2
summer 1
fall 1, 2
winter 2, 3

Esta estructura de datos está diseñada para responder a una pregunta con gran velocidad: ¿en qué documentos se muestra un término determinado? La respuesta a esta pregunta funciona más como una sencilla comprobación de igualdad que un bucle a través de una colección. De hecho, este es el motivo por el que en el caso de las colecciones de cadenas, Azure AI Search solo permite eq como un operador de comparación dentro de una expresión lambda para any.

A continuación, veremos cómo es posible combinar varias comprobaciones de igualdad en la misma variable de intervalo con or. Funciona gracias al álgebra y a la propiedad distributiva de los cuantificadores. Esta expresión:

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

equivale a:

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

y cada una de las dos subexpresiones any se puede ejecutar de forma eficaz mediante el índice invertido. Además, gracias a la ley de negación de cuantificadores, esta expresión:

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

equivale a:

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

motivo por el que se puede usar all con ne y and.

Nota:

Aunque los detalles están fuera del ámbito de este documento, estos mismos principios también se extienden a las pruebas de distancia e intersección para colecciones de puntos geoespaciales. Por este motivo, en any:

  • geo.intersects no se puede negar
  • geo.distance se debe comparar con lt o le
  • las expresiones se deben combinar con or, no con and

Para all se aplican las reglas inversas.

Al filtrar por colecciones de tipos de datos que admiten los operadores lt, gt, le y ge (como por ejemplo Collection(Edm.Int32)) se admite una mayor variedad de expresiones. En concreto, en any se pueden usar and y or, siempre y cuando las expresiones de comparación subyacentes se combinen en comparaciones de rango mediante and, que después se vuelven a combinar con or. Esta estructura de expresiones booleanas se denomina forma normal disyuntiva (DNF), también conocida como "OR de AND". Por el contrario, las expresiones lambda para all para estos tipos de datos deben estar en forma normal conjuntiva (CNF), también conocida como "AND de OR". Azure AI Search permite estas comparaciones de rango, ya que las puede ejecutar de forma eficaz mediante índices invertidos, al igual que puede realizar búsquedas de términos rápidas para las cadenas.

En resumen, estas son las reglas generales para lo que se permite en una expresión lambda:

  • Dentro de any, las comprobaciones positivas siempre se permiten, como las comparaciones de rango de igualdad, geo.intersects, o bien geo.distance comparado con lt o le (imagine que la "proximidad" es similar a la igualdad cuando se comprueba la distancia).
  • Dentro de any, or siempre se permite. Solo se puede usar and para los tipos de datos que pueden expresar comprobaciones de intervalo, y solo si se usan operadores OR de AND (DNF).
  • Dentro allde , las reglas se invierten. Solo se permiten comprobaciones negativas , siempre se pueden usar y solo se pueden usar andor para las comprobaciones de intervalo expresadas como AND de R (CNF).

De todos modos, en la práctica estos son los tipos de filtros que es más probable que use. Pero sigue siendo útil comprender los límites de lo que es posible.

Para obtener ejemplos específicos de qué tipos de filtros se permiten y cuáles no, vea Procedimientos para escribir filtros de colección válidos.

Pasos siguientes