SQL 到 Cypher 转换
介绍
将 SQL 查询转换为 Cypher® 是此驱动程序的一个可选功能,它由两部分组成
-
转换器 SPI,位于模块
org.neo4j:neo4j-jdbc-translator-spi
中。它包含两个接口:SqlTranslatorFactory
和实际的SqlTranslator
。 -
此 SPI 的一个具体实现,发布为
org.neo4j:neo4j-jdbc-translator-impl
。
转换器可以链式连接,并且类路径上可以有任意数量的转换器。它们的优先级是可配置的,我们的默认实现具有最低优先级。因此,您可以例如拥有一个自定义转换器,负责一组固定的查询,如果它收到一个无法翻译的查询,它会将其传递给我们的实现。
将任意 SQL 查询转换为 Cypher 是一项主观任务,因为没有一种正确的方法可以将表名映射到图对象:表名可以按原样用作标签,或者可以转换为单数形式等。映射关系甚至更棘手:关系类型应该从联接表、联接列(在这种情况下,是哪一个?)还是外键派生?
我们认为我们的假设适用于各种用例,并且我们没有提供配置来满足所有场景,而是提供了编写您自己的转换层的可能性。驱动程序将使用标准的 Java 服务加载机制在模块或类路径上查找 SPI 的实现。
某些工具(如 Tableau)使用类加载器,这会阻止驱动程序使用标准的 Java 服务加载机制。对于这些场景,我们提供了一个名为 translatorFactory 的附加配置属性。将其设置为 DEFAULT 可直接加载我们的默认实现,或者设置为任何其他工厂的完全限定类名。请注意,我们的默认实现或您的自定义实现必须位于类路径上。 |
将 SQL 转换为 Cypher
启用 SQL 到 Cypher 转换只有一个要求:您必须在类路径上有一个实现 SPI 的模块。如果您使用完整捆绑包 (org.neo4j:neo4j-jdbc-full-bundle
),这会自动实现。在这种情况下,您不需要添加任何其他依赖项。如果您使用独立分发或“小型”捆绑包 org.neo4j:neo4j-jdbc-bundle
,则必须添加 org.neo4j:neo4j-jdbc-translator-impl
工件。
实现将自动加载。如果您按需使用转换,它将进行惰性加载(即,没有额外的类被触及或加载到内存中)。如果您为所有语句配置自动转换,则实现将进行预加载。关于加载实现,没有其他配置选项。
按需使用
转换器可以通过官方 JDBC API nativeSQL
按需使用,该 API 位于 java.sql.Connection
类中。通过以下导入
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import java.util.logging.Logger;
您只需将 SQL 语句传递给 nativeSQL
,即可获得 Cypher。
try (var connection = DriverManager.getConnection(url, username, password)) {
var sql = connection.nativeSQL("SELECT * FROM Movie n");
assert """
MATCH (n:Movie)
RETURN *""".equals(sql);
}
对于所有查询
如果您使用 enableSQLTranslation=true
作为 URL 参数或配置属性打开与 Neo4j 实例的连接,则所有语句都将从 SQL 转换为 Cypher。如果您以这种方式配置驱动程序,转换器将进行预加载。
var url = "jdbc:neo4j://localhost:7687?enableSQLTranslation=true";
try (var connection = DriverManager.getConnection(url, username, password);
var stmnt = connection.createStatement();
var result = stmnt.executeQuery("SELECT n.title FROM Movie n")) {
while (result.next()) {
LOGGER.info(result.getString("n.title"));
}
}
有时您可能需要对某些语句回退到 Cypher,无论是为了使用无法用 SQL 表达的结构,还是因为我们的默认转换器无法处理您的查询。我们提供了一个特殊的注释,您可以将其用作语句中的提示,以停止自动转换:/*+ NEO4J FORCE_CYPHER */
。
var url = "jdbc:neo4j://localhost:7687?enableSQLTranslation=true";
var query = """
/*+ NEO4J FORCE_CYPHER */
MATCH (:Station { name: 'Denmark Hill' })<-[:CALLS_AT]-(d:Stop)
((:Stop)-[:NEXT]->(:Stop)){1,3}
(a:Stop)-[:CALLS_AT]->(:Station { name: 'Clapham Junction' })
RETURN localtime(d.departs) AS departureTime,
localtime(a.arrives) AS arrivalTime
""";
try (var connection = DriverManager.getConnection(url, username, password);
var stmnt = connection.createStatement();
var result = stmnt.executeQuery(query)) {
while (result.next()) {
LOGGER.info(result.getTime("departureTime").toString());
}
}
使用默认转换器
支持的 SQL 方言
我们的默认转换器使用 jOOQ 的 OSS 解析器,该解析器已支持广泛的 SQL 方言。我们选择 jOOQ 的通用默认方言作为我们的默认方言,但您可以在 SQL 到 Cypher 配置中使用参数 s2c.sqlDialect
并结合下方配置中列出的受支持方言之一来覆盖此设置。POSTGRES
对于多种集成来说可能是一个不错的选择。
但是请记住,翻译中的任何不足可能不是由于解析器缺乏功能,而是由于缺乏显而易见的、语义等效的 Cypher 结构。这意味着我们可能能够解析特定的 SQL 片段,但无法在没有额外上下文信息的情况下将其翻译成 Neo4j 可以理解的有意义的内容。
配置
默认实现提供了一些配置设置。它们在 URL 或配置选项中必须以 s2c
为前缀
名称 | 含义 | 默认值 |
---|---|---|
|
是否按原样解析表名。 |
|
|
从表名到标签的映射。 |
一个空映射 |
|
从列名到关系类型的映射。 |
一个空映射 |
|
是否格式化生成的 Cypher。 |
|
|
是否始终转义名称。 |
除非在美化打印开启时明确配置为 |
|
解析时使用的方言。支持的值有 |
|
接下来的几个示例使用 properties
配置,以避免本文档中出现过长的 URL,但所有属性也可以通过 URL 指定。
var properties = new Properties();
properties.put("username", "neo4j");
properties.put("password", "verysecret");
properties.put("enableSQLTranslation", "true");
properties.put("s2c.prettyPrint", "false");
properties.put("s2c.alwaysEscapeNames", "false");
properties.put("s2c.tableToLabelMappings", "people:Person;movies:Movie;movie_actors:ACTED_IN");
var url = "jdbc:neo4j://localhost:7687";
var query = """
SELECT p.name, m.title
FROM people p
JOIN movie_actors r ON r.person_id = p.id
JOIN movies m ON m.id = r.person_id""";
try (var connection = DriverManager.getConnection(url, properties)) {
var sql = connection.nativeSQL(query);
assert "MATCH (p:Person)-[r:ACTED_IN]->(m:Movie) RETURN p.name, m.title".equals(sql);
}
var properties = new Properties();
properties.put("username", "neo4j");
properties.put("password", "verysecret");
properties.put("enableSQLTranslation", "true");
properties.put("s2c.parseNameCase", "UPPER");
var url = "jdbc:neo4j://localhost:7687";
var query = "SELECT * FROM people";
try (var connection = DriverManager.getConnection(url, properties)) {
var sql = connection.nativeSQL(query);
assert """
MATCH (people:PEOPLE)
RETURN *""".equals(sql);
}
SQL 解析器中的命名参数语法默认为 :name
(例如 Oracle、JPA、Spring 支持的,一个冒号后跟一个名称)。以下示例将该前缀更改为 $
(与 Cypher 使用的前缀相同)
var properties = new Properties();
properties.put("username", "neo4j");
properties.put("password", "verysecret");
properties.put("enableSQLTranslation", "true");
properties.put("s2c.parseNamedParamPrefix", "$");
properties.put("s2c.joinColumnsToTypeMappings", "people.movie_id:DIRECTED");
var url = "jdbc:neo4j://localhost:7687";
var query = """
SELECT *
FROM people p
JOIN movies m ON m.id = p.movie_id
WHERE p.name = $1
""";
try (var connection = DriverManager.getConnection(url, properties)) {
var sql = connection.nativeSQL(query);
assert """
MATCH (p:people)-[:DIRECTED]->(m:movies)
WHERE p.name = $1
RETURN *""".equals(sql);
}
当工具生成此类名称且不允许自定义时,这很有帮助。
转换概念
表名到标签的映射
最简单的 SELECT
语句是那些没有 FROM
子句的语句,例如
SELECT 1
它等同于 Cypher RETURN
RETURN 1
没有 JOIN
子句的 SELECT
语句转换起来非常直接。这里的挑战是如何将表名映射到标签
-
默认情况下,我们对 SQL 语句进行区分大小写的解析
-
表名映射到节点标签
-
表别名用作可识别的符号名称
SELECT t.a, t.b
FROM My_Table (1)
AS t (2)
WHERE t.a = 1
1 | 将按原样用作匹配的标签,即 My_Table |
2 | 表别名将成为节点别名 |
整个构造将被翻译为
MATCH (t:My_Table)
WHERE t.a = 1
RETURN t.a, t.b
表别名是可选的,如果您省略它们,我们将从标签和类型中派生别名。如果您检查转换后的查询,我们建议使用别名,因为这会使查询更具可读性。
星号选择
星号选择或 *
选择有不同的形式
- 未限定的
-
SELECT * FROM table
- 限定的
-
SELECT t.* FROM table t
还有一个变体,选择关系本身:SELECT t FROM table t
。
我们利用这一点让您决定是希望将 Neo4j 节点和关系作为实体、映射还是扁平化为单独的列返回。后者要求我们的转换器能够访问底层 Neo4j 数据库的模式。以下各节描述了用例。
投影单个属性
不要使用星号选择,而是列举属性
SELECT m.title FROM Movie m
表别名将用作符号名称
MATCH (m:Movie)
RETURN m.title;
您可以省略表别名
SELECT title FROM Movie
小写表名将是符号名称,并且当只有一个标签需要匹配时,会为其创建别名,以便可以轻松地通过该名称检索。
MATCH (movie:Movie)
RETURN movie.title AS title;
通过名称访问 JDBC 列会导致难以维护的代码,因为列重命名也会影响代码。为避免这种情况,请为列设置别名
SELECT title AS title FROM Movie
以便它有一个稳定的、众所周知的名称
MATCH (movie:Movie)
RETURN movie.title AS title;
投影所有属性
SELECT *
语句的转换方式取决于与 Neo4j 数据库的连接是否可用。
SELECT * FROM Person p
如果您处于离线状态,您将获得以下 Cypher 语句
MATCH (p:Person) RETURN *
上述查询将返回一个包含 Neo4j 节点的列 (p
)。这通常不是您在关系型世界中所期望的。如果您在线运行转换并且可以检索 Neo4j 元数据,您将获得一个语句,该语句将每个节点和关系的属性以及它们的元素 ID 扁平化
如果 Person
节点具有属性 born
和 name
,
SELECT * FROM Person p
您将获得此 Cypher 语句
MATCH (p:Person)
RETURN elementId(p) AS `v$id`,
p.born AS born, p.name AS name
这也适用于多个表(Movie
具有属性 title
和 released
)
SELECT * FROM Person p JOIN Movie m ON m.id = p.acted_in
MATCH (p:Person)-[acted_in:ACTED_IN]->(m:Movie)
RETURN elementId(p) AS `v$id`, p.born AS born, p.name AS name,
elementId(m) AS `v$id1`, m.title AS title, m.released AS released
我们为冲突的列名附加递增的数字(例如,Movie
和 Person
中都包含 name
和 remark
属性)
SELECT * FROM Person p JOIN Movie m ON m.id = p.acted_in
注意每个重复名称的递增数字
MATCH (p:Person)-[acted_in:ACTED_IN]->(m:Movie)
RETURN elementId(p) AS `v$id`,
p.born AS born, p.name AS name, p.remark AS remark,
elementId(m) AS `v$id1`,
m.name AS name1, m.released AS released, m.remark AS remark1
以下示例使用联接表来访问关系(我们将在本手册后面讨论联接时解释这一点),并且属性的扁平化也在这里起作用
SELECT *
FROM people p
JOIN movie_actors r ON r.person_id = p.id
JOIN movies m ON m.id = r.person_id
MATCH (p:Person)-[r:ACTED_IN]->(m:Movie)
RETURN elementId(p) AS `v$id`,
p.born AS born, p.name AS name,
elementId(p) AS `v$person_id`,
elementId(r) AS `v$id1`, r.role AS role,
elementId(m) AS `v$movie_id`,
elementId(m) AS `v$id2`,
m.title AS title, m.released AS released
SELECT * FROM Person p ORDER BY name ASC
MATCH (p:Person)
RETURN elementId(p) AS `v$id`,
p.born AS born, p.name AS name
ORDER BY p.name
也可以使用限定别名。如果没有可用的 Neo4j 元数据,您将获得节点/关系的属性映射
SELECT m.*, p.*
FROM Person p
JOIN Movie m ON m.id = p.acted_in
相应的列必须在 JDBC 中向下转型为映射
MATCH (p:Person)-[acted_in:ACTED_IN]->(m:Movie)
RETURN m{.*} AS m, p{.*} AS p
如果我们添加更多数据(例如,Person
中的 born
和 name
),限定星号将投影所有这些数据(注意我们还从 Movie
表中投影了一个单一的、已知的列)
SELECT p.*, m.title AS title
FROM Person p
JOIN Movie m ON m.id = p.acted_in
MATCH (p:Person)-[acted_in:ACTED_IN]->(m:Movie)
RETURN elementId(p) AS `v$id`, p.born AS born, p.name AS name, m.title AS title
返回节点和关系
投影表别名的语句,例如
SELECT m FROM Movie m
将生成一个 Cypher 语句,返回匹配的节点作为节点。
MATCH (m:Movie)
RETURN m;
节点也可以设置别名
SELECT m AS node FROM Movie m
MATCH (m:Movie)
RETURN m AS node;
也可以使用未设置别名的表
SELECT movie FROM Movie
MATCH (movie:Movie)
RETURN movie;
也支持多个实体
SELECT p, r, m FROM Person p
JOIN ACTED_IN r ON r.person_id = p.id
JOIN Movie m ON m.id = r.movie_id
MATCH (p:Person)-[r:ACTED_IN]->(m:Movie) RETURN p, r, m
SQL 与 Cypher 示例比较
以下示例的来源是:SQL 与 Cypher 比较。
选择并返回记录
从 products
表中选择所有内容。
SELECT p.*
FROM products as p
同样,在 Cypher 中,您只需 MATCH
一个简单的模式:所有带有 label Product
的节点,并 RETURN
它们。
MATCH (p:Product)
RETURN p{.*} AS p
上述查询将投影匹配节点的所有属性。如果您想返回节点本身,请在不使用星号的情况下选择它
SELECT p
FROM products as p
MATCH (p:Product)
RETURN p
字段访问、排序和分页
只返回属性的子集效率更高,例如 ProductName
和 UnitPrice
。此外,我们还可以按价格排序,并只返回最昂贵的 10 个项目。(请记住,在 Neo4j 中,标签、关系类型和属性名区分大小写。)
SELECT p.`productName`, p.`unitPrice`
FROM products as p
ORDER BY p.`unitPrice` DESC
LIMIT 10
MATCH (p:Product)
RETURN p.productName, p.unitPrice ORDER BY p.unitPrice DESC LIMIT 10
默认的排序方向将按原样转换
SELECT * FROM Movies m ORDER BY m.title
MATCH (m:Movies)
RETURN * ORDER BY m.title
DISTINCT
投影
投影的 DISTINCT
关键字已处理
SELECT DISTINCT m.released FROM Movies m
MATCH (m:Movies)
RETURN DISTINCT m.released
它也适用于 *
投影
SELECT DISTINCT m.* FROM Movies m
MATCH (m:Movies)
RETURN DISTINCT m {.*} AS m
然而,由于限定星号在可用时会使用元数据,因此在数据库连接下的转换是不同的
SELECT DISTINCT m.* FROM Movies m
MATCH (m:Movies)
RETURN DISTINCT elementId(m) AS `v$id`, m.title AS title, m.released AS released
请注意,每行都包含 Neo4j 元素 ID,这使得每行都是唯一的。话虽如此,DISTINCT
子句与星号一起使用时作用有限。
表达式
大多数 SQL 表达式都有相应的 Cypher 表达式,并且可以直接转换。
算术表达式
算术表达式是 1:1 转换。
SELECT
1 + 2,
1 - 2,
1 * 2,
1 / 2,
square(2)
请注意,默认转换器的底层技术内部使用 Cypher-DSL,它将用括号括住算术(和逻辑)表达式
RETURN
(1 + 2),
(1 - 2),
(1 * 2),
(1 / 2),
(2 * 2)
函数
数值函数
我们可以转换 Neo4j 的 Cypher 实现支持的所有数值函数:数学函数 - 数值
SELECT
abs(1),
ceil(1),
floor(1),
round(1),
round(1, 1),
sign(1)
将被翻译为
RETURN
abs(1),
ceil(1),
floor(1),
round(1),
round(1, 1),
sign(1)
对数函数
Neo4j 支持广泛的对数函数。
SELECT
exp(1),
ln(1),
log(2, 1),
log10(1),
sqrt(1)
将被翻译为
RETURN
exp(1),
log(1),
(log(1) / log(2)),
log10(1),
sqrt(1)
三角函数
对三角函数的调用
SELECT
acos(1),
asin(1),
atan(1),
atan2(1, 2),
cos(1),
cot(1),
degrees(1),
pi(),
radians(1),
sin(1),
tan(1)
将被翻译为相应的 Neo4j 函数
RETURN
acos(1),
asin(1),
atan(1),
atan2(1, 2),
cos(1),
cot(1),
degrees(1),
pi(),
radians(1),
sin(1),
tan(1)
字符串函数
以下字符串操作保证有效
SELECT
lower('abc'),
cast(3 as varchar),
trim(' abc '),
length('abc'),
left('abc', 2),
ltrim(' abc '),
replace('abc', 'b'),
replace('abc', 'b', 'x'),
reverse('abc'),
right('abc', 2),
rtrim(' abc '),
substring('abc', 2 - 1),
substring('abc', 2 - 1, 2),
upper('abc')
RETURN
toLower('abc'),
toString(3),
trim(' abc '),
size('abc'),
left('abc', 2),
ltrim(' abc '),
replace('abc', 'b', NULL),
replace('abc', 'b', 'x'),
reverse('abc'),
right('abc', 2),
rtrim(' abc '),
substring('abc', (2 - 1)),
substring('abc', (2 - 1), 2),
toUpper('abc')
标量函数
输入
SELECT
coalesce(1, 2),
coalesce(1, 2, 3),
nvl(1, 2),
cast('1' as boolean),
cast(1 as float),
cast(1 as double precision),
cast(1 as real),
cast(1 as tinyint),
cast(1 as smallint),
cast(1 as int),
cast(1 as bigint)
将被翻译为(参见 标量函数)
RETURN
coalesce(1, 2),
coalesce(1, 2, 3),
coalesce(1, 2),
toBoolean('1'),
toFloat(1),
toFloat(1),
toFloat(1),
toInteger(1),
toInteger(1),
toInteger(1),
toInteger(1)
查询表达式
也支持一些高级 SQL 表达式。
简单 CASE
表达式
简单的 CASE
表达式
SELECT
CASE 1 WHEN 2 THEN 3 END,
CASE 1 WHEN 2 THEN 3 ELSE 4 END,
CASE 1 WHEN 2 THEN 3 WHEN 4 THEN 5 END,
CASE 1 WHEN 2 THEN 3 WHEN 4 THEN 5 ELSE 6 END
RETURN CASE 1 WHEN 2 THEN 3 END, CASE 1 WHEN 2 THEN 3 ELSE 4 END, CASE 1 WHEN 2 THEN 3 WHEN 4 THEN 5 END, CASE 1 WHEN 2 THEN 3 WHEN 4 THEN 5 ELSE 6 END
有关更多信息,请参阅 Cypher → 条件表达式 (CASE)。
高级 CASE
表达式
以及使用搜索的 CASE
语句
SELECT
CASE WHEN 1 = 2 THEN 3 END,
CASE WHEN 1 = 2 THEN 3 ELSE 4 END,
CASE WHEN 1 = 2 THEN 3 WHEN 4 = 5 THEN 6 END,
CASE WHEN 1 = 2 THEN 3 WHEN 4 = 5 THEN 6 ELSE 7 END
将被翻译为
RETURN
CASE WHEN 1 = 2 THEN 3 END,
CASE WHEN 1 = 2 THEN 3 ELSE 4 END,
CASE WHEN 1 = 2 THEN 3 WHEN 4 = 5 THEN 6 END,
CASE WHEN 1 = 2 THEN 3 WHEN 4 = 5 THEN 6 ELSE 7 END
有关更多信息,请参阅 Cypher → 条件表达式 (CASE)。
谓词
与表达式一样,许多用作谓词的逻辑 SQL 表达式和条件都可以直接翻译成 Cypher 谓词。
合取和析取
支持所有逻辑合取和析取。
SELECT 1 FROM p WHERE 1 = 1 AND 2 = 2 OR 3 = 3
MATCH (p:p)
WHERE ((1 = 1
AND 2 = 2)
OR 3 = 3)
RETURN 1
输入
SELECT 1 FROM p WHERE NOT 1 = 1 XOR 2 = 2
将被翻译为
MATCH (p:p)
WHERE (NOT (1 = 1)
XOR 2 = 2)
RETURN 1
运算符
算术运算符
输入
SELECT 1 FROM p WHERE 1 = 1 AND 2 > 1 AND 1 < 2 AND 1 <= 2 AND 2 >= 1 AND 1 != 2
将被翻译为
MATCH (p:p)
WHERE (1 = 1
AND 2 > 1
AND 1 < 2
AND 1 <= 2
AND 2 >= 1
AND 1 <> 2)
RETURN 1
BETWEEN
SQL 中的 Between 是包含的
SELECT 1 FROM p WHERE 2 BETWEEN 1 AND 3
并将转换为(由于底层生成器的限制,我们无法生成较短的形式 (1 ⇐ 2 ⇐ 3
))
MATCH (p:p)
WHERE (1 <= 2) AND (2 <= 3)
RETURN 1
SQL 的 BETWEEN
子句有一个 SYMMETRIC
关键字,表示您不关心范围的哪个边界更大
SELECT 1 FROM p WHERE 2 BETWEEN SYMMETRIC 3 AND 1
我们将其翻译为析取
MATCH (p:p)
WHERE (3 <= 2) AND (2 <= 1) OR (1 <= 2) AND (2 <= 3)
RETURN 1
逻辑行值表达式
上述示例基于标量表达式。行值表达式也将被转换
SELECT 1
FROM p
WHERE (1, 2) = (3, 4)
OR (1, 2) < (3, 4)
OR (1, 2) <= (3, 4)
OR (1, 2, 3) <> (4, 5, 6)
OR (1, 2, 3) > (4, 5, 6)
OR (1, 2, 3) >= (4, 5, 6)
产生语义等效的 Cypher
MATCH (p:p)
WHERE 1 = 3 AND 2 = 4
OR (1 < 3 OR 1 = 3 AND 2 < 4)
OR (1 < 3 OR 1 = 3 AND 2 <= 4)
OR (1 != 4 AND 2 != 5 AND 3 != 6)
OR (1 > 4 OR 1 = 4 AND (2 > 5 OR 2 = 5 AND 3 > 6))
OR (1 > 4 OR 1 = 4 AND (2 > 5 OR 2 = 5 AND 3 >= 6))
RETURN 1
使用联接映射关系
从表面上看,联接是 SQL 中具体化的关系(外键不是)。遗憾的是,将其映射到 Cypher 并不那么简单。有几种实现选项
-
当在列上联接两个表时,取左侧表的列,将其名称用作关系类型,并将其视为从左到右传出。
-
当使用交叉表(通常在 SQL 中为具有属性的
m:n
关系建模)联接两个表时,使用该交叉表的名称作为关系类型。
我们实现了一些变体,但我们不声称它们在所有情况下都绝对有用。
1:n 联接
自然联接
SQL NATURAL
联接是表示关系名称的最简单方法,无需进行任何映射。一次跳转的 NATURAL JOIN
将转换为匿名、通配符关系。
SELECT p, m FROM Person p
NATURAL JOIN Movie m
MATCH (p:Person)-->(m:Movie) RETURN p, m
NATURAL
联接可以链式连接,并且连接联接表不需要存在。这将转换为 Neo4j 关系
SELECT p.name, r.roles, m.* FROM Person p
NATURAL JOIN ACTED_IN r
NATURAL JOIN Movie m
MATCH (p:Person)-[r:ACTED_IN]->(m:Movie)
RETURN p.name, r.roles,
elementId(m) AS `v$id`, m.title AS title, m.released AS released
简单联接
假设我们配置了转换器以使用以下表映射
-
people
映射到标签People
-
movies
映射到标签Movie
有了这些,我们转换
SELECT p.name, m.title
FROM people p
JOIN movies m ON m.id = p.directed
为
MATCH (p:Person)-[directed:DIRECTED]->(m:Movie)
RETURN p.name, m.title
DIRECTED
是左表(p.directed
)中联接列的大写版本。
如果我们有不同的列名,我们可以添加 people.movie_id:DIRECTED
形式的联接列映射
SELECT p.name, m.title
FROM people p
JOIN movies m ON m.id = p.movie_id
为
MATCH (p:Person)-[directed:DIRECTED]->(m:Movie)
RETURN p.name, m.title
m:n 联接
交叉表是指包含对另外两个表的引用的表,至少以两列的形式。这种构造在关系模型中通常是创建 m:n
关系所必需的。在 Neo4j 中,这种辅助构造是不必要的。我们可以建模从一个标签到另一个标签的任意数量的出站和入站关系,它们也可以具有属性。因此,我们可以将这种构造用于我们的转换器。
以下示例使用如下配置的映射
-
people
映射到标签People
-
movies
映射到标签Movie
-
movie_actors
映射到ACTED_IN
SELECT p.name, m.title
FROM people p (1)
JOIN movie_actors r ON r.person_id = p.id (2)
JOIN movies m ON m.id = r.person_id (3)
1 | 用于映射出站关系的表 |
2 | 一个交叉表,在下一个 JOIN 子句中再次使用 |
3 | 最终的 JOIN 子句 |
我们不进行语义分析:联接的顺序很重要,并将导致以下查询
MATCH (p:Person)-[r:ACTED_IN]->(m:Movie)
RETURN p.name, m.title
多个联接将形成关系链
SELECT p.name AS actor, d.name AS director, m.title
FROM people p
JOIN movie_actors r ON r.person_id = p.id
JOIN movies m ON m.id = r.person_id
JOIN movie_directors r2 ON r2.movie_id = m.id
JOIN people d ON r2.person_id = d.id
MATCH (p:`Person`)-[r:`ACTED_IN`]->(m:`Movie`)<-[r2:`DIRECTED`]-(d:`Person`)
RETURN p.name AS actor, d.name AS director, m.title
请注意 DIRECTED
关系的方向是如何由联接列的顺序定义的。
DML 语句
本节列出了支持的数据操纵语言 (DML) 语句。尽管 SELECT
语句在技术上也属于 DML,但它在转换概念中有所介绍。
删除节点
节点可以通过 SQL DELETE
语句删除。
例如,无条件删除所有 person
节点
DELETE FROM person
MATCH (person:person)
DELETE person
可以添加 WHERE
子句来防止这种情况
DELETE FROM person
WHERE person.id = 1
MATCH (person:person)
WHERE person.id = 1
DELETE person
如果您想删除所有内容,但您的工具报错,只需添加一个始终为 true
的条件
DELETE FROM person
WHERE true
MATCH (person:person)
WHERE true
DELETE person
或者,条件也可以是始终评估为 false
,从不删除任何内容
DELETE FROM person
WHERE false
MATCH (person:person)
WHERE false
DELETE person
表可以设置别名,并且该别名也将在 Cypher 中使用
DELETE FROM person p
MATCH (p:person)
DELETE p
表别名也支持与指定表名映射到的标签结合使用。使用配置了 table_mappings=person:Person
的相同查询,
DELETE FROM person p
将被翻译为
MATCH (p:Person)
DELETE p
删除节点及其相关节点
您可以使用 SQL TRUNCATE
来DETACH DELETE
节点。
TRUNCATE TABLE people
将被翻译为
MATCH (people:Person)
DETACH DELETE people
插入数据
带有显式列和常量值的单个值列表可以通过简单的 INSERT
语句插入
INSERT INTO People (first_name, last_name, born) VALUES ('Helge', 'Schneider', 1955)
CREATE (people:`Person` {first_name: 'Helge', last_name: 'Schneider', born: 1955})
支持所有表达式,包括参数。参数在 Cypher 中将从 1 开始向上命名。
INSERT INTO People (first_name, last_name, born) VALUES (?, ?, ?)
CREATE (people:`Person` {first_name: $1, last_name: $2, born: $3})
如果您省略插入目标上的列名,我们将生成名称
INSERT INTO People VALUES ('Helge', 'Schneider', 1955)
注意 unknown field xxx
属性名称
CREATE (people:`Person` {`unknown field 0`: 'Helge', `unknown field 1`: 'Schneider', `unknown field 2`: 1955})
SQL VALUES
子句实际上支持值列表
INSERT INTO People (first_name, last_name, born) VALUES
('Helge', 'Schneider', 1955),
('Bela', 'B', 1962)
这些值将被转换为 Cypher 数组,以便在 Cypher 语句中展开。这是批量插入的绝佳解决方案
UNWIND [
{first_name: 'Helge', last_name: 'Schneider', born: 1955},
{first_name: 'Bela', last_name: 'B', born: 1962}]
AS properties
CREATE (people:`Person`)
SET people = properties
也支持返回子句
INSERT INTO People p (name) VALUES (?) RETURNING elementId(p)
CREATE (p:Person {name: $1}) RETURN elementId(p)
插入或更新 (Upserts)
我们通过非标准但相当常见的 ON DUPLICATE
和 ON CONFLICT
SQL 子句支持有限范围的“插入或更新”。插入或更新被翻译成 MERGE
语句。虽然它们在没有约束的情况下也能工作,但您应该在合并的节点属性上真正设置唯一性约束,否则 Neo4j 可能会创建重复项(参见 理解 MERGE 的工作原理)。
所有列的插入或更新可以通过 ON DUPLICATE KEY IGNORE
或 ON CONFLICT IGNORE
实现。虽然 ON DUPLICATE KEY
提供了升级选项,但它假设违反的主键(或唯一键)是已知的。尽管这在关系型系统中几乎总是如此,但此在没有数据库连接的情况下运行的转换层并不知道。
ON DUPLICATE KEY IGNORE
进行插入或更新INSERT INTO Movie(title, released) VALUES(?, ?) ON DUPLICATE KEY IGNORE
MERGE (movie:`Movie` {title: $1, released: $2})
ON CONFLICT IGNORE
进行插入或更新INSERT INTO actors(name, firstname) VALUES(?, ?) ON CONFLICT DO NOTHING
MERGE (actors:`Actor` {name: $1, firstname: $2})
如果您想定义一个动作,您必须使用 ON CONFLICT
并指定您要合并的键。
INSERT INTO tbl(i, j, k) VALUES (1, 40, 700)
ON CONFLICT (i) DO UPDATE SET j = 0, k = 2 * EXCLUDED.k
请注意特殊的引用 EXCLUDED
如何用于引用未作为键一部分的列的值。它们将在 ON MATCH SET
子句中与它们的值一起重用。
MERGE (tbl:`tbl` {i: 1})
ON CREATE SET tbl.j = 40, tbl.k = 700
ON MATCH SET tbl.j = 0, tbl.k = (2 * 700)
这也适用于参数
INSERT INTO tbl(i, j, k) VALUES (1, 2, ?)
ON CONFLICT (i) DO UPDATE SET j = EXCLUDED.k
MERGE (tbl:`tbl` {i: 1})
ON CREATE SET tbl.j = 2, tbl.k = $1
ON MATCH SET tbl.j = $1
也可以只指定一个具体的合并列,而不是合并所有列。它将使用 ON CREATE
进行转换
INSERT INTO tbl(i, j, k) VALUES (1, 40, 700)
ON CONFLICT (i) DO NOTHING
MERGE (tbl:`tbl` {i: 1})
ON CREATE SET tbl.j = 40, tbl.k = 700
使用 ON CONFLICT
并指定键是使用 MERGE
语句插入多行的唯一方法
INSERT INTO People (first_name, last_name, born) VALUES
('Helge', 'Schneider', 1955),
('Bela', 'B', 1962)
ON CONFLICT(last_name) DO UPDATE SET born = EXCLUDED.born
UNWIND [{first_name: 'Helge', last_name: 'Schneider', born: 1955}, {first_name: 'Bela', last_name: 'B', born: 1962}] AS properties
MERGE (people:`People` {last_name: properties['last_name']})
ON CREATE SET
people.first_name = properties.first_name,
people.born = properties.born
ON MATCH SET people.born = properties['born']