Spanner Graph queries overview

Note: This feature is available with the Spanner Enterprise edition and Enterprise Plus edition. For more information, see theSpanner editions overview.

This document provides an overview using the graph query language withSpanner Graph, including its syntax for graph pattern matching,and shows you how to run queries against your graph. WithSpanner Graph, you can run queries to find patterns, traverserelationships, and gain insights from your property graph data.

The examples in this document use the graph schema that you create inSet up and query Spanner Graph. This schema isillustrated in the following diagram:

An example of a Spanner Graph schema.
Figure 1.: An example of a Spanner Graph schema.

Run a Spanner Graph query

You can use the Google Cloud console, Google Cloud CLI, client libraries, the RESTAPI, or the RPC API to run a Spanner Graph query.

Google Cloud console

The following steps show you how to run a query in theGoogle Cloud console. These steps assume you have an instance namedtest-instance that contains a database namedexample-db. For informationabout how to create an instance with a database, seeSet up and query Spanner Graph.

  1. In the Google Cloud console, go to theSpanner Instances page.

    Go to Spanner instances

  2. Click the instance namedtest-instance.

  3. UnderDatabases, click the database namedexample-db.

  4. Open theSpanner Studio, then clickNew tab or use theeditor tab.

  5. Enter a query into the query editor.

  6. ClickRun.

gcloud CLI

To submit queries using thegcloud CLI command-line tool, dothe following:

  1. If it's not already installed,install the gcloud CLI.

  2. In the gcloud CLI, run the following command:

    gcloud spanner databases execute-sql

For more information, seeSpanner CLI quickstart.

REST API

To submit queries using the REST API, use one of the following commands:

For more information, seeQuery data using the REST APIandGet started with Spanner using REST.

RPC API

To submit queries using the RPC API, use one of the following commands:

Client libraries

To learn more about how to run a query with a Spanner clientlibrary, see the following:

For more information about the Spanner client libraries, see theSpanner client libraries overview.

Visualize Spanner Graph query results

You can view a visual representation of your Spanner Graph query results inSpanner Studio in the Google Cloud console. A query visualization lets yousee how the returned elements (nodes and edges) are connected. This can revealpatterns, dependencies, and anomalies that are difficult to see when you viewthe results in a table. To view a visualization of a query, the query mustreturn full nodes in JSON format. Otherwise, you can see the query results inonly tabular format. For more information, seeUse Spanner Graph query visualizations.

Spanner Graph query structure

A Spanner Graph query consists of several components, such as the propertygraph name, node and edge patterns, and quantifiers. You use these components tocreate a query that finds specific patterns in your graph. Each component isdescribed in theGraph pattern matching section of this document.

The query in Figure 2 demonstrates the basic structure of aSpanner Graph query. The query starts by specifying the targetgraph,FinGraph, using theGRAPH clause. TheMATCH clause then defines thepattern to search for. In this case, it's aPerson node connected to anAccount node through anOwns edge. TheRETURN clause specifies whichproperties of the matched nodes to return.

Example Spanner Graph query structure.
Figure 2.: An example of the structure of a Spanner Graph query.

Graph pattern matching

Graph pattern matching finds specific patterns within your graph. The most basicpatterns are element patterns, such as node patterns that match nodes and edgepatterns that match edges.

Node patterns

A node pattern matches nodes in your graph. This pattern contains matchingparentheses, which might optionally include a graph pattern variable, a labelexpression, and property filters.

Find all nodes

The following query returns all nodes in the graph. The variablen, a graphpattern variable, binds to the matching nodes. In this case, the node patternmatches all nodes in the graph.

GRAPHFinGraphMATCH(n)RETURNLABELS(n)ASlabel,n.id;

This query returnslabel andid:

labelid
Account7
Account16
Account20
Person1
Person2
Person3

Find all nodes with a specific label

The following query matches all nodes in the graph that have thePersonlabel.The query returns thelabel and theid,name properties of the matchednodes.

GRAPHFinGraphMATCH(p:Person)RETURNLABELS(p)ASlabel,p.id,p.name;

This query returns the following properties of the matched nodes:

labelidname
Person1Alex
Person2Dana
Person3Lee

Find all nodes matching a label expression

