Docs
Docs /SQL Editor /Graph queries
SQL EditorGQL

Graph queries

BigQuery property graphs let you write a JOIN chain as a pattern of nodes and edges in GQL. Querylab.io parses GQL, completes labels and properties from your graph's schema, converts JOIN queries to GRAPH_TABLE and back, and draws node-edge results in a Graph tab.

Query 1 · GQL
1234
GRAPH FinGraph
MATCH (sender:Person)-[t:Transfers]->(receiver:Person)
FILTER t.amount > 500
RETURN sender.name AS sender, receiver.name AS receiver, t.amount

Everything on this page is BigQuery’s own syntax (GRAPH … MATCH … RETURN as a standalone statement, GRAPH_TABLE(…) inside SQL, and CREATE PROPERTY GRAPH) except where marked as a Querylab.io feature: the converter, the completions, and the Graph tab. MEASURE properties and GRAPH_EXPAND are a BigQuery preview feature.

The query above is the same as this JOIN, and both return the same rows:

the same query as JOINs
12345
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

Writing a pattern

A node is parentheses, an edge is brackets with an arrow. Each can carry a variable, a label, both, or neither.

12345678910
(a:Person)                    -- variable and label
(:Person)                     -- label only
(a IS Person)                 -- IS instead of colon
(a:Person WHERE a.age > 30)   -- inline filter, evaluated as part of the match
()                            -- any node

-[t:Transfers]->              -- outgoing edge
<-[t:Transfers]-              -- incoming edge
-[t:Transfers]-               -- either direction
->                            -- outgoing, anonymous

Chain them for multi-hop paths, and separate patterns with commas:

1234
GRAPH FinGraph
MATCH (a:Person)-[t1:Transfers]->(mid:Person)-[t2:Transfers]->(b:Person),
    (b)-[o:Owns]->(acc:Account)
RETURN a.name AS origin, b.name AS destination, t1.amount + t2.amount AS total, acc.id

Building a linear query from statements

A linear query is GRAPH name, one or more statements, and a RETURN at the end.

StatementPurpose
MATCHThe pattern to find. Required
OPTIONAL MATCHKeeps rows with no match; unmatched variables are NULL
FILTER [WHERE] condFilters the working table after the previous statement
LET name = exprBinds a scalar for later statements
WITH colsPasses named columns forward, renaming or aggregating
FOR x IN array [WITH OFFSET]Unnests an array column
ORDER BYSorts; must be followed by LIMIT or OFFSET
LIMIT / OFFSET / SKIPPaging
RETURNOutput columns. Required, and last
NEXTChains linear queries: the RETURN of one is the input of the next

FILTER and an inline WHERE differ: MATCH (a:Person WHERE a.age > 30) is part of the match; FILTER a.age > 30 runs on the rows the match produced.

NEXT
1234567
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

Using a graph query inside SQL

GRAPH_TABLE(…) turns a graph query into a table source, so you can aggregate, order, or join it with ordinary tables.

123456789
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

Defining a graph

CREATE PROPERTY GRAPH names the node and edge tables, their keys, labels, and which columns are exposed as properties. Querylab.io validates the statement as you type: an edge table without both SOURCE KEY and DESTINATION KEY, or a graph without NODE TABLES, gets an error squiggle.

123456789101112131415
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)
);

Graphs you’ve created show in the schema tree under their dataset. Opening one gives a details tab with the DDL, node and edge tables, labels, and a connectivity summary.

MEASURE properties

MEASURE(aggregate) AS alias inside PROPERTIES (…) declares a per-key aggregate as part of the schema; the grouping by the table’s KEY is implicit. Allowed aggregates: SUM, AVG, COUNT (including COUNT(DISTINCT …)), MIN, MAX; the alias is required.

1234567891011121314
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, enrollment,
      MEASURE(AVG(enrollment)) AS avg_enrollment
    )
);

Measures can’t be read from MATCH / RETURN. Read them through GRAPH_EXPAND, which flattens the graph into Label_column columns, wrapping each measure in AGG(…):

