Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modules/ROOT/content-nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
*** xref:expressions/predicates/comparison-operators.adoc[]
*** xref:expressions/predicates/list-operators.adoc[]
*** xref:expressions/predicates/string-operators.adoc[]
*** xref:expressions/predicates/label-expression-predicates.adoc[]
*** xref:expressions/predicates/path-pattern-expressions.adoc[]
*** xref:expressions/predicates/type-predicate-expressions.adoc[]
** xref:expressions/node-relationship-operators.adoc[]
Expand Down
30 changes: 30 additions & 0 deletions modules/ROOT/images/label_expression_predicates_graph.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions modules/ROOT/pages/expressions/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ For details and examples of specific expressions, see the following sections:
** xref:expressions/predicates/comparison-operators.adoc[]: `=`, `<>`, `<`, `>`, `\<=`, `>=`, `IS NULL`, `IS NOT NULL`
** xref:expressions/predicates/list-operators.adoc[]: `IN`
** xref:expressions/predicates/string-operators.adoc[]: `STARTS WITH`, `ENDS WITH`, `CONTAINS`, `IS NORMALIZED`, `IS NOT NORMALIZED`, `=~`
** xref:expressions/predicates/label-expression-predicates.adoc[]: information about how to test whether a node or relationship matches a label expression or not.
** xref:expressions/predicates/path-pattern-expressions.adoc[]: information about filtering queries with path pattern expressions.
** xref:expressions/predicates/type-predicate-expressions.adoc[]: information about how to verify the value type of a Cypher expression.
* xref:expressions/node-relationship-operators.adoc[]: information about how to access `NODE` and `RELATIONSHIP` property values with `.` and `[]`.
Expand Down
1 change: 1 addition & 0 deletions modules/ROOT/pages/expressions/predicates/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This chapter is divided into the following sections:
* xref:expressions/predicates/comparison-operators.adoc[]: `=`, `<>`, `<`, `>`, `\<=`, `>=`, `IS NULL`, `IS NOT NULL`
* xref:expressions/predicates/list-operators.adoc[]: `IN`
* xref:expressions/predicates/string-operators.adoc[]: `STARTS WITH`, `ENDS WITH`, `CONTAINS`, `IS NORMALIZED`, `IS NOT NORMALIZED`, `=~`
* xref:expressions/predicates/label-expression-predicates.adoc[]: information about how to test whether a node or relationship matches a label expression or not.
* xref:expressions/predicates/path-pattern-expressions.adoc[]: information about filtering queries with path pattern expressions.
* xref:expressions/predicates/type-predicate-expressions.adoc[]: information about how to verify the value type of a Cypher expression.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
= Label expression predicates
:description: This page describes how to use label expression predicates with Cypher.

You can use a label expression predicate to verify that the labels of a node or the relationship type of a relationship match a given label expression.

[[label-expression-predicat-syntax]]
== Syntax

[source, syntax]
----
<expr>:<label-expression>
----

Where `<expr>` is any Cypher expression and `<label-expression>` is any Cypher xref::patterns/reference.adoc#label-expressions[label expression].

[[example-graph]]
== Example graph

The following graph is used for the examples on this page:

image::label_expression_predicates_graph.svg[width="700",role="middle"]

To recreate the graph, run the following query against an empty Neo4j database:

[source, cypher]
----
CREATE (alice:Person&Manager {name:'Alice', age: 65, skills: ['Java', 'Python']}),
(cecil:Person&Developer {name: 'Cecil', age: 25, skills: ['Java', 'Python']}),
(cecilia:Person&Developer {name: 'Cecilia', age: 31, skills: ['JavaScript', 'TypeScript']}),
(charlie:Person&Engineer {name: 'Charlie', age: 61, skills: ['C++', 'Python']}),
(daniel:Person&Director {name: 'Daniel', age: 39, skills: ['JavaScript', 'Slides']}),
(eskil:Person&CEO {name: 'Eskil', age: 39, skills: ['Slides', 'ChatGPT']}),

(cecil)-[:WORKS_FOR]->(alice),
(cecilia)-[:WORKS_FOR]->(alice),
(charlie)-[:WORKS_FOR]->(daniel),
(alice)-[:REPORTS_TO]->(daniel),
(daniel)-[:REPORTS_TO]->(eskil)
----

[node-label-expression-predicate]
== Test whether a node has a certain label

Given that `p` is a node, `p:Manager` tests whether `p` has the label `Manager` or not and results in `true` or `false`, respectively.

[source, cypher]
----
MATCH (p:Person)
RETURN p.name AS name, p:Manager AS isManager
----

