![258a9ebf775927dbfaa12262e912dc12.png](https://i-blog.csdnimg.cn/blog_migrate/06cbd9b8e5c376d326b70185ab5a5683.jpeg)
本指南教任何熟悉SQL的人如何编写等效的高效Cypher语句。我们将使用著名的罗斯文(Northwind)数据库来解释概念,并完成从简单到高级的查询。
阅读本文前,您应该对属性图模型有基本的了解,已经下载并安装Neo4j,数据导入请阅读数据导入指南。
一、关于Cypher的几句话
Cypher 就像 SQL 一样,是应用于图的一种声明性文本查询语言。
Cypher 包含语句、关键词和表达式,比如谓词、函数等,其中很多大家都很熟悉(如WHERE
,ORDER BY
,SKIP LIMIT
,AND
,p.unitPrice > 10
)。
与 SQL 不同,Cypher 完全是表达图模式的。我们添加了一个特殊子句MATCH
来匹配数据中的这些模式。这些图通常是我们在白板上绘制的图案,只是使用ASCII美术符号将其转换为文本。
使用圆括号表示节点实体的圆,比如:(p:Product)
。
关系的箭头是这样绘制的-->
,您可以在方括号中添加关系类型和其他信息-[:ORDERED]->
。将两者放在一起()-->()<--()
看起来几乎就像我们的原始图。我们来看图模式表达式的第一个示例:(cust:Customer)-[:ISSUED]->(o:Order)-[:CONTAINS]->(prod:Product)
。
Cypher 语言在其它方面的重点是图概念,例如路径、可变长度路径、最短路径函数;列表上许多功能,操作和谓词的支持以及链接查询的功能。
使用 Cypher 可以更新图结构和数据,甚至导入大量的CSV数据。
通过用户定义的过程,您可以使用所需的功能扩展语言。
Neo4j Cypher手册中提供了完整的Cypher语言文档以及完整的参考卡。
通过openCypher项目,Cypher成为了一种现代图查询语言的开放成果,该语言得到了多家数据库公司的支持。openCypher项目还提供了SQL常用的语法图:
![200de7478905e45a9f2ef167322fbaf4.png](https://i-blog.csdnimg.cn/blog_migrate/6b304687228210e6132495deeab42088.png)
二、数据模型转换
关系数据库将数据存储在具有固定结构的表,每列具有名称、类型、长度、约束等。表之间的引用通过将表的主键与另一个表外键关联。对于多对多引用,需要创建 JOIN 表(或链接表)作为连接表。
规范化的关系模型可以直接转换为等效图模型。图模型主要由用例驱动,因此之后将有机会进行优化和模型演化。
一个好的、规范化的实体关系图通常已经代表了一个不错的图模型。因此,如果您仍然可以使用数据库的原始 ER 图,请尝试继续使用。
对于这样一个合理的关系模型,转换并不难。实体表的行转换为节点和外键关系,JOIN表转换为关系。
在开始导入数据之前,对图模型有一个很好的了解是很重要的,然后它就变成了对该模型进行注水处理的任务。
三、数据导入
大多数关系数据库都允许将表轻松导出到CSV文件,例如在Postgres中COPY (SELECT * FROM customers) TO '/tmp/customers.csv' WITH CSV header;
。这些文件可以来自单个表,但也可以表示一组具有某些重复数据的联接表。
可以使用 Cypher 的LOAD CSV
功能将其导入,我们在这些指南中对此进行了详细说明:
- 指南:数据导入
- 网络研讨会:数据导入
- 指南:CSV导入
如果您是开发人员,还可以使用常规驱动程序连接到关系数据库,并使用SQL从那里加载数据。使用 Neo4j 驱动程序创建图结构,以将等效的参数化Cypher更新语句发送到Neo4j。
四、Cypher就是模式
如前所述,Cypher 语句的本质是您感兴趣的模式。
在节点模式中(variable:Label)
,可以为节点使用变量和一个或多个标签。您还可以提供属性作为键值结构,例如(item:Product {name:“ Chocolade”})。
对于类似的关系模式()-[someRel:REL_TYPE]→()
,只是您可能会选择一个变量like someRel
和一个或多个替代关系类型。
与使用SQL别名一样,您以后可以使用变量来引用它们表示的节点和关系,例如访问其属性或在其上调用函数。
模式既可用于查询,也可用于更新图结构。
它们通常在MATCH
子句中使用,但也可以视为表达式或谓词。当确保某些模式不存在时,这特别有用。
五、罗斯文(Northwind)示例模型
众所周知的罗斯文(Northwind)数据库代表零售应用程序的数据存储。您将找到客户、产品、订单、员工、托运人和类别以及他们之间的互动。
在以下查询中考虑数据结构时,请参考下面的关系图模型。
![1d36e32fd1f4f15102b0b5325f9e2373.png](https://i-blog.csdnimg.cn/blog_migrate/a71dade39cb237430c67d0f8c1ef97ba.jpeg)
![8c33005a05e7e70ce2cef9e16305b58b.png](https://i-blog.csdnimg.cn/blog_migrate/967c5bfba7356bd7f0d9b33fec4cc45d.jpeg)
六、逐步查询数据
本文通过将 Cypher 与等效的SQL语句进行比较,来介绍Cypher,以便通过现有的SQL知识,可以快速理解 Cypher。
6.1 查询
6.1.1 查询并返回记录(Select and Return Records)
在SQL中很容易,只需从products
表中查询所有数据。
SELECT p.*
FROM products as p;
在Cypher中,您只需匹配一个简单的模式:查询带有 标签 :Product 的
节点,并 RETURN
结果集。
MATCH (p:Product)
RETURN p;
6.1.2 列访问、排序和分页(Field Access, Ordering and Paging)更有效率的是仅返回具体列的子集,例如ProductName
和UnitPrice
。而且,在进行订购时,我们还按价格订购,只退还10个最昂贵的商品。
SELECT p.ProductName, p.UnitPrice
FROM products as p
ORDER BY p.UnitPrice DESC
LIMIT 10;
您可以将更改从SQL复制并粘贴到Cypher,非常令人惊讶。但是请记住,标签、关系和属性名称在Neo4j 中区分大小写。
MATCH (p:Product)
RETURN p.productName, p.unitPrice
ORDER BY p.unitPrice DESC
LIMIT 10;
6.2 按名称查找单个产品
6.2.1 等值筛选(Filter by Equality)如果我们只想查看单个产品,例如美味的Chocolade,则在SQL中使用WHERE
子句进行过滤。
SELECT p.ProductName, p.UnitPrice
FROM products AS p
WHERE p.ProductName = 'Chocolade';
在p.ProductName ='Chocolade'中,从产品AS中选择p.ProductName,p.UnitPrice 。
与Cypher相同,此处WHERE
属于MATCH
语句。
MATCH (p:Product)
WHERE p.productName = "Chocolade"
RETURN p.productName, p.unitPrice;
如果您匹配具有特定属性的带标签的节点,则Cypher中会有一个快捷方式。
MATCH (p:Product {productName:"Chocolade"})
RETURN p.productName, p.unitPrice;
6.2.2 索引(Indexing)如果要通过此节点标签和属性组合快速匹配,则可以在导入期间创建索引,这很有意义。
CREATE INDEX ON :Product(productName);
CREATE INDEX ON :Product(unitPrice);
6.3 过滤产品
6.3.1 按列表/范围过滤(Filter by List/Range)您还可以按多个值进行过滤。
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;
6.3.2 按多个数字和文本谓词过滤(Filter by Multiple Numeric and Textual Predicates)现在,让我们尝试找到一些以“ C”开头的昂贵东西。
SELECT p.ProductName, p.UnitPrice
FROM products AS p
WHERE p.ProductName LIKE 'C%' AND p.UnitPrice > 100;
在LIKE
操作者通过所取代STARTS WITH
(也有CONTAINS
和ENDS WITH
)所有其中的三个索引支持。
MATCH (p:Product)
WHERE p.productName STARTS WITH "C" AND p.unitPrice > 100
RETURN p.productName, p.unitPrice;
您还可以使用正则表达式,例如p.productName =~ "C.*"
。
6.4 与客户联合产品
6.4.1 合并记录,结果去重(Join Records, Distinct Results)我们想看看谁买了Chocolade。让我们将这四个表连接在一起,不确定时请参考模型(ER图)。
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';
图模型(看一下)要简单得多,因为我们不需要联接表,并且将连接表示为图模式也更易于阅读。
MATCH (p:Product {productName:"Chocolade"})<-[:PRODUCT]-(:Order)<-[:PURCHASED]-(c:Customer)
RETURN distinct c.companyName;
6.5 尚无订单的新客户
6.5.1 外部联接,聚合(Outer Joins, Aggregation)如果我们将问题转过来问“我总共购买和支付了什么?”,则JOIN保持不变,仅过滤器表达式发生变化。除非我们有没有任何订单的客户仍然想退货。然后,即使其他表中没有匹配的行,我们也必须使用OUTER联接来确保返回结果。
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 (c:Customer {companyName:"Drachenblut Delikatessen"})
OPTIONAL MATCH (p:Product)<-[pu:PRODUCT]-(:Order)<-[:PURCHASED]-(c)
RETURN p.productName, toInt(sum(pu.unitPrice * pu.quantity)) AS volume
ORDER BY volume DESC;
6.6 畅销员工6.6.1 聚合,分组(Aggregation, Grouping)
在上一个查询中,我们进行了一些汇总。通过汇总产品价格和订购数量,我们为该客户提供了每种产品的汇总视图。
您可以在SQL和Cypher中使用sum、 count、 avg、 max
等聚合函数。在SQL中,聚合是显式的,因此您必须在GROUP BY
子句中再次提供所有分组键。如果我们想看到我们最畅销的员工:
SELECT e.EmployeeID, count(*) AS Count
FROM Employee AS e
JOIN Order AS o ON (o.EmployeeID = e.EmployeeID)
GROUP BY e.EmployeeID
ORDER BY Count DESC LIMIT 10;
在Cypher中,聚合分组是隐式的。使用第一个聚合功能后,所有未聚合的列都会自动成为分组键。
MATCH (:Order)<-[:SOLD]-(e:Employee)
RETURN e.name, count(*) AS cnt
ORDER BY cnt DESC LIMIT 10
6.7 员工地区收集主从查询(Collecting Master-Detail Queries)在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.description, collect(e.lastName);
6.8 产品类别层次结构和树,可变长度联接(Hierarchies and Trees, Variable Length Joins)
如果必须在SQL中表示类别、区域或组织层次结构,则通常使用从子项到父项的外键通过自联接对它进行建模。添加数据没有问题,单级查询也没有问题(获取该父级的所有子级)。进入多级查询后,联接数将激增,尤其是在您的级别深度未固定的情况下。
以产品类别为例,我们必须预先决定要查询多少级类别。在这里,我们将只处理三个潜在级别(这意味着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)-[:CATEGORY]->(l:ProductCategory)-[:PARENT*0..]-(:ProductCategory {name:"Dairy Products"})
RETURN p.name
Cypher 的功能远不止本文所述,希望通过与SQL的比较可以帮助您理解Cypher的基础概念。如果您对这种可能性感兴趣,并且想尝试学习更多,则只需在计算机上安装Neo4j,然后阅读史上最全Neo4j资源传送门,里面有丰富的Cypher学习资源的链接。
For Relational Database Developers: A SQL to Cypher Guideneo4j.com![0585ca1c25e3bd6127292e668ad56e78.png](https://i-blog.csdnimg.cn/blog_migrate/f0966166e3e8051844ccee110b2fddac.jpeg)
![eabb6674e860fc9efa667e3e65bfbfee.png](https://i-blog.csdnimg.cn/blog_migrate/31301c0bff44286a4ea2c5d092b57a41.jpeg)