Complexe gegevenstypen modelleren in Azure AI Search

Externe gegevenssets die worden gebruikt om een Azure AI Search-index te vullen, kunnen in veel vormen worden geleverd. Soms bevatten ze hiërarchische of geneste substructuren. Voorbeelden zijn mogelijk meerdere adressen voor één klant, meerdere kleuren en grootten voor één SKU, meerdere auteurs van één boek, enzovoort. In modelleringstermen ziet u mogelijk dat deze structuren complexe, samengestelde, samengestelde of geaggregeerde gegevenstypen worden genoemd. De term die Azure AI Search voor dit concept gebruikt, is complex. In Azure AI Search worden complexe typen gemodelleerd met behulp van complexe velden. Een complex veld is een veld dat onderliggende elementen (subvelden) bevat die van elk gegevenstype kunnen zijn, inclusief andere complexe typen. Dit werkt op een vergelijkbare manier als gestructureerde gegevenstypen in een programmeertaal.

Complexe velden vertegenwoordigen één object in het document of een matrix met objecten, afhankelijk van het gegevenstype. Velden van het type Edm.ComplexType vertegenwoordigen afzonderlijke objecten, terwijl velden van het type Collection(Edm.ComplexType) matrices van objecten vertegenwoordigen.

Azure AI Search biedt systeemeigen ondersteuning voor complexe typen en verzamelingen. Met deze typen kunt u vrijwel elke JSON-structuur modelleren in een Azure AI Search-index. In eerdere versies van Azure AI Search-API's kunnen alleen platgemaakte rijensets worden geïmporteerd. In de nieuwste versie kan uw index nu nauwer overeenkomen met brongegevens. Met andere woorden, als uw brongegevens complexe typen hebben, kan uw index ook complexe typen hebben.

We raden u aan om aan de slag te gaan met de gegevensset Hotels, die u kunt laden in de wizard Gegevens importeren in Azure Portal. De wizard detecteert complexe typen in de bron en stelt een indexschema voor op basis van de gedetecteerde structuren.

Notitie

Ondersteuning voor complexe typen werd algemeen beschikbaar vanaf api-version=2019-05-06.

Als uw zoekoplossing is gebouwd op eerdere tijdelijke oplossingen voor platgemaakte gegevenssets in een verzameling, moet u uw index wijzigen in complexe typen zoals ondersteund in de nieuwste API-versie. Zie Upgrade uitvoeren naar de nieuwste REST API-versie of upgraden naar de nieuwste .NET SDK-versie voor meer informatie over het upgraden van API-versies.

Voorbeeld van een complexe structuur

Het volgende JSON-document bestaat uit eenvoudige velden en complexe velden. Complexe velden, zoals Address en Rooms, hebben subvelden. Address heeft één set waarden voor deze subvelden, omdat het één object in het document is. Er zijn daarentegen Rooms meerdere sets waarden voor de subvelden, één voor elk object in de verzameling.

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

Complexe typen indexeren

Tijdens het indexeren kunt u maximaal 3000 elementen voor alle complexe verzamelingen in één document hebben. Een element van een complexe verzameling is lid van die verzameling, dus in het geval van kamers (de enige complexe verzameling in het voorbeeld van het hotel), is elke kamer een element. In het bovenstaande voorbeeld, als het "Secret Point Motel" 500 kamers had, zou het hoteldocument 500 kamerelementen hebben. Voor geneste complexe verzamelingen wordt elk genest element ook geteld, naast het buitenste (bovenliggende) element.

Deze limiet geldt alleen voor complexe verzamelingen en niet voor complexe typen (zoals Adres) of tekenreeksverzamelingen (zoals Tags).

Complexe velden maken

Net als bij elke indexdefinitie kunt u de portal, REST API of .NET SDK gebruiken om een schema te maken dat complexe typen bevat.

