Condividi tramite


Modellare tipi di dati complessi in Azure AI Search

I set di dati esterni usati per popolare un indice di Azure AI Search possono presentarsi in diverse forme. In alcuni casi. includono sottostrutture gerarchiche o annidate. Alcuni esempi possono includere più indirizzi per un singolo cliente, più colori e dimensioni per un singolo SKU, più autori per un singolo libro e così via. Nel gergo di modellazione, queste strutture possono essere definite tipi di dati complessi, composti, compositi o aggregati. Il termine usato da Azure AI Search per questo concetto è tipo complesso. In Azure AI Search, i tipi complessi vengono modellati usando campi complessi. Un campo complesso è un campo contenente elementi figlio (campi secondari) che possono essere di qualsiasi tipo di dati, inclusi altri tipi complessi. Questa operazione funziona in modo analogo ai tipi di dati strutturati in un linguaggio di programmazione.

I campi complessi rappresentano un singolo oggetto nel documento o una matrice di oggetti, a seconda del tipo di dati. I campi di tipo Edm.ComplexType rappresentano singoli oggetti, mentre i campi di tipo Collection(Edm.ComplexType) rappresentano matrici di oggetti.

Azure AI Search supporta in modo nativo tipi e raccolte complessi. Questi tipi consentono di modellare quasi tutte le strutture JSON in un indice di Azure AI Search. Nelle versioni precedenti delle API di Azure AI Search, era possibile importare solo set di righe. Nella versione più recente, l'indice può ora corrispondere più precisamente ai dati di origine. In altre parole, se i dati di origine hanno tipi complessi, anche l'indice può avere tipi complessi.

Per iniziare, è consigliabile usare il set di dati Hotels, che può essere caricato nella procedura guidata Importa dati nel portale di Azure. La procedura guidata rileva tipi complessi nell'origine e suggerisce uno schema di indice basato sulle strutture rilevate.

Nota

Il supporto per i tipi complessi è diventato disponibile a livello generale a partire da api-version=2019-05-06.

Se la soluzione di ricerca è basata su soluzioni alternative precedenti di set di dati bidimensionati in una raccolta, è necessario modificare l'indice in modo da includere tipi complessi supportati nella versione più recente dell'API. Per altre informazioni sull'aggiornamento delle versioni dell'API, vedere Eseguire l'aggiornamento alla versione più recente dell'API REST o eseguire l'aggiornamento alla versione più recente di .NET SDK.

Esempio di struttura complessa

Il documento JSON seguente è costituito da campi semplici e campi complessi. I campi complessi, ad esempio Address e Rooms, dispongono di sottocampi. Address ha un singolo set di valori per tali sottocampi, poiché si tratta di un singolo oggetto nel documento. Al contrario, Rooms dispone di più set di valori per i relativi sottocampi, uno per ogni oggetto dell'insieme.

{
  "HotelId": "1",
  "HotelName": "Secret Point Motel",
  "Description": "Ideally located on the main commercial artery of the city in the heart of New York.",
  "Tags": ["Free wifi", "on-site parking", "indoor pool", "continental breakfast"],
  "Address": {
    "StreetAddress": "677 5th Ave",
    "City": "New York",
    "StateProvince": "NY"
  },
  "Rooms": [
    {
      "Description": "Budget Room, 1 Queen Bed (Cityside)",
      "RoomNumber": 1105,
      "BaseRate": 96.99,
    },
    {
      "Description": "Deluxe Room, 2 Double Beds (City View)",
      "Type": "Deluxe Room",
      "BaseRate": 150.99,
    }
    . . .
  ]
}

Indicizzazione di tipi complessi

Durante l'indicizzazione, è possibile avere un massimo di 3000 elementi in tutte le raccolte complesse all'interno di un singolo documento. Un elemento di una raccolta complessa è un membro di tale raccolta; nel caso di Rooms (l'unica raccolta complessa nell'esempio Hotel), ogni stanza consiste in un elemento. Nell'esempio precedente, se il "Secret Point Motel" aveva 500 camere, il documento dell'hotel avrebbe 500 elementi stanza. Per raccolte complesse annidate, viene conteggiato anche ogni elemento annidato, oltre all'elemento esterno (padre).

Questo limite si applica solo a raccolte complesse e non a tipi complessi (ad esempio Address) o raccolte di stringhe (ad esempio Tag).

Creare campi complessi