You can create alabel expressionwith one or more logical operators. For example, the following query matches allnodes in the graph that have either thePerson orAccount label. The graphpattern variablen exposes allpropertiesfrom nodes with thePerson orAccount label.

GRAPHFinGraphMATCH(n:Person|Account)RETURNLABELS(n)ASlabel,n.id,n.birthday,n.create_time;

In the following results of this query:

  • All nodes have theid property.
  • Nodes matching theAccount label have thecreate_time property, butdon't have thebirthday property. Thebirthday property isNULL forthese nodes.
  • Nodes matching thePerson label have thebirthday property, but don'thave thecreate_time property. Thecreate_time property isNULL forthese nodes.
labelidbirthdaycreate_time
Account7NULL2020-01-10T14:22:20.222Z
Account16NULL2020-01-28T01:55:09.206Z
Account20NULL2020-02-18T13:44:20.655Z
Person11991-12-21T08:00:00ZNULL
Person21980-10-31T08:00:00ZNULL
Person31986-12-07T08:00:00ZNULL
Note:NULL doesn't necessarily indicate the absence of a property, because anelement can expose a property with aNULL value. To check the existence of aproperty, use thePROPERTY_EXISTS predicate.

Find all nodes matching the label expression and property filter

This query matches all nodes in the graph that have thePerson label and wherethe propertyid is equal to1.

GRAPHFinGraphMATCH(p:Person{id:1})RETURNLABELS(p)ASlabel,p.id,p.name,p.birthday;

Here are the query results:

labelidnamebirthday
Person1Alex1991-12-21T08:00:00Z

You can use theWHERE clause to form more complex filtering conditions onlabels and properties.

The following query uses theWHERE clause to form a more complex filteringcondition on properties. It matches all nodes in the graph that have thePerson label, and the propertybirthday is before1990-01-10.

GRAPHFinGraphMATCH(p:PersonWHEREp.birthday <'1990-01-10')RETURNLABELS(p)ASlabel,p.name,p.birthday;

Here are the query results:

labelnamebirthday
PersonDana1980-10-31T08:00:00Z
PersonLee1986-12-07T08:00:00Z

Edge patterns

An edge pattern matches edges or relationships between nodes. Edge patterns areenclosed in square brackets ([]) and include symbols such as-,->, or<- to indicate directions. An edge pattern might optionally include a graphpattern variable to bind to matching edges.

Find all edges with matching labels

This query returns all edges in the graph with theTransfers label. The querybinds the graph pattern variablee to the matching edges.

GRAPHFinGraphMATCH-[e:Transfers]->RETURNe.Idassrc_account,e.order_number

Here are the query results:

src_accountorder_number
7304330008004315
7304120005529714
16103650009791820
20304120005529714
20302290001255747

Find all edges matching the label expression and property filter

This query's edge pattern uses a label expression and a property filter to findall edges labeled withTransfers that match a specifiedorder_number.

GRAPHFinGraphMATCH-[e:Transfers{order_number:"304120005529714"}]->RETURNe.IdASsrc_account,e.order_number

Here are the query results:

src_accountorder_number
7304120005529714
20304120005529714

Find all edges using any direction edge pattern

You can use theany direction edge pattern (-[]-) in a query to match edgesin either direction. The following query finds all transfers with a blockedaccount.

GRAPHFinGraphMATCH(account:Account)-[transfer:Transfers]-(:Account{is_blocked:true})RETURNtransfer.order_number,transfer.amount;

Here are the query results:

order_numberamount
304330008004315300
304120005529714100
103650009791820300
302290001255747200

Path patterns

A path pattern is built from alternating node and edge patterns.

Find all paths from a specific node using a path pattern

The following query finds all transfers to an account initiated from an accountowned byPerson withid equal to2.

Each matched result represents a path fromPerson{id: 2} through aconnectedAccount using theOwns edge, into anotherAccount using theTransfers edge.

GRAPHFinGraphMATCH(p:Person{id:2})-[:Owns]->(account:Account)-[t:Transfers]->(to_account:Account)RETURNp.idASsender_id,account.idASfrom_id,to_account.idASto_id;

Here are the query results:

sender_idfrom_idto_id
2207
22016

Quantified path patterns

A quantified pattern repeats a pattern within a specified range.

