Graph Queries
Query property graphs with GQL (Graph Query Language), convert between SQL and GQL, and visualize graph results.
What are Graph Queries?
BigQuery property graphs let you model data as nodes and edges. Instead of writing multi-table JOINs, you describe traversal patterns:
Standard SQL:
SELECT p1.name AS sender, p2.name AS receiver, t.amount
FROM Person AS p1
JOIN Transfers AS t ON p1.id = t.source_id
JOIN Person AS p2 ON t.dest_id = p2.id
WHERE t.amount > 500
GQL:
GRAPH FinGraph
MATCH (p1:Person)-[t:Transfers]->(p2:Person)
WHERE t.amount > 500
RETURN p1.name AS sender, p2.name AS receiver, t.amount
Both produce the same result. GQL makes the relationship structure explicit.
GQL Syntax
Node Patterns
Nodes are enclosed in parentheses. You can specify a variable, a label, or both:
(a:Person) -- variable + label
(:Person) -- label only
(a) -- variable only
() -- anonymous node
(a IS Person) -- IS label syntax
(a:Person WHERE a.age > 30) -- inline WHERE
Edge Patterns
Edges connect nodes with direction arrows:
-[t:Transfers]-> -- outgoing edge with variable + label
<-[t:Transfers]- -- incoming edge
-[t:Transfers]- -- undirected edge
-> -- abbreviated outgoing (no variable/label)
<- -- abbreviated incoming
Full Patterns
Chain nodes and edges to describe graph traversals:
-- Person sends a transfer to another person
(a:Person)-[t:Transfers]->(b:Person)
-- Multi-hop: person transfers through an intermediary
(a:Person)-[t1:Transfers]->(mid:Person)-[t2:Transfers]->(b:Person)
-- Multiple separate patterns
(a:Person)-[t:Transfers]->(b:Person), (b)-[o:Owns]->(acc:Account)
Linear Query Structure
A GQL query starts with GRAPH, followed by one or more statements, and ends with RETURN:
GRAPH FinGraph
MATCH (a:Person)-[t:Transfers]->(b:Person)
OPTIONAL MATCH (b)-[o:Owns]->(acc:Account)
FILTER t.amount > 100
LET sender_name = a.name
RETURN sender_name, b.name AS receiver, t.amount
| Statement | Purpose |
|---|---|
MATCH | Required. Define the graph pattern to find |
OPTIONAL MATCH | Like MATCH, but keeps rows with no match (unmatched columns become NULL) |
FILTER [WHERE] | Filter rows by a boolean condition (evaluated after the previous statement) |
LET | Bind scalar expressions to named variables for use later in the same query |
WITH | Pass specified columns forward, optionally renaming or aggregating them |
FOR ... IN | Unnest an array column, with optional WITH OFFSET |
ORDER BY | Sort the working table (must be immediately followed by LIMIT or OFFSET) |
LIMIT | Limit the number of rows |
OFFSET / SKIP | Skip a specified number of rows |
NEXT | Chain multiple linear query statements together (output of one feeds next) |
RETURN | Required as last statement. Define output columns |
FILTER [WHERE] and WHERE inside a MATCH pattern are different: the FILTER statement is evaluated after the previous step, while WHERE inside MATCH (... WHERE ...) is evaluated as part of the pattern match.
Chaining with NEXT
Use NEXT to chain multiple linear query statements. The RETURN of the first becomes the input of the next:
GRAPH FinGraph
MATCH (:Account)-[:Transfers]->(account:Account)
RETURN account, COUNT(*) AS num_incoming
GROUP BY account
NEXT
MATCH (account:Account)<-[:Owns]-(owner:Person)
RETURN account.id AS account_id, owner.name AS owner_name, num_incoming
GRAPH_TABLE Syntax
An alternative way to embed GQL inside standard SQL using GRAPH_TABLE:
SELECT *
FROM GRAPH_TABLE(
FinGraph
MATCH (a:Person)-[t:Transfers]->(b:Person)
RETURN a.name AS sender, b.name AS receiver, t.amount
)
WHERE amount > 500
ORDER BY amount DESC
Use this to combine graph results with ORDER BY, LIMIT, or JOINs against non-graph tables.
Property Graph Schema
A property graph is defined with CREATE PROPERTY GRAPH, declaring which tables are nodes and edges:
CREATE PROPERTY GRAPH FinGraph
NODE TABLES (
Person KEY (id)
LABEL Person PROPERTIES (id, name, age),
Account KEY (id)
LABEL Account PROPERTIES (id, balance)
)
EDGE TABLES (
Transfers KEY (transfer_id)
SOURCE KEY (source_id) REFERENCES Person (id)
DESTINATION KEY (dest_id) REFERENCES Person (id)
LABEL Transfers PROPERTIES (transfer_id, amount, date),
Owns KEY (account_id)
SOURCE KEY (owner_id) REFERENCES Person (id)
DESTINATION KEY (account_id) REFERENCES Account (id)
LABEL Owns PROPERTIES (since)
);
- NODE TABLES — each entry defines a base table, key column, label, and exposed properties
- EDGE TABLES — each entry defines a base table, key column, source/destination references, label, and properties
- Labels — used in MATCH patterns to reference node/edge types
- Properties — columns available via dot notation (e.g.,
a.name)
Schema-time Aggregates: MEASURE Properties
MEASURE(<aggregate>) AS <alias> declares a per-key aggregate property as part of a graph element's schema. The aggregate is implicitly grouped by the table's KEY, so you don't repeat that grouping every time you want the rolled-up value.
CREATE PROPERTY GRAPH UniversityGraph
NODE TABLES (
Department KEY (dept_id)
LABEL Department PROPERTIES (
dept_id,
dept_name,
budget,
MEASURE(SUM(budget)) AS total_budget,
MEASURE(COUNT(*)) AS dept_count
),
Course KEY (course_id)
LABEL Course PROPERTIES (
course_id,
course_name,
enrollment,
MEASURE(AVG(enrollment)) AS avg_enrollment,
MEASURE(MAX(enrollment)) AS max_enrollment
)
);
Allowed inside MEASURE(...): SUM, AVG, COUNT (including COUNT(DISTINCT col)), MIN, MAX. The alias after AS is required.
MEASURE is a BigQuery preview feature. Behavior may change before GA.
Reading Measures: GRAPH_EXPAND + AGG
Measures cannot be referenced from GQL MATCH/RETURN. Access them at query time via the GRAPH_EXPAND table-valued function and the AGG wrapper. GRAPH_EXPAND flattens the graph and exposes every property as a column named <Label>_<column>:
SELECT
Department_dept_name,
AGG(Department_total_budget) AS dept_budget,
AGG(Course_avg_enrollment) AS avg_class_size
FROM GRAPH_EXPAND("UniversityGraph")
GROUP BY Department_dept_name
ORDER BY dept_budget DESC
Querylab.io recognises both GRAPH_EXPAND and AGG, resolves the named graph from your project, and offers Department_total_budget-style column completions inside the SELECT and GROUP BY clauses.
IDE Help for MEASURE
Inside PROPERTIES (...), typing M offers MEASURE(SUM(column)) AS ... snippets, one per allowed aggregate. Hover on MEASURE shows the user-declared aggregate and alias (MEASURE SUM → total_budget) plus the access-pattern reminder. Querylab.io flags scalar expressions, window functions like SUM(x) OVER (...), and nested MEASURE(MEASURE(...)) while you type, and rejects aggregates outside the SUM/AVG/COUNT/MIN/MAX allowlist.
GQL ↔ SQL Conversion
Convert between standard SQL with JOINs and GQL GRAPH_TABLE syntax.
In the Editor
Right-click (or use the quick-fix lightbulb) on a query to see conversion options:
- "Convert to GQL GRAPH_TABLE syntax" — appears on SELECT statements with at least one JOIN
- "Convert GRAPH_TABLE to standard SQL JOIN" — appears inside GRAPH_TABLE clauses
The converter uses the property graph schema to map:
- FROM/JOIN tables → node and edge labels
- JOIN ON conditions → edge SOURCE/DESTINATION references
- WHERE conditions → FILTER clauses
- SELECT list → RETURN columns
Online Converter
GQL ↔ SQL Converter — paste SQL or GQL, edit the schema DDL, convert in either direction. No login.
What Converts
| SQL Pattern | GQL Equivalent |
|---|---|
FROM t1 JOIN t2 ON ... | MATCH (n1)-[e]->(n2) |
LEFT JOIN | OPTIONAL MATCH |
WHERE condition | FILTER condition |
SELECT col AS alias | RETURN col AS alias |
| Multi-JOIN chains | Multi-hop patterns |
Limitations
- Only simple SELECT statements (no CTEs, subqueries, or set operations)
- Requires a property graph schema to map tables to labels
- ORDER BY and LIMIT are stripped during conversion
Graph Result Visualization
When a query returns graph elements, a Graph tab appears in the results panel.
When Does the Graph Tab Appear?
The Graph tab activates when query results contain JSON objects with "kind": "node" or "kind": "edge". This happens when you use BigQuery's TO_JSON() on graph element columns:
SELECT TO_JSON(n) AS node_data, TO_JSON(e) AS edge_data, TO_JSON(m) AS target_data
FROM GRAPH_TABLE(
FinGraph
MATCH (n:Person)-[e:Transfers]->(m:Person)
RETURN n, e, m
)
Interacting with the Graph
- Drag nodes to reposition them
- Click a node to see its identifier
- Scroll to zoom in/out
- Pan by dragging the background
- Use the minimap (bottom-right) for overview navigation
- Use controls (bottom-left) for zoom and fit-to-view
Node Cards
Each node displays:
- Label (e.g., "Person") as the header
- Primary property — the first identifying value (id or name)
- Top properties — up to 3 key-value pairs
- Click to expand and see all properties
Edges show their label on the connecting line between nodes.
IDE Features
Auto-Completions
Context-aware completions inside GQL queries:
- After
GRAPH graph_name→ suggestsMATCH,RETURN,FILTER,OPTIONAL MATCH,LET,FOR,WITH - After
MATCH (pattern)→ suggests next statements - Inside
RETURN→ suggests graph functions and variables - Inside
FILTER→ suggests predicates and operators - Inside node
()and edge[]patterns → suggests label separator: - Inside
CREATE PROPERTY GRAPH ... PROPERTIES (...)→ suggests source-table columns plusMEASURE(SUM(column)) AS …snippets (one per allowed aggregate: SUM/AVG/COUNT/MIN/MAX) - Inside
SELECT ... FROM GRAPH_EXPAND("g")→ suggests<Label>_<column>columns from the named graph (regular and measure properties)
Diagnostics
Real-time error checking for GQL queries:
| Error | What it Means |
|---|---|
| RETURN must be last statement | GQL linear queries must end with RETURN |
| Missing NODE TABLES | CREATE PROPERTY GRAPH requires at least NODE TABLES |
| Missing SOURCE/DESTINATION KEY | Edge tables must declare both key references |
| Empty RETURN | RETURN must have at least one expression |
MEASURE() not an aggregate | MEASURE(...) must wrap one of SUM, AVG, COUNT, MIN, MAX |
MEASURE() window disallowed | MEASURE(...) cannot wrap a function with an OVER clause |
MEASURE() cannot be nested | MEASURE(MEASURE(...)) is rejected |
Examples
Basic Graph Query
GRAPH FinGraph
MATCH (p:Person)
RETURN p.name, p.age
Find All Transfers Over $500
GRAPH FinGraph
MATCH (sender:Person)-[t:Transfers]->(receiver:Person)
FILTER t.amount > 500
RETURN sender.name, receiver.name, t.amount
Multi-Hop Path
GRAPH FinGraph
MATCH (a:Person)-[t1:Transfers]->(b:Person)-[t2:Transfers]->(c:Person)
RETURN a.name AS origin, c.name AS destination, t1.amount + t2.amount AS total
GRAPH_TABLE with Standard SQL
SELECT sender, SUM(amount) AS total_sent
FROM GRAPH_TABLE(
FinGraph
MATCH (s:Person)-[t:Transfers]->(r:Person)
RETURN s.name AS sender, t.amount AS amount
)
GROUP BY sender
ORDER BY total_sent DESC
LIMIT 10
Graph Visualization Query
To see results in the Graph tab, return graph elements as JSON:
SELECT TO_JSON(n) AS node, TO_JSON(e) AS edge, TO_JSON(m) AS target
FROM GRAPH_TABLE(
FinGraph
MATCH (n:Person)-[e:Transfers]->(m:Person)
RETURN n, e, m
)