Come per qualsiasi definizione di indice, è possibile usare il portale, l’API RESTo .NET SDK per creare uno schema che includa tipi complessi.

Altri SDK di Azure forniscono campioni in Python, Java e JavaScript.

  1. Accedere al portale di Azure.

  2. Nella pagina Panoramica del servizio di ricerca, selezionare la scheda Indici.

  3. Aprire un indice esistente o creare un nuovo indice.

  4. Selezionare la scheda Campi e quindi selezionare Aggiungi campo. Verrà aggiunto un campo vuoto. Se si usa una raccolta di campi esistenti, scorrere verso il basso per configurare il campo.

  5. Assegnare un nome al campo e impostarne il tipo su Edm.ComplexType o Collection(Edm.ComplexType).

  6. Selezionare i puntini di sospensione all'estrema destra, quindi selezionare Aggiungi campo o Aggiungi sottocampo e quindi assegnare attributi.

Aggiornare campi complessi

Tutte le regole di reindicizzazione generalmente applicabili a campi si applicano allo stesso modo a campi complessi. Reiterando alcune delle regole principali, l'aggiunta di un campo a un tipo complesso non richiede una ricompilazione dell'indice, ma la maggior parte delle modifiche la richiede.

Aggiornamenti strutturali alla definizione

È possibile aggiungere nuovi campi secondari a un campo complesso in qualsiasi momento senza la necessità di una ricompilazione dell'indice. Ad esempio, l'aggiunta di "ZipCode" a Address o "Servizi" a Rooms è consentita, proprio come l'aggiunta di un campo di primo livello a un indice. I documenti esistenti hanno un valore Null per nuovi campi fino a quando questi campi non vengono popolati esplicitamente aggiornando i dati.

Si noti che all'interno di un tipo complesso, ogni sottocampo ha un tipo e può avere attributi, proprio come i campi di primo livello

Aggiornamenti dei dati

L'aggiornamento di documenti esistenti in un indice con l'azione upload funziona allo stesso modo per i campi complessi e quelli semplici: tutti i campi vengono sostituiti. Tuttavia, merge (o mergeOrUpload quando applicato a un documento esistente) non funziona allo stesso modo in tutti i campi. In particolare, merge non supporta l'unione di elementi all'interno di una raccolta. Questa limitazione è presente per raccolte di tipi primitivi e raccolte complesse. Per aggiornare una raccolta, è necessario recuperarne il valore completo, apportare modifiche e quindi includere la nuova raccolta nella richiesta DELL'API Index.

Cerca campi complessi

Le espressioni di ricerca in formato libero funzionano come previsto con tipi complessi. Se un campo o un sottocampo ricercabile in qualsiasi parte di un documento è corrispondente, il documento stesso è corrispondente.

Le query diventano più complesse e sfaccettate quando si hanno più termini e operatori e alcuni termini hanno nomi di campo specificati, come possibile con la sintassi Lucene. Ad esempio, questa query tenta di trovare una corrispondenza tra due termini, "Portland" e "OR", rispetto a due sottocampi del campo Indirizzo:

search=Address/City:Portland AND Address/State:OR

Le query di questo tipo sono non correlate per la ricerca a tutto testo, a differenza dei filtri. Nei filtri, le query sui sottocampi di una raccolta complessa vengono correlate usando variabili di intervallo in any o all. La query Lucene precedente restituisce documenti contenenti sia "Portland, Maine" che "Portland, Oregon", insieme ad altre città in Oregon. Ciò si verifica perché ogni clausola si applica a tutti i valori del relativo campo nell'intero documento, quindi non esiste un concetto di "sottodocumento corrente". Per altre informazioni su questo argomento, vedere Informazioni sui filtri di raccolta OData in Azure AI Search.

Selezionare campi complessi

Il parametro $select viene usato per scegliere quali campi vengono restituiti nei risultati della ricerca. Per utilizzare questo parametro per selezionare campi secondari specifici di un campo complesso, includere il campo padre e il sottocampo separati da una barra (/).

$select=HotelName, Address/City, Rooms/BaseRate

I campi devono essere contrassegnati come Recuperabili nell'indice se si desidera che siano presenti tra i risultati della ricerca. In un'istruzione $select è possibile usare solo i campi contrassegnati come Recuperabile.

Filtrare, eseguire facet e ordinare campi complessi

