16.3. Combining operators

Node Hash Join

Using a hash table, a node hash join joins the inputs coming from the left with the inputs coming from the right. The join key is specified in the arguments of the operator.

Query 

MATCH (andy:Person { name:'Andreas' })-[:WORKS_IN]->(loc)<-[:WORKS_IN]-(matt:Person { name:'Mattis'
  })
RETURN loc

Query Plan 

+------------------+----------------+------+---------+-------------------------------------+---------------------------+
| Operator         | Estimated Rows | Rows | DB Hits | Identifiers                         | Other                     |
+------------------+----------------+------+---------+-------------------------------------+---------------------------+
| +ProduceResults  |             35 |    0 |       0 | loc                                 | loc                       |
| |                +----------------+------+---------+-------------------------------------+---------------------------+
| +Filter          |             35 |    0 |       0 | anon[37], anon[56], andy, loc, matt | NOT(anon[37] == anon[56]) |
| |                +----------------+------+---------+-------------------------------------+---------------------------+
| +NodeHashJoin    |             35 |    0 |       0 | anon[37], anon[56], andy, loc, matt | loc                       |
| |\               +----------------+------+---------+-------------------------------------+---------------------------+
| | +Expand(All)   |             35 |    0 |       0 | anon[56], loc, matt                 | (matt)-[:WORKS_IN]->(loc) |
| | |              +----------------+------+---------+-------------------------------------+---------------------------+
| | +NodeIndexSeek |              1 |    0 |       1 | matt                                | :Person(name)             |
| |                +----------------+------+---------+-------------------------------------+---------------------------+
| +Expand(All)     |             35 |    0 |       1 | anon[37], andy, loc                 | (andy)-[:WORKS_IN]->(loc) |
| |                +----------------+------+---------+-------------------------------------+---------------------------+
| +NodeIndexSeek   |              1 |    1 |       2 | andy                                | :Person(name)             |
+------------------+----------------+------+---------+-------------------------------------+---------------------------+

Total database accesses: 4

Apply

Apply works by performing a nested loop. Every row being produced on the left hand side of the Apply operator will be fed to the Argument operator on the right hand side, and then Apply will yield the results coming from the RHS. Apply, being a nested loop, can be seen as a warning that a better plan was not found.

Query 

MATCH (p:Person)-[:FRIENDS_WITH]->(f)
WITH p, count(f) AS fs
WHERE fs > 0
OPTIONAL MATCH (p)-[:WORKS_IN*1..2]->(city)
RETURN p, city

Query Plan 

+---------------------------+----------------+------+---------+----------------------------------+--------------------------+
| Operator                  | Estimated Rows | Rows | DB Hits | Identifiers                      | Other                    |
+---------------------------+----------------+------+---------+----------------------------------+--------------------------+
| +ProduceResults           |              1 |    2 |       0 | city, p                          | p, city                  |
| |                         +----------------+------+---------+----------------------------------+--------------------------+
| +Apply                    |              1 |    2 |       0 | anon[92], anon[126], city, fs, p |                          |
| |\                        +----------------+------+---------+----------------------------------+--------------------------+
| | +Apply                  |              1 |    2 |       0 | anon[92], anon[126], city, fs, p |                          |
| | |\                      +----------------+------+---------+----------------------------------+--------------------------+
| | | +Optional             |              1 |    2 |       0 | anon[126], city, p               |                          |
| | | |                     +----------------+------+---------+----------------------------------+--------------------------+
| | | +VarLengthExpand(All) |              1 |    2 |       6 | anon[126], city, p               | (p)-[:WORKS_IN*]->(city) |
| | | |                     +----------------+------+---------+----------------------------------+--------------------------+
| | | +Argument             |              1 |    2 |       0 | p                                |                          |
| | |                       +----------------+------+---------+----------------------------------+--------------------------+
| | +Filter                 |              1 |    2 |       0 | anon[92], fs, p                  | anon[92]                 |
| | |                       +----------------+------+---------+----------------------------------+--------------------------+
| | +Argument               |              1 |    2 |       0 | anon[92], fs, p                  |                          |
| |                         +----------------+------+---------+----------------------------------+--------------------------+
| +Projection               |              1 |    2 |       0 | anon[92], fs, p                  | p; fs; fs > {  AUTOINT0} |
| |                         +----------------+------+---------+----------------------------------+--------------------------+
| +EagerAggregation         |              1 |    2 |       0 | fs, p                            | p                        |
| |                         +----------------+------+---------+----------------------------------+--------------------------+
| +Expand(All)              |              2 |    2 |      16 | anon[17], f, p                   | (p)-[:FRIENDS_WITH]->(f) |
| |                         +----------------+------+---------+----------------------------------+--------------------------+
| +NodeByLabelScan          |             14 |   14 |      15 | p                                | :Person                  |
+---------------------------+----------------+------+---------+----------------------------------+--------------------------+

