SQL REFERENCE

SQL/PGQ — Graph Pattern Matching

Teide implements the SQL/PGQ (Property Graph Queries) extension, allowing you to define property graphs over relational tables and query them with powerful graph pattern matching. Graph queries compose naturally with standard SQL — you can use GROUP BY, ORDER BY, CTEs, and subqueries on graph results.

CREATE PROPERTY GRAPH

A property graph is a named overlay on existing relational tables. You declare which tables serve as vertex (node) tables and which serve as edge tables, specifying keys, labels, and which columns to expose as properties.

Syntax

CREATE PROPERTY GRAPH graph_name
  VERTEX TABLES (
    table_name
      KEY (column)
      LABEL label_name
      PROPERTIES (col1, col2, ...)
    [, ...]
  )
  EDGE TABLES (
    table_name
      KEY (column)
      SOURCE KEY (column) REFERENCES vertex_table (column)
      DESTINATION KEY (column) REFERENCES vertex_table (column)
      LABEL label_name
      PROPERTIES (col1, col2, ...)
    [, ...]
  )

Example: Social Network

Consider a social network with persons and friendship relationships:

import { Context } from 'teide-js';

const ctx = new Context();

// Create the underlying relational tables
ctx.executeSync(`
  CREATE TABLE persons (
    id INTEGER,
    name VARCHAR,
    age INTEGER,
    city VARCHAR
  )
`);

ctx.executeSync(`
  INSERT INTO persons VALUES
    (1, 'Alice',   30, 'London'),
    (2, 'Bob',     25, 'Paris'),
    (3, 'Charlie', 35, 'London'),
    (4, 'Diana',   28, 'Berlin'),
    (5, 'Eve',     32, 'Paris')
`);

ctx.executeSync(`
  CREATE TABLE friendships (
    id INTEGER,
    person1_id INTEGER,
    person2_id INTEGER,
    since INTEGER
  )
`);

ctx.executeSync(`
  INSERT INTO friendships VALUES
    (1, 1, 2, 2020),
    (2, 1, 3, 2019),
    (3, 2, 4, 2021),
    (4, 3, 5, 2022),
    (5, 4, 5, 2021)
`);

// Define the property graph
ctx.executeSync(`
  CREATE PROPERTY GRAPH social
    VERTEX TABLES (
      persons
        KEY (id)
        LABEL Person
        PROPERTIES (name, age, city)
    )
    EDGE TABLES (
      friendships
        KEY (id)
        SOURCE KEY (person1_id) REFERENCES persons (id)
        DESTINATION KEY (person2_id) REFERENCES persons (id)
        LABEL FriendOf
        PROPERTIES (since)
    )
`);

DROP PROPERTY GRAPH

Removes a property graph definition. The underlying tables are not affected.

ctx.executeSync(`DROP PROPERTY GRAPH social`);

GRAPH_TABLE with MATCH

The GRAPH_TABLE function lets you query a property graph using the MATCH clause for pattern matching. Results are returned as a standard relational table.

Basic 1-Hop Pattern

Find all direct friendships:

const friends = ctx.executeSync(`
  SELECT * FROM GRAPH_TABLE (social
    MATCH (a:Person)-[e:FriendOf]->(b:Person)
    COLUMNS (a.name AS person, b.name AS friend, e.since)
  )
`);

// person   | friend  | since
// ---------|---------|------
// Alice    | Bob     | 2020
// Alice    | Charlie | 2019
// Bob      | Diana   | 2021
// ...

WHERE Filters

Filter graph patterns with WHERE inside the MATCH:

const londonFriends = ctx.executeSync(`
  SELECT * FROM GRAPH_TABLE (social
    MATCH (a:Person)-[e:FriendOf]->(b:Person)
    WHERE a.city = 'London'
    COLUMNS (a.name AS person, b.name AS friend, a.city)
  )
`);

COLUMNS Clause

The COLUMNS clause specifies which properties to project from matched nodes and edges. You can use aliases and expressions just like in a SELECT:

const result = ctx.executeSync(`
  SELECT * FROM GRAPH_TABLE (social
    MATCH (a:Person)-[e:FriendOf]->(b:Person)
    COLUMNS (
      a.name AS person_name,
      b.name AS friend_name,
      a.age - b.age AS age_diff,
      e.since AS friends_since
    )
  )
`);