Match a quantified edge pattern

To find paths of a variable length, you can apply a quantifier to an edgepattern. The following query demonstrates this by finding destination accountsthat are one to three transfers away from a sourceAccount with anid of7.

The query applies the quantifier{1, 3} to the edge pattern-[e:Transfers]->. This instructs the query to match paths that repeat theTransfers edge pattern one, two, or three times. TheWHERE clause is used toexclude the source account from the results. TheARRAY_LENGTH function is usedto access thegroup variablee. For more information,seeaccess group variable.

GRAPHFinGraphMATCH(src:Account{id:7})-[e:Transfers]->{1,3}(dst:Account)WHEREsrc!=dstRETURNsrc.idASsrc_account_id,ARRAY_LENGTH(e)ASpath_length,dst.idASdst_account_id;

Here are the query results:

src_account_idpath_lengthdst_account_id
7116
7116
7316
7316
7220
7220

Some rows in the results are repeated. This is because multiple paths that matchthe pattern can exist between the same source and destination nodes, and thequery returns all of them.

Match a quantified path pattern

The following query finds paths betweenAccount nodes with one to twoTransfers edges through intermediate accounts that are blocked.

The parenthesized path pattern is quantified, and itsWHERE clause specifiesconditions for the repeated pattern.

GRAPHFinGraphMATCH(src:Account)((a:Account)-[:Transfers]->(b:Account{is_blocked:true})WHEREa!=b){1,2}-[:Transfers]->(dst:Account)RETURNsrc.idASsrc_account_id,dst.idASdst_account_id;

Here are the query results:

src_account_iddst_account_id
720
720
2020

Group variables

A graph pattern variable declared in a quantified pattern becomes agroupvariable when accessed outside that pattern. It then binds to an array ofmatched graph elements.

You can access a group variable as an array. Its graph elements are preserved inthe order of their appearance along the matched paths. You can aggregate a groupvariable usinghorizontal aggregation.

Access group variable

In the following example, the variablee is accessed as follows:

  • A graph pattern variable bound to a single edge in theWHERE clausee.amount > 100 when it's within the quantified pattern.
  • A group variable bound to an array of edge elements inARRAY_LENGTH(e) intheRETURN statement when it's outside the quantified pattern.
  • A group variable bound to an array of edge elements, which is aggregated bySUM(e.amount) outside the quantified pattern. This is an example ofhorizontal aggregation.
GRAPHFinGraphMATCH(src:Account{id:7})-[e:TransfersWHEREe.amount >100]->{0,2}(dst:Account)WHEREsrc.id!=dst.idLETtotal_amount=SUM(e.amount)RETURNsrc.idASsrc_account_id,ARRAY_LENGTH(e)ASpath_length,total_amount,dst.idASdst_account_id;

Here are the query results:

src_account_idpath_lengthtotal_amountdst_account_id
7130016
7260020

Path search prefixes

To limit matched paths within groups that share source and destination nodes,you can use theANY,ANY SHORTEST, orANY CHEAPEST pathsearch prefix.You can only apply these prefixes before an entire path pattern, and you can'tapply them inside parentheses.

Match usingANY

The following query finds all reachable unique accounts that are one or twoTransfers away from a givenAccount node.

TheANY path search prefix ensures that the query returns only one pathbetween a unique pair ofsrc anddstAccount nodes. In the followingexample, although you can reach theAccount node with{id: 16} in twodifferent paths from the sourceAccount node, the query returns only one path.

GRAPHFinGraphMATCHANY(src:Account{id:7})-[e:Transfers]->{1,2}(dst:Account)LETids_in_path=ARRAY_CONCAT(ARRAY_AGG(e.Id),[dst.Id])RETURNsrc.idASsrc_account_id,dst.idASdst_account_id,ids_in_path;

Here are the query results:

src_account_iddst_account_idids_in_path
7167,16
7207,16,20

Match usingANY SHORTEST

TheANY SHORTEST path search prefix returns a single path for each pair ofsource and destination nodes, selected from those with the minimum number ofedges.

For example, the following query finds one of the shortest paths between anAccount node withid7 and anAccount node with anid of20. Thequery considers paths with one to threeTransfers edges.

