WHERE: фильтрация совпадений
Как сузить найденные паттерны условиями — от простых сравнений до фильтра по наличию связи.
WHERE — предикат, отбрасывающий те совпадения MATCH, которые не удовлетворяют условию; работает по свойствам, меткам и даже по наличию подграфов.
Свойство в паттерне или в WHERE
Фильтр по равенству можно записать двумя способами — оба верны:
// inline в паттерне
MATCH (m:Movie {released:1999}) RETURN m.title
// через WHERE
MATCH (m:Movie) WHERE m.released = 1999 RETURN m.titleInline удобен для простого равенства, а WHERE нужен для всего остального: диапазонов, сравнений, логики. Есть и стилистическое соображение: когда фильтров на узле много, инлайновая форма {a:1, b:2, c:3} загромождает паттерн и мешает разглядеть его форму. Вынесли условия в WHERE — и паттерн снова читается как чистый рисунок графа, а условия аккуратно лежат отдельным блоком. Хорошее эмпирическое правило: одно простое равенство, определяющее что это за узел, оставляйте в паттерне; всё остальное — в WHERE.
Сравнения, диапазоны, списки
Операторы привычные. Экранируем угловые скобки в записи, но в редакторе вы пишете обычные < и >:
MATCH (m:Movie)
WHERE m.released >= 2000 AND m.released < 2010
RETURN m.title, m.releasedIN проверяет вхождение в список, STARTS WITH / CONTAINS / ENDS WITH — строковые предикаты:
MATCH (p:Person)
WHERE p.name STARTS WITH 'Tom' OR p.born IN [1956, 1964]
RETURN p.name, p.bornЛогику комбинируют через AND, OR, NOT и скобки — приоритет привычный, AND связывает сильнее OR, скобки расставляйте при малейшем сомнении. Маленькая памятка по операторам, которую стоит держать перед глазами (помните: в реальном редакторе вы пишете обычные угловые скобки, здесь они экранированы для отображения):
| Оператор | Смысл |
= | равно |
<> | не равно |
<, <=, >, >= | сравнения чисел и дат |
IN | вхождение в список |
STARTS WITH / CONTAINS / ENDS WITH | строковые предикаты |
=~ | совпадение с регулярным выражением |
Обратите внимание на «не равно»: в Cypher это <>, а не !=. Знак =~ включает регулярные выражения — например, WHERE p.name =~ '(?i)tom.*' найдёт всех Томов без учёта регистра.
Фильтр по наличию связи
Вот где Cypher красивее SQL: условие можно ставить на наличие подграфа. «Режиссёры, которые сами нигде не снимались»:
MATCH (p:Person)-[:DIRECTED]->(:Movie)
WHERE NOT (p)-[:ACTED_IN]->(:Movie)
RETURN DISTINCT p.nameЗдесь NOT (p)-[:ACTED_IN]->(:Movie) — это паттерн-предикат: «не существует такой связи». В новых версиях то же пишут через WHERE NOT EXISTS { (p)-[:ACTED_IN]->(:Movie) }.
Это та самая возможность, ради которой многие и переходят на графовые базы. Сформулируйте на SQL вопрос «режиссёры, которые сами нигде не снимались» — и вы получите подзапрос с NOT EXISTS или LEFT JOIN ... IS NULL поверх таблицы-связки, который не всякий джуниор напишет с первого раза. В Cypher вы просто дорисовываете к паттерну фразу «и при этом нет вот такой стрелки». Условие живёт на форме графа, а не на значениях столбцов. Положительный вариант тоже бывает нужен: WHERE EXISTS { (p)-[:DIRECTED]->(:Movie) } оставит только тех, у кого режиссёрская связь есть. Связка EXISTS/NOT EXISTS над подпаттерном — один из самых выразительных инструментов Cypher.
Фильтр по метке и null
Метку тоже можно проверять в WHERE: WHERE p:Director. А IS NULL / IS NOT NULL ловят отсутствие свойства — в графе у узлов схема гибкая, и свойства может просто не быть:
MATCH (p:Person)
WHERE p.twitter IS NOT NULL
RETURN p.name, p.twitterКак работает под капотом
WHERE — это фильтр в конвейере: он отбрасывает строки потока, не прошедшие предикат. Важно для производительности, что планировщик старается протолкнуть фильтр как можно раньше (predicate pushdown) — например, применить условие на свойство сразу при сканировании узлов по индексу, а не после полного обхода. Поэтому условие на индексированное свойство якорного узла почти бесплатно, а условие на дальний узел после большого обхода — дороже.
Отдельно стоит понять трёхзначную логику Cypher, унаследованную от SQL. Сравнение с null даёт не true и не false, а третье значение — null («неизвестно»). Предикат WHERE пропускает строку, только если итог истинен; «неизвестно» трактуется как «не пропускать». Отсюда классическая ловушка: WHERE p.age > 18 молча выкинет всех, у кого свойства age вообще нет, потому что null > 18 — это null, а не false. То же с отрицанием: WHERE NOT p.age > 18 тоже не вернёт безвозрастных, ведь NOT null по-прежнему null. Если хотите явно учесть таких, добавляйте OR p.age IS NULL. В графе, где схема гибкая и свойства у узлов могут отсутствовать, эта тонкость встречается куда чаще, чем в строго-табличном SQL.
Частые ошибки
- Путать
=и присваивание. В WHERE сравнение — это=(одинарное), как в SQL, не==. - Сравнивать с несуществующим свойством.
WHERE p.age > 18молча отбросит узлы, у которых свойстваageвообще нет (там null). - Тяжёлый NOT-паттерн на большом результате. Проверка «нет такой связи» по миллионам узлов дорогая — фильтруйте раньше.
Итоги
WHEREфильтрует совпадения по свойствам, диапазонам, спискам и строковым предикатам.- Уникальная сила Cypher — фильтр по наличию/отсутствию связи (паттерн-предикат, EXISTS).
IS NULL/IS NOT NULLважны: в графе свойство может просто отсутствовать.- Планировщик проталкивает фильтр раньше; условие на индексированный якорь — почти бесплатно.