1234567
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

Inside PROPERTIES (…), typing M offers one MEASURE(SUM(column)) AS … snippet per allowed aggregate; inside SELECT … FROM GRAPH_EXPAND("g") the column list is the graph’s Label_column names. A window function, a nested MEASURE, or any other function inside MEASURE() is an error.

Converting JOINs to GQL and back

Two code actions, in the right-click menu and the quick-fix lightbulb. Both need the graph’s schema, which Querylab.io reads from your project.

  • Convert to GQL GRAPH_TABLE syntax is offered on a SELECT with at least one JOIN whose tables are node or edge tables of a known graph.
  • Convert GRAPH_TABLE to standard SQL JOIN is offered with the cursor inside a GRAPH_TABLE(…), or a standalone GRAPH … query.
before
1234567
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
ORDER BY t.amount DESC
LIMIT 100
after
12345678
SELECT * FROM GRAPH_TABLE(
FinGraph
MATCH (p1:Person)-[t:Transfers]->(p2:Person)
WHERE t.amount > 500
RETURN p1.name AS sender, p2.name AS receiver, t.amount
ORDER BY t.amount DESC
LIMIT 100
)
SQLGQL
FROM t1 JOIN t2 ON …MATCH (n1)-[e]->(n2)
LEFT JOIN / FULL OUTER JOINOPTIONAL MATCH
WHERE condWHERE / FILTER cond
SELECT col AS aliasRETURN col AS alias
ORDER BY, LIMIT, OFFSETCarried through

Set operations, subqueries and PIVOT in the FROM clause, and CTEs other than a recursive path CTE don’t convert; the action isn’t offered. GQL → SQL accepts all four input shapes (bare GRAPH …, GRAPH_TABLE(…), SELECT * FROM GRAPH_TABLE(…), and a SELECT with its own projection) and keeps any trailing WHERE, ORDER BY, LIMIT, OFFSET unchanged.

The same converter, with an editable schema DDL, runs without an account at querylab.io/tools/gql-to-sql.

Completing keywords, labels, and properties

Keyword completions follow the statement grammar: after GRAPH name you get MATCH; after a MATCH you get OPTIONAL MATCH, FILTER, LET, WITH, FOR, ORDER BY, RETURN; inside () and [] you get the label separator, and after RETURN or FILTER the pattern variables.

With Settings → Editor → SQL Editor → Enable Graph Schema Completions on, Querylab.io also fetches property-graph metadata from INFORMATION_SCHEMA, and then labels complete after the colon and properties after a variable’s dot. This needs a BigQuery Enterprise edition.

123
GRAPH FinGraph
MATCH (p:Person)-[t:Transfers]->(q:Person)
RETURN p.
id INT64
name STRING
age INT64

Errors the editor reports

ErrorCause
Linear query statement must end with a RETURN or COLUMNS statementA statement after the last RETURN, or no RETURN
RETURN statement requires at least one expressionEmpty RETURN
Duplicate column alias in RETURNTwo output columns with the same name
CREATE PROPERTY GRAPH requires NODE TABLESOnly EDGE TABLES declared
Edge table definition requires SOURCE KEY and DESTINATION KEYOne of the two missing
MEASURE() requires SUM, AVG, COUNT, MIN, or MAXAny other function, a window function, or a nested MEASURE

Viewing results as a graph

When a result column holds graph elements (the JSON BigQuery produces for TO_JSON(node) or TO_JSON(edge), with "kind": "node" or "kind": "edge"), the result panel adds Mode: Graph next to Table and Json.

123456
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
)
results → Mode: Graph
TransfersTransfersPersonAlexid: 1age: 34PersonDanaid: 3age: 29PersonLeeid: 5age: 41
Each card is a node: label, its identifying value, and up to three properties; click a node to see all of them. Edges carry their label on the line.

Drag nodes to rearrange, scroll to zoom, drag the background to pan. Zoom and fit-to-view controls sit bottom-left; a minimap appears bottom-right once the graph has more than six nodes.