Descripción del funcionamiento de los filtros de colección de OData en la Búsqueda de Azure AI
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 la Búsqueda de Azure AI ejecuta estos filtros.
Al compilar un filtro en campos de colección en la Búsqueda de Azure AI, puede utilizar los operadoresany
all
y 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 operadores any
y all
son análogos a un bucle for
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:
- Solo se admiten determinados operadores para determinados tipos de datos. Por ejemplo, no tiene sentido comparar los valores booleanos
true
yfalse
conlt
,gt
y así sucesivamente. - Azure AI Search no admite la búsqueda correlacionada en campos del tipo
Collection(Edm.ComplexType)
. - 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.
Diferencias entre búsqueda correlacionada y no correlacionada
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 sencillas, como Collection(Edm.Int32)
, Collection(Edm.GeographyPoint)
, y etc. Esto se debe a que la Búsqueda de Azure AI 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 negargeo.distance
se debe comparar conlt
ole
- las expresiones se deben combinar con
or
, no conand
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 biengeo.distance
comparado conlt
ole
(imagine que la "proximidad" es similar a la igualdad cuando se comprueba la distancia). - Dentro de
any
,or
siempre se permite. Solo se puede usarand
para los tipos de datos que pueden expresar comprobaciones de intervalo, y solo si se usan operadores OR de AND (DNF). - Dentro de
all
, se invierten las reglas. Solo se permiten comprobaciones negativas, puede usarand
siempre y solo puede usaror
para las comprobaciones de intervalo expresadas como AND de ENTIDADES organizativas (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
- Solución de problemas de los filtros de colección de OData en Azure AI Search
- Filtros de Azure AI Search
- Información general sobre el lenguaje de las expresiones de OData para Azure AI Search
- Referencia de la sintaxis de las expresiones de OData para Azure AI Search
- Buscar documentos (API REST de Azure AI Search)