Andere Azure SDK's bieden voorbeelden in Python, Java en JavaScript.

  1. Meld u aan bij het Azure-portaal.

  2. Selecteer op de pagina Overzicht van de zoekservice het tabblad Indexen.

  3. Open een bestaande index of maak een nieuwe index.

  4. Selecteer het tabblad Velden en selecteer vervolgens Veld toevoegen. Er wordt een leeg veld toegevoegd. Als u met een bestaande verzameling velden werkt, schuift u omlaag om het veld in te stellen.

  5. Geef het veld een naam en stel het type in op of Edm.ComplexTypeCollection(Edm.ComplexType).

  6. Selecteer het beletselteken uiterst rechts en selecteer vervolgens Veld toevoegen of Subveld toevoegen en wijs vervolgens kenmerken toe.

Complexe velden bijwerken

Alle herindexeringsregels die van toepassing zijn op velden in het algemeen, zijn nog steeds van toepassing op complexe velden. Als u een aantal van de belangrijkste regels hier optelt, is het toevoegen van een veld aan een complex type geen herbouw van een index vereist, maar de meeste wijzigingen wel.

Structurele updates voor de definitie

U kunt op elk gewenst moment nieuwe subvelden toevoegen aan een complex veld zonder dat u een index opnieuw hoeft op te bouwen. Het toevoegen van 'ZipCode' aan Address of 'Voorzieningen' Rooms is bijvoorbeeld toegestaan, net zoals het toevoegen van een veld op het hoogste niveau aan een index. Bestaande documenten hebben een null-waarde voor nieuwe velden totdat u deze velden expliciet vult door uw gegevens bij te werken.

U ziet dat elk subveld binnen een complex type een type heeft en kenmerken kan hebben, net zoals velden op het hoogste niveau doen

Gegevensupdates

Het bijwerken van bestaande documenten in een index met de upload actie werkt op dezelfde manier voor complexe en eenvoudige velden: alle velden worden vervangen. merge (of mergeOrUpload wanneer dit wordt toegepast op een bestaand document) werkt echter niet hetzelfde voor alle velden. merge Met name biedt geen ondersteuning voor het samenvoegen van elementen binnen een verzameling. Deze beperking bestaat voor verzamelingen primitieve typen en complexe verzamelingen. Als u een verzameling wilt bijwerken, moet u de volledige verzamelingswaarde ophalen, wijzigingen aanbrengen en vervolgens de nieuwe verzameling opnemen in de index-API-aanvraag.

Complexe velden doorzoeken

Vrije zoekexpressies werken zoals verwacht met complexe typen. Als een doorzoekbaar veld of subveld ergens in een document overeenkomt, is het document zelf een overeenkomst.

Query's worden genuanceerder wanneer u meerdere termen en operators hebt en sommige termen veldnamen hebben opgegeven, zoals mogelijk met de Lucene-syntaxis. Met deze query worden bijvoorbeeld twee termen, Portland en OR, vergeleken met twee subvelden van het veld Adres:

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

Query's zoals deze zijn niet gerelateerd aan zoekopdrachten in volledige tekst, in tegenstelling tot filters. In filters worden query's over subvelden van een complexe verzameling gecorreleerd met bereikvariabelen in any of all. De Lucene-query hierboven retourneert documenten met zowel Portland, Maine als Portland, Oregon, samen met andere steden in Oregon. Dit gebeurt omdat elke component van toepassing is op alle waarden van het veld in het hele document, dus er is geen concept van een 'huidig subdocument'. Zie Inzicht in OData-verzamelingsfilters in Azure AI Search voor meer informatie hierover.

Complexe velden selecteren

De $select parameter wordt gebruikt om te kiezen welke velden worden geretourneerd in zoekresultaten. Als u deze parameter wilt gebruiken om specifieke subvelden van een complex veld te selecteren, neemt u het bovenliggende veld en subveld op, gescheiden door een slash (/).

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

Velden moeten worden gemarkeerd als Ophaalbaar in de index als u ze in zoekresultaten wilt weergeven. Alleen velden die als ophaalbaar zijn gemarkeerd, kunnen in een $select instructie worden gebruikt.

Complexe velden filteren, facet en sorteren

