3.8 Existence and intersection across sources (EXISTS / NOT EXISTS / OR)
MATCH (me:AssistantUser)-[:EMAILED]->(p:Person)WHERE EXISTS { (p)-[:HAS_GITHUB]->(:GitHubIdentity) } AND EXISTS { (p)-[:HAS_HUBSPOT_CONTACT]->(:HubSpotContact) }RETURN p.nameMeans: “People I email who are both on GitHub and in HubSpot.”
How it executes:
- The engine sees the two virtual labels inside the
EXISTSsubqueries and materializes both, anchored on each emailed personp. (The edges insideEXISTS/NOT EXISTS/OPTIONAL/COUNT/COLLECTare treated as non-required — they do not narrow which anchors get probed, but their targets are materialized so the existence test can be evaluated.) - After materialization,
EXISTS { … }is a normal Cypher test: keep only people who got both aGitHubIdentityand aHubSpotContactmaterialized. - What it costs. An existence test needs one record per key, so the fetch behind it is capped
at one. Whether that is one call per key depends on the source: one that pages is asked one
key at a time, because a batched page walk would stop at page one and read the keys not on it as
empty; one that does not page answers a batch completely under the cap, so its keys go in batches
of
maxKeysPerCall(three hundred anchors, six calls of fifty).batchSafe: falsekeeps one key per call either way (§3.5).
The same mechanism powers NOT EXISTS (“in HubSpot but not on GitHub”), OR (“on GitHub or
in HubSpot”), and COUNT { … }. Express existence with EXISTS { }, not an OPTIONAL MATCH … WHERE x IS NOT NULL (which does not filter) and not a self-referential round-trip (p)-[:R]->(x)<-[:R]-(p)
(which matches nothing under Cypher’s relationship-uniqueness rule).