La stessa sintassi di percorso OData usata per i filtri e le ricerche a campi può essere usata anche per eseguire facet, ordinare e selezionare campi in una richiesta di ricerca. Per i tipi complessi, si applicano regole che determinano quali sottocampi possono essere contrassegnati come ordinabili o facetable. Per altre informazioni su queste regole, vedere le Informazioni di riferimento sull'API Crea Indice.

Sottocampi in facet

Qualsiasi sottocampo può essere contrassegnato come facetable a meno che non sia di tipo Edm.GeographyPoint o Collection(Edm.GeographyPoint).

I conteggi dei documenti restituiti nei risultati del facet vengono calcolati per il documento padre (un hotel), non per i documenti secondari in una raccolta complessa (sale). Si supponga, ad esempio, che un hotel abbia 20 camere di tipo "suite". Dato questo parametro facet facet=Rooms/Type, il numero di facet sarà 1 per l'hotel, non 20 per le camere.

Ordinamento di campi complessi

Le operazioni di ordinamento si applicano ai documenti (Hotel) e non ai documenti secondari (Stanze). Quando si dispone di una raccolta di tipi complessa, ad esempio Camere, è importante tenere presente che non è possibile effettuare ordinamenti di alcun tipo su Stanze. Infatti, non è possibile ordinare alcuna raccolta.

Le operazioni di ordinamento funzionano quando i campi hanno un singolo valore per ogni documento, indipendentemente dal fatto che il campo sia un campo semplice o un sottocampo in un tipo complesso. Ad esempio, Address/City può essere ordinato perché esiste un solo indirizzo per hotel, quindi $orderby=Address/City ordina gli hotel in base alla città.

Applicare filtri a campi complessi

È possibile fare riferimento a sottocampi di un campo complesso in un'espressione di filtro. È sufficiente usare la stessa sintassi del percorso OData usata per eseguire il faceting, ordinare e selezionare campi. Ad esempio, il filtro seguente restituisce tutti gli hotel in Canada:

$filter=Address/Country eq 'Canada'

Per filtrare in base a un campo di raccolta complesso, è possibile usare un'espressione lambda con gli operatori any eall. In tal caso, la variabile di intervallo dell'espressione lambda è un oggetto con campi secondari. È possibile fare riferimento a tali campi secondari con la sintassi del percorso OData standard. Ad esempio, il filtro seguente restituisce tutti gli hotel con almeno una camera deluxe e tutte le camere per non fumatori:

$filter=Rooms/any(room: room/Type eq 'Deluxe Room') and Rooms/all(room: not room/SmokingAllowed)

Come per i campi semplici di primo livello, i sottocampi semplici di campi complessi possono essere inclusi in filtri solo se l'attributo filtrabile è impostato su true nella definizione dell'indice. Per altre informazioni, vedere le informazioni di riferimento sull’API Crea Indice.

Ricerca di Azure presenta una limitazione: gli oggetti complessi nelle raccolte in un singolo documento non possono superare un massimo di 3000.

Gli utenti riscontrano l'errore seguente durante l'indicizzazione quando le raccolte complesse superano il limite massimo di 3000.

“Una raccolta nel documento supera il limite massimo di tutti gli insiemi complessi. Il documento con chiave “1052” ha “4303” oggetti nelle raccolte (matrici JSON). Le raccolte nell’intero documento possono contenere complessivamente massimo “3000” oggetti. Rimuovere oggetti dalle raccolte e riprovare a indicizzare il documento."

In alcuni casi d'uso, potrebbe essere necessario aggiungere più di 3000 elementi a una raccolta. In questi casi d'uso, è possibile inviare tramite pipe (|) o usare qualsiasi tipo di delimitatore per delimitare i valori, concatenarli e archiviarli come stringa delimitata. Non esiste alcuna limitazione al numero di stringhe archiviate in una matrice in Ricerca di Azure. L'archiviazione di questi valori complessi come stringhe evita limitazioni. Il cliente deve verificare se questa soluzione alternativa soddisfi i requisiti dello specifico scenario.

Ad esempio, non sarebbe possibile usare tipi complessi se la matrice "searchScope" seguente avesse più di 3000 elementi.


"searchScope": [
  {
     "countryCode": "FRA",
     "productCode": 1234,
     "categoryCode": "C100" 
  },
  {
     "countryCode": "USA",
     "productCode": 1235,
     "categoryCode": "C200" 
  }
]

L'archiviazione di questi valori complessi come stringhe con un delimitatore evita limitazioni