GRAPHFinGraphMATCHANYSHORTEST(src:Account{id:7})-[e:Transfers]->{1,3}(dst:Account{id:20})RETURNsrc.idASsrc_account_id,dst.idASdst_account_id,ARRAY_LENGTH(e)ASpath_length;

Here are the query results:

src_account_iddst_account_idpath_length
7202

Match usingANY CHEAPEST

TheANY CHEAPEST path search prefix ensures that for each pair of source anddestination accounts, the query returns only one path with the minimum totalcompute cost.

The following query finds a path with the minimum total compute cost betweenAccount nodes. This cost is based on the sum of theamount property of theTransfers edges. The search considers paths with one to threeTransfersedges.

GRAPHFinGraphMATCHANYCHEAPEST(src:Account)-[e:TransfersCOSTe.amount]->{1,3}(dst:Account)LETtotal_cost=sum(e.amount)RETURNsrc.idASsrc_account_id,dst.idASdst_account_id,total_cost

Here are the query results:

src_account_iddst_account_idtotal_cost
77900
716100
720400
167800
1616500
1620300
207500
2016200
2020500

Graph patterns

A graph pattern consists of one or more path patterns, separated by a comma (,).Graph patterns can contain aWHERE clause, which lets you access all the graphpattern variables in the path patterns to form filtering conditions. Each pathpattern produces a collection of paths.

Match using a graph pattern

The following query identifies intermediary accounts and their owners involvedin transactions amounts exceeding 200, through which funds are transferred froma source account to a blocked account.

The following path patterns form the graph pattern:

  • The first pattern finds paths where the transfer occurs from one account toa blocked account using an intermediate account.
  • The second pattern finds paths from an account to its owning person.

The variableinterm acts as a common link between the two path patterns, whichrequiresinterm to reference the same element node in both path patterns. Thiscreates an equi-join operation based on theinterm variable.

Note: If there is no shared variable among the path patterns, across join isperformed between the collection of matches for each path pattern.
GRAPHFinGraphMATCH(src:Account)-[t1:Transfers]->(interm:Account)-[t2:Transfers]->(dst:Account),(interm)<-[:Owns]-(p:Person)WHEREdst.is_blocked=TRUEANDt1.amount >200ANDt2.amount >200RETURNsrc.idASsrc_account_id,dst.idASdst_account_id,interm.idASinterm_account_id,p.idASowner_id;

Here are the query results:

src_account_iddst_account_idinterm_account_idowner_id
201671

Linear query statements

You can chain multiple graph statements together to form a linear querystatement. The statements are executed in the same order as they appear in thequery.

  • Each statement takes the output from the previous statement as input. Theinput is empty for the first statement.

  • The output of the last statement is the final result.

For example, you can use linear query statements to find the maximum transfer toa blocked account. The following query finds the account and its owner with thelargest outgoing transfer to a blocked account.

GRAPHFinGraphMATCH(src_account:Account)-[transfer:Transfers]->(dst_account:Account{is_blocked:true})ORDERBYtransfer.amountDESCLIMIT1MATCH(src_account:Account)<-[owns:Owns]-(owner:Person)RETURNsrc_account.idASaccount_id,owner.nameASowner_name;

The following table illustrates this process by showing the intermediate resultspassed between each statement. For brevity, only some properties are shown.

StatementIntermediate result (abbreviated)
MATCH  (src_account:Account)    -[transfer:Transfers]->  (dst_account:Account {is_blocked:true})
src_accounttransferdst_account
{id: 7}{amount: 300.0}{id: 16, is_blocked: true}
{id: 7}{amount: 100.0}{id: 16, is_blocked: true}
{id: 20}{amount: 200.0}{id: 16, is_blocked: true}

ORDER BY transfer.amount DESC
src_accounttransferdst_account
{id: 7}{amount: 300.0}{id: 16, is_blocked: true}
{id: 20}{amount: 200.0}{id: 16, is_blocked: true}
{id: 7}{amount: 100.0}{id: 16, is_blocked: true}

LIMIT 1
src_accounttransferdst_account
{id: 7}{amount: 300.0}{id: 16, is_blocked: true}