[role="queryresult",options="header,footer",cols="2*<m"]
|===
| name | isManager

| "Alice" | true
| "Cecil" | false
| "Cecilia" | false
| "Charlie" | false
| "Daniel" | false
| "Eskil" | false

2+d|Rows: 6
|===

Given that `p` is a node and a more complex xref::patterns/reference.adoc#label-expressions[label expression], e.g, `Manager|Director|CEO`, `p:Manager|Director|CEO` tests whether node `p` matches the label expression `Manager|Director|CEO` or not and results in `true` or `false`, respectively.
More specifically, `p:Manager|Director|CEO` tests whether `p` has at least one of the three labels `Manager`, `Director`, and `CEO`.

[source, cypher]
----
MATCH (p:Person)
RETURN p.name AS name,
p:Manager|Director|CEO AS isManager
----

[role="queryresult",options="header,footer",cols="2*<m"]
|===
| name | isManager

| "Alice" | true
| "Cecil" | false
| "Cecilia" | false
| "Charlie" | false
| "Daniel" | true
| "Eskil" | true

2+d|Rows: 6
|===

`p:!CEO` tests whether `p` does not have the label `CEO`.

[source, cypher]
----
MATCH (p:Person)-[r]->(m)
RETURN p.name AS name,
m:!CEO AS doesNotWorkDirectlyForTheCEO
----
Comment on lines +93 to +97
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no tags for this node-label-expression-predicate?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I never the remember the purpose of the tags. I thought they mark which examples go into the cheat sheet, so I picked just some of them. But maybe the cheat sheet things was somethings else. If all need a tag, then so be it.


[role="queryresult",options="header,footer",cols="2*<m"]
|===
| name | doesNotWorkDirectlyForTheCEO

| "Alice" | true
| "Cecil" | true
| "Cecilia" | true
| "Charlie" | true
| "Daniel" | false

2+d|Rows: 5
|===

