天天看點

er圖一對多的關系怎麼展現_圖資料庫 Neo4j Cypher 實戰(一):從SQL到Cypher實操指南

er圖一對多的關系怎麼展現_圖資料庫 Neo4j Cypher 實戰(一):從SQL到Cypher實操指南

本指南教任何熟悉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常用的文法圖:

er圖一對多的關系怎麼展現_圖資料庫 Neo4j Cypher 實戰(一):從SQL到Cypher實操指南

二、資料模型轉換

關系資料庫将資料存儲在具有固定結構的表,每列具有名稱、類型、長度、限制等。表之間的引用通過将表的主鍵與另一個表外鍵關聯。對于多對多引用,需要建立 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)資料庫代表零售應用程式的資料存儲。您将找到客戶、産品、訂單、員工、托運人和類别以及他們之間的互動。

在以下查詢中考慮資料結構時,請參考下面的關系圖模型。

er圖一對多的關系怎麼展現_圖資料庫 Neo4j Cypher 實戰(一):從SQL到Cypher實操指南

關系模型

er圖一對多的關系怎麼展現_圖資料庫 Neo4j Cypher 實戰(一):從SQL到Cypher實操指南

圖模型

六、逐漸查詢資料

本文通過将 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 Guide​neo4j.com

er圖一對多的關系怎麼展現_圖資料庫 Neo4j Cypher 實戰(一):從SQL到Cypher實操指南

資料大魚:史上最全Neo4j資源傳送門​zhuanlan.zhihu.com

er圖一對多的關系怎麼展現_圖資料庫 Neo4j Cypher 實戰(一):從SQL到Cypher實操指南