Dezelfde OData-padsyntaxis die wordt gebruikt voor filteren en zoeken in velden, kan ook worden gebruikt voor facetten, sorteren en selecteren van velden in een zoekaanvraag. Voor complexe typen gelden regels die bepalen welke subvelden kunnen worden gemarkeerd als sorteerbaar of facetabel. Zie de naslaginformatie over de index-API maken voor meer informatie over deze regels.

Subvelden facet

Elk subveld kan worden gemarkeerd als facet, tenzij het van het type Edm.GeographyPoint is of Collection(Edm.GeographyPoint).

De aantallen documenten die in de facetresultaten worden geretourneerd, worden berekend voor het bovenliggende document (een hotel), niet de subdocumenten in een complexe verzameling (ruimten). Stel dat een hotel 20 kamers van het type 'suite' heeft. Gezien deze facetparameter facet=Rooms/Typeis het aantal facetten één voor het hotel, niet 20 voor de kamers.

Complexe velden sorteren

Sorteerbewerkingen zijn van toepassing op documenten (Hotels) en niet op subdocumenten (Ruimten). Wanneer u een complexe verzameling typen hebt, zoals Ruimten, is het belangrijk om te beseffen dat u helemaal niet op ruimten kunt sorteren. In feite kunt u niet sorteren op een verzameling.

Sorteerbewerkingen werken wanneer velden één waarde per document hebben, ongeacht of het veld een eenvoudig veld is of een subveld in een complex type. Het is bijvoorbeeld Address/City toegestaan om te sorteren omdat er slechts één adres per hotel is, dus $orderby=Address/City sorteert hotels op stad.

Filteren op complexe velden

U kunt verwijzen naar subvelden van een complex veld in een filterexpressie. Gebruik gewoon dezelfde OData-padsyntaxis die wordt gebruikt voor het faceten, sorteren en selecteren van velden. Met het volgende filter worden bijvoorbeeld alle hotels in Canada geretourneerd:

$filter=Address/Country eq 'Canada'

Als u wilt filteren op een complex verzamelingsveld, kunt u een lambda-expressie met de any en all operators gebruiken. In dat geval is de bereikvariabele van de lambda-expressie een object met subvelden. U kunt naar deze subvelden verwijzen met de standaard syntaxis van het OData-pad. Het volgende filter retourneert bijvoorbeeld alle hotels met ten minste één deluxe kamer en alle niet-mokkende kamers:

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

Net als bij eenvoudige velden op het hoogste niveau kunnen eenvoudige subvelden van complexe velden alleen worden opgenomen in filters als ze het filterbare kenmerk hebben ingesteld true in de indexdefinitie. Zie de naslaginformatie voor de Index-API maken voor meer informatie.

Azure Search heeft de beperking dat de complexe objecten in de verzamelingen in één document niet groter zijn dan 3000.

Gebruikers krijgen de onderstaande fout tijdens het indexeren wanneer complexe verzamelingen de limiet van 3000 overschrijden.

"Een verzameling in uw document overschrijdt de maximumelementen voor alle complexe verzamelingen. Het document met sleutel 1052 bevat 4303 objecten in verzamelingen (JSON-matrices). Maximaal 3000 objecten mogen zich in verzamelingen in het hele document bevinden. Verwijder objecten uit verzamelingen en probeer het document opnieuw te indexeren.'

In sommige gevallen moeten we mogelijk meer dan 3000 items toevoegen aan een verzameling. In deze gebruiksvoorbeelden kunnen we een sluis (|) gebruiken of een willekeurige vorm van scheidingsteken gebruiken om de waarden te scheiden, ze samen te voegen en op te slaan als een gescheiden tekenreeks. Er is geen beperking voor het aantal tekenreeksen dat is opgeslagen in een matrix in Azure Search. Het opslaan van deze complexe waarden als tekenreeksen voorkomt de beperking. De klant moet valideren of deze tijdelijke oplossing voldoet aan de scenariovereisten.

Het zou bijvoorbeeld niet mogelijk zijn om complexe typen te gebruiken als de 'searchScope'-matrix hieronder meer dan 3000 elementen bevat.


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

Het opslaan van deze complexe waarden als tekenreeksen met een scheidingsteken voorkomt de beperking

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

