Compreender como os filtros de coleção OData funcionam no Azure AI Search

Este artigo fornece informações básicas para desenvolvedores que estão escrevendo filtros avançados com expressões lambda complexas. O artigo explica por que as regras para filtros de coleção existem explorando como o Azure AI Search executa esses filtros.

Ao criar um filtro em campos de coleção no Azure AI Search, você pode usar os any operadores e all junto com expressões lambda. Expressões lambda são expressões booleanas que se referem a uma variável de intervalo. Em filtros que usam uma expressão lambda, os any operadores e all são análogos a um for loop na maioria das linguagens de programação, com a variável range assumindo o papel de variável loop e a expressão lambda como o corpo do loop. A variável range assume o valor "atual" da coleção durante a iteração do loop.

Pelo menos é assim que funciona conceitualmente. Na realidade, o Azure AI Search implementa filtros de uma maneira muito diferente de como for os loops funcionam. Idealmente, essa diferença seria invisível para você, mas em certas situações não é. O resultado final é que há regras que você tem que seguir ao escrever expressões lambda.

Nota

Para obter informações sobre quais são as regras para filtros de coleção, incluindo exemplos, consulte Solucionando problemas de filtros de coleção OData no Azure AI Search.

Por que os filtros de coleta são limitados

Há três razões subjacentes pelas quais os recursos de filtro não são totalmente suportados para todos os tipos de coleções:

  1. Apenas determinados operadores são suportados para determinados tipos de dados. Por exemplo, não faz sentido comparar os valores true booleanos e false usar lt, gt, e assim por diante.
  2. O Azure AI Search não suporta pesquisa correlacionada em campos do tipo Collection(Edm.ComplexType).
  3. O Azure AI Search usa índices invertidos para executar filtros sobre todos os tipos de dados, incluindo coleções.

A primeira razão é apenas uma consequência de como a linguagem OData e o sistema de tipo EDM são definidos. Os dois últimos são explicados com mais detalhes no restante deste artigo.

Quando você aplica vários critérios de filtro sobre uma coleção de objetos complexos, os critérios são correlacionados porque se aplicam a cada objeto da coleção. Por exemplo, o filtro a seguir retorna hotéis que têm pelo menos um quarto deluxe com tarifa inferior a 100:

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

Se a filtragem não estiver correlacionada, o filtro acima pode retornar hotéis em que um quarto é deluxe e um quarto diferente tem uma tarifa básica inferior a 100. Isso não faria sentido, uma vez que ambas as cláusulas da expressão lambda se aplicam à mesma variável de intervalo, ou seja room, . É por isso que esses filtros estão correlacionados.

No entanto, para pesquisa de texto completo, não há como fazer referência a uma variável de intervalo específica. Se você usar a pesquisa em campo para emitir uma consulta Lucene completa como esta:

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

Você pode recuperar hotéis onde um quarto é deluxe, e um quarto diferente menciona "vista para a cidade" na descrição. Por exemplo, o documento abaixo com Id de 1 corresponderia à 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" }
      ]
    }
  ]
}

A razão é que Rooms/Type se refere a todos os termos analisados do Rooms/Type campo em todo o documento, e da mesma forma para Rooms/Description, como mostrado nas tabelas abaixo.

Como Rooms/Type é armazenado para pesquisa de texto completo:

Termo em Rooms/Type IDs do documento
luxo 1, 2
padrão 1

Como Rooms/Description é armazenado para pesquisa de texto completo:

Termo em Rooms/Description IDs do documento
pátio 2
cidade 1
jardim 1
grande 1
Motel 2
quarto 1, 2
padrão 1
suíte 1
ver 1

Assim, ao contrário do filtro acima, que basicamente diz "combinar documentos onde um quarto tem Type igual a 'Quarto Deluxe' e esse mesmo quarto tem BaseRate menos de 100", a consulta de pesquisa diz "combinar documentos onde Rooms/Type tem o termo "deluxe" e Rooms/Description tem a frase "vista da cidade". Não há conceito de salas individuais cujos campos possam ser correlacionados neste último caso.

