Why do I get a "Cartesian Product" warning?
Asked Answered
S

2

6

I am still trying to understand why I get a cartesian product warning for a certain format for a query in neo4j and not for another. This is how I set up my database:

CREATE (q:Form {version: "1.0"})
CREATE (q:Question {text: "Sector de la empresa", active: true})

I then tried the following query:

MATCH
(f:Form {version: "1.0"}),
(q:Question {text: "Sector de la empresa"})
CREATE (f)-[:asks]->(q)
RETURN f, q

However, I get the following warning:

This query builds a cartesian product between disconnected patterns.
If a part of a query contains multiple disconnected patterns,
this will build a cartesian product between all those parts.
This may produce a large amount of data and slow down query processing.
While occasionally intended, it may often be possible to reformulate the
query that avoids the use of this cross product, perhaps by adding a
relationship between the different parts or by using OPTIONAL MATCH
(identifier is: (q))

When I use the following query, it does not give me this warning:

MATCH (f:Form {version: "1.0"})
WITH f
(q:Question {text: "Sector de la empresa"})
CREATE (f)-[:asks]->(q)
RETURN f, q

nor when I use this query:

MATCH (f:Form {version: "1.0"})
MATCH (q:Question {text: "Sector de la empresa"})
CREATE (f)-[:asks]->(q)
RETURN f, q

I used this following article as a resource, but it still didn't fully answer my question: Why does neo4j warn: "This query builds a cartesian product between disconnected patterns"?

Why do I get a cartesian product for some formats of a query and not others? Also, I do not fully understand what a cartesian product warning is.

Surgeon answered 10/6, 2016 at 14:46 Comment(0)
U
7

If you are MATCHing on two different labels without any relationships between them, then you'll get this warning. The reason is because if you do:

MATCH (a:Foo), (b:Bar)

It's Neo4j's job to find every possible combination of those two nodes. So for the first match of a it will return a row for every match of b, for the second match of a it will again return a row for every match of b, and so on. So you'll get (number of Foo nodes) x (number of Bar nodes) total rows in your result. As your database grows this is really bad for performance.

I can see that you're filtering on version for Form and text for Question, so that would help. That may even give you just one Form node and one Question node. So as long as you have an index on the Form(version) and Question(text) the query should be quite quick. Neo4j can't tell (or at least, isn't currently implemented to be able to tell) how many rows are going to be returned, so it gives a warning saying that your query could be potentially slow.

Underset answered 10/6, 2016 at 16:47 Comment(0)
D
1

They are all cartesian

Having read your question, for a second there, my cypher-world imploded - all three queries should involve a cartesian product.

Having checked (on both the console and a local DB - both version 3.3.0), turns out I'm sane - they do all involve a cartesian product:

The execution plan of the first query, showing a cartesian product

The execution plan of the second query, showing a cartesian product

The execution plan of the third query, showing a cartesian product

Why there is only a warning in the first case (still in version 3.3.0 I have no clue - you simply need to run the planner to figure this out, and if this isn't firing the warning what does? Some dumb cypher logic?

Cypher basics

Cypher queries are made of parts, each can be either update (write) or read.

As far as read parts are concerned, this is what happens:

  • Neo4j picks a starting point which it reckons will yield the least 'hits'. It will go through each of these hits...
  • ...traversing the graph using the node/relationship pattern.
  • It does so repeatedly until no more patterns are matched.

If you have something like this:

(a {name:'Bill'})-->(b:Dog)

The plan might look something like this.

  • For each node (AKA AllNodeScan):
    • Filter based on the predicate (name == 'bill')
    • Get all outgoing --> relationships
    • For each relationship:
      • Get the end node
      • Filter based on predicate (:Dog)

The important thing is that whilst to find (a) we need to scan each node. But we simply traverse the graph to find the (b)s - no AllNodeScan for the latter.

(There are variants of AllNodeScan, see Starting Node Operators)

When your query is something like this:

MATCH (f:Form {version: "1.0"}), (q:Question {text: "Sector de la empresa"})

Neo is forced to do an AllNodeScan for both f and q - There is no pattern to traverse between them. This can potentially create a result set of an f * q size, which could be huge.

Dkl answered 21/11, 2017 at 0:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.