Modellera relations-SQL-data för import och indexering i Azure AI Search

Azure AI Search accepterar en platt raduppsättning som indata till indexeringspipelinen. Om dina källdata kommer från anslutna tabeller i en SQL Server-relationsdatabas förklarar den här artikeln hur du konstruerar resultatuppsättningen och hur du modellerar en överordnad-underordnad relation i ett Azure AI Search-index.

Som en illustration refererar vi till en hypotetisk hotelldatabas, baserad på demodata. Anta att databasen består av en Hotels$ tabell med 50 hotell och en Rooms$ tabell med rum av olika typer, priser och bekvämligheter, för totalt 750 rum. Det finns en en-till-många-relation mellan tabellerna. I vår metod tillhandahåller en vy frågan som returnerar 50 rader, en rad per hotell, med tillhörande rumsinformation inbäddad i varje rad.

Tabeller och vy i databasen Hotell

Problemet med avnormaliserade data

En av utmaningarna med att arbeta med en-till-många-relationer är att standardfrågor som bygger på anslutna tabeller returnerar avnormaliserade data, vilket inte fungerar bra i ett Azure AI Search-scenario. Tänk dig följande exempel som ansluter till hotell och rum.

SELECT * FROM Hotels$
INNER JOIN Rooms$
ON Rooms$.HotelID = Hotels$.HotelID

Resultat från den här frågan returnerar alla hotellfält, följt av alla rumsfält, där preliminär hotellinformation upprepas för varje rumsvärde.

Avnormaliserade data, redundanta hotelldata när rumsfält läggs till

Även om den här frågan lyckas på ytan (vilket ger alla data i en platt raduppsättning), misslyckas den med att leverera rätt dokumentstruktur för den förväntade sökupplevelsen. Under indexeringen skapar Azure AI Search ett sökdokument för varje rad som matas in. Om dina sökdokument såg ut som ovanstående resultat skulle du ha uppfattat dubbletter - sju separata dokument enbart för Old Century Hotel. En fråga om "hotell i Florida" skulle returnera sju resultat för bara Old Century Hotel, vilket pressade andra relevanta hotell djupt in i sökresultaten.

För att få den förväntade upplevelsen av ett dokument per hotell bör du ange en raduppsättning på rätt kornighet, men med fullständig information. I den här artikeln förklaras hur.

Definiera en fråga som returnerar inbäddad JSON

För att leverera den förväntade sökupplevelsen bör datauppsättningen bestå av en rad för varje sökdokument i Azure AI Search. I vårt exempel vill vi ha en rad för varje hotell, men vi vill också att våra användare ska kunna söka på andra rumsrelaterade fält som de bryr sig om, till exempel nattpris, storlek och antal bäddar, eller en utsikt över stranden, som alla ingår i en rumsdetalj.

