Skip to main content

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
StatementPurpose
MATCHRequired. Define the graph pattern to find
OPTIONAL MATCHLike MATCH, but keeps rows with no match (unmatched columns become NULL)
FILTER [WHERE]Filter rows by a boolean condition (evaluated after the previous statement)
LETBind scalar expressions to named variables for use later in the same query
WITHPass specified columns forward, optionally renaming or aggregating them
FOR ... INUnnest an array column, with optional WITH OFFSET
ORDER BYSort the working table (must be immediately followed by LIMIT or OFFSET)
LIMITLimit the number of rows
OFFSET / SKIPSkip a specified number of rows
NEXTChain multiple linear query statements together (output of one feeds next)
RETURNRequired as last statement. Define output columns
note

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.

Preview feature

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 PatternGQL Equivalent
FROM t1 JOIN t2 ON ...MATCH (n1)-[e]->(n2)
LEFT JOINOPTIONAL MATCH
WHERE conditionFILTER condition
SELECT col AS aliasRETURN col AS alias
Multi-JOIN chainsMulti-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 → suggests MATCH, 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 plus MEASURE(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:

ErrorWhat it Means
RETURN must be last statementGQL linear queries must end with RETURN
Missing NODE TABLESCREATE PROPERTY GRAPH requires at least NODE TABLES
Missing SOURCE/DESTINATION KEYEdge tables must declare both key references
Empty RETURNRETURN must have at least one expression
MEASURE() not an aggregateMEASURE(...) must wrap one of SUM, AVG, COUNT, MIN, MAX
MEASURE() window disallowedMEASURE(...) cannot wrap a function with an OVER clause
MEASURE() cannot be nestedMEASURE(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
)