Я пытаюсь написать запрос Cypher, который является своего рода запросом на объединение, в основном что-то вроде:
MATCH (myNodeUsedInAllSubQueries)
WITH myNodeUsedInAllSubQueries
MATCH (a)-[...]->(myNodeUsedInAllSubQueries)
RETURN a
UNION
MATCH (b)-[...]->(myNodeUsedInAllSubQueries)
RETURN b
...
Я хочу сопоставить (myNodeUsedInAllSubQueries)
однажды в начале, потому что этот узел используется во всех подзапросах.
Конечно, извлечение этого узла в моем примере не так просто, как здесь (это включает в себя отношения и предложение WHERE
, но здесь я буду упрощать).
Я попытаюсь объяснить это на простом примере и на том, что я пробовал до сих пор.
Графически структура графа выглядит следующим образом:
(John: Person) ----------[LIKES]----------> (pizza: Food)
^
(Developers: Group) ------[LIKES]------┘
(Jim: Person) --[BELONGS_TO]--> (Business: Group) --[LIKES]--> (apple: Food)
Пример доступен здесь, в консоли Neo4J. Сценарий импорта находится здесь (на случай, если вы хотите воспроизвести проблему локально):
CREATE (john: Person{name: "John"});
CREATE (jim: Person{name: "Jim"});
CREATE (devs: Group{name: "Developers"});
CREATE (business: Group{name: "Business"});
CREATE (pizza: Food{name: "pizza"});
CREATE (apple: Food{name: "apple"});
MATCH (john: Person{name: "John"}), (pizza: Food{name: "pizza"})
CREATE (john)-[:LIKES]->(pizza);
MATCH (jim: Person{name: "Jim"}), (business: Group{name: "Business"})
CREATE (jim)-[:BELONGS_TO]->(business);
MATCH (devs: Group{name: "Developers"}), (pizza: Food{name: "pizza"})
CREATE (devs)-[:LIKES]->(pizza);
MATCH (business: Group{name: "Business"}), (apple: Food{name: "apple"})
CREATE (business)-[:LIKES]->(apple);
Для этого конкретного примера мой вопрос: как получить все узлы Person
, которые любят пиццу? Знаю это :
Person
может понравиться Food
(прямая связь)Group
может лайкнуть Food
, и транзитивно, если человек A
BELONGS_TO
группу,
он LIKES
еще и еда, что в группе LIKES
(но есть нет прямого отношения
между Person
и Food
в этом случае)Я также хотел бы получить узел pizza
один раз. Как я сказал во введении, в моем реальном примере поиск pizza
может быть довольно сложным, поэтому я не хочу дублировать MATCH
в каждом случае.
UNION
MATCH (pizza: Food{name: "pizza"})
WITH pizza
MATCH (p: Person)-[:LIKES]->(pizza)
RETURN p
UNION
MATCH (p: Person)-[:BELONGS_TO]->(g: Group)-[:LIKES]->(pizza)
RETURN p
Этот запрос неожиданно возвращает 2 узла: John
и Jim
. После расследования я понял, что
это потому, что (pizza)
во 2-м предложении UNION
не относится к узлу pizza
возвращается первым MATCH
, а скорее любым узлом. Мы можем подтвердить это, запустив:
MATCH (pizza: Food{name: "pizza"})
WITH pizza
MATCH (p: Person)-[:LIKES]->(pizza)
RETURN p
UNION
MATCH (p: Person)-[:BELONGS_TO]->(g: Group)-[:LIKES]->(anything)
WHERE anything = pizza
RETURN p
Это возвращает ошибку:
Neo.ClientError.Statement.SyntaxError: Variable `pizza` not defined (line 7, column 18 (offset: 184))
"WHERE anything = pizza"
, подтверждая тот факт, что узел pizza
не распространяется на второй узел MATCH
.
WITH
и COLLECT
Это устраняет проблему узла pizza
, который нельзя передать из первого MATCH
в 3-й MATCH
.
MATCH (pizza: Food{name: "pizza"})
WITH pizza
MATCH (p: Person)-[:LIKES]->(pizza)
WITH pizza, COLLECT(p) AS people
MATCH (p: Person)-[:BELONGS_TO]->(g: Group)-[:LIKES]->(pizza)
RETURN people + COLLECT(p) AS people
Но этот запрос ничего не возвращает: это потому, что 3-й MATCH
ничего не возвращает
(нет Person
в Group
том LIKES
pizza
).
Если я добавлю Person
в Developers
Group
:
MATCH (devs: Group{name: "Developers"})
CREATE (emma: Person{name: "Emma"})-[:BELONGS_TO]->(devs)
Предыдущий запрос успешно возвращает Джона и Эмму, потому что последний MATCH
возвращает по меньшей мере один Person
.
Но в моем случае возможно, что этот MATCH
ничего не возвращает, так что не работает.
Я надеюсь, что этот пример прост и понятен. В этом случае запрос, который я ищу, должен возвращать только Джона, потому что он единственный Person
здесь, кому нравится pizza
.
[ОБНОВЛЕНО]
Это должно работать для вашего простого варианта использования:
MATCH (p:Person)-[:BELONGS_TO|LIKES*..2]->(food:Food {name:"pizza"})
RETURN food, COLLECT(p) AS people;
Мой ответ был скорректирован.
В качестве альтернативы ответу киберсама, если структура вашего графика такова, что MATCH (p:Person)-[:BELONGS_TO|LIKES*..2]->(food:Food)
не будет работать (например, если вы работаете над графиком, где люди могут нравиться друг другу, и вы не хотите возвращать человека, который любит другого человек, который любит пиццу, который будет соответствовать вашему шаблону), вы можете использовать отношение длины 0..1
для представления необязательного шага в шаблоне:
MATCH (p:Person)-[:BELONGS_TO*0..1]->()-[:LIKES]->(food:Food)
WHERE food.name = "pizza"
RETURN COLLECT(p) AS people;
Это позволяет сопоставлять оба этих шаблона одновременно:
(p:Person)-[:LIKES]->(food:Food)
а также
(p:Person)-[:BELONGS_TO]->()-[:LIKES]->(food:Food)
Я написал статья базы знаний по этому подходу.
Спасибо, это работает для примера, который я предоставил. Я уже читал вашу статью, но переписать UNION
запросы не всегда так просто. В моем реальном примере у меня больше двух UNION
«подзапросов», и представлять это одним MATCH
очень утомительно. Можно ли написать союз прямо в предложении MATCH
? Что-то вроде: MATCH [...] OR [...] WHERE food.name = "pizza" ...
?
К сожалению, этот подход в настоящее время невозможен. Для более сложных случаев вам действительно может понадобиться UNION, или вы можете использовать другие подходы, особенно если вам нужно выполнить дополнительную работу с объединенными результатами.
Понятно, тогда попробую переписать свой запрос с вашими предложениями, спасибо за помощь.
Спасибо, это должно быть
MATCH (p:Person)-[:BELONGS_TO|LIKES*..2]->(food:Food{name: "pizza"})
здесь, потому что я хотел бы найти только тех, кто любит пиццу.