Compartilhar via


Entender como os filtros de coleção OData funcionam na Pesquisa de IA do Azure

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

Ao criar um filtro em campos de coleção na Pesquisa de IA do Azure, você pode usar os operadores any e all junto com expressões lambda. As 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 operadores any e all são análogos a um loop for na maioria das linguagens de programação, com a variável de intervalo assumindo a função de variável de loop e a expressão lambda como o corpo do loop. A variável de intervalo assume o valor "current" da coleção durante a iteração do loop.

Pelo menos é assim que funciona conceitualmente. Na realidade, o IA do Azure Search implementa filtros de uma maneira muito diferente de como os loops for funcionam. O ideal é que essa diferença seja invisível, mas em determinadas situações ela não é. O resultado final é que há regras que precisam ser seguidas ao escrever expressões lambda.

Observação

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

Por que os filtros de coleção são limitados

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

  1. Somente determinados operadores têm suporte para determinados tipos de dados. Por exemplo, não faz sentido comparar os valores Boolianos true e false usando lt, gt e assim por diante.
  2. O IA do Azure Search não dá suporte à pesquisa correlacionada em campos do tipo Collection(Edm.ComplexType).
  3. O IA do Azure Search usa índices invertidos para executar filtros em todos os tipos de dados, incluindo coleções.

A primeira razão é apenas uma consequência de como o sistema de linguagem OData e de tipos de 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 em uma coleção de objetos complexos, os critérios são correlacionados porque se aplicam a cada objeto na coleção. Por exemplo, o filtro a seguir retorna hotéis com pelo menos um quarto de luxo com uma 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 poderá retornar Hotéis em que um quarto de luxo e um quarto diferente têm uma tarifa base menor que 100. Isso não faria sentido, pois as duas 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 usar a pesquisa de campo para emitir uma consulta Lucene completa como esta:

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

você pode ver resultados de hotéis em que um quarto é de luxo, e um quarto diferente menciona "vista da cidade" na descrição. Por exemplo, o documento abaixo com Id de 1 deve corresponder à 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" }
      ]
    }
  ]
}

O motivo é que Rooms/Type se refere a todos os termos analisados do campo Rooms/Type no documento inteiro e, da mesma forma para Rooms/Description, conforme mostrado nas tabelas abaixo.

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

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

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

Termo em Rooms/Description IDs de documento
pátio 2
cidade 1
jardim 1
large 1
motel 2
room 1, 2
padrão 1
suíte 1
view 1

Portanto, ao contrário do filtro acima, que basicamente diz "corresponder documentos onde um espaço tem Type igual a" quarto de luxo "e que o mesmo espaço tem BaseRate menos de 100", a consulta de pesquisa indica "corresponder documentos onde Rooms/Type tem o termo" de luxo "e Rooms/Description tem a frase "vista da cidade". Não há nenhum conceito de quartos individuais cujos campos podem ser correlacionados no último caso.

Índices e coleções invertidos

Você deve ter notado que há muito menos restrições em expressões lambda sobre coleções complexas do que há para coleções simples como Collection(Edm.Int32), Collection(Edm.GeographyPoint) e assim por diante. Isso ocorre porque a Pesquisa de IA do Azure 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 nesse índice podem ter a seguinte 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 campo seasons são armazenados em uma estrutura chamada de índice invertido, que se parece com isto:

Termo IDs de documento
spring 1, 2
verão 1
outono 1, 2
inverno 2, 3

Essa estrutura de dados foi projetada para responder a uma pergunta com grande velocidade: em quais documentos determinado termo aparece? Responder a essa pergunta funciona mais como uma verificação de igualdade simples do que um loop em uma coleção. Na verdade, é por isso que, para coleções de cadeias de caracteres, o IA do Azure 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. Isso funciona graças à Algebra e à propriedade distributiva de 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 subexpressãos any pode ser executada com eficiência usando o índice invertido. Além disso, graças à lei de negação de 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 essa razão que é possível usar all com ne e and.

Observação

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 deve ser comparado usando lt ou le
  • as expressões devem ser combinadas com or, não com and

As regras de inverso se aplicam para all.

Uma variedade maior de expressões é permitida ao filtrar em coleções de tipos de dados que dão suporte aos operadores lt, gt, le e ge, como Collection(Edm.Int32), por exemplo. Especificamente, é possível 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, posteriormente, são combinadas usando or. Essa estrutura de expressões boolianas é chamada de DNF (Forma Disjuntiva Normal), também conhecida como "ORs de ANDs". Por outro lado, as expressões lambda para all, para esses tipos de dados devem estar na CNF (Norma Conjuntiva Normal), também conhecida como "ANDs de ORs". O IA do Azure Search permite comparações de intervalo, pois pode executá-las usando índices invertidos com eficiência, assim como pode fazer a pesquisa de termos rápidos para cadeias de caracteres.

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

  • Dentro de any, as verificações positivas sempre são permitidas, como igualdade, comparações de intervalo, geo.intersects ou geo.distance comparadas com lt ou le (considere a "proximidade" como igualdade quando se trata de verificar a distância).
  • Dentro de any, or é sempre permitido. É possível usar and apenas para tipos de dados que podem expressar verificações de intervalo e apenas se usar ORs de ANDs (DNF).
  • Dentro de all, as regras são invertidas. Somente verificações negativas são permitidas, você pode usar and sempre e pode usar or apenas para verificações de intervalo expressas como ANDs de ORs (CNF).

Na prática, esses são os tipos de filtros que provavelmente serão usados de qualquer forma. No entanto, ainda é útil compreender 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óximas etapas