MATCH  (src_account:Account)    <-[owns:Owns]-  (owner:Person)
src_accounttransferdst_accountownsowner
{id: 7}{amount: 300.0}{id: 16, is_blocked: true}{person_id: 1, account_id: 7}{id: 1, name: Alex}
RETURN  src_account.id AS account_id,  owner.name AS owner_name
account_idowner_name
7Alex

Here are the query results:

account_idowner_name
7Alex

Return statement

TheRETURN statementspecifies what to return from the matched patterns. It can accessgraph pattern variables and include expressions and other clauses, such asORDER BY andGROUP BY.

Spanner Graph doesn't support returning graph elements as query results. Toreturn the entire graph element, use theTO_JSON functionorSAFE_TO_JSON function.Of these two functions, we recommend that you useSAFE_TO_JSON.

Return graph elements as JSON

GRAPHFinGraphMATCH(n:Account{id:7})-- Returning a graph element in the final results is NOT allowed. Instead, use-- the TO_JSON function or explicitly return the graph element's properties.RETURNTO_JSON(n)ASn;
GRAPHFinGraphMATCH(n:Account{id:7})-- Certain fields in the graph elements, such as TOKENLIST, can't be returned-- in the TO_JSON function. In those cases, use the SAFE_TO_JSON function instead.RETURNSAFE_TO_JSON(n)ASn;

Here are the query results:

n
{"identifier":"mUZpbkdyYXBoLkFjY291bnQAeJEO","kind":"node","labels":["Account"],"properties":{"create_time":"2020-01-10T14:22:20.222Z","id":7,"is_blocked":false,"nick_name":"VacationFund"}}

Composing larger queries with NEXT keyword

You can chain multiple graph linear query statements using theNEXT keyword.The first statement receives an empty input, and the output of each subsequentstatement becomes the input for the next.

The following example finds the owner of the account with the most incomingtransfers by chaining multiple graph linear statements. You can use the samevariable, for example,account, to refer to the same graph element acrossmultiple linear statements.

GRAPHFinGraphMATCH(:Account)-[:Transfers]->(account:Account)RETURNaccount,COUNT(*)ASnum_incoming_transfersGROUPBYaccountORDERBYnum_incoming_transfersDESCLIMIT1NEXTMATCH(account:Account)<-[:Owns]-(owner:Person)RETURNaccount.idASaccount_id,owner.nameASowner_name,num_incoming_transfers;

Here are the query results:

account_idowner_namenum_incoming_transfers
16Lee3

Functions and expressions

You can use all GoogleSQLfunctions (both aggregateand scalar functions),operators, andconditional expressionsin Spanner Graph queries. Spanner Graph also supports graph-specificfunctions and operators.

Built-in functions and operators

The followingfunctions andoperators are used inGQL:

  • PROPERTY_EXISTS(n, birthday): Returns whethern has thebirthdayproperty.
  • LABELS(n): Returns the labels ofn as defined in the graph schema.
  • PROPERTY_NAMES(n): Returns the property names ofn.
  • TO_JSON(n): Returnsn in JSON format. For more information, see theTO_JSON function.

thePROPERTY_EXISTS predicate,LABELS function, andTO_JSON function, aswell as other built-in functions likeARRAY_AGG andCONCAT.

GRAPHFinGraphMATCH(person:Person)-[:Owns]->(account:Account)RETURNperson,ARRAY_AGG(account.nick_name)ASaccountsGROUPBYpersonNEXTRETURNLABELS(person)ASlabels,TO_JSON(person)ASperson,accounts,CONCAT(person.city,", ",person.country)ASlocation,PROPERTY_EXISTS(person,is_blocked)ASis_blocked_property_exists,PROPERTY_EXISTS(person,name)ASname_property_existsLIMIT1;

Here are the query results:

is_blocked_property_existsname_property_existslabelsaccountslocationperson
falsetruePerson["Vacation Fund"]Adelaide, Australia{"identifier":"mUZpbkdyYXBoLlBlcnNvbgB4kQI=","kind":"node","labels":["Person"],"properties":{"birthday":"1991-12-21T08:00:00Z","city":"Adelaide","country":"Australia","id":1,"name":"Alex"}}

Subqueries

