Cypher: объединение запросов с начальным поиском узла

Я пытаюсь написать запрос 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, и транзитивно, если человек ABELONGS_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 том LIKESpizza).

Если я добавлю Person в DevelopersGroup :

MATCH (devs: Group{name: "Developers"})
CREATE (emma: Person{name: "Emma"})-[:BELONGS_TO]->(devs)

Предыдущий запрос успешно возвращает Джона и Эмму, потому что последний MATCH возвращает по меньшей мере один Person. Но в моем случае возможно, что этот MATCH ничего не возвращает, так что не работает.

Я надеюсь, что этот пример прост и понятен. В этом случае запрос, который я ищу, должен возвращать только Джона, потому что он единственный Person здесь, кому нравится pizza.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
140
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

[ОБНОВЛЕНО]

Это должно работать для вашего простого варианта использования:

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{name: "pizza"}) здесь, потому что я хотел бы найти только тех, кто любит пиццу.

norbjd 28.05.2019 11:02

Мой ответ был скорректирован.

cybersam 28.05.2019 19:48
Ответ принят как подходящий

В качестве альтернативы ответу киберсама, если структура вашего графика такова, что 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" ...?

norbjd 28.05.2019 11:08

К сожалению, этот подход в настоящее время невозможен. Для более сложных случаев вам действительно может понадобиться UNION, или вы можете использовать другие подходы, особенно если вам нужно выполнить дополнительную работу с объединенными результатами.

InverseFalcon 28.05.2019 23:21

Понятно, тогда попробую переписать свой запрос с вашими предложениями, спасибо за помощь.

norbjd 29.05.2019 10:17

Другие вопросы по теме