In plaats van deze op te slaan met jokertekens, kunnen we ook een aangepaste analyse gebruiken waarmee het woord wordt gesplitst in | om de opslaggrootte te verkleinen.

De reden waarom we de waarden met jokertekens hebben opgeslagen in plaats van ze op te slaan zoals hieronder

|FRA|1234|C100|

is om te voldoen aan zoekscenario's waarbij de klant mogelijk wil zoeken naar items met land Frankrijk, ongeacht producten en categorieën. Op dezelfde manier moet de klant mogelijk zoeken om te zien of het artikel product 1234 heeft, ongeacht het land of de categorie.

Als we slechts één vermelding hadden opgeslagen

|FRA|1234|C100|

zonder jokertekens, als de gebruiker alleen op Frankrijk wil filteren, kunnen we de gebruikersinvoer niet converteren zodat deze overeenkomt met de "searchScope"-matrix omdat we niet weten welke combinatie van Frankrijk aanwezig is in onze "searchScope"-matrix

Als de gebruiker alleen wil filteren op land, zeggen we Frankrijk. We nemen de gebruikersinvoer en bouwen deze als een tekenreeks zoals hieronder:

|FRA|*|*|

die we vervolgens kunnen gebruiken om te filteren in Azure Search terwijl we zoeken in een matrix met itemwaarden

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

Als de gebruiker zoekt naar Frankrijk en de 1234-productcode, nemen we de gebruikersinvoer, maken we deze als een tekenreeks met scheidingstekens zoals hieronder en vergelijken we deze met onze zoekmatrix.

|FRA|1234|*|

Als de gebruiker zoekt naar 1234-productcode, nemen we de gebruikersinvoer, bouwen we deze samen als een tekenreeks met scheidingstekens zoals hieronder en vergelijken we deze met onze zoekmatrix.

|*|1234|*|

Als de gebruiker zoekt naar de C100-categoriecode, nemen we de gebruikersinvoer, bouwen we deze samen als een tekenreeks met scheidingstekens zoals hieronder en vergelijken we deze met onze zoekmatrix.

|*|*|C100|

Als de gebruiker zoekt naar Frankrijk en de 1234-productcode en C100-categoriecode, nemen we de gebruikersinvoer, bouwen we deze samen als een tekenreeks met scheidingstekens zoals hieronder en komen we deze overeen met onze zoekmatrix.

|FRA|1234|C100|

Als een gebruiker probeert te zoeken naar landen die niet aanwezig zijn in onze lijst, komt deze niet overeen met de gescheiden matrix 'searchScope' die is opgeslagen in de zoekindex en worden er geen resultaten geretourneerd. Een gebruiker zoekt bijvoorbeeld naar Canada en productcode 1234. De zoekopdracht van de gebruiker wordt geconverteerd naar

|CAN|1234|*|

Dit komt niet overeen met een van de vermeldingen in de gescheiden matrix in onze zoekindex.

Alleen de bovenstaande ontwerpkeuze vereist deze jokerkaartinvoer; als het als een complex object is opgeslagen, kunnen we gewoon een expliciete zoekopdracht hebben uitgevoerd zoals hieronder wordt weergegeven.

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

We kunnen dus voldoen aan vereisten waarbij we moeten zoeken naar een combinatie van waarden door deze op te slaan als een gescheiden tekenreeks in plaats van een complexe verzameling als onze complexe verzamelingen de Limiet van Azure Search overschrijden. Dit is een van de tijdelijke oplossingen en de klant moet valideren of dit aan hun scenariovereisten voldoet.

Volgende stappen

Probeer de gegevensset Hotels in de wizard Gegevens importeren. U hebt de Azure Cosmos DB-verbindingsgegevens in het leesmij-bestand nodig om toegang te krijgen tot de gegevens.

De eerste stap in de wizard bestaat uit het maken van een nieuwe Azure Cosmos DB-gegevensbron. Verderop in de wizard ziet u, wanneer u bij de doelindexpagina bent, een index met complexe typen. Maak en laad deze index en voer vervolgens query's uit om inzicht te hebben in de nieuwe structuur.