本指南教任何熟悉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常用的文法圖:
二、資料模型轉換
關系資料庫将資料存儲在具有固定結構的表,每列具有名稱、類型、長度、限制等。表之間的引用通過将表的主鍵與另一個表外鍵關聯。對于多對多引用,需要建立 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)資料庫代表零售應用程式的資料存儲。您将找到客戶、産品、訂單、員工、托運人和類别以及他們之間的互動。
在以下查詢中考慮資料結構時,請參考下面的關系圖模型。
關系模型
圖模型
六、逐漸查詢資料
本文通過将 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
結果集。
6.1.2 列通路、排序和分頁(Field Access, Ordering and Paging)MATCH (p:Product)
RETURN p;
更有效率的是僅傳回具體列的子集,例如
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中會有一個快捷方式。
6.2.2 索引(Indexing)MATCH (p:Product {productName:"Chocolade"})
RETURN p.productName, p.unitPrice;
如果要通過此節點标簽和屬性組合快速比對,則可以在導入期間建立索引,這很有意義。
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
運算符,還包括集合函數、謂詞和轉換。
6.3.2 按多個數字和文本謂詞過濾(Filter by Multiple Numeric and Textual Predicates)MATCH (p:Product)
WHERE p.productName IN ['Chocolade','Chai']
RETURN p.productName, p.unitPrice;
現在,讓我們嘗試找到一些以“ 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查詢中,客戶和訂單之間的比對成為可選比對,這等效于外部聯接。
6.6 暢銷員工6.6.1 聚合,分組(Aggregation, Grouping)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;
在上一個查詢中,我們進行了一些彙總。通過彙總産品價格和訂購數量,我們為該客戶提供了每種産品的彙總視圖。
您可以在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中,聚合分組是隐式的。使用第一個聚合功能後,所有未聚合的列都會自動成為分組鍵。
6.7 員工地區收集主從查詢(Collecting Master-Detail Queries)MATCH (:Order)<-[:SOLD]-(e:Employee)
RETURN e.name, count(*) AS cnt
ORDER BY cnt DESC LIMIT 10
在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
聚合函數,該函數将值聚合到一個集合(清單、數組)中。是以,每個父級隻傳回一行,其中包含一個内聯的子級值集合。這也适用于嵌套值。
6.8 産品類别層次結構和樹,可變長度聯接(Hierarchies and Trees, Variable Length Joins)MATCH (t:Territory)<-[:IN_TERRITORY]-(e:Employee)
RETURN t.description, collect(e.lastName);
如果必須在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
資料大魚:史上最全Neo4j資源傳送門zhuanlan.zhihu.com