Lösningen är att avbilda rumsinformationen som kapslad JSON och sedan infoga JSON-strukturen i ett fält i en vy, som visas i det andra steget.

  1. Anta att du har två anslutna tabeller, Hotels$ och Rooms$, som innehåller information för 50 hotell och 750 rum och är anslutna till fältet HotelID. Individuellt innehåller dessa tabeller 50 hotell och 750 relaterade rum.

    CREATE TABLE [dbo].[Hotels$](
      [HotelID] [nchar](10) NOT NULL,
      [HotelName] [nvarchar](255) NULL,
      [Description] [nvarchar](max) NULL,
      [Description_fr] [nvarchar](max) NULL,
      [Category] [nvarchar](255) NULL,
      [Tags] [nvarchar](255) NULL,
      [ParkingIncluded] [float] NULL,
      [SmokingAllowed] [float] NULL,
      [LastRenovationDate] [smalldatetime] NULL,
      [Rating] [float] NULL,
      [StreetAddress] [nvarchar](255) NULL,
      [City] [nvarchar](255) NULL,
      [State] [nvarchar](255) NULL,
      [ZipCode] [nvarchar](255) NULL,
      [GeoCoordinates] [nvarchar](255) NULL
    ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
    GO
    
    CREATE TABLE [dbo].[Rooms$](
      [HotelID] [nchar](10) NULL,
      [Description] [nvarchar](255) NULL,
      [Description_fr] [nvarchar](255) NULL,
      [Type] [nvarchar](255) NULL,
      [BaseRate] [float] NULL,
      [BedOptions] [nvarchar](255) NULL,
      [SleepsCount] [float] NULL,
      [SmokingAllowed] [float] NULL,
      [Tags] [nvarchar](255) NULL
    ) ON [PRIMARY]
    GO
    
  2. Skapa en vy som består av alla fält i den överordnade tabellen (SELECT * from dbo.Hotels$), med ett nytt rumsfält som innehåller utdata från en kapslad fråga. En FOR JSON AUTO-sats för SELECT * from dbo.Rooms$ strukturerar utdata som JSON.

    CREATE VIEW [dbo].[HotelRooms]
    AS
    SELECT *, (SELECT *
             FROM dbo.Rooms$
             WHERE dbo.Rooms$.HotelID = dbo.Hotels$.HotelID FOR JSON AUTO) AS Rooms
    FROM dbo.Hotels$
    GO
    

    Följande skärmbild visar den resulterande vyn med fältet Rum nvarchar längst ned. Fältet Rum finns bara i vyn Hotellrum.

    Hotellrumsvy

  3. Kör SELECT * FROM dbo.HotelRooms för att hämta raduppsättningen. Den här frågan returnerar 50 rader, en per hotell, med tillhörande rumsinformation som en JSON-samling.

    Raduppsättning från hotellrumsvyn

Den här raduppsättningen är nu redo för import till Azure AI Search.

Kommentar

Den här metoden förutsätter att inbäddad JSON ligger under de maximala kolumnstorleksgränserna för SQL Server.

Använda en komplex samling för "många"-sidan av en en-till-många-relation

På Azure AI Search-sidan skapar du ett indexschema som modellerar en-till-många-relationen med kapslad JSON. Resultatuppsättningen som du skapade i föregående avsnitt motsvarar vanligtvis indexschemat som anges härnäst (vi skär några fält för korthet).

Följande exempel liknar exemplet i Modellera komplexa datatyper. Rumsstrukturen, som har varit i fokus för den här artikeln, finns i fältsamlingen för ett index med namnet hotels. Det här exemplet visar också en komplex typ för Adress, som skiljer sig från Rum eftersom den består av en fast uppsättning objekt, i motsats till det flera godtyckliga antalet objekt som tillåts i en samling.

{
  "name": "hotels",
  "fields": [
    { "name": "HotelId", "type": "Edm.String", "key": true, "filterable": true },
    { "name": "HotelName", "type": "Edm.String", "searchable": true, "filterable": false },
    { "name": "Description", "type": "Edm.String", "searchable": true, "analyzer": "en.lucene" },
    { "name": "Description_fr", "type": "Edm.String", "searchable": true, "analyzer": "fr.lucene" },
    { "name": "Category", "type": "Edm.String", "searchable": true, "filterable": true, "facetable": true },
    { "name": "ParkingIncluded", "type": "Edm.Boolean", "filterable": true, "facetable": true },
    { "name": "Tags", "type": "Collection(Edm.String)", "searchable": true, "filterable": true, "facetable": true },
    { "name": "Address", "type": "Edm.ComplexType",
      "fields": [
        { "name": "StreetAddress", "type": "Edm.String", "filterable": false, "sortable": false, "facetable": false, "searchable": true },
        { "name": "City", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true },
        { "name": "StateProvince", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }
      ]
    },
    { "name": "Rooms", "type": "Collection(Edm.ComplexType)",
      "fields": [
        { "name": "Description", "type": "Edm.String", "searchable": true, "analyzer": "en.lucene" },
        { "name": "Description_fr", "type": "Edm.String", "searchable": true, "analyzer": "fr.lucene" },
        { "name": "Type", "type": "Edm.String", "searchable": true },
        { "name": "BaseRate", "type": "Edm.Double", "filterable": true, "facetable": true },
        { "name": "BedOptions", "type": "Edm.String", "searchable": true, "filterable": true, "facetable": false },
        { "name": "SleepsCount", "type": "Edm.Int32", "filterable": true, "facetable": true },
        { "name": "SmokingAllowed", "type": "Edm.Boolean", "filterable": true, "facetable": false},
        { "name": "Tags", "type": "Edm.Collection", "searchable": true }
      ]
    }
  ]
}

Med tanke på den tidigare resultatuppsättningen och indexschemat ovan har du alla nödvändiga komponenter för en lyckad indexeringsåtgärd. Den utplattade datauppsättningen uppfyller indexeringskraven men bevarar ändå information. I Azure AI Search-indexet hamnar sökresultaten enkelt i hotellbaserade entiteter, samtidigt som kontexten för enskilda rum och deras attribut bevaras.

Fasetteringsbeteende på komplexa underfält av typen

Fält som har en överordnad, till exempel fälten under Adress och Rum, kallas underfält. Även om du kan tilldela ett "facetable"-attribut till ett underfält, är antalet fasetter alltid för huvuddokumentet.

För komplexa typer som Adress, där det bara finns en "Adress/stad" eller "Adress/tillståndProvince" i dokumentet, fungerar fasetteringsbeteendet som förväntat. Men när det gäller Rum, där det finns flera underdokument för varje huvuddokument, kan antalet fasetter vara vilseledande.

Som anges i Modellkomplextyper: "De dokumentantal som returneras i fasetteringsresultatet beräknas för det överordnade dokumentet (ett hotell), inte underdokumenten i en komplex samling (rum). Anta till exempel att ett hotell har 20 rum av typen "suite". Med tanke på den här aspektparametern facet=Rum/typ är antalet fasetter ett för hotellet, inte 20 för rummen."

Nästa steg

Med din egen datauppsättning kan du använda guiden Importera data för att skapa och läsa in indexet. Guiden identifierar den inbäddade JSON-samlingen, till exempel den som finns i Rum, och härleder ett indexschema som innehåller en komplex typsamling.

Index som härleds av guiden Importera data

Prova följande snabbstart för att lära dig de grundläggande stegen i guiden Importera data.