Total database accesses: 37

Anti Semi Apply

Tests for the absence of a pattern predicate. A pattern predicate that is prepended by NOT is solved with AntiSemiApply.

Query 

MATCH (me:Person { name: "me" }),(other:Person)
WHERE NOT (me)-[:FRIENDS_WITH]->(other)
RETURN other

Query Plan 

+--------------------+----------------+------+---------+---------------------+-------------------------------+
| Operator           | Estimated Rows | Rows | DB Hits | Identifiers         | Other                         |
+--------------------+----------------+------+---------+---------------------+-------------------------------+
| +ProduceResults    |              4 |   13 |       0 | other               | other                         |
| |                  +----------------+------+---------+---------------------+-------------------------------+
| +AntiSemiApply     |              4 |   13 |       0 | me, other           |                               |
| |\                 +----------------+------+---------+---------------------+-------------------------------+
| | +Expand(Into)    |              0 |    0 |      50 | anon[73], me, other | (me)-[:FRIENDS_WITH]->(other) |
| | |                +----------------+------+---------+---------------------+-------------------------------+
| | +Argument        |             14 |   14 |       0 | me, other           |                               |
| |                  +----------------+------+---------+---------------------+-------------------------------+
| +CartesianProduct  |             14 |   14 |       0 | me, other           |                               |
| |\                 +----------------+------+---------+---------------------+-------------------------------+
| | +NodeByLabelScan |             14 |   14 |      15 | other               | :Person                       |
| |                  +----------------+------+---------+---------------------+-------------------------------+
| +NodeIndexSeek     |              1 |    1 |       2 | me                  | :Person(name)                 |
+--------------------+----------------+------+---------+---------------------+-------------------------------+

Total database accesses: 67

Let Anti Semi Apply

Tests for the absence of a pattern predicate. When a query contains multiple pattern predicates LetSemiApply will be used to evaluate the first of these. It will record the result of evaluating the predicate but will leave any filtering to another operator. The following query will find all the people who don’t have anyfriend or who work somewhere. The LetSemiApply operator will be used to check for the absence of the FRIENDS_WITH relationship from each person.

Query 

MATCH (other:Person)
WHERE NOT ((other)-[:FRIENDS_WITH]->()) OR (other)-[:WORKS_IN]->()
RETURN other

Query Plan 

+--------------------+----------------+------+---------+---------------------------+-----------------------------+
| Operator           | Estimated Rows | Rows | DB Hits | Identifiers               | Other                       |
+--------------------+----------------+------+---------+---------------------------+-----------------------------+
| +ProduceResults    |             11 |   14 |       0 | other                     | other                       |
| |                  +----------------+------+---------+---------------------------+-----------------------------+
| +SelectOrSemiApply |             11 |   14 |       0 | anon[42], other           | anon[42]                    |
| |\                 +----------------+------+---------+---------------------------+-----------------------------+
| | +Expand(All)     |             15 |    0 |       2 | anon[82], anon[96], other | (other)-[:WORKS_IN]->()     |
| | |                +----------------+------+---------+---------------------------+-----------------------------+
| | +Argument        |             14 |    2 |       0 | other                     |                             |
| |                  +----------------+------+---------+---------------------------+-----------------------------+
| +LetAntiSemiApply  |             14 |   14 |       0 | anon[42], other           |                             |
| |\                 +----------------+------+---------+---------------------------+-----------------------------+
| | +Expand(All)     |              2 |    0 |      14 | anon[50], anon[68], other | (other)-[:FRIENDS_WITH]->() |
| | |                +----------------+------+---------+---------------------------+-----------------------------+
| | +Argument        |             14 |   14 |       0 | other                     |                             |
| |                  +----------------+------+---------+---------------------------+-----------------------------+
| +NodeByLabelScan   |             14 |   14 |      15 | other                     | :Person                     |
+--------------------+----------------+------+---------+---------------------------+-----------------------------+

