er图一对多的关系怎么体现_图数据库 Neo4j Cypher 实战(一):从SQL到Cypher实操指南

258a9ebf775927dbfaa12262e912dc12.png

本指南教任何熟悉SQL的人如何编写等效的高效Cypher语句。我们将使用著名的罗斯文(Northwind)数据库来解释概念,并完成从简单到高级的查询。

阅读本文前,您应该对属性图模型有基本的了解,已经下载并安装Neo4j,数据导入请阅读数据导入指南。

一、关于Cypher的几句话

Cypher 就像 SQL 一样,是应用于图的一种声明性文本查询语言。

Cypher 包含语句、关键词和表达式,比如谓词、函数等,其中很多大家都很熟悉(如WHEREORDER BYSKIP LIMITANDp.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

二、数据模型转换

关系数据库将数据存储在具有固定结构的表,每列具有名称、类型、长度、约束等。表之间的引用通过将表的主键与另一个表外键关联。对于多对多引用,需要创建 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
关系模型

8c33005a05e7e70ce2cef9e16305b58b.png
图模型

六、逐步查询数据


本文通过将 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)更有效率的是仅返回具体列的子集,例如ProductNameUnitPrice。而且,在进行订购时,我们还按价格订购,只退还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(也有CONTAINSENDS 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 Guide​neo4j.com
0585ca1c25e3bd6127292e668ad56e78.png
数据大鱼:史上最全Neo4j资源传送门​zhuanlan.zhihu.com
eabb6674e860fc9efa667e3e65bfbfee.png
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值