diff --git a/pages/querying/expressions.mdx b/pages/querying/expressions.mdx index f03ba99a9..886f77d6f 100644 --- a/pages/querying/expressions.mdx +++ b/pages/querying/expressions.mdx @@ -202,12 +202,58 @@ MATCH (n) RETURN CASE WHEN n.height < 30 THEN "short" WHEN n.height > 300 THEN "tall" END; ``` -Most expressions that take `null` as input will produce `null`. This includes boolean expressions that are used as -predicates. In this case, anything that is not true is interpreted as being false. This also concludes that logically `null!=null`. +A `WHEN` predicate that evaluates to `null` counts as false, so its branch isn't +taken. See [comparisons with null](#comparisons-with-null) for which +comparisons evaluate to `null`. The [`exists()`](/querying/functions#pattern-functions) function and the `EXISTS { … }`, `COUNT { … }` and `COLLECT { … }` [subquery expressions](/querying/subquery-expressions) can all be used inside `CASE`. +## Comparisons with null + +`null` stands for a value that isn't known, so most expressions that take +`null` as input return `null`. A comparison with `null` returns `null` too: +`null = null` and `1 <> null` are both `null`. In a `WHERE` clause or a `CASE` +predicate, `null` counts as false, so the row is filtered out or the branch +isn't taken. To test for a missing value, use `IS NULL` or `IS NOT NULL`. + +The same rule applies inside lists and maps. Two lists are equal when they +have the same length and equal elements in the same positions; two maps are +equal when they have the same keys and equal values under each key. If one +pair of elements differs, or the lengths or keys differ, the comparison +returns `false`. Otherwise, if a pair of elements compares to `null`, the +whole comparison returns `null`: + +| Comparison | Result | +|---|---| +| `[1] = [1]` | `true` | +| `[null] = [null]` | `null` | +| `[null] = [1]` | `null` | +| `[null] <> [null]` | `null` | +| `{k: null} = {k: null}` | `null` | +| `[null, 1] = [null, 2]` | `false`: the second elements differ | +| `[null] = [null, null]` | `false`: the lengths differ | +| `{a: null} = {b: null}` | `false`: the keys differ | + +`IN` compares each element of the list with `=`, so it follows the same rule: +`1 IN [1, null]` is `true`, while `3 IN [1, null]` and `[null] IN [[null]]` +are `null`. As a result, `WHERE NOT (n.code IN ['A', 'B'])` keeps no row where +`n.code` is `null`. + +A filter that compares a property with a list or map holding `null` matches +nothing, with or without an index on the property: +`MATCH (n:Order) WHERE n.tags = ['urgent', null] RETURN n` returns no nodes, +because the comparison is never `true`. The same holds for relationship +properties and for joins such as `WHERE a.tags = b.tags`. + +`DISTINCT` and grouping keys treat two `null` values, and two lists or maps +that hold `null` in the same places, as the same value: +`UNWIND [{k: null}, {k: null}] AS x RETURN DISTINCT x` returns one row. + +Comparing lists and maps that hold `null` changed in Memgraph 3.14. Before, +`[null] = [null]` returned `true` and a map holding `null` was never equal to +another map. + ## Pattern existence (exists(pattern)) `exists(pattern)` is a short form for a simple pattern existence check. It is accepted in the same