Total database accesses: 31

Let Semi Apply

Tests for the existence of a pattern predicate. When a query contains multiple pattern predicates LetSemiApply will be used to evaluate the first of these. It will record the result of evaluating the predicate but will leave any filtering to a another operator. The following query will find all the people who have a friend or who work somewhere. The LetSemiApply operator will be used to check for the existence of the FRIENDS_WITH relationship from each person.

Query 

MATCH (other:Person)
WHERE (other)-[:FRIENDS_WITH]->() OR (other)-[:WORKS_IN]->()
RETURN other

Query Plan 

+--------------------+----------------+------+---------+---------------------------+-----------------------------+
| Operator           | Estimated Rows | Rows | DB Hits | Identifiers               | Other                       |
+--------------------+----------------+------+---------+---------------------------+-----------------------------+
| +ProduceResults    |             13 |   14 |       0 | other                     | other                       |
| |                  +----------------+------+---------+---------------------------+-----------------------------+
| +SelectOrSemiApply |             13 |   14 |       0 | anon[38], other           | anon[38]                    |
| |\                 +----------------+------+---------+---------------------------+-----------------------------+
| | +Expand(All)     |             15 |    0 |      12 | anon[77], anon[91], other | (other)-[:WORKS_IN]->()     |
| | |                +----------------+------+---------+---------------------------+-----------------------------+
| | +Argument        |             14 |   12 |       0 | other                     |                             |
| |                  +----------------+------+---------+---------------------------+-----------------------------+
| +LetSemiApply      |             14 |   14 |       0 | anon[38], other           |                             |
| |\                 +----------------+------+---------+---------------------------+-----------------------------+
| | +Expand(All)     |              2 |    0 |      14 | anon[46], anon[64], other | (other)-[:FRIENDS_WITH]->() |
| | |                +----------------+------+---------+---------------------------+-----------------------------+
| | +Argument        |             14 |   14 |       0 | other                     |                             |
| |                  +----------------+------+---------+---------------------------+-----------------------------+
| +NodeByLabelScan   |             14 |   14 |      15 | other                     | :Person                     |
+--------------------+----------------+------+---------+---------------------------+-----------------------------+

Total database accesses: 41

Select Or Anti Semi Apply

Tests for the absence of a pattern predicate and evaluates a predicate.

Query 

MATCH (other:Person)
WHERE other.age > 25 OR NOT (other)-[:FRIENDS_WITH]->()
RETURN other

Query Plan 

+------------------------+----------------+------+---------+---------------------------+-----------------------------+
| Operator               | Estimated Rows | Rows | DB Hits | Identifiers               | Other                       |
+------------------------+----------------+------+---------+---------------------------+-----------------------------+
| +ProduceResults        |              4 |   12 |       0 | other                     | other                       |
| |                      +----------------+------+---------+---------------------------+-----------------------------+
| +SelectOrAntiSemiApply |              4 |   12 |      28 | other                     | other.age > {  AUTOINT0}    |
| |\                     +----------------+------+---------+---------------------------+-----------------------------+
| | +Expand(All)         |              2 |    0 |      14 | anon[68], anon[86], other | (other)-[:FRIENDS_WITH]->() |
| | |                    +----------------+------+---------+---------------------------+-----------------------------+
| | +Argument            |             14 |   14 |       0 | other                     |                             |
| |                      +----------------+------+---------+---------------------------+-----------------------------+
| +NodeByLabelScan       |             14 |   14 |      15 | other                     | :Person                     |
+------------------------+----------------+------+---------+---------------------------+-----------------------------+

Total database accesses: 57

Select Or Semi Apply

Tests for the existence of a pattern predicate and evaluates a predicate. This operator allows for the mixing of normal predicates and pattern predicates that check for the existing of a pattern. First the normal expression predicate is evaluated, and only if it returns FALSE the costly pattern predicate evaluation is performed.

Query 

MATCH (other:Person)
WHERE other.age > 25 OR (other)-[:FRIENDS_WITH]->()
RETURN other

Query Plan 