Índices e coleções invertidos

Você deve ter notado que há muito menos restrições em expressões lambda em coleções complexas do que em coleções simples como Collection(Edm.Int32), Collection(Edm.GeographyPoint)e assim por diante. Isso ocorre porque o Azure AI Search armazena coleções complexas como coleções reais de subdocumentos, enquanto coleções simples não são armazenadas como coleções.

Por exemplo, considere um campo de coleção de cadeia de caracteres filtrável como seasons em um índice para um varejista online. Alguns documentos carregados para este índice podem ter esta aparência:

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

Os valores do seasons campo são armazenados em uma estrutura chamada índice invertido, que se parece com isto:

Termo IDs do documento
primavera 1, 2
verão 1
queda 1, 2
inverno 2, 3

Esta estrutura de dados foi concebida para responder a uma pergunta com grande rapidez: em que documentos aparece um determinado termo? Responder a esta pergunta funciona mais como uma simples verificação de igualdade do que um loop sobre uma coleção. Na verdade, é por isso que, para coleções de cadeia de caracteres, o Azure AI Search só permite eq como um operador de comparação dentro de uma expressão lambda para any.

Em seguida, analisamos como é possível combinar várias verificações de igualdade na mesma variável de intervalo com or. Funciona graças à álgebra e à propriedade distributiva dos quantificadores. Esta expressão:

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

é equivalente a:

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

e cada uma das duas any subexpressões pode ser executada eficientemente usando o índice invertido. Além disso, graças à lei de negação dos quantificadores, esta expressão:

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

é equivalente a:

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

é por isso que é possível usar all com ne e and.

Nota

Embora os detalhes estejam além do escopo deste documento, esses mesmos princípios se estendem a testes de distância e interseção para coleções de pontos geoespaciais também. É por isso que, em any:

  • geo.intersects não pode ser negado
  • geo.distance devem ser comparados utilizando lt ou le
  • as expressões devem ser combinadas com or, não and

As regras inversas aplicam-se a all.

Uma maior variedade de expressões é permitida ao filtrar coleções de tipos de dados que suportam os ltoperadores , gt, le, e , como ge , Collection(Edm.Int32) por exemplo. Especificamente, você pode usar and bem como or em any, desde que as expressões de comparação subjacentes sejam combinadas em comparações de intervalo usando and, que são então combinadas usando or. Esta estrutura de expressões booleanas é chamada de Forma Normal Disjuntiva (DNF), também conhecida como "ORs de ANDs". Por outro lado, as expressões lambda para all esses tipos de dados devem estar na Forma Normal Conjuntiva (CNF), também conhecida como "ANDs de RUPs". O Azure AI Search permite essas comparações de intervalo porque pode executá-las usando índices invertidos de forma eficiente, assim como pode fazer pesquisas rápidas de prazo para cadeias de caracteres.

Em resumo, aqui estão as regras básicas para o que é permitido em uma expressão lambda:

  • No interior any, verificações positivas são sempre permitidas, como igualdade, comparações de intervalo, geo.intersectsou geo.distance comparadas com lt ou le (pense em "proximidade" como sendo como igualdade quando se trata de verificar distância).
  • No interior any, or é sempre permitido. Você pode usar and somente para tipos de dados que podem expressar verificações de intervalo e somente se usar ORs de ANDs (DNF).
  • No interior all, as regras invertem-se. Somente verificações negativas são permitidas, você pode usar and sempre, e você pode usar or apenas para verificações de intervalo expressas como ANDs de RUPs (CNF).

Na prática, esses são os tipos de filtros que você provavelmente usará de qualquer maneira. No entanto, ainda é útil entender os limites do que é possível.

Para obter exemplos específicos de quais tipos de filtros são permitidos e quais não são, consulte Como escrever filtros de coleção válidos.

Próximos passos