Skip to content

Expressions

GQL supports a rich expression language for conditional logic, type conversion, subqueries and list operations.

CASE Expressions

Searched CASE

Evaluate conditions in order and return the first matching result:

MATCH (p:Person)
RETURN p.name,
    CASE
        WHEN p.age < 18 THEN 'minor'
        WHEN p.age < 65 THEN 'adult'
        ELSE 'senior'
    END AS category

Simple CASE

Compare an expression against specific values:

MATCH (p:Person)
RETURN p.name,
    CASE p.status
        WHEN 'A' THEN 'Active'
        WHEN 'I' THEN 'Inactive'
        WHEN 'P' THEN 'Pending'
        ELSE 'Unknown'
    END AS status_label

NULLIF

Returns null if both arguments are equal, otherwise returns the first argument. Useful for avoiding division by zero:

MATCH (d:Department)
RETURN d.name,
    d.budget / NULLIF(d.headcount, 0) AS per_capita

COALESCE

Returns the first non-null value from the argument list:

-- Display name fallback chain
MATCH (p:Person)
RETURN COALESCE(p.nickname, p.firstName, p.email) AS display_name

CAST

Convert a value to a different type with CAST(expr AS type):

-- Numeric conversions
RETURN CAST('42' AS INT)        -- 42
RETURN CAST(42 AS FLOAT)        -- 42.0
RETURN CAST('1.50' AS DECIMAL)  -- 1.50, exact with its scale
RETURN CAST(2.345 AS DECIMAL(10, 2))  -- 2.35, rounded half away from zero
RETURN CAST(123.4 AS DEC(4, 2)) -- fails with 22003: more integer digits than DEC(4, 2) holds
RETURN CAST(42 AS STRING)       -- '42'
RETURN CAST('true' AS BOOLEAN)  -- true

-- Temporal conversions
RETURN CAST('2024-01-15' AS DATE)
RETURN CAST('14:30:00' AS TIME)
RETURN CAST('2024-01-15T14:30:00Z' AS DATETIME)
RETURN CAST('P1Y2M' AS DURATION)

-- Zoned temporal conversions
RETURN CAST('2024-01-15T14:30:00+05:30' AS ZONED DATETIME)
RETURN CAST('14:30:00+05:30' AS ZONED TIME)

-- Wrap a scalar in a list
RETURN CAST(42 AS LIST)         -- [42]

Supported target types: INT/INTEGER, FLOAT, DECIMAL/DEC (bare, or with a precision from 1 to 38 and a scale up to it: DECIMAL(10, 2), DEC(5)), STRING, BOOLEAN, DATE, TIME, DATETIME, DURATION, ZONED DATETIME, ZONED TIME, LIST, LIST<type>.

A cast of text to an integer, a float or a decimal trims the spaces around it and reads the number it spells; to an integer it is truncated toward zero (CAST(' 4.7 ' AS INT) is 4). Text that spells no number fails with 22018 (CAST('Alix' AS INT)), as SQL does, where toInteger('Alix') is NULL. A float outside the integer range, an infinity or NaN cast to an integer fails with 22003, and so does a value with more integer digits than a declared decimal holds (CAST(12345.6 AS DECIMAL(5, 2))). A value that cannot convert otherwise, such as a date cast to an integer, is NULL.

Dynamic Parameters

Use $param placeholders in queries and provide values from the host language:

-- Parameterized query
MATCH (p:Person)
WHERE p.name = $name AND p.age > $min_age
RETURN p
# Python
result = db.execute(
    "MATCH (p:Person) WHERE p.name = $name RETURN p",
    {"name": "Alix"}
)
// Node.js
const result = await db.execute(
    "MATCH (p:Person) WHERE p.name = $name RETURN p",
    { name: "Alix" }
);

A list or map parameter nests at most 128 levels deep, the limit of a stored value. A deeper one is refused with an error before the query runs, in every binding.

LET ... IN ... END

Bind local variables within an expression. The variables are scoped to the expression only.

MATCH (p:Person)
RETURN p.name,
    LET base = p.salary,
        bonus = base * 0.1
    IN base + bonus
    END AS total_compensation

List Comprehensions

Transform and filter lists in a single expression:

-- Filter: keep only even numbers
RETURN [x IN [1, 2, 3, 4, 5] WHERE x % 2 = 0] AS evens
-- [2, 4]

-- Transform: double each value
RETURN [x IN [1, 2, 3] | x * 2] AS doubled
-- [2, 4, 6]

-- Filter and transform
RETURN [x IN [1, 2, 3, 4, 5] WHERE x > 2 | x * 10] AS result
-- [30, 40, 50]

-- With graph data
MATCH (p:Person)-[:KNOWS]->(friend)
RETURN p.name,
    [f IN collect(friend) WHERE f.age > 30 | f.name] AS older_friends

List Predicates

Test whether list elements satisfy a condition:

-- all(): every element must match
RETURN all(x IN [2, 4, 6] WHERE x % 2 = 0)  -- true

