- Die Property Graphs in Postgres 19 sind eine SQL/PGQ-Funktion, mit der bestehende Tabellen als Knoten und Kanten deklariert und feste Beziehungsmuster per
MATCH abgefragt werden; Daten werden dabei weder kopiert noch wird eine separate Graph-Ausführungs-Engine aufgebaut
- Graphmuster werden in relationale Joins kompiliert und nutzen daher den bestehenden Optimizer, Indizes und Statistiken unverändert; langsame Traversierungen lassen sich damit wie normale Joins analysieren und optimieren
- Dimensionstabellen lassen sich natürlich als Knoten und reine Join-Tabellen als Kanten abbilden; Faktentabellen mit mehreren Fremdschlüsseln und eigenen Attributen modelliert man dagegen besser als Hub-Knoten, die Events darstellen
- Eine einzelne Tabelle kann mit unterschiedlichen Aliasen gleichzeitig als Knoten und mehrere Kanten deklariert werden, sodass anhand der bestehenden Fremdschlüssel in
results keine separaten Kantentabellen erzeugt und gespeichert werden müssen
- Postgres 19 unterstützt keine Pfade variabler Länge und eignet sich daher nicht für kürzeste Pfade, N-Hop-Erreichbarkeit oder PageRank, sondern für Suchen mit fester Länge, bei denen die Beziehungsstruktur vorab bekannt ist
Ein relationales Schema als Graph lesen
- In einem normalisierten relationalen Schema enthalten Dimensionstabellen wie
drivers, constructors und circuits Entitäten, während Faktentabellen Events wie Rennergebnisse oder Qualifying-Sessions erfassen
- Jede Zeile ist ein potenzieller Knoten, und jeder Fremdschlüssel ist eine potenzielle Kante, die auf eine andere Zeile verweist
- Ein ER-Diagramm entspricht einem Graph auf Schemaebene
- Die tatsächlichen Zeilen und Fremdschlüsselbeziehungen entsprechen einem Graph auf Instanzebene
- Die Anfrage „finde den constructor zu jedem Ergebnis“ ist eine Graph-Traversierung, die man in relationalem SQL als
results JOIN constructors ON ... schreibt
- SQL/PGQ fügt keinen neuen Graphen selbst hinzu, sondern ermöglicht es, Fragen in Graphform zu formulieren, statt Joins direkt aufzulisten
- Der Ansatz von RelBench lädt Tabellen in pandas und baut daraus im Speicher PyTorch-Graphen auf; Postgres Property Graphs nutzen dagegen die bestehenden Tabellenbeziehungen direkt in der Datenbank
- Auch Beispiele von PyG verwenden meist Flat Files oder In-Memory-Graphen
- Den mit dem Formula-1-Datensatz getesteten Code findet man im GitHub-Repository
Aufbau eines Property Graph
CREATE PROPERTY GRAPH ist ein benanntes Deklarationsobjekt, das auf bestehenden Tabellen erstellt wird
VERTEX TABLES legt fest, welche Tabellenzeilen als Knoten gelesen werden
EDGE TABLES legt fest, welche Tabellenzeilen als Verbindungsbeziehungen gelesen werden
- Für jede Knotentabelle werden die folgenden Elemente definiert
KEY: der Bezeichner des Knotens, meist der Primärschlüssel
LABEL: der Name des Knotentyps, der in MATCH verwendet wird
PROPERTIES: die Liste der Spalten, auf die in Graphabfragen zugegriffen werden kann
- Für jede Kantentabelle werden
SOURCE und DESTINATION angegeben; beide Werte referenzieren jeweils den Schlüssel eines Knotens
CREATE PROPERTY GRAPH verschiebt oder kopiert keine Daten
- Die Zeilen bleiben in ihren ursprünglichen Tabellen
- Die Deklaration definiert nur, wie die bestehende Fremdschlüsselstruktur als Graph gelesen wird
- Die strukturellen Elemente eines Property Graph bestehen nur aus zwei Arten: Knoten und Kanten
- Labels und Properties sind Attribute von Knoten oder Kanten
- In
psql ist Element Kind für jedes Element entweder vertex oder edge
Feste Muster mit MATCH abfragen
- Graphabfragen werden geschrieben, indem ein
MATCH-Muster in GRAPH_TABLE(...) angegeben wird
- Das folgende Muster beschreibt eine Beziehung, die vom driver über result zur race führt
MATCH (d IS driver)<-[IS of_driver]-(res IS result)-[IS in_race]->(ra IS race)
- Die
COLUMNS-Klausel legt fest, welche Spalten die Graphabfrage nach außen zurückgibt
- Im äußeren
SELECT kann das Ergebnis von GRAPH_TABLE(...) wie eine normale Tabelle abgefragt werden
- Da Muster Richtung und Labels enthalten, lassen sich Beziehungen wie Sätze lesen; dadurch ist die Struktur leichter zu erkennen als bei einem entsprechenden Multi-Join
Zur Laufzeit in relationale Joins übersetzte Struktur
MATCH ist keine separat an Postgres angehängte Graph-Ausführungs-Engine, sondern Syntax, die in relationale Joins kompiliert wird
- Führt man
EXPLAIN auf dem driver–result–race-Muster aus, erscheinen vier Hash-Joins auf den zugrunde liegenden Tabellen
- Wie bei manuell geschriebenen Joins werden die folgenden Komponenten genutzt
- der bestehende Postgres-Optimizer
- bestehende Indizes
- bestehende Statistiken
- Wenn eine Graph-Traversierung langsam ist, sind Ursache und Optimierungsmethode dieselben wie bei langsamen normalen Joins
- Auch in
psql kann man Graphen mit tabellenähnlichen Befehlen untersuchen
\dG: zeigt die Liste der Property Graphs an
\d f1: zeigt jeden Knoten und jede Kante, die zugrunde liegende Tabelle, die Elementart sowie Start- und Zielknoten von Kanten an
\d+ f1: rekonstruiert das vollständige CREATE PROPERTY GRAPH-Statement und enthält auch aus dem Primärschlüssel der Kantentabelle abgeleitete Kantenschlüssel
Unterschied zwischen Keys und Properties
- Eine als
KEY eines Knotens angegebene Spalte wird nicht automatisch zu einer Property
- Wenn
driver_id nur als Knotenschlüssel angegeben wurde und man mit d.driver_id = 1 filtert, schlägt dies fehl, weil diese Spalte keine abfragbare Property ist
- Der Key identifiziert den Knoten, legt ihn aber nicht für Abfragen offen
- Wenn ID-Spalten gefiltert oder zurückgegeben werden sollen, müssen sie explizit in die Liste
PROPERTIES aufgenommen werden
Einschränkungen bei Pfaden variabler Länge in Postgres 19
- Postgres 19 unterstützt keine Elementmuster-Quantifizierer wie „folge einer Kante ein- bis dreimal“
ERROR: element pattern quantifier is not supported
- Um zwei Hops zu durchsuchen, müssen in
MATCH zwei Kantenmuster explizit angegeben werden
- Pfade beliebiger Länge lassen sich mit der Property-Graph-Syntax nicht ausdrücken
- Suchen mit offener Tiefe müssen über eine rekursive CTE auf den zugrunde liegenden Tabellen verarbeitet werden; damit verlässt man die Property-Graph-Syntax
Bestehende Tabellen auf Knoten und Kanten abbilden
-
Dimensionstabellen sind Knoten
- Entitätstabellen mit stabilem Primärschlüssel und Attributen wie
drivers, constructors oder circuits entsprechen direkt Knoten
- Man verwendet den Primärschlüssel als Knotenschlüssel und legt die benötigten Spalten als Properties offen
-
Reine Join-Tabellen sind Kanten
- Viele-zu-viele-Bridge-Tabellen wie
student_courses(student_id, course_id) haben von Natur aus die Aufgabe, zwei Entitäten zu verbinden, und entsprechen daher natürlich Kanten
- Ein Fremdschlüssel wird als Startknoten, der andere als Zielknoten deklariert
- Da jede Zeile der Join-Tabelle selbst eine Beziehung ist, ist keine separate Datentransformation für Graphen nötig
- In dieser Struktur sind
students und courses Knoten, während student_courses zur Kante enrolled_in wird
-
Faktentabellen sind Event-Knoten
- Eine
results-Zeile verweist auf die drei Entitäten driver, race und constructor und besitzt außerdem eigene Daten wie grid, position, points und status
- SQL/PGQ-Kanten sind binäre Beziehungen mit einem Start- und einem Zielpunkt; daher kann eine ganze Zeile mit drei Fremdschlüsseln nicht zu einer einzelnen Kante gemacht werden
- Wenn die Faktenzeile selbst Gegenstand der Analyse ist, sollte diese Tabelle besser als Knoten deklariert werden
- Der
results-Knoten enthält das Event und dessen Attribute
- Schmale Kanten wie
results_driver, results_race und results_constructor verbinden ihn mit externen Entitäten
- Dieses Modell erzeugt eine Hub-Struktur wie
driver <- result -> race, in der man am result-Knoten anhalten, filtern oder Attribute abfragen kann
- Wenn die Beziehung selbst im Fokus steht, modelliert man sie als Kante; wenn eine bestimmte Zeile oder ein Event mit eigenen Attributen im Fokus steht, als Knoten
- Join-Tabellen stellen Beziehungen dar, Faktentabellen Events
Eine einzelne Tabelle gleichzeitig als Knoten und Kante verwenden
- Die Unterscheidung „Knoten oder Kante“ gilt nicht für die zugrunde liegende Tabelle, sondern für jede Elementdeklaration im Graphen
- Eine einzelne Tabelle kann mit unterschiedlichen Aliasen sowohl in
VERTEX TABLES als auch in EDGE TABLES deklariert werden
- Während
results als result-Knoten verwendet wird, kann dieselbe Tabelle als folgende Kanten-Aliase wiederverwendet werden
results AS res_driver: verbindet result mit driver
results AS res_race: verbindet result mit race
results AS res_constr: verbindet result mit constructor
- Jeder Kanten-Alias nutzt die bereits in
results vorhandenen Primärschlüssel- und Fremdschlüsselspalten
- Es ist nicht nötig, separate Tabellen
results_driver, results_race oder results_constructor zu erstellen; Aliase speichern keine Daten
- Eine Faktentabelle mit drei Fremdschlüsseln muss nicht als eine Kante mit mehreren Endpunkten deklariert werden, sondern als drei Kanten-Aliase, die jeweils genau einen Start- und Zielpunkt haben
- Dieser Ansatz verwendet drei deklarative Aliase auf einer einzigen zugrunde liegenden Tabelle statt drei physischer Tabellen oder Views
Kollisionen bei Property-Namen und -Typen
- Wenn
PROPERTIES weggelassen wird, legt SQL/PGQ alle Spalten der Tabelle als Properties offen
- Wenn sowohl
results als auch qualifying eine Spalte number haben, diese aber jeweils double precision und bigint ist, tritt folgender Fehler auf
ERROR: property "number" data type mismatch: double precision vs. bigint
- Gleichnamige Properties müssen im gesamten Graphen einen einzigen Typ haben, daher kollidieren gleichnamige Spalten mit unterschiedlichen Typen
- Wenn nur benötigte Spalten explizit in
PROPERTIES angegeben werden, lassen sich kollidierende Spalten aus dem Graphen ausschließen
- Sowohl das Problem, dass Keys nicht automatisch zu Properties werden, als auch Typkollisionen lassen sich durch eine explizite Allowlist für Properties lösen
Geeignete und ungeeignete Abfragen
-
Feste Beziehungsmuster
- Property Graphs eignen sich für Fragen wie „Finde die Objekte, die mit X über genau dieses Muster verbunden sind“
- Im Formula-1-Graphen lassen sich zum Beispiel folgende Abfragen schreiben
- Ermitteln, bei welchem constructor ein bestimmter driver gefahren ist
- Konkurrenten über ein Muster finden, das vom driver über result und race zu einem anderen result und driver führt
- Struktur- und Attributbedingungen gemeinsam filtern, etwa Ergebnisse, bei denen die Startposition außerhalb der Top 10 lag und der driver Italian ist
- Wenn die Form der Beziehungen vorab bekannt und die Struktur fest und endlich ist, wird die Syntax zum Suchen, Filtern und Aggregieren lesbarer
- Auch Abfragen, die mehrere Self-Joins erfordern, lassen sich als ein gut lesbares
MATCH-Muster ausdrücken
-
Probleme, bei denen der Pfad selbst unbekannt ist
- Die folgenden Probleme passen nicht zu den Property Graphs in Postgres 19
- kürzester Pfad zwischen zwei drivern
- alle innerhalb von N Hops erreichbaren Ziele
- Verbindungssuche mit vorab unbekannter Tiefe
- Solche Abfragen erfordern Traversierungen variabler Länge und müssen daher mit einer rekursiven CTE auf den zugrunde liegenden Tabellen umgesetzt werden
- Auch Graphalgorithmen wie PageRank, Community Detection oder Zentralitätsberechnung sind andere Probleme als Pattern Matching und fallen nicht in den Umfang dieser Funktion
- Property Graphs finden verbundene Ziele, wenn die Pfadstruktur bekannt ist; sie können aber keine Pfade entdecken, wenn unbekannt ist, über welchen Pfad X und Y verbunden sind, und auch keine strukturelle Bedeutung im Graphen berechnen
Punkte, die vor der Einführung zu prüfen sind
- SQL/PGQ ist ein deklaratives Overlay auf bestehenden Fremdschlüsselstrukturen und speichert keine zusätzlichen Daten, sofern man dies nicht separat wählt
- Die wichtigsten Vorteile sind zwei Punkte
- Traversierungen mit fester Form lassen sich lesbarer schreiben als relationale Joins
- Es stellt ein benanntes Objekt bereit, das das Schema als Graph dokumentiert
- Die größte Einschränkung ist das Fehlen von Pfaden variabler Länge, sodass tiefe oder offene Suchen per rekursivem SQL verarbeitet werden müssen
- Tabellen sind nicht von Natur aus Knoten oder Kanten; in jedem Graphen kann entschieden werden, ob ihre Zeilen als Knoten, Kanten oder beides gelesen werden
- Wenn man in Postgres 19 feste Graphmuster auf einem bestehenden Schema abfragen möchte, kann man
CREATE PROPERTY GRAPH und MATCH prüfen, bevor man Daten in eine separate Graphdatenbank verschiebt
- Für Suchen mit unbekannter Tiefe ist eine rekursive CTE nötig, aber unabhängig vom gewählten Ansatz müssen die Daten Postgres nicht verlassen
- Vor einer echten Einführung sollte man selbst testen, ob die benötigte Performance erreicht wird
Noch keine Kommentare.