比较 Cypher 与 SQL

relational vs graph
图 1. Northwind 数据集的关系型模型和图模型。

以下查询示例使用 Northwind 数据集。它们旨在帮助熟悉 SQL 的任何人都能理解 Cypher® 并编写等同于 SQL 的 Cypher 查询。有关以下查询中使用的数据集的关系型模型和图模型之间差异的视觉表示,请参见图 1。

有关如何将 Northwind 加载到您的 Neo4j 实例并尝试这些示例的说明,请参阅 示例数据集

有关图数据库和关系型数据库之间差异和相似性的更深入说明,请参阅 从关系型数据库到图数据库的过渡

查询示例

选择和返回记录

SQL Cypher

要选择和返回记录,请从 products 表中选择所有内容

SELECT p.*
FROM products as p;

在 Cypher 中,您 MATCH 一个简单的模式:所有具有 **标签** :Product 的节点,然后 RETURN 它们

MATCH (p:Product)
RETURN p;

字段访问、排序和分页

您可以筛选出您感兴趣的属性(例如 ProductNameUnitPrice),而不是返回所有属性。

SQL Cypher

在 SQL 中,您可以按照以下方式按价格对项目进行排序并返回最昂贵的 10 个项目

SELECT p.ProductName, p.UnitPrice
FROM products as p
ORDER BY p.UnitPrice DESC
LIMIT 10;

Cypher 中的语句类似,除了模式匹配部分

MATCH (p:Product)
RETURN p.productName, p.unitPrice
ORDER BY p.unitPrice DESC
LIMIT 10;

请记住,Neo4j 中的标签、关系类型和属性名称是 **区分大小写** 的。有关命名规则的更多详细信息,请参阅 Cypher 手册 → 命名规则和建议

按名称查找单个产品

有多种方法可以查询数据库并检索单个项目(例如,名为 Chocolade 的产品)。

按相等进行筛选

SQL Cypher

在 SQL 中,您可以使用 WHERE 子句筛选数据

SELECT p.ProductName, p.UnitPrice
FROM products AS p
WHERE p.ProductName = 'Chocolade';

在 Cypher 中,WHERE 子句属于 MATCH 语句

MATCH (p:Product)
WHERE p.productName = 'Chocolade'
RETURN p.productName, p.unitPrice;

更简短的选择是使用标签 productNameMATCH 语句中指定产品

MATCH (p:Product {productName:'Chocolade'})
RETURN p.productName, p.unitPrice;

索引

SQL 和 Cypher 中都提供索引,它们可以使搜索特定节点标签和属性组合变得更加高效。

Cypher 中的索引仅用于查找查询的起点;所有后续的模式匹配都是通过图结构完成的。Cypher 支持范围索引、文本索引、点索引、查找索引、全文索引和向量索引。

在 Northwind 数据集中,在节点标签 productNameunitPrice 上添加索引可以使搜索产品及其价格变得更快

CREATE INDEX Product_productName IF NOT EXISTS FOR (p:Product) ON p.productName;
CREATE INDEX Product_unitPrice IF NOT EXISTS FOR (p:Product) ON p.unitPrice;

Cypher 中的索引仅用于查找查询的起点。所有后续的模式匹配都是通过图结构完成的。Cypher 支持范围索引、文本索引、点索引、查找索引、全文索引和向量索引。有关如何使用索引的更多信息,请参阅 Cypher 手册 → 使用索引

筛选产品

Cypher 中有多种类似于 SQL 的筛选结果的方法。

按列表/范围筛选

SQL Cypher

在 SQL 中,您可以使用运算符 IN

SELECT p.ProductName, p.UnitPrice
FROM products as p
WHERE p.ProductName IN ('Chocolade','Chai');

Cypher 提供了完整的集合支持,包括 IN 以及其他集合函数、谓词和转换

MATCH (p:Product)
WHERE p.productName IN ['Chocolade','Chai']
RETURN p.productName, p.unitPrice;

按多个数值和文本谓词筛选

SQL Cypher

此查询将检索名称以 "C" 开头且价格大于 100 的产品

SELECT p.ProductName, p.UnitPrice
FROM products AS p
WHERE p.ProductName LIKE 'C%' AND p.UnitPrice > 100;

在 Cypher 中,LIKE 运算符被 STARTS WITHCONTAINSENDS WITH 运算符替换

MATCH (p:Product)
WHERE p.productName STARTS WITH 'C' AND p.unitPrice > 100
RETURN p.productName, p.unitPrice;

您也可以使用正则表达式

MATCH (p:Product)
WHERE p.productName =~ '^C.*'
RETURN p.productName, p.unitPrice

将产品与客户连接

SQL Cypher

在 SQL 中,如果您想查看谁购买了 Chocolade,您可以将四个表连接在一起

SELECT DISTINCT c.CompanyName
FROM customers AS c
JOIN orders AS o ON (c.CustomerID = o.CustomerID)
JOIN order_details AS od ON (o.OrderID = od.OrderID)
JOIN products AS p ON (od.ProductID = p.ProductID)
WHERE p.ProductName = 'Chocolade';

在 Cypher 中,无需 JOIN 表。您可以将连接表示为图模式

