Глава 14. От SQL к Cypher
Это руководство для тех людей, которые понимают SQL. Вы можете использовать эти знания, чтобы быстро освоить Cypher и начать исследовать Neo4j.
14.1 START
SQL начинает с результата, который вы хотите получить — мы ВЫБИРАЕМ (SELECT) то, что нам нужно, а затем описываем источники данных. В Cypher, предложение START имеет отличную концепцию, которая определяет стартовые точки в графе, откуда должен выполняться запрос.
С точки зрения SQL, идентификаторы в START подобны именам таблиц, которые указывают на набор узлов или связей. Набор может быть перечислен литрально, передан через параметры или, как я показываю в следующем примере, быть определен индексным поиском.
Фактически предложение START больше похоже не на SELECT, а на что-то среднее между предложениями FROM и WHERE в SQL.
SQL-запрос
SELECT * FROM "Person" WHERE name = 'Anakin'
NAME | ID | AGE | HAIR |
---|---|---|---|
Anakin | 1 | 20 | blonde |
Cypher-запрос.
START person=node:Person(name = 'Anakin') RETURN person
person |
---|
Node[0]{name:"Anakin",id:1,age:20,hair:"blonde"} |
Cypher позволяет определить несколько стартовых точек. Это не должно показаться необычным с точки зрения SQL — каждая таблица в предложении FROM представляет собой еще одну стартовую точку.
14.2 MATCH
В отличие от SQL, который оперирует множествами, Cypher в основном работает с подграфами. Реляционным эквивалентом является текущее множество кортежей, оцениваемых во время запроса SELECT.
Форма подграфа специфицируется в предложении MATCH. Предложение MATCH аналогично JOIN в SQL. Обычная связь a→b есть внутреннее соединение (inner join) между узлами a и b — обе стороны должны иметь, по меньшей мере, одно совпадение, в противном случае ничего не возвращается.
Мы начнём с простого примера, в котором вы найдём все адреса электронной почты, которые связаны с лицом “Anakin”. Это обычная связь один-ко-многим.
SQL-запрос
SELECT "Email".* FROM "Person" JOIN "Email" ON "Person".id = "Email".person_id WHERE "Person".name = 'Anakin'
ADDRESS | COMMENT | PERSON_ID |
---|---|---|
anakin@example.com | home | 1 |
anakin@example.org | work | 1 |
Cypher-запрос
START person=node:Person(name = 'Anakin') MATCH person-[:email]->email RETURN email
Node[6]{address:"anakin@example.com",comment:"home"} |
Node[7]{address:"anakin@example.org",comment:"work"} |
Здесь нет соединенной таблицы, но если она необходима, следующий пример покажет, как это сделать с помощью написания связи образца типа:
-[r:belongs_to]->
которая вводит (эквивалент) соединенной таблицы, доступной через переменную r. В действительности это – именованная связь в Cypher, поэтому мы говорим «соединить Person с Group через belongs_to». Посмотрите иллюстрацию, на которой сравнивается модель SQL и Neo4j/Cypher.
А вот примеры запросов:
SQL-запрос
SELECT "Group".*, "Person_Group".* FROM "Person" JOIN "Person_Group" ON "Person".id = "Person_Group".person_id JOIN "Group" ON "Person_Group".Group_id="Group".id WHERE "Person".name = 'Bridget'
NAME | ID | BELONGS_TO_GROUP_ID | PERSON_ID | GROUP_ID |
---|---|---|---|---|
Admin | 4 | 3 | 2 | 4 |
Cypher-запрос
START person=node:Person(name = 'Bridget') MATCH person-[r:belongs_to]->group RETURN group, r
group | r |
---|---|
Node[5]{name:"Admin",id:4} | :belongs_to[0]{} |
Внешнее соединение выполняются очень просто. Добавьте OPTIONAL перед match, и эта необязательная связь между узлами и есть внешнее соединение в Cypher.
Будет ли это левым внешним соединением или же правым внешним соединением зависит от того, с какой стороны образца определена стартовая точка. Этот пример на левое внешнее соединение, поскольку связанный узел находится слева:
SQL-запрос
SELECT "Person".name, "Email".address FROM "Person" LEFT JOIN "Email" ON "Person".id = "Email".person_id
NAME | ADDRESS |
---|---|
Anakin | anakin@example.com |
Anakin | anakin@example.org |
Bridget | null |
Cypher-запрос
START person=node:Person('name: *') OPTIONAL MATCH person-[:email]->email RETURN person.name, email.address
person.name | email.address |
---|---|
"Anakin" | "anakin@example.com" |
"Anakin" | "anakin@example.org" |
"Bridget" | null |
Связи в Neo4j являются гражданами первого класса – это подобно таблицам SQL, предварительно соединенным друг с другом. Поэтому, естественно, Cypher спроектирован так, чтобы быть в состоянии легко обрабатывать сильно связанные данные.
Одной из таких областей являются древовидные структуры – каждый, кто пытался сохранять древовидные структуры в SQL, знает, что это было непросто, т.к. приходилось обходить ограничения реляционной модели. Существуют даже книги на эту тему.
Чтобы найти все группы и подгруппы, в которые входит Бриджит (Bridget), достаточно выполнить такой запрос в Cypher:
Cypher-запрос
START person=node:Person('name: Bridget') MATCH person-[:belongs_to*]->group RETURN person.name, group.name
person.name | group.name |
---|---|
"Bridget" | "Admin" |
"Bridget" | "Technichian" |
"Bridget" | "User" |