"searchScope": [
        "|FRA|1234|C100|",
        "|FRA|*|*|",
        "|*|1234|*|",
        "|*|*|C100|",
        "|FRA|*|C100|",
        "|*|1234|C100|"
]

Anziché archiviarli con caratteri jolly, è anche possibile usare un analizzatore personalizzato che suddivida la parola in | per ridurre le dimensioni di archiviazione.

Il motivo per cui i valori sono stati archiviati con caratteri jolly invece che come indicato di seguito

|FRA|1234|C100|

è quello di soddisfare scenari di ricerca in cui il cliente desideri cercare articoli con paese Francia, indipendentemente da prodotti e categorie. Analogamente, il cliente potrebbe voler verificare se l'articolo ha prodotto 1234, indipendentemente dal paese o dalla categoria.

Se fosse stata archiviata una sola voce

|FRA|1234|C100|

senza caratteri jolly, se l'utente desiderasse applicare un filtro per solo in Francia, non sarebbe possibile convertire il suo input in modo che corrisponda alla matrice "searchScope", perché non si è a conoscenza di quale combinazione di Francia sia presente nella matrice "searchScope"

Se l'utente vuole filtrare solo in base al paese, come ad esempio la Francia. L'input dell'utente verrà acquisito e costruito come stringa come indicato di seguito:

|FRA|*|*|

È quindi possibile usarlo per filtrare in Ricerca di Azure durante la ricerca in una matrice di valori di elemento

foreach (var filterItem in filterCombinations)
        {
            var formattedCondition = $"searchScope/any(s: s eq '{filterItem}')";
            combFilter.Append(combFilter.Length > 0 ? " or (" + formattedCondition + ")" : "(" + formattedCondition + ")");
        }

Analogamente, se l'utente cerca Francia e il codice prodotto 1234, l'input dell'utente verrà acquisito, costruito come stringa delimitata come di seguito e confrontato con la matrice di ricerca.

|FRA|1234|*|

Se l'utente cerca il codice prodotto 1234, l'input dell'utente verrà creato come stringa delimitata come riportato di seguito e verrà confrontato con la matrice di ricerca.

|*|1234|*|

Se l'utente cerca il codice di categoria C100, l'input dell'utente verrà creato come stringa delimitata come riportato di seguito e verrà confrontato con la matrice di ricerca.

|*|*|C100|

Se l'utente cerca Francia, il codice prodotto 1234 e il codice di categoria C100, l'input dell'utente verrà acquisito, costruito come stringa delimitata come riportato di seguito e confrontato con la matrice di ricerca.

|FRA|1234|C100|

Se un utente tenta di cercare Paesi non presenti nell'elenco, non ci saranno corrispondenze con la matrice delimitata "searchScope" archiviata nell'indice di ricerca e non verranno restituiti risultati. Ad esempio, un utente cerca Canada e il codice prodotto 1234. La ricerca dell'utente verrà convertita in

|CAN|1234|*|

Questa operazione non corrisponderà ad alcuna delle voci nella matrice delimitata nell'indice di ricerca.

Solo la scelta di progettazione precedente richiede questa voce di caratteri jolly; se fosse stato salvato come oggetto complesso, si sarebbe potuta semplicemente eseguire una ricerca esplicita, come illustrato di seguito.

           var countryFilter = $"searchScope/any(ss: search.in(countryCode ,'FRA'))";
            var catgFilter = $"searchScope/any(ss: search.in(categoryCode ,'C100'))";
            var combinedCountryCategoryFilter = "(" + countryFilter + " and " + catgFilter + ")";

È quindi possibile soddisfare requisiti in cui si necessiti di cercare una combinazione di valori archiviandola come stringa delimitata invece di una raccolta complessa, se le raccolte complesse superano il limite di Ricerca di Azure. Si tratta di una delle soluzioni alternative e sta al cliente verificare se soddisfi i requisiti dello scenario specifico.

Passaggi successivi

Provare il set di dati Hotel nella procedura guidata Importare dati. Per accedere ai dati, sono necessarie le informazioni di connessione di Azure Cosmos DB fornite nel file leggimi.

Con queste informazioni, il primo passaggio della procedura guidata consiste nel creare una nuova origine dati di Azure Cosmos DB. Più avanti nella procedura guidata, alla pagina dell'indice di destinazione, verrà visualizzato un indice con tipi complessi. Creare e caricare questo indice e quindi eseguire query per comprendere la nuova struttura.