Modellieren von relationalen SQL-Daten für Import und Indizierung in Azure AI Search
Azure AI Search akzeptiert ein vereinfachtes Rowset als Eingabe für die Indizierungspipeline. Wenn Ihre Quelldaten aus verknüpften Tabellen in einer relationalen SQL Server-Datenbank stammen, wird in diesem Artikel erläutert, wie das Resultset erstellt und eine Beziehung zwischen übergeordneten und untergeordneten Elementen in einem Azure AI Search-Index modelliert wird.
Zur Veranschaulichung verwenden wir eine hypothetische Hoteldatenbank, die auf Demodaten basiert. Angenommen, die Datenbank besteht aus einer Tabelle Hotels$
mit 50 Hotels und einer Tabelle Rooms$
mit insgesamt 750 Zimmern unterschiedlicher Typen, Preise und Ausstattungen. Zwischen den Tabellen besteht eine 1:n-Beziehung. In unserem Ansatz stellt eine Sicht die Abfrage bereit, die 50 Zeilen zurückgibt, eine Zeile pro Hotel, wobei zugeordnete Zimmerdetails in die einzelnen Zeilen eingebettet sind.
Das Problem denormalisierter Daten
Eine der Herausforderungen bei der Arbeit mit 1: n-Beziehungen besteht darin, dass Standardabfragen, die auf verknüpften Tabellen basieren, denormalisierte Daten zurückgeben, was in einem Azure AI Search-Szenario nicht gut funktioniert. Sehen Sie sich das folgende Beispiel an, das Hotels und Räume verknüpft.
SELECT * FROM Hotels$
INNER JOIN Rooms$
ON Rooms$.HotelID = Hotels$.HotelID
Als Ergebnis dieser Abfrage werden alle Hotel-Felder zurückgegeben, gefolgt von allen Room-Feldern, wobei sich grundlegende Hotelinformationen für jeden Room-Wert wiederholen.
Diese Abfrage ist zwar oberflächlich erfolgreich (alle Daten werden in einem vereinfachten Rowset bereitgestellt), kann aber nicht die richtige Dokumentstruktur für die erwartete Sucherfahrung bereitstellen. Während der Indizierung erstellt Azure AI Search ein Suchdokument für jede erfasste Zeile. Wenn Ihre Suchdokumente wie die oben aufgeführten Ergebnisse aussehen, wären auch Duplikate vorhanden, sieben separate Dokumente allein schon für das Hotel Twin Dome. Eine Abfrage nach „Hotels in Florida“ würde sieben Ergebnisse nur für das Hotel Twin Dome zurückgeben, wodurch weitere relevante Hotels in den Suchergebnissen weiter nach unten rücken.
Um die erwartete Darstellung eines Dokuments pro Hotel zu erhalten, sollte ein Rowset mit der richtigen Granularität, jedoch mit umfassenden Informationen bereitgestellt werden. Die Vorgehensweise wird in diesem Artikel erläutert.
Definieren einer Abfrage, die eingebetteten JSON-Code zurückgibt
Um die erwartete Suchumgebung bereitzustellen, sollte Ihr Dataset aus einer Zeile für jedes Suchdokument in Azure AI Search bestehen. In unserem Beispiel möchten wir eine Zeile für jedes Hotel bereitstellen, aber die Benutzer sollen auch in der Lage sein, nach anderen zimmerbezogenen Feldern zu suchen, die für sie wichtig sind, wie z.B. Preis pro Nacht, Größe und Anzahl der Betten oder Strandblick – allen Informationen, die Teil der Zimmerdetails sind.
Die Lösung besteht darin, die Zimmerdetails als geschachtelten JSON-Code zu erfassen und die JSON-Struktur dann in ein Feld in einer Sicht einzufügen, wie im zweiten Schritt gezeigt.
Angenommen, Sie verfügen über zwei verknüpfte Tabellen,
Hotels$
undRooms$
, die Details über 50 Hotels und 750 Zimmer enthalten und über das Feld „HotelID“ verknüpft sind. Diese Tabellen enthalten einzeln 50 Hotels bzw. 750 zugehörige Zimmer.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
Erstellen Sie eine Sicht die alle Felder in der übergeordneten Tabelle (
SELECT * from dbo.Hotels$
) enthält, und fügen Sie ein neues Rooms-Feld hinzu, das die Ausgabe einer geschachtelten Abfrage enthält. Eine FOR JSON AUTO-Klausel fürSELECT * from dbo.Rooms$
strukturiert die Ausgabe als 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
Der folgende Screenshot zeigt die resultierende Sicht mit dem nvarchar-Feld Rooms unten. Das Feld Rooms ist nur in der Sicht „HotelRooms“ vorhanden.
Führen Sie
SELECT * FROM dbo.HotelRooms
aus, um das Rowset abzurufen. Diese Abfrage gibt 50 Zeilen, eine pro Hotel, mit zugeordneten Zimmerinformationen als JSON-Sammlung zurück.
Dieses Rowset kann nun in Azure AI Search importiert werden.
Hinweis
Bei dieser Vorgehensweise wird davon ausgegangen, dass eingebetteter JSON-Code die maximalen Spaltengrößenbeschränkungen von SQL Server nicht überschreitet.
Verwenden einer komplexen Sammlung für die „n“-Seite einer 1: n-Beziehung
Erstellen Sie auf der Azure AI Search-Seite ein Indexschema, das die 1: n-Beziehung mithilfe von geschachteltem JSON modelliert. Das Resultset, das Sie im vorherigen Abschnitt erstellt haben, entspricht im Allgemeinen dem folgenden Indexschema (aus Platzgründen haben wir einige Felder gekürzt).
Das folgende Beispiel ähnelt dem Beispiel in Modellieren komplexer Datentypen. Die Rooms-Struktur, die im Mittelpunkt dieses Artikels steht, befindet sich in der Feldsammlung eines Indexes namens Hotels. Dieses Beispiel zeigt auch einen komplexen Typ für Address, der sich von Rooms darin unterscheidet, dass er aus einem festgelegten Satz von Elementen besteht, im Gegensatz zu der in einer Sammlung zulässigen beliebigen Anzahl von Elementen.
{
"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 }
]
}
]
}
Mit dem vorherigen Resultset und dem oben aufgeführten Indexschema verfügen Sie über alle erforderlichen Komponenten für einen erfolgreichen Indizierungsvorgang. Das vereinfachte Dataset erfüllt die Indizierungsanforderungen, behält jedoch Detailinformationen bei. Im Azure KI-Suche-Index werden Suchergebnisse problemlos in die hotelbasierten Entitäten eingeteilt, wobei der Kontext einzelner Räume und ihre Attribute beibehalten werden.
Facetverhalten für komplexe Unterfelder des Typs
Felder mit einem übergeordneten Element, z. B. die Felder unter „Address“ und „Rooms“, werden als Unterfelderbezeichnet. Obwohl Sie einem Unterfeld ein „facetable“-Attribut zuweisen können, wird die Anzahl des Facets immer für das Hauptdokument verwendet.
Bei komplexen Typen wie „Address/City“ oder „Address/stateProvince“ im Dokument funktioniert das Facetverhalten wie erwartet. Im Fall von „Rooms“, bei denen für jedes Hauptdokument mehrere Filialdokumente vorhanden sind, kann die Facetanzahl jedoch irreführend sein.
Wie beim Modellieren komplexer Typen erwähnt: Die in den Facettenergebnissen zurückgegebene Anzahl von Dokumenten wird für das übergeordnete Dokument (ein Hotel) berechnet, nicht für die untergeordneten Dokumente in einer komplexen Sammlung (Rooms). Beispiel: Ein Hotel hat 20 Zimmer vom Typ „suite“. Angesichts dieses Facetparameters facet=Rooms/Type ist die Facetanzahl für das Hotel eins, nicht 20 für die Zimmer."
Nächste Schritte
Sie können den Datenimport-Assistenten mit Ihrem eigenen Dataset zum Erstellen und Laden des Index verwenden. Der Assistent erkennt die eingebettete JSON-Sammlung, z.B. die in Rooms enthaltene Sammlung, und leitet ein Indexschema ab, das eine Sammlung komplexer Typen enthält.
Verwenden Sie den folgenden Schnellstart, um die grundlegenden Schritte des Datenimport-Assistenten kennen zu lernen.