MATCH (p:Product {productName:'Chocolade'})<-[:ORDERS]-(:Order)<-[:PURCHASED]-(c:Customer)
RETURN DISTINCT c.companyName;

返回没有现有订单的客户

如果您想要查看 **谁** 购买了 **什么** 以及他们 **总共支付了多少钱**,则前一个 SQL 查询中的 JOIN 保持不变,只有筛选表达式发生了变化。但是,如果您有没有订单的客户,并且仍然想返回他们,则需要进行一些调整。

SQL Cypher

在 SQL 中,您必须使用 OUTER JOINS 确保即使其他表中没有匹配的行,也会返回结果

SELECT p.ProductName, sum(od.UnitPrice * od.Quantity) AS Volume
FROM customers AS c
LEFT OUTER JOIN orders AS o ON (c.CustomerID = o.CustomerID)
LEFT OUTER JOIN order_details AS od ON (o.OrderID = od.OrderID)
LEFT OUTER JOIN products AS p ON (od.ProductID = p.ProductID)
WHERE c.CompanyName = 'Drachenblut Delikatessen'
GROUP BY p.ProductName
ORDER BY Volume DESC;

在 Cypher 中,客户和订单之间的 MATCH 成为 OPTIONAL MATCH,这相当于 OUTER JOIN

MATCH (c:Customer {companyName:'Drachenblut Delikatessen'})
OPTIONAL MATCH (p:Product)<-[o:ORDERS]-(:Order)<-[:PURCHASED]-(c)
RETURN p.productName, toInteger(sum(o.unitPrice * o.quantity)) AS volume
ORDER BY volume DESC;

不存在的节点和关系将具有 null 值,这将导致属性为 null 并且不会被 sum 聚合。

销量最高的员工

前面的示例提到了聚合。通过将产品价格和订购数量加起来,提供了每个客户的聚合产品视图。

您可以在 SQL 和 Cypher 中都使用聚合函数,例如 sumcountavgmax

SQL Cypher

在 SQL 中,聚合是显式的,因此您必须在 GROUP BY 子句中再次提供所有分组键。要查看销量最高的员工,请运行以下查询

SELECT e.EmployeeID, e.FirstName, e.LastName, COUNT(*) AS Count
FROM Employee AS e
JOIN Orders AS o ON (o.EmployeeID = e.EmployeeID)
GROUP BY e.EmployeeID, e.FirstName, e.LastName
ORDER BY Count DESC
LIMIT 10;

在 Cypher 中,聚合的分组是隐式的。只要您使用第一个聚合函数,所有未聚合的列就会自动成为分组键

MATCH (:Order)<-[:SOLD]-(e:Employee)
WITH e, count(*) as cnt
ORDER BY cnt DESC LIMIT 10
RETURN e.employeeID, e.firstName, e.lastName, cnt

还提供了其他聚合函数,例如 collectpercentileContstdDev 等。

员工区域

在 SQL 中,处理主细节信息可能很困难。例如,当您有一个主实体(主、头、父级)和许多从属实体(细节、位置、子级)时。

您可以编写一个查询来连接两者并多次返回主数据(每个细节一次),或者只获取主数据的主键,然后通过该外键提取所有细节行。

SQL Cypher

在 SQL 中,如果您查看每个区域的员工,则每个员工都会返回区域信息

SELECT e.LastName, et.Description
FROM Employee AS e
JOIN EmployeeTerritory AS et ON (et.EmployeeID = e.EmployeeID)
JOIN Territory AS t ON (et.TerritoryID = t.TerritoryID);

在 Cypher 中,您可以返回 SQL 中的结构,也可以使用 collect() 聚合函数,该函数将值聚合到集合(列表、数组)中。这样,每个父级只会返回一行,其中包含子级值的内联集合

MATCH (t:Territory)<-[:IN_TERRITORY]-(e:Employee)
RETURN t.territoryDescription, collect(e.lastName);

这对嵌套值也有效。

产品类别

如果您需要在 SQL 中表达类别、区域或组织层次结构,通常使用从子节点到父节点的外键进行自连接建模。在产品类别示例中,您需要预先决定要查询多少级类别。

SQL Cypher

这里只显示了三个可能的级别(这意味着 ProductCategory 表的 1+2+3 = 6 次自连接)

SELECT p.ProductName
FROM Product AS p
JOIN ProductCategory pc ON (p.CategoryID = pc.CategoryID AND pc.CategoryName = "Dairy Products")

JOIN ProductCategory pc1 ON (p.CategoryID = pc1.CategoryID)
JOIN ProductCategory pc2 ON (pc2.ParentID = pc2.CategoryID AND pc2.CategoryName = "Dairy Products")

JOIN ProductCategory pc3 ON (p.CategoryID = pc3.CategoryID)
JOIN ProductCategory pc4 ON (pc3.ParentID = pc4.CategoryID)
JOIN ProductCategory pc5 ON (pc4.ParentID = pc5.CategoryID AND pc5.CategoryName = "Dairy Products")
;

Cypher 能够使用适当的关系表达任何深度的层次结构。可变级别由可变长度路径表示,这些路径由关系类型后面的星号 * 和可选限制 (min..max) 表示

MATCH (p:Product)-[:PART_OF]->(l:Category)-[:PARENT*0..]-(:Category {name:'Dairy Products'})
RETURN p.name;