Asubquery is a query nested in another query. The following listsSpanner Graph subquery rules:

  • A subquery is enclosed within a pair of braces{}.
  • A subquery might start with the leadingGRAPH clause to specify thegraph in scope. The specified graph doesn't need to be the same as the oneused in the outer query.
  • When theGRAPH clause is omitted in the subquery, the following occurs:
    • The graph in scope is inferred from the closest outer query context.
    • The subquery must start from a graph pattern matching statementwithMATCH.
  • A graph pattern variable declared outside the subquery scope can't bedeclared again inside the subquery, but it can be referred to inexpressions or functions inside the subquery.

Use a subquery to find the total number of transfers from each account

The following query illustrates the use of theVALUE subquery. The subquery isenclosed in braces{} prefixed by theVALUE keyword. The query returns the totalnumber of transfers initiated from an account.

GRAPHFinGraphMATCH(p:Person)-[:Owns]->(account:Account)RETURNp.name,account.idASaccount_id,VALUE{MATCH(a:Account)-[transfer:Transfers]->(:Account)WHEREa=accountRETURNCOUNT(transfer)ASnum_transfers}ASnum_transfers;

Here are the query results:

nameaccount_idnum_transfers
Alex72
Dana202
Lee161

For a list of supported subquery expressions, seeSpanner Graph subqueries.

Use a subquery to find accounts owned by each person

The following query uses theCALL statement with an inline subquery. TheMATCH(p:Person) statement creates a table with a single column namedp.Each row in this table contains aPerson node. TheCALL (p) statementexecutes the enclosed subquery for each row in this working table. The subqueryfinds accounts owned by each matched personp. Multiple accounts for the sameperson are ordered by account ID.

The example declares the outer-scoped node variablep from theMATCH(p:Person) clause. TheCALL (p) statement references this variable. Thisdeclaration lets you redeclare ormultiply-declare the node variable in a pathpattern of the subquery. This ensures that the inner and outerp nodevariables bind to the samePerson node in the graph. If theCALL statementdoesn't declare the node variablep, the subquery treats the redeclaredvariablep as a new variable. This new variable is independent of theouter-scoped variable, and the subquery doesn'tmultiply-declare it becauseit returns different results. For more information, seeCALLstatement.

GRAPHFinGraphMATCH(p:Person)CALL(p){MATCH(p)-[:Owns]->(a:Account)RETURNa.IdASaccount_IdORDERBYaccount_Id}RETURNp.nameASperson_name,account_IdORDERBYperson_name,account_Id;

Result

person_nameaccount_Id
Alex7
Dana20
Lee16

Query parameters

You can query Spanner Graph with parameters. For more information, see thesyntax andlearn how toquery data with parametersin the Spanner client libraries.

The following query illustrates the use of query parameters.

GRAPHFinGraphMATCH(person:Person{id:@id})RETURNperson.name;

Query graphs and tables together

You can use Graph queries in conjunction with SQL to access information fromyour Graphs and Tables together in a single statement.

TheGRAPH_TABLE operator takes a linear graph query and returns its result ina tabular form that can be integrated into a SQL query. This interoperabilitylets you enrich graph query results with non-graph content and the other wayaround.

For example, you can create aCreditReports table and insert a few creditreports, as shown in the following example:

CREATETABLECreditReports(person_idINT64NOTNULL,create_timeTIMESTAMPNOTNULL,scoreINT64NOTNULL,)PRIMARYKEY(person_id,create_time);
INSERTINTOCreditReports(person_id,create_time,score)VALUES(1,"2020-01-10 06:22:20.222",700),(2,"2020-02-10 06:22:20.222",800),(3,"2020-03-10 06:22:20.222",750);

Next, you can identify specific persons through graph pattern matching inGRAPH_TABLE and join the graph query results with theCreditReports table toretrieve credit scores.

SELECTgt.person.id,credit.scoreASlatest_credit_scoreFROMGRAPH_TABLE(FinGraphMATCH(person:Person)-[:Owns]->(:Account)-[:Transfers]->(account:Account{is_blocked:true})RETURNDISTINCTperson)ASgtJOINCreditReportsAScreditONgt.person.id=credit.person_idORDERBYcredit.create_time;

Here are the query results:

person_idlatest_credit_score
1700
2800

What's next

Learnbest practices for tuning queries.

Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2026-02-19 UTC.