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 在逐案基础上使用翻译器,您可以在 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 到 Cypher 翻译器实现时,将抛出带有消息 No SQL translators availableNoSuchElementException,并且您使用了 java.sql.Connection.nativeSQL 或启用了自动翻译。在后者情况下,异常将在您访问方法时或在打开连接时急切地抛出。

使用默认翻译器

支持的 SQL 方言

我们的默认翻译器使用来自 jOOQ 的 OSS 解析器,该解析器已经支持广泛的 SQL 方言。我们选择了 jOOQ 的通用默认方言作为我们的默认方言,但您可以在 SQL 到 Cypher 配置中使用参数 s2c.sqlDialect 覆盖它,使用 以下配置中列出的受支持方言之一。对于多个集成,POSTGRES 可能是一个不错的选择。

但请记住,翻译中的任何缺点可能不是由于解析器不足,而是由于缺少明显的语义等效 Cypher 结构。这意味着我们可能能够解析某些 SQL 片段,但无法将其翻译成 Neo4j 可以理解的有意义的内容,而无需额外的上下文信息。

配置

默认实现提供了一些配置设置。它们必须以 s2c 为前缀,出现在 URL 或配置选项中

名称 含义 默认值

parseNameCase

是否按原样解析表名。

true

tableToLabelMappings

从表名到标签的映射。

空映射

joinColumnsToTypeMappings

从列名到关系类型的映射。

空映射

prettyPrint

是否格式化生成的 Cypher。

true

alwaysEscapeNames

是否始终转义名称。

除非在启用漂亮打印时明确配置为 false,否则为 true

sqlDialect

解析时使用哪个方言。支持的值为 POSTGRESSQLITEMYSQLH2HSQLDBDERBYDEFAULT

DEFAULT

接下来的几个示例使用 properties 配置来避免在此文档中出现可怕的长 URL,但所有属性也可以通过 URL 指定。

清单 1. 禁用漂亮打印;仅在必要时转义;配置专用的表映射
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);
}
清单 2. 将表名解析为大写
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 使用的前缀相同)

清单 3. 更改参数前缀并添加联接列的映射
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;

通过名称访问 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 *

上述查询将返回一列 (p),其中包含一个 Neo4j 节点。这通常不是您在关系世界中期望的结果。如果您在线运行转换并且可以检索 Neo4j 元数据,您将获得一个语句,该语句会展平每个节点和关系的属性,以及它们的元素 ID

如果 Person 节点具有属性 bornname

SELECT * FROM Person p

您将获得此 Cypher 语句

MATCH (p:Person)
RETURN elementId(p) AS element_id,
       p.born AS born, p.name AS name

这也可以很好地用于多个表(Movie 具有属性 titlereleased

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 element_id, p.born AS born, p.name AS name,
       elementId(m) AS element_id1, m.title AS title, m.released AS released

我们向列名追加递增数字以避免冲突(例如,MoviePerson 中都具有 nameremark 属性)

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 element_id,
       p.born AS born, p.name AS name, p.remark AS remark,
       elementId(m) AS element_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 element_id,
       p.born AS born, p.name AS name,
       elementId(r) AS element_id1, r.role AS role,
       elementId(m) AS element_id2,
       m.title AS title, m.released AS released

清单 4. 未指定表别名的排序
SELECT * FROM Person p ORDER BY name ASC
MATCH (p:Person)
RETURN elementId(p) AS element_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

如果我们添加更多数据(例如,将 bornname 添加到 Person),限定星号将投影所有这些数据(请注意我们如何也从 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 element_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 一个简单的模式:所有具有标签 Product 的节点并 RETURN 它们。

MATCH (p:Product)
RETURN p{.*} AS p

上述查询将投影匹配节点的所有属性。如果要返回节点本身,请在不使用星号的情况下选择它

SELECT p
FROM products as p
MATCH (p:Product)
RETURN p
字段访问、排序和分页

返回属性的子集(如 ProductNameUnitPrice)效率更高。 趁此机会,我们还按价格排序,只返回 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 element_id, m.title AS title, m.released AS released

请注意,每行都包含 Neo4j 元素 ID,这使得每行都是唯一的。话虽如此,DISCTINCT 子句在使用星号时用途有限。

表达式

大多数 SQL 表达式都有相应的 Cypher 表达式,并且可以直接转换。

文字值

文字值是一对一的转换。

SELECT
    1, TRUE, FALSE, NULL, 'a'
RETURN 1, TRUE, FALSE, NULL, 'a'

算术表达式

算术表达式是一对一的转换。

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')

并将转换为Neo4j 的版本

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)

CASE 缩写(不是 COALESCENVL

输入

SELECT
    nullif(1, 2),
    nvl2(1, 2, 3)

将转换为

RETURN
    CASE WHEN 1 = 2 THEN NULL ELSE 1 END,
    CASE WHEN 1 IS NOT NULL THEN 2 ELSE 3 END

谓词

与表达式一样,许多用作谓词的逻辑 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 具有 SYMMETRIC 关键字用于 BETWEEN 子句,以指示您不关心范围的哪个边界更大

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

空值处理

对于标量表达式

输入

SELECT 1 FROM p WHERE 1 IS NULL AND 2 IS NOT NULL

将转换为

MATCH (p:p)
WHERE (1 IS NULL
  AND 2 IS NOT NULL)
RETURN 1
对于行值表达式

输入

SELECT 1 FROM p WHERE (1, 2) IS NULL OR (3, 4) IS NOT NULL

将转换为

MATCH (p:p)
WHERE
  (1 IS NULL AND 2 IS NULL)
  OR (3 IS NOT NULL AND 4 IS NOT NULL)
RETURN 1

LIKE 运算符

LIKE 运算符

SELECT * FROM movies m WHERE m.title LIKE '%Matrix%'

将转换为正则表达式,将 % 替换为 .*

MATCH (m:`movies`) WHERE m.title =~ '.*Matrix.*'
RETURN *

使用联接映射关系

从表面上看,联接是在 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.role, 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.role,
       elementId(m) AS element_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
使用 ON 子句

我们在这里为表和列名使用了反引号,并且没有映射。

SELECT p.name, m.title
FROM `Person` as p
JOIN `Movie` as m ON (m.id = p.`DIRECTED`)

转换与之前相同

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 TRUNCATEDETACH 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 DUPLICATEON CONFLICT SQL子句支持有限范围的“upserts”。Upserts被转换为MERGE语句。虽然它们可以在没有约束的情况下工作,但您确实应该在要合并的节点属性上设置唯一性约束,否则Neo4j可能会创建重复项(请参阅了解合并的工作原理)。

所有列上的Upserts可以通过ON DUPLICATE KEY IGNOREON CONFLICT IGNORE发生。虽然ON DUPLICATE KEY确实提供了升级选项,但它假设违反的主键(或唯一键)是已知的。虽然在关系系统中肯定如此,但这个在没有数据库连接的情况下运行的转换层不知道。

清单 5. 使用ON DUPLICATE KEY IGNORE的Upsert
INSERT INTO Movie(title, released) VALUES(?, ?) ON DUPLICATE KEY IGNORE
MERGE (movie:`Movie` {title: $1, released: $2})
清单 6. 使用ON CONFLICT IGNORE的Upsert
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']