-- any(): at least one element must match
RETURN any(x IN [1, 2, 3] WHERE x > 2)  -- true

-- none(): no element must match
RETURN none(x IN [1, 2, 3] WHERE x > 5)  -- true

-- single(): exactly one element must match
RETURN single(x IN [1, 2, 3] WHERE x = 2)  -- true

-- Practical example: check path properties
MATCH path = (a:Person)-[:KNOWS*]->(b:Person)
WHERE a.name = 'Alix'
    AND all(n IN nodes(path) WHERE n.active = true)
RETURN path

reduce()

Fold a list into a single value using an accumulator:

-- Sum a list
RETURN reduce(acc = 0, x IN [1, 2, 3, 4, 5] | acc + x) AS total
-- 15

-- Build a string
RETURN reduce(s = '', name IN ['Alix', 'Gus', 'Harm'] | s + name + ', ') AS names

-- Compute from graph data
MATCH (p:Person)
WITH collect(p.salary) AS salaries
RETURN reduce(total = 0, s IN salaries | total + s) AS payroll

Subquery Expressions

EXISTS

Check whether a pattern or subquery has any results:

-- Pattern exists
MATCH (p:Person)
WHERE EXISTS { MATCH (p)-[:MANAGES]->(:Team) }
RETURN p.name AS manager

-- Subquery with filtering
MATCH (p:Person)
WHERE EXISTS {
    MATCH (p)-[:ORDERED]->(o:Order)
    WHERE o.total > 1000
}
RETURN p.name AS high_value_customer

COUNT

Count the number of results from a subquery:

MATCH (p:Person)
WHERE COUNT { MATCH (p)-[:KNOWS]->() } > 5
RETURN p.name AS popular_person

VALUE

Return a single scalar value from a subquery:

MATCH (p:Person)
RETURN p.name,
    VALUE {
        MATCH (p)-[:KNOWS]->(friend)
        RETURN count(friend)
    } AS friend_count

Index Access

Access list elements by index (0-based):

RETURN [10, 20, 30, 40][0]   -- 10
RETURN [10, 20, 30, 40][2]   -- 30
RETURN [10, 20, 30, 40][-1]  -- 40, a negative index counts from the end

-- Slices: from the first bound up to, not including, the second
RETURN [10, 20, 30, 40][1..3]  -- [20, 30]
RETURN [10, 20, 30, 40][-2..]  -- [30, 40]
RETURN [10, 20, 30, 40][..-2]  -- [10, 20]
RETURN 'Grafeo'[-3..]          -- 'feo', strings slice the same way

-- With graph data
MATCH (p:Person)
RETURN p.tags[0] AS first_tag

Map Literals

Create and access map (dictionary) values:

-- Map literal
RETURN {name: 'Alix', age: 30, active: true} AS person

-- Access map properties
LET person = {name: 'Alix', age: 30}
RETURN person.name, person.age

-- Maps in mutations
MATCH (p:Person {name: 'Alix'})
SET p += {city: 'Amsterdam', role: 'engineer'}

A property can hold a map. Dotted access reads its keys, also nested, the same as a subscript; a missing key, or a value that is not a map, gives null. A column without an alias is named as written:

INSERT (:Doc {id: 'd1', meta: {route: 'directory', score: 0.5, source: {kind: 'file'}}})

MATCH (d:Doc)
WHERE d.meta.route = 'directory'
RETURN d.meta.score, d.meta.source.kind, d.meta['route']
-- columns: d.meta.score, d.meta.source.kind, d.meta['route']

Dotted access reads a key of a map value (a property, a variable, a parameter, a map literal or properties(n)), and the property of a node or edge that an expression returns, such as startNode(r).name. It also reads the result of an aggregate in the same clause: in head(collect(n)).name, collect(n.name)[0] and {names: collect(n.name)} the aggregate is computed first, also inside LET ... IN ... END.

SESSION_USER

Returns the current session user name. In embedded databases, this returns 'default'.

RETURN SESSION_USER

Introspection Functions

Query session state and database metadata:

-- Current schema name
RETURN CURRENT_SCHEMA

-- Current graph name
RETURN CURRENT_GRAPH

-- Database metadata (version, node count, edge count)
RETURN info()

-- Schema information (node types, edge types, indexes, constraints)
RETURN schema()

String Concatenation

The || operator concatenates strings:

MATCH (p:Person)
RETURN p.firstName || ' ' || p.lastName AS fullName

LIKE Pattern Matching

Match strings against patterns using % (any characters) and _ (single character):

MATCH (p:Person)
WHERE p.name LIKE 'A%'
RETURN p.name

MATCH (p:Person)
WHERE p.email LIKE '%@example.com'
RETURN p.name, p.email

IN Operator

Test membership in a list:

MATCH (p:Person)
WHERE p.city IN ['Amsterdam', 'Berlin', 'Paris']
RETURN p.name, p.city