Variable-Length Paths

Pattern quantifiers let you match paths of variable length, enabling reachability queries and multi-hop traversals.

Bounded Length {min,max}

Match paths between 1 and 3 hops:

const nearby = ctx.executeSync(`
  SELECT * FROM GRAPH_TABLE (social
    MATCH (a:Person)-[:FriendOf]->{1,3}(b:Person)
    WHERE a.name = 'Alice'
    COLUMNS (a.name AS source, b.name AS reachable)
  )
`);

One-or-More +

Match paths of at least one hop (equivalent to {1,}):

const reachable = ctx.executeSync(`
  SELECT DISTINCT * FROM GRAPH_TABLE (social
    MATCH (a:Person)-[:FriendOf]->+(b:Person)
    WHERE a.name = 'Alice'
    COLUMNS (b.name AS reachable_person)
  )
`);

Zero-or-More *

Match paths of zero or more hops (includes the start node itself):

const allReachable = ctx.executeSync(`
  SELECT DISTINCT * FROM GRAPH_TABLE (social
    MATCH (a:Person)-[:FriendOf]->*(b:Person)
    WHERE a.name = 'Alice'
    COLUMNS (b.name AS reachable_person)
  )
`);

Shortest Path

Use ANY SHORTEST to find the shortest path between two nodes. The path_length() function returns the number of hops.

const shortest = ctx.executeSync(`
  SELECT * FROM GRAPH_TABLE (social
    MATCH ANY SHORTEST
      (a:Person)-[:FriendOf]->+(b:Person)
    WHERE a.name = 'Alice' AND b.name = 'Eve'
    COLUMNS (
      a.name AS source,
      b.name AS target,
      path_length() AS hops
    )
  )
`);

// source | target | hops
// -------|--------|-----
// Alice  | Eve    | 2

Edge Directions

SQL/PGQ supports three edge direction patterns:

Pattern Direction Description
(a)-[e]->(b) Forward Follows edge from source to destination
(a)<-[e]-(b) Reverse Follows edge from destination to source
(a)-[e]-(b) Undirected Matches edge in either direction
// Find who considers Alice a friend (reverse direction)
const admirers = ctx.executeSync(`
  SELECT * FROM GRAPH_TABLE (social
    MATCH (a:Person)<-[:FriendOf]-(b:Person)
    WHERE a.name = 'Alice'
    COLUMNS (b.name AS admirer)
  )
`);

// Undirected: find all connections regardless of direction
const connections = ctx.executeSync(`
  SELECT * FROM GRAPH_TABLE (social
    MATCH (a:Person)-[:FriendOf]-(b:Person)
    WHERE a.name = 'Alice'
    COLUMNS (b.name AS connected_to)
  )
`);

Composing with SQL

Graph query results are standard tables, so they compose freely with all SQL features.

GROUP BY on Graph Results

// Count friends per person
const friendCounts = ctx.executeSync(`
  SELECT person, COUNT(*) AS num_friends
  FROM GRAPH_TABLE (social
    MATCH (a:Person)-[:FriendOf]-(b:Person)
    COLUMNS (a.name AS person, b.name AS friend)
  )
  GROUP BY person
  ORDER BY num_friends DESC
`);

CTEs with Graph Tables

// Use a CTE to find well-connected people, then join with their cities
const result = ctx.executeSync(`
  WITH connections AS (
    SELECT person, COUNT(*) AS degree
    FROM GRAPH_TABLE (social
      MATCH (a:Person)-[:FriendOf]-(b:Person)
      COLUMNS (a.name AS person)
    )
    GROUP BY person
  )
  SELECT p.name, p.city, c.degree
  FROM persons p
  JOIN connections c ON p.name = c.person
  WHERE c.degree >= 2
  ORDER BY c.degree DESC
`);

Subqueries with Graph Tables

// Find persons who are reachable from Alice within 2 hops
// and live in a city with more than one person
const result = ctx.executeSync(`
  SELECT * FROM GRAPH_TABLE (social
    MATCH (a:Person)-[:FriendOf]->{1,2}(b:Person)
    WHERE a.name = 'Alice'
      AND b.city IN (
        SELECT city FROM persons
        GROUP BY city HAVING COUNT(*) > 1
      )
    COLUMNS (b.name AS person, b.city)
  )
`);