+--------------------+----------------+------+---------+---------------------------+-----------------------------+
| Operator           | Estimated Rows | Rows | DB Hits | Identifiers               | Other                       |
+--------------------+----------------+------+---------+---------------------------+-----------------------------+
| +ProduceResults    |             11 |    2 |       0 | other                     | other                       |
| |                  +----------------+------+---------+---------------------------+-----------------------------+
| +SelectOrSemiApply |             11 |    2 |      28 | other                     | other.age > {  AUTOINT0}    |
| |\                 +----------------+------+---------+---------------------------+-----------------------------+
| | +Expand(All)     |              2 |    0 |      14 | anon[64], anon[82], other | (other)-[:FRIENDS_WITH]->() |
| | |                +----------------+------+---------+---------------------------+-----------------------------+
| | +Argument        |             14 |   14 |       0 | other                     |                             |
| |                  +----------------+------+---------+---------------------------+-----------------------------+
| +NodeByLabelScan   |             14 |   14 |      15 | other                     | :Person                     |
+--------------------+----------------+------+---------+---------------------------+-----------------------------+

Total database accesses: 57

Semi Apply

Tests for the existence of a pattern predicate. SemiApply takes a row from it’s child operator and feeds it to the Argument operator on the right hand side of SemiApply. If the right hand side operator tree yields at least one row, the row from the left hand side is yielded by the SemiApply operator. This makes SemiApply a filtering operator, used mostly for pattern predicates in queries.

Query 

MATCH (other:Person)
WHERE (other)-[:FRIENDS_WITH]->()
RETURN other

Query Plan 

+------------------+----------------+------+---------+---------------------------+-----------------------------+
| Operator         | Estimated Rows | Rows | DB Hits | Identifiers               | Other                       |
+------------------+----------------+------+---------+---------------------------+-----------------------------+
| +ProduceResults  |             11 |    2 |       0 | other                     | other                       |
| |                +----------------+------+---------+---------------------------+-----------------------------+
| +SemiApply       |             11 |    2 |       0 | other                     |                             |
| |\               +----------------+------+---------+---------------------------+-----------------------------+
| | +Expand(All)   |              2 |    0 |      14 | anon[46], anon[64], other | (other)-[:FRIENDS_WITH]->() |
| | |              +----------------+------+---------+---------------------------+-----------------------------+
| | +Argument      |             14 |   14 |       0 | other                     |                             |
| |                +----------------+------+---------+---------------------------+-----------------------------+
| +NodeByLabelScan |             14 |   14 |      15 | other                     | :Person                     |
+------------------+----------------+------+---------+---------------------------+-----------------------------+

Total database accesses: 29

Triadic

Triadic is used to solve triangular queries, such as the very common "find my friend-of-friends that are not already my friend". It does so by putting all the "friends" in a set, and use that set to check if the friend-of-friends are already connected to me.

Query 

MATCH (me:Person)-[:FRIENDS_WITH]-()-[:FRIENDS_WITH]-(other)
WHERE NOT (me)-[:FRIENDS_WITH]-(other)
RETURN other

Query Plan 

+-------------------+----------------+------+---------+-----------------------------------------+----------------------------+
| Operator          | Estimated Rows | Rows | DB Hits | Identifiers                             | Other                      |
+-------------------+----------------+------+---------+-----------------------------------------+----------------------------+
| +ProduceResults   |              0 |    2 |       0 | other                                   | other                      |
| |                 +----------------+------+---------+-----------------------------------------+----------------------------+
| +TriadicSelection |              0 |    2 |       0 | anon[18], anon[35], anon[37], me, other | me, anon[35], other        |
| |\                +----------------+------+---------+-----------------------------------------+----------------------------+
| | +Filter         |              0 |    2 |       0 | anon[18], anon[35], anon[37], me, other | NOT(anon[18] == anon[37])  |
| | |               +----------------+------+---------+-----------------------------------------+----------------------------+
| | +Expand(All)    |              0 |    6 |      10 | anon[18], anon[35], anon[37], me, other | ()-[:FRIENDS_WITH]-(other) |
| | |               +----------------+------+---------+-----------------------------------------+----------------------------+
| | +Argument       |              4 |    4 |       0 | anon[18], anon[35], me                  |                            |
| |                 +----------------+------+---------+-----------------------------------------+----------------------------+
| +Expand(All)      |              4 |    4 |      18 | anon[18], anon[35], me                  | (me)-[:FRIENDS_WITH]-()    |
| |                 +----------------+------+---------+-----------------------------------------+----------------------------+
| +NodeByLabelScan  |             14 |   14 |      15 | me                                      | :Person                    |
+-------------------+----------------+------+---------+-----------------------------------------+----------------------------+

Total database accesses: 43