When `<exp>` results in ´null`, then the label expression predicate results in `null`, e.g. if `p` is `null`, then `p:!CEO` results in `null`.

[source, cypher]
----
MATCH (p:Person)
OPTIONAL MATCH (p)-[r]->(m)
RETURN p.name AS name,
m:!CEO AS doesNotWorkDirectlyForTheCEO
----

[role="queryresult",options="header,footer",cols="2*<m"]
|===
| name | doesNotWorkDirectlyForTheCEO

| "Alice" | true
| "Cecil" | true
| "Cecilia" | true
| "Charlie" | true
| "Daniel" | false
| "Eskil" | null

2+d|Rows: 6
|===

The function xref:functions/scalar.adoc#functions-coalesce[coalesce] allows to turn `null` into a default value:

[source, cypher]
----
MATCH (p:Person)
OPTIONAL MATCH (p)-[r]->(m)
RETURN p.name AS name,
coalesce(m:!CEO, false) AS doesNotWorkDirectlyForTheCEO
----

[role="queryresult",options="header,footer",cols="2*<m"]
|===
| name | doesNotWorkDirectlyForTheCEO

| "Alice" | true
| "Cecil" | true
| "Cecilia" | true
| "Charlie" | true
| "Daniel" | false
| "Eskil" | false

2+d|Rows: 6
|===

[role=label--new-Neo4j-2025.07 label--cypher-25-only]
[[dynamic-node-label-expression-predicate]]
== Test whether a node matches a dynamic label expression

If you don't know the manager labels, you can infer a list of `managerLabels` by collecting all non-`Person` labels from people with a direct report.
You can test nodes with the label expression predicate to match a xref:patterns/reference.adoc#dynamic-label-and-type-expressions[dynamic label expression].
`p:$any(managerLabels)` tests whether a node `p` has at least on of the label in the list `managerLabels`.

[source, cypher]
----
MATCH ()-[r]->(p:Person)
UNWIND labels(p) AS label
FILTER label <> "Person"
RETURN COLLECT(label) AS managerLabels
NEXT
MATCH (p)
RETURN p.name AS name, p:$any(managerLabels) AS isManager
----
Comment on lines +169 to +177
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, no tags? (not sure how many we need for the cheat sheet 🤔 )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I remembered correctly that they are for the cheat sheet. The ones with tags are my picks. But please, just take them as uninformed suggestions. I have no higher understanding of what typically is expected to go into the cheat sheet. I am just running on very basic subjective assumptions. Feel free to adjust that.


[role="queryresult",options="header,footer",cols="2*<m"]
|===
| name | isManager

| "Alice" | true
| "Cecil" | false
| "Cecilia" | false
| "Charlie" | false
| "Daniel" | true
| "Eskil" | true

2+d|Rows: 6
|===

[node-label-expression-predicate]
== Test whether a relationship has a certain relationship type

Given that `r` is a relationship, `r:WORKS_FOR` tests whether `r` has the relationship type `WORKS_FOR` or not and result in `true` or `false`, respectively.

[source, cypher]
----
MATCH (p:Person)-[r]->()
RETURN p.name AS name,
r:WORKS_FOR AS isNotManager
----

[role="queryresult",options="header,footer",cols="2*<m"]
|===
| name | isNotManager

| "Alice" | false
| "Cecil" | true
| "Cecilia" | true
| "Charlie" | true
| "Daniel" | false

2+d|Rows: 5
|===

If `r` is `null`, then the label expression predicate, e.g. `r:WORKS_FOR|REPORTS_TO`, results in `null`.
The function xref:functions/scalar.adoc#functions-coalesce[coalesce] allows to turn `null` into a default value.

[source, cypher]
----
MATCH (p:Person)
OPTIONAL MATCH ()-[r]->(p)
RETURN DISTINCT
p.name AS name,
coalesce(r:WORKS_FOR|REPORTS_TO, false) AS hasReports
----

[role="queryresult",options="header,footer",cols="2*<m"]
|===
| name | hasReports

| "Alice" | true
| "Cecil" | false
| "Cecilia" | false
| "Charlie" | false
| "Daniel" | true
| "Eskil" | true

2+d|Rows: 6
|===

Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,59 @@ RETURN exists((:Person)-[:WORKS_FOR {since: 2023}]->(:Person {name: "Alice"})) A

1+d|Rows: 1
|===

[[not-patterns]]
== Expressions similar to path pattern expressions

Since any expression can be wrapped in parentheses, some are very similar to path pattern expressions.
For example:

`(p:Person)` is not a path pattern expression since it does not have at least one xref::patterns/reference.adoc#relationship-patterns[relationship] or xref::patterns/reference.adoc#variable-length-relationships[variable-length relationship].
Instead, `(p:Person)` is the xref:expressions/predicates/label-expression-predicates.adoc[label expression predicate] `p:Person` in parentheses.

.Parenthesized label expression predicate
[source, cypher]
----
MATCH (employee:Person)-[:WORKS_FOR]->(p)
WHERE (p:Person)
RETURN employee.name AS employee, (p:Person) AS workForAPeron
----

The query result however is the same for both `p:Person` and `(p:Person)`:
[source, cypher]
----
MATCH (employee:Person)-[:WORKS_FOR]->(p)
WHERE p:Person
RETURN employee.name AS employee, p:Person AS workForAPerson
----

.Result
[role="queryresult",options="header,footer",cols="2*<m"]
|===
| employee | workForAPerson

| "Cecil" | true
| "Cecilia" | true

2+d|Rows: 2
|===

`(p)` is not a path pattern expression since it does not have at least one xref::patterns/reference.adoc#relationship-patterns[relationship] or xref::patterns/reference.adoc#variable-length-relationships[variable-length relationship].
Instead, `(p)` is the variable `p` in parentheses.

`(p)` does result in the value of variable `p`.
If `p` is not a Boolean, then `(p)` does not result in a Boolean either.
Hence, the following query throws an error:

.Parenthesized node variable
[source, cypher, role=test-fail]
----
MATCH (employee:Person)-[:WORKS_FOR]->(p)
WHERE (p)
RETURN employee.name AS employee
----

[source, error]
----
Invalid input 'Node' for `p`. Expected to be Boolean.
----
Comment on lines +182 to +185
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary for this PR, but we should maybe think about starting to use the GQL errors in the Cypher manual instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean the showing the code? — because the message is what I get on 2025.10.1, i.e. it is the message that comes with the code.

I guess that is a topic for the error team sync. It should have PM involvement, considering how error should be shown in docs (e.g. with position and carrot) and be done as a concerted change to the whole docs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, showing the code and status description rather than or additional to the message. I will write it up for an upcoming error meeting, I agree it does not make much sense to do it for one random example but it should be consistently applied

Copy link
Contributor

@Lojjs Lojjs Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added it like this to next meeting:

[LB] Displaying errors in Cypher docs
Currently errors in the Cypher manual looks like this with the current message:
<screen shot from this PR>

Should we instead/additionally show GQLSTATUS and status description (with a link to the error manual maybe)? How do we keep status descriptions up to date in that case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linking to the status code doc would be very reasonable. The docs colleagues maybe can even come up if a macro so that in the ascii doc sources you only have put the marco with the status code and it will generate a nice up-to-date presentation of the error incl. link to the respective status code doc page.