GQL clauses

Switch services using the Version drop-down list. Learn more about navigation.
Applies to: ✅ Azure Data Explorer

This article lists the clauses supported in GQL (Graph Query Language).

The examples use the movies graph G() from Create a graph and set a graph reference: Person nodes (Name, Born), Movie nodes (Title, Description, Year), and ACTED_IN / DIRECTED edges (Role).

WHERE

WHERE filters with a Boolean expression. Place it inside a node, inside an edge, or after the pattern. Inline {property: value} is shorthand for equality.

Filter by properties

Use WHERE after the pattern to compare element properties with operators such as =, <>, >, >=, <, and <=. Combine conditions with AND, OR, and NOT, and group them with parentheses.

match (p:Person)-[:ACTED_IN]->(m:Movie)
where (p.Name = 'Tom' or p.Name = 'Ron') and m.Title <> 'T1' and p.Born > 1901
return p.Name as name, m.Title as title
name title
Tom T3
Tom T2

Negate a condition with NOT

NOT reverses the result of a Boolean condition. Write it as a prefix, NOT (condition), and combine it with AND and OR. Many predicates also accept an inline NOT, such as NOT STARTS WITH, NOT ENDS WITH, NOT CONTAINS, and IS NOT NULL.

match (p:Person)
where not (p.Born < 1960)
return p.Name as name
name
Julia

The following two queries are equivalent. The first uses prefix NOT; the second uses the inline NOT CONTAINS predicate:

match (p:Person)
where not (p.Name contains 'n')
return p.Name as name
name
Tom
Julia
match (p:Person)
where p.Name not contains 'n'
return p.Name
name
Tom
Julia

Inline property filters

An inline {property: value} inside an element pattern is shorthand for an equality test on that property. The following two queries are equivalent:

match (p:Person {Name: 'Tom'})
return p.Name as name, p.Born as born
name born
Tom 1956
match (p:Person)
where p.Name = 'Tom'
return p.Name as name, p.Born as born
name born
Tom 1956

Inline filters apply to edges as well as nodes:

match (p:Person)-[:ACTED_IN {Role: 'Role2'}]->(m:Movie)
return p.Name as name, m.Title as title
name title
Kevin T1

Multiple inline conditions

List several properties in one inline filter to require all of them; the pairs are combined with AND. This pattern matches only Person nodes where both Name is 'Tom' and Born is 1956:

match (p:Person {Name: 'Tom', Born: 1956})
return p.Name as name, p.Born as born
name born
Tom 1956

Apply inline filters to several elements in the same pattern to constrain each node and edge at once:

match (p:Person {Name: 'Kevin'})-[:ACTED_IN {Role: 'Role2'}]->(m:Movie {Title: 'T1'})
return p.Name as name, m.Title as title
name title
Kevin T1

Inline filters and WHERE combine freely: use inline {...} for the equality checks and WHERE for everything else, such as ranges or OR:

match (p:Person {Name: 'Tom'})-[:ACTED_IN]->(m:Movie)
where m.Year >= 1990
return p.Name as name, m.Title as title
name title
Tom T1
Tom T2
Tom T3

Inside a node or edge:

match (p:Person)-[e where 'DIRECTED' in labels(e)]->(m:Movie where m.Title <> 'T1')
return p.Name as name, m.Title as title
name title
Tom T2

The IN predicate tests whether a value is one of the items in a list:

match (p:Person)
where p.Name in ['Tom', 'Kevin']
return p.Name as name
name
Tom
Kevin

To compare entities, use element_id(n) or property comparisons (n.Name <> m.Name); direct entity equality isn't supported.

RETURN

RETURN shapes the output. Project properties, rename with AS, return all bound variables with *, and deduplicate with DISTINCT.

match (p:Person)-[:ACTED_IN]->(m:Movie)
return m.Title as Title, p.Name as Name
Title Name
T3 Tom
T2 Tom
T1 Tom
T1 Kevin

Return every bound variable with *:

match (p:Person)-[:ACTED_IN]->(m:Movie where m.Description starts with 'W')
return *
p m
{"Name": "Tom", .. , , .. ,"Description": "War film","Year": 2020}

DISTINCT

DISTINCT removes duplicate rows from a RETURN. It applies to scalar values; to deduplicate entities (nodes, edges, paths) either use some object identifier or element_id() function or convert the whole entity to a string, for example with to_json_string(object_variable_name) or with CAST(object_variable_name AS string).

Deduplicate projected rows:

match (p:Person)-[:ACTED_IN]->(m:Movie)
return distinct p.Name as name
name
Tom
Kevin

DISTINCT also works inside aggregations, to aggregate over distinct values only:

match (p:Person)-[:ACTED_IN]->(m:Movie)
return m.Title as Title, count(p.Name) as Actors, collect_list(distinct p.Name) as Names
Title Actors Names
T3 1 ["Tom"]
T2 1 ["Tom"]
T1 2 ["Tom","Kevin"]

ORDER BY

ORDER BY sorts rows. Add ASC (default) or DESC per column. It can sit after RETURN or directly after the MATCH pattern.

match (p:Person) return p.Name as name, p.Born as born order by p.Born desc
name born
Julia 1967
Kevin 1958
Tom 1956
Ron 1954

Sort by multiple keys, each with its own direction:

match (p:Person)-[:ACTED_IN]->(m:Movie)
return p.Name as Name, m.Title as Title
order by Name desc, Title asc
Name Title
Tom T1
Tom T3
Tom T2
Kevin T1

Note

Using ORDER BY directly after MATCH might not have an effect if the query has subsequent NEXT or join statements.

LIMIT

LIMIT caps the number of rows. Placed after RETURN it bounds the final output; placed directly after the MATCH pattern it bounds the matched rows before projection and aggregation, which can change the result.

match (p:Person) return p.Name as name order by p.Born asc limit 2
name
Ron
Tom
match (p:Person) order by p.Born asc limit 2 return p.Name as name
name
Ron
Tom

OFFSET

OFFSET skips a number of rows before returning the rest; SKIP is a synonym. The value must be a positive integer.

match (m:Movie) return m.Title as title order by m.Title offset 1 limit 1
title
T3

Like LIMIT, OFFSET can also sit directly after the MATCH pattern, before RETURN. In that position it skips matched rows before projection:

match (m:Movie) order by m.Title offset 1 return m.Title as title
title
T3

FOR

FOR iterates over the items of a list, producing one row per item. Two optional clauses expose the item's position: WITH OFFSET binds a zero-based index, and WITH ORDINALITY binds a one-based index.

match () limit 1
for i in [5, 7]
return i
i
5
7
match () limit 1
for c in ['a', 'b'] with offset idx
return c, idx
c idx
a 0
b 1
match () limit 1
for c in ['a', 'b'] with ordinality idx
return c, idx
c idx
a 1
b 2

LET

LET binds a name to a value so you can reuse it later in the query. Separate multiple bindings with commas; a later binding can reference an earlier one. Because every query begins with MATCH, LET can't be the first statement — place it after a MATCH (and after WHERE).

Bind a per-row value, then return it:

match (p:Person)
where p.Name starts with 'K'
let BirthYear = p.Born
return p.Name as Name, BirthYear
Name BirthYear
Kevin 1958

Bind a constant and use it to filter:

match (m:Movie)
let threshold = 2000
filter m.Year >= threshold
return m.Title as title
Title
T2
T3

Define multiple values, where one builds on another:

match (p:Person)
let a = 1
let b = a + 3
return p.Name as name, a, b
name a b
Tom 1 4
Kevin 1 4
Ron 1 4
Julia 1 4

NEXT

NEXT chains statements into a linear pipeline. The rows produced by one statement become the input of the next, similar to passing a table from one step to another. Only the columns projected by the preceding RETURN (either RETURN * or explicitly named with AS) are visible to the following statement; renaming carries the new name forward. After NEXT, you can start another MATCH, a FILTER, or a RETURN. You can chain NEXT multiple times.

Project columns, then filter and reshape them:

match (p:Person)-[:ACTED_IN]->(m:Movie)
return p.Name as Actor, m.Title as Title, m.Year as `Year`
next
filter `Year` = 2020
return Actor, Title
Actor Title
Tom T3

Carry a path forward, then compute over it:

match p = (n0:Person)-[:DIRECTED]->(:Movie {Title: 'T1'})
return *
next
return p, path_length(p) as Hops
order by Hops

Pass a node variable into a later MATCH:

match (p:Person {Name: 'Tom'})
return p
next
match (p)-[:ACTED_IN]->(m:Movie)
return m.Title

Aggregate the result of an earlier statement:

match (p:Person)-[:ACTED_IN]->(m:Movie)
return p.Name as Actor
next
return count(*) as Rows

FILTER

FILTER is a standalone statement that keeps only the rows satisfying a condition, much like WHERE but written as its own step in a linear query. It's commonly used after NEXT to filter the rows produced by a previous statement, and can also follow a MATCH pattern.

Tip

Whenever a condition can be expressed against the matched pattern, prefer WHERE over a standalone FILTER. WHERE is coupled to MATCH and is evaluated as part of pattern matching, so the whole query runs more efficiently. Use FILTER only when you need to filter on values produced by a later stage, such as after NEXT or an aggregation.

Filter the rows passed forward by NEXT:

match (p:Person)-[:ACTED_IN]->(m:Movie)
return p.Name as Actor, m.Year as Year
next
filter Year >= 2000
return Actor, Year

Note

The Year variable is escaped because year is a reserved keyword. To differentiate between GQL syntax and user variables, you can escape user variables. Alternatively, add a prefix or suffix _.

Filter directly after a MATCH pattern:

match (p:Person)
filter p.Born = 1956
return p.Name as name
name
Tom

UNION

UNION combines the results of two or more queries; use UNION ALL to keep duplicates. Each query must return matching columns, and each UNION leg must end with a RETURN statement.

match (p:Person) return p.Name as name
union
match (m:Movie) return m.Title as name
name
Tom
Kevin
Ron
Julia
T1
T2
T3