Neo4j【從無到有從有到無】【N3】使用圖進行數據建模

目錄

1.模式與目標(Models and Goals)

2.標記的屬性圖模型

3.查詢圖:Cypher簡介

3.1.Cypher Philosophy

3.2.MATCH

3.3.RETURN

3.4.其他Cypher語法

4.關係和圖建模的比較

4.1.系統管理領域中的關係建模

4.2.系統管理域中的圖形建模

4.3.測試模型

5.跨域模型

5.1.創建莎士比亞圖

5.2.開始查詢

5.3.聲明要查找的信息模式

5.4.約束匹配

5.5.處理結果

5.6.查詢鏈接

5.7.常見的建模陷阱

5.7.1.電子郵件出處問題域

5.7.2.明智的第一次迭代?

5.7.3.第二次的魅力

5.7.4.不斷髮展的領域

6.識別節點和關係

7.避免反模式

8.摘要


在前面的章節中,我們描述了圖數據庫與其他NOSQL存儲和傳統關係數據庫相比的巨大優勢。 但是選擇採用圖形數據庫後,出現了一個問題:如何在圖形中建模?

本章重點介紹圖建模。 首先從標籤化的屬性圖模型(最廣泛使用的圖數據模型)的概述開始,然後概述本書中大多數代碼示例所使用的圖查詢語言:Cypher。 儘管存在幾種圖形查詢語言,但是Cypher部署最廣泛,使其成爲事實上的標準。 它也很容易學習和理解,特別是對於那些來自SQL背景的人。 有了這些基礎知識之後,我們將直接研究一些圖形建模的示例。 在基於系統管理域的第一個示例中,我們比較了關係和圖形建模技術。 在第二個例子(莎士比亞文學的生產和消費)中,我們使用圖來連接和查詢幾個不同的領域。 在本章的結尾,我們介紹了使用圖形建模時的一些常見陷阱,並重點介紹了一些好的做法。

1.模式與目標(Models and Goals)

在深入研究圖形建模之前,請先對模型進行概括。 建模是受特定需求或目標推動的抽象活動。 我們進行建模是爲了將不守法域的特定方面帶入可以對其進行結構化和處理的空間。 世界沒有“really is,”的自然表現,只有許多有目的的選擇,抽象和簡化,其中一些在滿足特定目標方面比其他更爲有用。

在這方面,圖形表示形式沒有什麼不同。 但是,它們與許多其他數據建模技術的不同之處可能在於邏輯模型與物理模型之間的緊密聯繫。 關係數據管理技術要求我們偏離域的自然語言表示形式:首先通過將表示形式組合成邏輯模型,然後將其強制爲物理模型。 這些轉換在我們對世界的概念化與該模型的數據庫實例化之間引入了語義上的矛盾。 使用圖形數據庫,這個差距大大縮小了。

我們已經在圖形中進行交流

圖形建模自然符合我們傾向於使用圓和框從域中提取細節,然後通過用箭頭和線將它們連接起來來描述這些事物之間的聯繫的方式。 當今的圖形數據庫比其他任何數據庫技術都更“whiteboard friendly”。問題的典型白板視圖是圖形。 我們在創意和分析模式下繪製的內容與我們在數據庫內部實現的數據模型緊密對應。

在表達性方面,圖形數據庫減少了困擾關係數據庫實現多年的分析和實現之間的阻抗失配。 這種圖模型特別有趣的是,它們不僅傳達了我們認爲事物之間的關係,而且還清楚地傳達了我們想問的領域內的各種問題。

我們將在本章中看到,圖模型和圖查詢實際上只是同一枚硬幣的兩個方面。

2.標記的屬性圖模型

我們在第1章中介紹了標記的屬性圖模型。總而言之,這些是其顯着的特徵:

  • 帶標籤的屬性圖由節點,關係,屬性和標籤組成。
  • 節點包含屬性。 將節點視爲以任意鍵值對形式存儲屬性的文檔。 在Neo4j中,鍵是字符串,值是Java字符串和原始數據類型,以及這些類型的數組。
  • 節點可以用一個或多個標籤標記。 標籤將節點分組在一起,並指示它們在數據集中扮演的角色。
  • 關係連接節點並構造圖。 關係始終具有一個方向,一個名稱以及一個開始節點和一個結束節點,沒有懸空的關係。 關係的方向和名稱共同爲節點的結構增加了語義的清晰度。
  • 像節點一樣,關係也可以具有屬性。 向關係添加屬性的功能對於爲圖形算法提供其他元數據,爲關係添加其他語義(包括質量和權重)以及在運行時約束查詢特別有用。

這些簡單的原語是我們創建複雜且語義豐富的模型所需要的。 到目前爲止,我們所有的模型都是以圖表的形式。 圖表非常適合在任何技術環境之外描述圖表,但是在使用數據庫時,我們需要其他一些機制來創建,處理和查詢數據。 我們需要一種查詢語言。

3.查詢圖:Cypher簡介

Cypher是一種表現力強(但很緊湊)的圖形數據庫查詢語言。 儘管當前特定於Neo4j,但它與我們將圖形表示爲圖形的習慣緊密相關,這使其非常適合以編程方式描述圖形。 因此,在本書的其餘部分中,我們將使用Cypher來說明圖形查詢和圖形構造。 Cypher可以說是最容易學習的圖形查詢語言,並且是學習圖形的重要基礎。 一旦瞭解了Cypher,就可以很容易地進行分支和學習其他圖形查詢語言。

在以下各節中,我們將簡要介紹Cypher。 但是,這不是Cypher的參考文檔,它只是一個友好的介紹,因此以後我們可以探索更有趣的圖形查詢方案。

其他查詢語言

其他圖形數據庫具有其他查詢數據的方式。許多人,包括的Neo4j,支持RDF查詢語言SPARQL和勢在必行,基於路徑的查詢語言 Gremlin。 但是,我們的興趣在於將屬性圖的表達能力與聲明性查詢語言結合使用,因此在本書中,我們幾乎只關注Cypher。

3.1.Cypher Philosophy

Cypher旨在使開發人員,數據庫專業人員和業務利益相關者易於閱讀和理解。它更容易從一個事實,即它是在與雅閣的方式導出使用我們直觀地描述使用圖圖表。

Cypher使用戶(或代表用戶運行的應用程序)能夠要求數據庫查找與特定模式匹配的數據。 通俗地說,我們要求數據庫“查找類似的東西”。我們描述“類似的東西”外觀的方式是使用ASCII藝術畫它們。 圖3-1顯示了一個簡單模式的示例。

此模式描述了三個共同的朋友。 這是Cypher中等效的ASCII藝術作品表示形式:

(emil)<-[:KNOWS]-(jim)-[:KNOWS]->(ian)-[:KNOWS]->(emil)

此模式描述了一條路徑,該路徑將一個稱爲jim的節點連接到兩個稱爲ian和emil的節點,還將ian節點連接到emil節點。 ian,jim和emil是標識符。 標識符使我們在描述模式時可以多次引用同一個節點—一種技巧,可以幫助我們繞過以下事實:查詢語言只有一個維度(文本從左到右),而可以放置圖形圖 二維化。 儘管偶爾需要以這種方式重複標識符,但意圖仍然很明確。 Cypher 模式非常自然地遵循我們在白板上繪製圖形的方式。

先前的Cypher模式描述了一種簡單的圖形結構,但尚未引用數據庫中的任何特定數據。 要將模式綁定到現有數據集中的特定節點和關係,我們必須指定一些屬性值和節點標籤,以幫助在數據集中定位相關元素。 例如:

(emil:Person {name:'Emil'}) <-[:KNOWS]-(jim:Person {name:'Jim'})-[:KNOWS]->(ian:Person {name:'Ian'})-[:KNOWS]->(emil)

在這裏,我們使用其name屬性和Person標籤將每個節點綁定到其標識符。 例如,emil identifer綁定到數據集中的一個節點,該節點的標籤爲Person,名稱屬性爲Emil。 以這種方式將模式的某些部分固定到實際數據是Cypher的常規做法,我們將在以下各節中看到。

舉例說明

關於圖的有趣之處在於,它們傾向於包含節點和關係的特定實例,而不是類或原型。 通常甚至會使用由實節點和關係構成的較小子圖來說明甚至非常大的圖。 換句話說,我們傾向於通過示例來描述圖形。

ASCII藝術圖形模式是Cypher的基礎。 Cypher查詢使用謂詞將模式的一個或多個部分錨定到圖形中的特定位置,然後彎曲未錨定的部分以查找局部匹配項。

Cypher根據查詢中的標籤和屬性謂詞確定實際圖形中與模式的某些部分綁定到的錨點。 在大多數情況下,Cypher使用有關現有索引,約束和謂詞的元信息來自動找出問題。 但是,有時它有助於指定一些其他提示。

像大多數查詢語言一樣,Cypher由子句組成。 最簡單的查詢由一個MATCH子句和一個RETURN子句組成(我們將在本章稍後介紹在Cypher查詢中可以使用的其他子句)。 這是一個使用以下三個子句來查找名爲Jim的用戶的共同朋友的Cypher查詢示例:

MATCH (a:Person {name:'Jim'})-[:KNOWS]->(b)-[:KNOWS]->(c),
(a)-[:KNOWS]->(c)
RETURN b, c

讓我們更詳細地查看每個子句。

3.2.MATCH

MATCH子句是大多數Cypher查詢的核心。 這是示例說明部分。 使用ASCII字符表示節點和關係,我們繪製感興趣的數據。我們用括號繪製節點,並使用帶有大於或小於號(->和<-)的破折號對繪製關係。 <和>符號指示關係方向。 在短劃線之間,用方括號括起來,並用冒號作爲前綴,我們放置了關係名稱。 節點標籤類似地以冒號作爲前綴。 然後,在花括號(非常像Javascript對象)中指定節點(和關係)屬性鍵值對。

在示例查詢中,我們正在尋找一個名爲Person的節點,其名稱屬性爲Jim。 該查詢的返回值綁定到標識符a。該標識符使我們可以在整個查詢的其餘部分中引用表示Jim的節點。

此起始節點是簡單模式 (a)-[:KNOWS]->(b)-[:KNOWS]->(c), (a)-[:KNOWS]->(c) 描述了一個包含三個節點的路徑,其中一個是
綁定到標識符a,其他綁定到b和c。 這些節點通過以下方式連接多個KNOWS關係,如圖3-1所示。

從理論上講,這種模式可能會在整個圖形數據中多次出現。 對於較大的用戶集,可能存在許多與此模式相對應的相互關係。要本地化查詢,我們需要它的某些部分錨定到圖中的一個或多個地點。 在指定我們要尋找一個標記爲Person且名稱屬性值爲Jim的節點時,我們已將模式綁定到圖中的特定節點,即代表Jim的節點。 然後,Cypher將模式的其餘部分與緊靠該錨點的圖形進行匹配。 這樣,它將發現要綁定到其他標識符的節點。 儘管a將始終固定在Jim上,但是b和c將在查詢執行時綁定到一系列節點上。

另外,我們可以在WHERE子句中將錨表示爲謂詞。

MATCH (a:Person)-[:KNOWS]->(b)-[:KNOWS]->(c), (a)-[:KNOWS]->(c)
WHERE a.name = 'Jim'
RETURN b, c

在這裏,我們已將屬性查找從MATCH子句移到WHERE子句。結果與之前的查詢相同。

3.3.RETURN

此子句指定應將匹配數據中的哪些節點,關係和屬性返回給客戶端。 在示例查詢中,我們有興趣返回綁定到b和c標識符的節點。 當客戶端迭代結果時,每個匹配節點都被延遲綁定到其標識符。

3.4.其他Cypher語法

我們可以在Cypher查詢中使用的其他子句包括:

  • WHERE 
    • 提供用於過濾模式匹配結果的條件。
  • CREATE and  CREATE UNIQUE
    • 創建節點和關係。
  • MERGE
    • 通過重用與提供的謂詞匹配的現有節點和關係,或通過創建新的節點和關係,確保圖中存在提供的模式。
  • DELETE
    • 刪除節點,關係和屬性。
  • SET
    • 設置屬性值。
  • FOREACH
    • 對列表中的每個元素執行更新操作。
  • UNION
    • 合併兩個或多個查詢的結果。
  • WITH
    • 鏈接後續查詢部分並將結果從一個查詢轉發到下一個查詢。 類似於Unix中的管道命令。
  • START
    • 在圖中指定一個或多個明確的起點-節點或關係。 (不建議使用START,而應在MATCH子句中指定錨點。)

如果這些條款看起來很熟悉(尤其是您是SQL開發人員),那就太好了! Cypher旨在使您足夠熟悉,以幫助您沿着學習曲線快速移動。 同時,它的區別足以強調我們是在處理圖形,而不是關係集。

我們將在本章後面看到這些條款的一些示例。 在它們發生的地方,我們將更詳細地描述它們如何工作。

現在,我們已經瞭解瞭如何使用Cypher來描述和查詢圖形,下面我們來看一些圖形建模的示例。

4.關係和圖建模的比較

爲了介紹圖形建模,我們將研究如何使用基於關係和基於圖形的技術對域進行建模。 大多數開發人員和數據專業人員都熟悉RDBMS(關係數據庫管理系統)和相關的數據建模技術。 結果,比較將突出一些相似之處和許多差異。 特別是,我們將看到從概念圖模型轉換爲物理圖模型有多麼容易,並且相對於關係模型,圖模型扭曲了我們要表示的內容。

爲了便於進行比較,我們將研究一個簡單的數據中心管理域。 在此領域中,數個數據中心代表使用不同基礎架構(從虛擬機到物理負載平衡器)的許多客戶,支持許多應用程序。 此域的示例如圖3-2所示。

在圖3-2中,我們看到了一些應用程序以及支持它們的必要數據中心基礎結構的簡化視圖。 由節點App 1,App 2和App 3表示的應用程序依賴於標記爲Database Server 1、2、3的數據庫集羣。 儘管用戶在邏輯上取決於應用程序及其數據的可用性,但用戶與應用程序之間存在其他物理基礎架構; 此基礎結構包括虛擬客戶端(VM10、11、20、30、31),真實服務器(Service1、2、3),服務器機架(Rack1、2)和負載均衡器(Balance 1、2) ),位於應用程序的前面。 當然,在每個組件之間都有許多網絡元素:電纜,交換機,配線架,NIC(網絡接口控制器),電源,空調等,所有這些元素在不方便的時候都會發生故障。 爲了完成圖片,我們有一個應用程序3的單一用戶,由用戶3表示。

作爲此類系統的運營商,我們有兩個主要關注點:

  • 持續提供滿足(或超過)服務水平協議的功能,包括執行前瞻性分析以確定單點故障的能力以及追溯分析以快速確定引起客戶對服務可用性投訴的原因的能力。
  • 爲消耗的資源開賬單,包括硬件,虛擬化,網絡配置的成本,甚至軟件開發和運營的成本(因爲這些只是我們在此處看到的系統的邏輯擴展)。

如果我們要構建數據中心管理解決方案,則需要確保基礎數據模型允許我們以有效解決這些主要問題的方式來存儲和查詢數據。 我們還將希望能夠隨着應用程序產品組合的變化,數據中心的物理佈局的發展以及虛擬機實例的遷移而更新基礎模型。 考慮到這些需求和約束,讓我們看看關係模型和圖形模型的比較。

4.1.系統管理領域中的關係建模

關係世界中建模的初始階段與許多其他數據建模技術的初始階段相似:也就是說,我們試圖理解並同意域中的實體,它們之間的相互關係以及控制其狀態轉換的規則 。 其中大多數傾向於非正式地完成,通常是通過白板草圖以及主題專家,系統與數據架構師之間的討論來完成。 爲了表達我們的共同理解和共識,我們通常創建一個如圖3-2所示的圖表。

下一階段以更嚴格的形式捕獲此協議,例如實體關係(E-R)圖-另一個圖。 使用更嚴格的表示法將概念模型轉換爲邏輯模型,爲我們提供了第二次機會來完善我們的領域詞彙,以便可以與關係數據庫專家共享。 (這種方法並非總是必要的:熟練的關係用戶經常直接進行表設計和規範化操作,而無需先描述中間的E-R圖。)在我們的示例中,我們已在圖3-3所示的E-R圖中捕獲了域。

儘管是圖,但ER圖立即證明了關係模型捕獲富域的缺點。儘管它們允許命名關係(圖數據庫完全包含了某種東西,但關係存儲卻沒有),但ER圖僅允許單個,無向 ,實體之間的命名關係。 在這方面,關係模型不適用於實體域之間的關係既豐富又語義豐富多樣的現實世界域。

找到合適的邏輯模型後,我們將其映射到表和關係中,對它們進行規範化以消除數據冗餘。在許多情況下,此步驟很簡單,例如將E-R圖轉錄爲表格形式,然後通過SQL命令將這些表加載到數據庫中。但是,即使是最簡單的情況也可以用來突出關係模型的特質。例如,在圖3-4中,我們看到大量意外複雜性以外鍵約束(所有帶註釋[FK])的形式進入了模型,該約束支持一對多關係以及聯接表(例如AppDatabase),它支持多對多關係-在添加一行實際用戶數據之前,所有這些都需要支持。這些約束是簡單存在的模型級元數據,因此我們可以在查詢時具體化表之間的關係。但是,人們還是敏銳地感覺到這種結構性數據的存在,因爲它會使服務於數據庫而不是用戶的數據變得混亂和模糊。

現在,我們有了一個相對於該領域相對忠誠的規範化模型。 儘管該模型以外鍵和聯接表的形式帶來了相當大的意外複雜性,但其中沒有重複的數據。 但是我們的設計工作尚未完成。 關係範式的挑戰之一是規範化模型通常不夠快,無法滿足實際需求。 對於許多生產系統,規範化的模式在理論上適合於回答我們可能希望對域提出的任何特殊問題,在實踐中必須對其進行進一步調整並使其專門用於特定的訪問模式。 換句話說,爲了使關係存儲能夠很好地滿足常規應用程序的需求,我們必須放棄任何具有真實域親和力的痕跡,並接受必須更改用戶的數據模型以適合數據庫引擎(而不是用戶)的觀點。 這種技術稱爲非規範化

爲了獲得查詢性能,非規範化涉及複製數據(在某些情況下,實質上是重複的)。 以用戶及其聯繫方式爲例。 典型的用戶通常有幾個電子郵件地址,在完全規範化的模型中,我們會將其存儲在單獨的EMAIL表中。 但是,爲了減少聯接和兩個表之間的聯接帶來的性能損失,在USER表中內聯此數據,添加一個或多個列來存儲用戶最重要的電子郵件地址是很常見的。

儘管進行非規範化可能是安全的事情(假設開發人員瞭解非規範化模型及其如何映射到以域爲中心的代碼,並從數據庫獲得強大的事務支持),但這通常不是一項瑣碎的任務。爲了獲得最佳結果, 我們通常會請一位真正的RDBMS專家來將我們的規範化模型改成與基礎RDBMS和物理存儲層的特徵一致的非規範化模型。 爲此,我們接受可能存在大量的數據冗餘。

我們可能會認爲所有這些設計規範化非規範化工作都是可以接受的,因爲這是一項一次性的任務。 這種學派認爲,工作成本在系統的整個生命週期(包括開發和生產)中攤銷,因此與項目的總成本相比,生成高效的關係模型的工作量相對較小。 這是一個吸引人的概念,但是在許多情況下,它與現實不符,因爲系統不僅在開發過程中會發生變化,而且在產品生命週期中也會發生變化。

我們可能會認爲所有這些設計規範化非規範化工作都是可以接受的,因爲這是一項一次性的任務。 這種學派認爲,工作成本在系統的整個生命週期(包括開發和生產)中攤銷,因此與項目的總成本相比,生成高效的關係模型的工作量相對較小。 這是一個吸引人的概念,但是在許多情況下,它與現實不符,因爲系統不僅在開發過程中會發生變化,而且在產品生命週期中也會發生變化。

數據模型更改的攤銷視圖(在模型中,開發過程中代價高昂的更改被生產中穩定模型的長期利益所掩蓋)假定系統在生產環境中花費了大部分時間,並且這些生產環境是穩定的。 儘管大多數系統可能會在生產環境中花費大部分時間,但這些環境很少穩定。 隨着業務需求的變化或法規要求的發展,我們的系統和基於它們的數據結構也必須隨之變化。

在項目的設計和開發階段,數據模型總是會經過實質性的修訂,並且在幾乎每種情況下,這些修訂都旨在滿足模型的需求,以適應將要在生產中使用它的應用程序的需求。 這些最初的設計影響力如此之大,以至於在生產後就無法修改應用程序和模型以適應最初設計時無法做的事情。

我們將結構更改引入數據庫的技術機制稱爲遷移(Migrations),這種遷移已被Rails等應用程序開發框架所普及。 遷移提供了一種結構化的,逐步的方法,可以將一組數據庫重構應用到數據庫,以便可以負責任地發展它以滿足使用它的應用程序不斷變化的需求。 但是,與我們通常在幾秒鐘或幾分鐘內完成的代碼重構不同,數據庫重構可能需要數週或數月的時間才能完成,而架構更改則需要停機時間。 數據庫重構速度慢,風險大且昂貴。

因此,非規範化模型的問題在於其對系統業務需求的快速發展的抵制。正如我們在數據中心示例中看到的那樣,在實施關係解決方案的過程中對白板模型施加的更改,在概念世界和數據物理佈局方式之間產生了鴻溝。這種概念上的關係失調幾乎阻止了業務利益相關者在系統的進一步發展中進行積極的協作。利益相關者的參與停止在關係大廈的門檻上。在開發方面,將已更改的業務需求轉換爲基礎和根深蒂固的關係結構的困難使系統的發展滯後於業務的發展。沒有專家的幫助和嚴格的計劃,遷移非規範化的數據庫會帶來很多風險。如果遷移無法維持存儲親和性,則性能可能會受到影響。同樣嚴重的是,如果在遷移後將故意重複的數據留爲孤立,我們有可能危及整個數據的完整性。

4.2.系統管理域中的圖形建模

我們已經看到了關係建模及其伴隨的實現活動,如何使我們沿着將應用程序的基礎存儲模型與利益相關者的概念世界觀分離開來的道路。 具有剛性模式和複雜建模特性的關係數據庫並不是支持快速變化的特別好的工具。 我們需要一個與領域緊密相關的模型,但它不會犧牲性能,並且在數據進行快速更改和增長時,既支持演化又保持數據的完整性。 該模型是圖模型。 那麼,使用圖形數據模型實現時,此過程有何不同?

在分析的早期階段,我們所需的工作類似於關係方法:使用lo-fi方法(例如白板草圖),我們描述並同意了該領域。 但是,此後方法有所不同。 我們沒有豐富領域模型的圖形表示形式,而是將其豐富化,目的是對領域中與我們的應用目標相關的部分進行準確的表示。 也就是說,對於我們域中的每個實體,我們都確保已捕獲其相關角色(作爲標籤),其屬性(作爲屬性)以及與相鄰實體的關係(作爲關係)。

記住,領域模型不是通向現實的透明,無上下文的窗口:相反,它是對領域中與我們的應用程序目標有關的那些方面的有目的抽象。 建立模型總是有動力。 通過使用其他屬性和關聯性來豐富我們的第一手域圖,我們可以有效地生成與應用程序的數據需求相匹配的圖模型。 也就是說,我們提供了回答我們的應用程序將詢問其數據的各種問題。

有用的是,領域建模與圖建模完全同構。 通過確保域模型的正確性,我們隱式改進了圖形模型,因爲在圖形數據庫中,您在白板上繪製的內容通常是您在數據庫中存儲的內容。

用圖形表示,我們正在做的是確保每個節點具有適當的角色特定的標籤和屬性,以便其能夠履行其以數據爲中心的專用域職責。 但是,我們還要確保每個節點都位於正確的語義上下文中; 爲此,我們通過在節點之間創建命名和定向(通常是屬性)關係來捕獲域的結構方面。 對於我們的數據中心場景,生成的圖形模型如圖3-5所示。

從邏輯上講,這就是我們需要做的。 沒有表,沒有規範化,沒有非規範化。 一旦我們有了域模型的準確表示,將其移到數據庫中就變得微不足道了,我們很快就會看到。

請注意,這裏的大多數節點都有兩個標籤:特定類型的標籤(例如Database,App或Server)和更通用的Asset標籤。 這使我們能夠通過某些查詢來定位特定類型的資產,並通過其他查詢來定位所有資產,而不論類型如何。

4.3.測試模型

完善域模型後,下一步就是測試它是否適合回答實際查詢。 儘管圖形非常適合於支持不斷髮展的結構(因此可以糾正任何錯誤的早期設計決策),但是有許多設計決策一旦被應用到我們的應用程序中,便會進一步阻礙我們前進。 通過在此早期階段回顧域模型和結果圖模型,我們可以避免這些陷阱。 圖結構的後續更改將僅由業務更改驅動,而不是由減輕不良設計決策的需求驅動。

實際上,我們可以在此處應用兩種技術。 第一個,也是最簡單的,只是檢查圖是否讀得好。 我們選擇一個開始節點,然後關注與其他節點的關係,並在進行過程中讀取每個節點的標籤和每個關係的名稱。 這樣做應該創造出明智的句子。 對於我們的數據中心示例,我們可以讀取如下語句:“由應用程序實例1、2和3組成的應用程序使用駐留在數據庫服務器1、2和3上的數據庫,”和“服務器3運行承載應用程序實例3的VM31。”如果以這種方式讀取圖形是有意義的,那麼我們可以合理地確信它對域是忠實的。

爲了進一步提高信心,我們還需要考慮將在圖(graph)上運行的查詢。在這裏,我們採用可查詢性思維方式的設計。爲了驗證圖是否支持我們期望在其上運行的查詢,我們必須描述這些查詢。這要求我們瞭解最終用戶的目標;也就是說,將要應用圖形的用例。例如,在我們的數據中心場景中,我們的一個使用案例涉及最終用戶報告應用程序或服務無響應。爲了幫助這些用戶,我們必須找出無響應的原因,然後加以解決。爲了確定可能出了什麼問題,我們需要確定用戶與應用程序之間路徑上的內容,以及應用程序向用戶交付功能所依賴的內容。給定數據中心域的特定圖形表示形式,如果我們可以設計一個解決該用例的Cypher查詢,則我們甚至可以確定該圖形滿足我們域的需求。

繼續我們的示例用例,假設我們可以從常規的網絡監控工具中更新圖表,從而爲我們提供網絡狀態的近實時視圖。 對於大型物理網絡,我們可能會使用複雜事件處理(CEP)處理低級網絡事件流,僅在CEP解決方案引發重大領域事件時才更新圖形。 當用戶報告問題時,我們可以將物理故障查找限制在用戶與應用程序之間以及應用程序及其依存關係之間的有問題的網絡元素上。 在我們的圖形中,可以通過以下查詢找到有故障的設備:

MATCH (user:User)-[*1..5]-(asset:Asset)
WHERE user.name = 'User 3' AND asset.status = 'down'
RETURN DISTINCT asset

這裏的MATCH子句描述了一個長度介於一到五個關係之間的可變長度路徑。 關係是未命名和無方向的(方括號之間沒有冒號或關係名稱,也沒有箭頭指示方向)。這使我們可以匹配以下路徑:

(user)-[:USER_OF]->(app)
(user)-[:USER_OF]->(app)-[:USES]->(database)
(user)-[:USER_OF]->(app)-[:USES]->(database)-[:SLAVE_OF]->(another-database)
(user)-[:USER_OF]->(app)-[:RUNS_ON]->(vm)
(user)-[:USER_OF]->(app)-[:RUNS_ON]->(vm)-[:HOSTED_BY]->(server)
(user)-[:USER_OF]->(app)-[:RUNS_ON]->(vm)-[:HOSTED_BY]->(server)-[:IN]->(rack)
(user)-[:USER_OF]->(app)-[:RUNS_ON]->(vm)-[:HOSTED_BY]->(server)-[:IN]->(rack)
<-[:IN]-(load-balancer)

就是說,從報告問題的用戶開始,我們沿着長度爲1到5的無向路徑匹配圖中的所有資產。我們添加了資產節點,這些資產節點的status屬性的值小於我們的結果。 如果節點沒有狀態屬性,則該節點不會包含在結果中。 RETURN DISTINCT資產可確保無論匹配多少次,結果中都將返回唯一有價值的事物。

鑑於我們的圖形很容易支持這樣的查詢,因此我們可以確信該設計適合目標。

5.跨域模型

業務洞察力通常取決於我們瞭解複雜價值鏈中隱藏的網絡效應。 爲了產生這種理解,我們需要將域合併在一起,而不會扭曲或犧牲每個域特有的細節。 屬性圖在此處提供瞭解決方案。 使用屬性圖,我們可以將價值鏈建模爲圖的圖,其中特定的關係連接並區分組成子域。

在圖3-6中,我們看到了圍繞莎士比亞文學的生產和消費的價值鏈的圖形表示。 在這裏,我們可以獲得有關莎士比亞和他的一些戲劇的高質量信息,以及最近進行過戲劇表演的公司之一的詳細信息,以及劇院場地和一些地理空間數據。 我們甚至添加了評論。 總之,該圖描述並連接了三個不同的域。 在圖表中,我們區分了這三種具有不同格式關係的域:點號代表文學領域,實線代表戲劇領域,點劃線代表地理空間領域。

首先看一下文學領域,我們有一個代表莎士比亞本人的節點,帶有一個標籤Author和屬性firstname:'William'和 lastname:'Shakespeare'。 該節點通過名爲WROTE_PLAY的關係連接到一對節點,每個節點都標記爲Play,分別代表戲劇Julius Caesar( title:'Julius Caesar')和The Tempest( title:'The Tempest')。

按照關係箭頭的方向從左到右閱讀該子圖,告訴我們作家威廉·莎士比亞(William Shakespeare)創作了戲劇《凱撒大帝》和《暴風雨》。 如果我們對出處感興趣,那麼每個WROTE_PLAY關係都有一個date屬性,它告訴我們Julius Caesar於1599年編寫,而Tempest於1610年編寫。瞭解如何添加莎士比亞的其他作品(戲劇)是一件微不足道的事情。 只需添加更多代表每個作品的節點,然後通過WROTE_PLAY和WROTE_POEM關係將它們加入莎士比亞節點,就可以將詩和詩歌插入圖表中。

通過用手指跟蹤WROTE_PLAY關係箭頭,我們可以有效地完成圖形數據庫執行的工作,儘管是以人的速度而不是計算機的速度進行的。 稍後我們將看到,此簡單的遍歷操作是任意複雜的圖形查詢的基礎。

接下來轉到戲劇領域,我們添加了有關皇家莎士比亞劇團(通常簡稱爲RSC)的一些信息,其形式爲帶有標籤Company和屬性鍵名稱(其值爲RSC)的節點。 毫無疑問,戲劇領域與文學領域息息相關。 在我們的圖表中,RSC具有Julius Caesar和The Tempest的PRODUCED版本。 反過來,這些戲劇作品通過PRODUCTION_OF關係與文學領域的戲劇聯繫起來。

該圖還捕獲了特定性能的詳細信息。 例如,作爲RSC夏季巡迴演出的一部分,RSC在2012年7月29日進行了Julius Caesar的製作。 如果我們對錶演場地感興趣,我們只需遵循表演節點的傳出VENUE關係,即可發現該表演是在皇家劇院進行的,由一個標有Venue的節點表示。

該圖還允許我們捕獲對特定性能的評論。 在我們的示例圖中,我們僅包含了用戶Billy撰寫的7月29日效果的評論。 我們可以在性能,評級和用戶節點的相互作用中看到這一點。 在這種情況下,我們有一個標記爲User的節點,表示Billy(屬性name:'Billy'),其傳出的WROTE_REVIEW關係連接到表示其評論的節點。 “Review”節點包含一個數字評級屬性和一個自由文本審閱屬性。 該評論通過傳出的REVIEW_OF關係鏈接到特定的效果。 爲了將其擴展到許多用戶,許多評論和許多性能,我們只需向圖添加更多帶有適當標籤和更同名關係的節點。

第三個域是地理空間數據域,它包含一個簡單的位置層次樹。 該地理空間域在圖中的幾個點連接到其他兩個域。 雅芳的斯特拉特福市(財產名稱:“雅芳河畔的斯特拉福德福特”)由於是莎士比亞的發源地而與文學領域息息相關(莎士比亞的名字是BORN_IN斯特拉特福德)。 只要它是RSC的所在地(RSC是BASED_IN Stratford),它就連接到戲劇域。 要根據雅芳的地理條件進一步瞭解斯特拉特福,我們可以通過與COUNTRY的往來COUNTRY關係來了解它是否位於名爲England的國家/地區。

注意圖如何減少跨域重複數據的實例。 例如,埃文河畔的斯特拉特福就參與了這三個領域。

該圖可以捕獲更復雜的地理空間數據。 例如,查看與Theatre Royal相連的節點上的標籤,我們發現它位於Gray Street上,該大街位於Newcastle市中,該市位於Tyne and Wear縣,而該市最終位於 英格蘭國家-就像埃文河畔的斯特拉特福一樣。

關係和標籤

我們在這裏使用關係名稱和節點標籤來構造圖併爲每個節點建立語義上下文。

關係的名稱和方向通過以有意義的方式連接兩個節點來幫助建立語義上下文。 例如,通過遵循傳出的WROTE_REVIEW關係,我們瞭解到該關係末尾的節點表示評論。

關係有助於將圖形劃分到單獨的域中並連接這些域。 從莎士比亞的例子中可以看出,通過屬性圖模型,可以輕鬆組合不同的域(每個域都具有自己的特定實體,標籤,屬性和關係),這種方式不僅使每個域都可以訪問,而且可以產生洞察力 從域之間的連接。

標籤代表每個節點在我們的域中扮演的角色。 因爲一個節點可以連接到許多其他節點,其中一些節點可能來自非常不同的域,所以一個節點可以潛在地履行幾個不同的角色。

標籤是屬性圖模型的一等公民。 標籤除了表明不同節點在我們的域中扮演的角色外,還允許我們將元數據與那些標籤所附加的節點相關聯。 例如,我們可以爲所有帶有用戶標籤的節點建立索引,或者要求所有帶有客戶標籤的節點具有唯一的電子郵件屬性值。

5.1.創建莎士比亞圖

要創建如圖3-6所示的莎士比亞圖,我們使用CREATE來構建整體結構。 該語句由Cypher運行時在單個事務中執行,因此一旦執行了該語句,我們就可以確信該圖完整地存在於數據庫中。 如果事務失敗,則數據庫中將不包含任何圖形。 如我們所料,Cypher具有人性化和可視化的圖形生成方式:

CREATE (shakespeare:Author {firstname:'William', lastname:'Shakespeare'}),
(juliusCaesar:Play {title:'Julius Caesar'}),
(shakespeare)-[:WROTE_PLAY {year:1599}]->(juliusCaesar),
(theTempest:Play {title:'The Tempest'}),
(shakespeare)-[:WROTE_PLAY {year:1610}]->(theTempest),
(rsc:Company {name:'RSC'}),
(production1:Production {name:'Julius Caesar'}),
(rsc)-[:PRODUCED]->(production1),
(production1)-[:PRODUCTION_OF]->(juliusCaesar),
(performance1:Performance {date:20120729}),
(performance1)-[:PERFORMANCE_OF]->(production1),
(production2:Production {name:'The Tempest'}),
(rsc)-[:PRODUCED]->(production2),
(production2)-[:PRODUCTION_OF]->(theTempest),
(performance2:Performance {date:20061121}),
(performance2)-[:PERFORMANCE_OF]->(production2),
(performance3:Performance {date:20120730}),
(performance3)-[:PERFORMANCE_OF]->(production1),
(billy:User {name:'Billy'}),
(review:Review {rating:5, review:'This was awesome!'}),
(billy)-[:WROTE_REVIEW]->(review),
(review)-[:RATED]->(performance1),
(theatreRoyal:Venue {name:'Theatre Royal'}),
(performance1)-[:VENUE]->(theatreRoyal),
(performance2)-[:VENUE]->(theatreRoyal),
(performance3)-[:VENUE]->(theatreRoyal),
(greyStreet:Street {name:'Grey Street'}),
(theatreRoyal)-[:STREET]->(greyStreet),
(newcastle:City {name:'Newcastle'}),
(greyStreet)-[:CITY]->(newcastle),
(tyneAndWear:County {name:'Tyne and Wear'}),
(newcastle)-[:COUNTY]->(tyneAndWear),
(england:Country {name:'England'}),
(tyneAndWear)-[:COUNTRY]->(england),
(stratford:City {name:'Stratford upon Avon'}),
(stratford)-[:COUNTRY]->(england),
(rsc)-[:BASED_IN]->(stratford),
(shakespeare)-[:BORN_IN]->stratford

前面的Cypher代碼有兩個不同的作用。它創建帶標籤的節點(及其屬性),然後將它們與關係(必要時及其關係屬性)連接起來。

例如, CREATE (shakespeare:Author {firstname:'William', lastname:'Shakespeare'})  創建一個Author節點,表示威廉·莎士比亞。

新創建的節點被分配給標識符shakespeare。該shakespeare標識符稍後在代碼中用於將關係附加到基礎節點。

例如,(shakespeare)-[:WROTE_PLAY {year:1599}]->(juliusCaesar) 創建了從莎士比亞到劇本Julius Caesar的WROTE關係。

該關係的 year屬性值爲1599。

標識符在當前查詢範圍內一直可用,但不再可用。如果我們希望爲節點提供長久的名稱,我們只需爲特定標籤和鍵屬性組合創建索引。 我們在“索引和約束”中討論索引。

與關係模型不同,這些命令不會在圖表中引入任何意外的複雜性。 信息元模型(即通過標籤和關係建立節點的結構)與業務數據保持分離,業務數據僅作爲屬性存在。 我們再也不必擔心外鍵和基數約束會污染我們的真實數據,因爲在圖模型中,這兩者都是節點形式以及將它們互連的語義豐富的關係,是顯式的。

我們可以在以後的某個時間以兩種不同的方式修改圖形。 當然,我們可以繼續使用CREATE語句簡單地添加到圖形中。 但是我們也可以使用MERGE,它的語義是確保一旦執行命令,節點和關係的特定子圖結構(其中一些可能已經存在,其中一些可能會丟失)已經存在。 在實踐中,當我們添加到圖表中時,我們傾向於使用CREATE,並且不介意重複;而在域中不允許重複時,我們傾向於使用MERGE。

5.2.開始查詢

現在我們有了一個圖,我們可以開始查詢它了。 在Cypher中,我們總是從圖中一個或多個衆所周知的起點(稱爲綁定節點)開始查詢。 Cypher使用MATCH和WHERE子句中提供的任何標籤和屬性謂詞,以及索引和約束提供的元數據,來找到錨定我們的圖形模式的起點。

例如,如果我們想了解有關皇家劇院表演的更多信息,我們將從皇家劇院節點開始查詢,我們可以通過指定其Venue標籤和name屬性來查找。 但是,如果我們對某個人的評論更感興趣,則可以使用該人的節點作爲查詢的起點,並在“User”標籤和名稱屬性組合上進行匹配。

假設我們想了解在紐卡斯爾皇家劇院發生的所有莎士比亞活動。 這三件事—名爲莎士比亞的作者,名爲皇家劇院的場所和名爲紐卡斯爾的城市—爲我們的新查詢提供了起點:

MATCH (theater:Venue {name:'Theatre Royal'}),
(newcastle:City {name:'Newcastle'}),
(bard:Author {lastname:'Shakespeare'})

該MATCH子句使用屬性key名稱和屬性值Theatre Royal標識所有Venue節點,並將它們綁定到標識符Theater。(如果該圖中有許多Theatre Royal節點,該怎麼辦?我們將盡快處理。)下一步,我們找到代表紐卡斯爾市的節點; 我們將此節點綁定到標識符newcastle。 最後,與先前的莎士比亞查詢一樣,要查找莎士比亞節點本身,我們將查找標籤爲Author且姓氏屬性爲Shakespeare的節點。 我們將此查詢的結果綁定到bard。

從現在開始,在我們的查詢中,無論我們在模式中使用標識符Theater,Newcastle和Bard的哪個位置,該模式都將錨定到與這三個標識符關聯的實際節點上。 實際上,此信息將查詢綁定到圖形的特定部分,從而爲我們提供了起點,以匹配緊鄰的節點和關係中的模式。

索引與約束

索引有助於優化查找特定節點的過程。

在大多數情況下,查詢圖表時,我們很樂意讓遍歷過程發現滿足我們信息目標的節點和關係。 通過遵循與特定圖形模式匹配的關係,我們會遇到有助於查詢結果的元素。 但是,在某些情況下,我們需要選擇特定的節點,而不是遍歷整個過程發現它們。 例如,確定遍歷的起始節點需要我們根據標籤和屬性值的某種組合來找到一個或多個特定節點。

爲了支持有效的節點查找,Cypher允許我們爲每個標籤和屬性組合創建索引。 對於唯一屬性值,我們還可以指定確保唯一性的約束。 在我們需要直接查找場所的莎士比亞圖表中,我們可以選擇基於標記爲Venue的所有節點的名稱屬性值來爲其編制索引。 爲此的命令是:

CREATE INDEX ON :Venue(name)

爲了確保所有國家/地區名稱都是唯一的,我們可以添加唯一性約束:

CREATE CONSTRAINT ON (c:Country) ASSERT c.name IS UNIQUE

在現有數據庫上,索引將在後臺填充,並且在建立索引後即可使用。

查找不需要索引,但是可以通過添加索引來提高其性能。  MATCH (theater:Venue {name:'Theatre Royal'})無論有沒有索引都可以使用。但是在擁有數千個場所的大型數據集中,索引將有助於提高性能。 如果沒有索引,則選擇Theatre Royal作爲查詢的起點,將使Neo4j掃描並過濾所有標記爲Venue的節點。

5.3.聲明要查找的信息模式

Cypher中的MATCH子句是神奇的地方。 由於CREATE子句試圖使用ASCII藝術來傳達意圖以描述圖的所需狀態,因此MATCH子句使用相同的語法來描述要在數據庫中發現的模式。 我們已經研究了一個非常簡單的MATCH子句; 現在我們來看一個更復雜的模式,它可以找到紐卡斯爾皇家劇院所有莎士比亞的表演:

MATCH (theater:Venue {name:'Theatre Royal'}),
      (newcastle:City {name:'Newcastle'}),
      (bard:Author {lastname:'Shakespeare'}),
      (newcastle)<-[:STREET|CITY*1..2]-(theater)
        <-[:VENUE]-()-[:PERFORMANCE_OF]->()
        -[:PRODUCTION_OF]->(play)<-[:WROTE_PLAY]-(bard)
RETURN DISTINCT play.title AS play

這種MATCH模式使用了一些我們尚未遇到的語法元素。 除了我們前面討論的錨定節點之外,它還使用模式節點,任意深度路徑和匿名節點。 讓我們依次看一下其中的每個:

  • 根據指定的標籤和屬性值,將標識符newcastle,Theater和bard錨定到圖中的實際節點。
  • 如果我們的數據庫中有幾個皇家劇院(Theatre Royal)(例如,英國的普利茅斯,巴斯,溫徹斯特和諾里奇等城市都擁有皇家劇院),那麼劇院將綁定到所有這些節點。 爲了將我們的模式限制爲紐卡斯爾的皇家劇院(Theatre Royal),我們使用語法 <-[:STREET|CITY*1..2]-,這意味着劇院節點最多隻能有兩個傳出的STREET and/or CITY關係 從代表泰恩河畔紐卡斯爾市(Newcastle-upon-Tyne)的節點出發。 通過提供可變的深度路徑,我們允許使用相對較細粒度的地址層次結構(例如,包括街道,地區或自治市鎮和城市)。
  • 語法  (theater)<-[:VENUE]-() 使用匿名節點,因此括號爲空。 在瞭解數據的同時,我們希望匿名節點與性能匹配,但是由於我們對查詢或結果中其他位置的各個性能的詳細信息不感興趣,因此我們不會命名該節點或將其綁定到 標識符。
  • 我們再次使用匿名節點將性能鏈接到生產 ( ()-[:PERFORMANCE_OF]->() ) 。 如果我們有興趣返回表演和作品的詳細信息,則可以將這些出現的匿名節點替換爲標識符:(performance)-[:PERFORMANCE_OF]->(production)
  • MATCH的其餘部分是簡單的 (play)<-[:WROTE_PLAY]-(bard)  節點到關係到節點模式匹配。 這種模式確保我們只返回莎士比亞寫的戲劇。 由於(play)已加入到匿名製作節點,並且通過該節點又加入了表演節點,因此我們可以安全地推斷它已經在紐卡斯爾的皇家劇院進行了表演。 在命名播放節點時,我們將其帶入範圍,以便稍後在查詢中使用它。

運行此查詢將返回在紐卡斯爾皇家劇院進行的所有莎士比亞戲劇:

+-----------------+
| play |
+-----------------+
| "Julius Caesar" |
| "The Tempest" |
+-----------------+
2 rows

如果我們對莎士比亞在皇家劇院的整個歷史感興趣,那很好,但是如果我們僅對特定的戲劇,作品或表演感興趣,我們就需要以某種方式來限制結果。

5.4.約束匹配

我們使用WHERE子句約束圖匹配。 通過指定以下一項或多項,WHERE允許我們從結果中消除匹配的子圖:

  • 在匹配的子圖中必須存在(或不存在)某些路徑。
  • 該節點必須具有某些標籤或與某些名稱的關係。
  • 無論值如何,匹配節點和關係上的特定屬性都必須存在(或不存在)。
  • 匹配的節點和關係上的某些屬性必須具有特定的值。
  • 必須滿足其他謂詞的要求(例如,表演必須在特定日期或之前進行)。

與描述結構關係併爲模式的各個部分分配標識符的MATCH子句相比,WHERE約束了當前的模式匹配。 例如,讓我們想象一下,我們希望將結果的播放範圍限制在莎士比亞最後一個時期(通常被認爲始於1608年)。我們通過過濾匹配的WROTE_PLAY關係的year屬性來做到這一點。 爲了啓用此過濾,我們調整了MATCH子句,將WROTE_PLAY關係綁定到一個標識符,我們將其稱爲w(關係標識符在冒號之前加一個關係名稱)。 然後,我們添加WHERE子句,以對該關係的year屬性進行過濾:

MATCH (theater:Venue {name:'Theatre Royal'}),
      (newcastle:City {name:'Newcastle'}),
      (bard:Author {lastname:'Shakespeare'}),
      (newcastle)<-[:STREET|CITY*1..2]-(theater)
        <-[:VENUE]-()-[:PERFORMANCE_OF]->()
        -[:PRODUCTION_OF]->(play)<-[w:WROTE_PLAY]-(bard)
WHERE w.year > 1608
RETURN DISTINCT play.title AS play

添加此WHERE子句意味着,對於每個成功的匹配,數據庫都會檢查莎士比亞節點和匹配的劇本之間的WROTE_PLAY關係是否具有Year屬性,其年份值大於1608。與WROTE_PLAY關係的匹配如果年份值大於1608,則將 通過測試; 這些戲劇將被包括在結果中。 未通過測試的比賽將不包括在結果中。 通過添加此子句,我們確保僅返回莎士比亞後期的演出:

+---------------+
| play |
+---------------+
| "The Tempest" |
+---------------+
1 row

5.5.處理結果

藉助Cypher的RETURN子句,我們可以對匹配的圖形數據執行一些處理,然後再將其返回給執行查詢的用戶(或應用程序)。

正如我們在之前的查詢中所看到的,我們最簡單的方法就是返回找到的劇本:

RETURN DISTINCT play.title AS play

DISTINCT確保我們返回獨特的結果。 由於每個劇本可以在同一個劇院中多次執行,有時甚至在不同的作品中進行,因此我們可以得到重複的劇本標題。 DISTINCT過濾掉這些。

我們可以通過幾種方式來豐富此結果,包括聚合,排序,過濾和限制返回的數據。 例如,如果我們只對符合條件的打法數量感興趣,則可以應用count函數:

RETURN count(play)

如果要按演出次數對戲劇進行排名,首先需要將MATCH子句中的PERFORMANCE_OF關係綁定到一個名爲p的標識符,然後我們可以對其進行計數和排序:

MATCH (theater:Venue {name:'Theatre Royal'}),
      (newcastle:City {name:'Newcastle'}),
      (bard:Author {lastname:'Shakespeare'}),
      (newcastle)<-[:STREET|CITY*1..2]-(theater)
        <-[:VENUE]-()-[p:PERFORMANCE_OF]->()
        -[:PRODUCTION_OF]->(play)<-[:WROTE_PLAY]-(bard)
RETURN play.title AS play, count(p) AS performance_count
ORDER BY performance_count DESC

這裏的RETURN子句使用標識符p(綁定到MATCH子句中的PERFORMANCE_OF關係)對PERFORMANCE_OF關係的數量進行計數,並將結果別名爲performance_count。 然後,它根據performance_count排序結果,並首先列出執行最頻繁的播放:

+-------------------------------------+
| play | performance_count |
+-------------------------------------+
| "Julius Caesar" | 2 |
| "The Tempest" | 1 |
+-------------------------------------+
2 rows

5.6.查詢鏈接

在結束對Cypher的簡要介紹之前,需要了解另外一個有用的功能-WITH子句。 有時候,一次MATCH做所有您想做的事是不切實際(或不可能)的。 WITH子句允許我們將多個匹配項鍊接在一起,並將上一個查詢部分的結果傳遞到下一個查詢中。在以下示例中,我們找到了莎士比亞寫的劇本,並根據寫劇的年份對它們進行排序, 最新的優先。 然後使用WITH,將結果通過管道傳遞到RETURN子句,該子句使用collect函數生成有限的播放標題列表:

MATCH (bard:Author {lastname:'Shakespeare'})-[w:WROTE_PLAY]->(play)
WITH play
ORDER BY w.year DESC
RETURN collect(play.title) AS plays

對我們的樣本圖執行此查詢將產生以下結果:

+---------------------------------+
| plays |
+---------------------------------+
| ["The Tempest","Julius Caesar"] |
+---------------------------------+
1 row

WITH可以用於將只讀子句與以寫爲中心的SET操作分開。更普遍的是,WITH通過允許我們將單個複雜查詢分解爲幾個更簡單的模式來幫助劃分和解決複雜查詢。

5.7.常見的建模陷阱

儘管圖建模是掌握問題域中複雜性的一種非常有表現力的方式,但是僅憑表現力並不能保證特定的圖適合目的。 實際上,在有些情況下,甚至我們每天使用圖形的人都會犯錯。 在本部分中,我們將研究出現問題的模型。 這樣,我們將學習如何在建模工作中及早發現問題,以及如何解決這些問題。

5.7.1.電子郵件出處問題域

本示例涉及電子郵件通信的分析。 溝通模式分析是一個經典的圖形問題,涉及到查詢圖形以發現主題專家,關鍵影響者以及傳播信息的通信渠道。 但是,在這種情況下,我們不是在尋找積極的榜樣(以專家的形式),而是在尋找流氓:也就是說,可疑的電子郵件通信模式會違反公司治理,甚至會觸犯法律。

5.7.2.明智的第一次迭代?

在分析領域時,我們瞭解了潛在的不法行爲者用來掩蓋其足跡的所有聰明模式:使用盲目複製(BCC),使用別名-甚至與這些別名進行對話以模仿實際業務涉衆之間的合法交互。 基於此分析,我們生成了一個具有代表性的圖形模型,該模型似乎捕獲了所有相關實體及其活動。

爲了說明這個早期模型,我們將使用Cypher的CREATE子句生成一些表示用戶和別名的節點。 我們還將生成一個關係,該關係表明Alice是Bob的已知別名之一。 (我們假設底層圖形數據庫正在爲這些節點建立索引,以便我們以後可以查找它們並將它們用作查詢的起點。)以下是Cypher查詢,用於創建我們的第一個圖形:

CREATE (alice:User {username:'Alice'}),
       (bob:User {username:'Bob'}),
       (charlie:User {username:'Charlie'}),
       (davina:User {username:'Davina'}),
       (edward:User {username:'Edward'}),
       (alice)-[:ALIAS_OF]->(bob)

生成的圖形模型使我們很容易觀察到Alice是Bob的別名,如圖3-7所示。

現在,我們通過用戶交換的電子郵件將他們聚集在一起:

MATCH (bob:User {username:'Bob'}),
      (charlie:User {username:'Charlie'}),
      (davina:User {username:'Davina'}),
      (edward:User {username:'Edward'})
CREATE (bob)-[:EMAILED]->(charlie),
       (bob)-[:CC]->(davina),
       (bob)-[:BCC]->(edward)

乍一看,這似乎是對該域的合理忠實表示。 每個子句都易於從左到右閱讀,從而通過了我們的一項非正式測試,以確保準確性。 例如,我們從圖表中可以看到“Bob通過電子郵件發送給Charlie”。只有在有必要確切確定潛在的不法行爲Bob(以及他的另一個自我是Alice)交換的內容時,該模型的侷限性纔會出現。 我們可以看到Bob CC’d或 BCC’d有一些人,但我們看不到最重要的東西:電子郵件本身。

第一次建模嘗試產生了一個以Bob爲中心的星形圖形。 他的電子郵件,複製和盲目複製行爲由從Bob延伸到代表他的郵件收件人的節點的關係表示。 但是,如圖3-8所示,數據中最關鍵的元素,即實際的電子郵件,丟失了。

這種圖結構是有損的,當我們提出以下內容時,這一事實變得明顯查詢:

MATCH (bob:User {username:'Bob'})-[e:EMAILED]->
      (charlie:User {username:'Charlie'})
RETURN e

此查詢返回Bob和Charlie之間的EMAILED關係(Bob發送給Charlie的每封電子郵件可能都有一個)。 這告訴我們電子郵件已經交換過,但是卻沒有告訴我們有關電子郵件本身的信息:

+----------------+
| e |
+----------------+
| :EMAILED[1] {} |
+----------------+
1 row

我們可能認爲可以通過在EMAILED關係中添加屬性以表示電子郵件的屬性來糾正這種情況,但這只是時間的作用。 即使將屬性附加到每個EMAILED關係中,我們仍將無法在EMAILED,CC和BCC關係之間建立關聯。 也就是說,我們將無法說出複製了哪些電子郵件,複製了哪些電子郵件以及複製給了誰。

事實是,我們不經意間犯了一個簡單的建模錯誤,這主要是由於英語使用不多而不是圖論的任何缺點造成的。 我們日常使用的語言使我們專注於動詞“電子郵件”而不是電子郵件本身,因此,我們製作的模型缺乏真正的領域洞察力。

用英語,將短語“Bob sent an email to Charlie”縮寫爲“Bob emailed Charlie.”很容易和方便。在大多數情況下,名詞(實際電子郵件)的丟失並不重要,因爲意圖仍然很清楚 。 但是,在我們的法證場景中,這些被遺忘的陳述是有問題的。 意圖保持不變,但是Bob所發送的電子郵件的數量,內容和收件人的詳細信息由於被摺疊成EMAILED關係而丟失了,而不是被明確地建模爲自身的節點。

5.7.3.第二次的魅力

要修復我們的有損模型,我們需要插入電子郵件節點以表示業務中交換的真實電子郵件,並擴展我們的關係名稱集以涵蓋電子郵件支持的整個尋址字段。 現在,而不是創建像這樣的有損結構:

CREATE (bob)-[:EMAILED]->(charlie)

我們將改爲創建更詳細的結構,如下所示:

CREATE (email_1:Email {id:'1', content:'Hi Charlie, ... Kind regards, Bob'}),
       (bob)-[:SENT]->(email_1),
       (email_1)-[:TO]->(charlie),
       (email_1)-[:CC]->(davina),
       (email_1)-[:CC]->(alice),
       (email_1)-[:BCC]->(edward)

這導致了另一個星形圖形結構,但是這次電子郵件處於中心位置,如圖3-9所示。

當然,在真實的系統中,會有更多這樣的電子郵件,每個電子郵件都有自己複雜的交互網絡供我們探索。 很容易想象,隨着時間的推移,隨着電子郵件服務器記錄交互,還會執行更多的CREATE語句,就像這樣(爲簡潔起見,我們省略了錨節點):

CREATE (email_1:Email {id:'1', content:'email contents'}),
       (bob)-[:SENT]->(email_1),
       (email_1)-[:TO]->(charlie),
       (email_1)-[:CC]->(davina),
       (email_1)-[:CC]->(alice),
       (email_1)-[:BCC]->(edward);
CREATE (email_2:Email {id:'2', content:'email contents'}),
       (bob)-[:SENT]->(email_2),
       (email_2)-[:TO]->(davina),
       (email_2)-[:BCC]->(edward);
CREATE (email_3:Email {id:'3', content:'email contents'}),
       (davina)-[:SENT]->(email_3),
       (email_3)-[:TO]->(bob),
       (email_3)-[:CC]->(edward);
CREATE (email_4:Email {id:'4', content:'email contents'}),
       (charlie)-[:SENT]->(email_4),
       (email_4)-[:TO]->(bob),
       (email_4)-[:TO]->(davina),
       (email_4)-[:TO]->(edward);
CREATE (email_5:Email {id:'5', content:'email contents'}),
       (davina)-[:SENT]->(email_5),
       (email_5)-[:TO]->(alice),
       (email_5)-[:BCC]->(bob),
       (email_5)-[:BCC]->(edward);

這將導致我們在圖3-10中看到更復雜,更有趣的圖形。

現在,我們可以查詢此圖以識別潛在的可疑行爲:

MATCH (bob:User {username:'Bob'})-[:SENT]->(email)-[:CC]->(alias),
      (alias)-[:ALIAS_OF]->(bob)
RETURN email.id

在這裏,我們檢索Bob抄送給他自己的別名之一的地方發送的所有電子郵件。 與此模式匹配的所有電子郵件都表明流氓行爲。 而且由於Cypher和基礎圖形數據庫都具有圖形相似性,因此這些查詢(即使是在大型數據集上)也可以非常快速地運行。 該查詢返回以下結果:

+------------------------------------------+
| email |
+------------------------------------------+
| Node[6]{id:"1",content:"email contents"} |
+------------------------------------------+
1 row

5.7.4.不斷髮展的領域

與任何數據庫一樣,我們的圖形服務於一個可能隨着時間而發展的系統。 那麼當圖演化時我們該怎麼辦? 我們怎麼知道什麼壞了,或者實際上,我們怎麼知道某件事已經壞了? 事實是,我們無法完全避免在圖形數據庫中進行遷移:就像任何數據存儲一樣,它們是生活中不可或缺的事實。 但是在圖形數據庫中,它們通常要簡單得多。

在圖中,要添加新的事實或構圖,我們傾向於添加新的節點和關係,而不是更改模型。 使用新的關係添加到圖形不會影響任何現有查詢,並且是完全安全的。 使用現有的關係類型來更改圖形,以及更改現有節點的屬性(不僅是屬性值)可能是安全的,但是我們需要運行一組代表性的查詢,以確保在結構化之後圖形仍然適合於目的 變化。但是,這些活動與我們在常規數據庫操作期間執行的動作完全相同,因此在圖形世界中,遷移實際上只是照常進行。

至此,我們有了一個圖表,描述了誰發送和接收了電子郵件,以及電子郵件本身的內容。 但是,當然,電子郵件的樂趣之一是收件人可以轉發或回覆他們收到的電子郵件。 這樣可以增加互動和知識共享,但是在某些情況下會泄漏重要的業務信息。由於我們正在尋找可疑的通信模式,因此我們也應該考慮轉發和答覆。

乍一看,似乎不需要使用數據庫遷移來更新我們的圖以支持我們的新用例。 我們可以做的最簡單的添加就是將FORWARDED和EPLIED_TO關係添加到圖中,如圖3-11所示。 這樣做不會影響任何先前存在的查詢,因爲它們沒有經過編碼以識別新的關係。

但是,這種方法很快被證明是不合適的。 與我們最初使用EMAILED關係的方式幾乎相同,添加FORWARDED或REPLIED關係是幼稚和有損的。 爲了說明這一點,請考慮以下CREATE語句:

...
MATCH (email:Email {id:'1234'})
CREATE (alice)-[:REPLIED_TO]->(email)
CREATE (davina)-[:FORWARDED]->(email)-[:TO]->(charlie)

在第一個CREATE語句中,我們試圖記錄Alice回覆特定電子郵件的事實。 從左至右閱讀該陳述是合乎邏輯的,但這種情緒是有損的-我們無法確定愛麗絲是回信給所有電子郵件收件人還是直接回信給作者。 我們所知道的是,已經發送了一些答覆。 第二條語句從左到右也很讀:達維娜將電子郵件轉發給了查理。 但是我們已經使用TO關係來指示給定的電子郵件具有一個TO標頭,用於標識主要收件人。 在這裏重複使用TO使得無法分辨誰是收件人以及誰收到了電子郵件的轉發版本。

要解決此問題,我們必須考慮領域的基礎。 回覆電子郵件本身就是新的電子郵件,但它也是回覆。 換句話說,回覆具有兩個角色,在圖中可以通過將兩個標籤Email和Reply附加到回覆節點來表示。 無論回覆是發給原始發件人,所有收件人還是子集,都可以使用相同的熟悉的TO,CC和BCC關係輕鬆建模,而原始電子郵件本身可以通過REPLY_TO關係進行引用。 這是一系列修改後的一系列寫法,它們是由多個電子郵件操作產生的(再次,我們省略了必要的節點錨定):

CREATE (email_6:Email {id:'6', content:'email'}),
       (bob)-[:SENT]->(email_6),
       (email_6)-[:TO]->(charlie),
       (email_6)-[:TO]->(davina);
CREATE (reply_1:Email:Reply {id:'7', content:'response'}),
       (reply_1)-[:REPLY_TO]->(email_6),
       (davina)-[:SENT]->(reply_1),
       (reply_1)-[:TO]->(bob),
       (reply_1)-[:TO]->(charlie);
CREATE (reply_2:Email:Reply {id:'8', content:'response'}),
       (reply_2)-[:REPLY_TO]->(email_6),
       (bob)-[:SENT]->(reply_2),
       (reply_2)-[:TO]->(davina),
       (reply_2)-[:TO]->(charlie),
       (reply_2)-[:CC]->(alice);
CREATE (reply_3:Email:Reply {id:'9', content:'response'}),
       (reply_3)-[:REPLY_TO]->(reply_1),
       (charlie)-[:SENT]->(reply_3),
       (reply_3)-[:TO]->(bob),
       (reply_3)-[:TO]->(davina);
CREATE (reply_4:Email:Reply {id:'10', content:'response'}),
       (reply_4)-[:REPLY_TO]->(reply_3),
       (bob)-[:SENT]->(reply_4),
       (reply_4)-[:TO]->(charlie),
       (reply_4)-[:TO]->(davina);

這將在圖3-12中創建該圖,該圖顯示了衆多答覆和逐項答覆。

現在,很容易看到誰回覆了鮑勃的原始電子郵件。 首先,找到感興趣的電子郵件,然後與所有傳入的REPLY_TO關係(可能有多個答覆)進行匹配,然後從此處與傳入的SENT關係進行匹配:這揭示了發件人。 在Cypher中,這很容易表達。 實際上,Cypher使得查找回復到回覆的過程變得很容易,依此類推可以任意深度(儘管在這裏我們將深度限制爲四級):

MATCH p=(email:Email {id:'6'})<-[:REPLY_TO*1..4]-(:Reply)<-[:SENT]-(replier)
RETURN replier.username AS replier, length(p) - 1 AS depth
ORDER BY depth

在這裏,我們捕獲每個匹配的路徑,並將其綁定到標識符p。 然後,在RETURN子句中,計算答覆鏈的長度(SENT關係減去1),並返回答覆者的姓名和答覆者的深度。此查詢返回以下結果:

+-------------------+
| replier | depth |
+-------------------+
| "Davina" | 1 |
| "Bob" | 1 |
| "Charlie" | 2 |
| "Bob" | 3 |
+-------------------+
4 rows

我們看到Davina和Bob都直接回復了Bob的原始電子郵件; Charlie回答了其中一項答覆; 然後Bob回覆了其中一封回覆。

轉發電子郵件的方式與此類似,可以將其視爲新電子郵件,恰好包含原始電子郵件的某些文本。 與回覆情況一樣,我們明確地對新電子郵件建模。 我們還會引用轉發郵件中的原始電子郵件,以便始終提供詳細而準確的出處數據。 如果轉發的郵件本身是轉發的,則同樣適用,依此類推。 例如,如果愛麗絲(Alice)(鮑勃(Bob)的另一位自我)通過電子郵件向鮑勃(Bob)嘗試建立單獨的具體身份,然後Bob(希望進行一些變通)將其轉發給查理(Charlie),然後查理(Charlie)將其轉發給達維納(Davina),我們實際上有三封電子郵件 考慮。 假設用戶(及其別名)已經存在於數據庫中,在Cypher中,我們將審覈信息寫入數據庫,如下所示:

CREATE (email_11:Email {id:'11', content:'email'}),
       (alice)-[:SENT]->(email_11)-[:TO]->(bob);
CREATE (email_12:Email:Forward {id:'12', content:'email'}),
       (email_12)-[:FORWARD_OF]->(email_11),
       (bob)-[:SENT]->(email_12)-[:TO]->(charlie);
CREATE (email_13:Email:Forward {id:'13', content:'email'}),
       (email_13)-[:FORWARD_OF]->(email_12),
       (charlie)-[:SENT]->(email_13)-[:TO]->(davina);

完成這些寫操作後,我們的數據庫將包含圖3-13所示的子圖。

使用此圖,我們可以確定轉發的電子郵件鏈的各種路徑。

MATCH (email:Email {id:'11'})<-[f:FORWARD_OF*]-(:Forward)
RETURN count(f)

此查詢從給定的電子郵件開始,然後與轉發的電子郵件樹中所有傳入的FOR WARD_OF關係進行任何深度的匹配。 這些關係綁定到標識符f。 爲了計算電子郵件被轉發的次數,我們使用Cypher的count函數計算與f綁定的FORWARD_OF關係的數量。 在此示例中,我們看到原始電子郵件已轉發兩次:

+----------+
| count(f) |
+----------+
| 2 |
+----------+
1 row

6.識別節點和關係

建模過程可以最好地概括爲嘗試創建一個圖形結構,以表達我們要針對我們的領域提出的問題。 也就是說,設計可查詢性:

  1. 描述激發我們模型的客戶或最終用戶目標。
  2. 將這些目標重寫爲我們的領域要問的問題。
  3. 確定出現在這些問題中的實體和關係。
  4. 將這些實體和關係轉換爲Cypher路徑表達式。
  5. 使用類似於我們用來建模領域的路徑表達式,以圖形模式表達我們想以域模式問我們的領域的問題。

通過檢查用於描述領域的語言,我們可以非常快速地確定圖中的核心元素:

  • 普通名詞成爲標籤:例如“ user”和“ email”成爲標籤User和Email。
  • 帶對象的動詞成爲關係名稱:例如,“sent”和“wrote”成爲SENT和WROTE。
  • 專有名詞(例如,人或公司的名稱)是指事物的一個實例,我們使用一個或多個屬性來捕獲該事物的屬性,將其建模爲節點。

7.避免反模式

通常,不要將實體編碼爲關係。 使用關係來傳達有關實體之間如何關聯以及這些關係的質量的語義。

通常,不要將實體編碼爲關係。 使用關係來傳達有關實體之間如何關聯以及這些關係的質量的語義。域實體在語音中並不總是立即可見,因此我們必須仔細考慮我們實際處理的名詞。 Verbing是名詞將名詞轉換爲動詞的語言習慣,通常可以隱藏名詞和相應域實體的存在。 技術和商業術語在此類新詞中尤爲流行:如我們所見,我們互相“email”,而不是發送電子郵件,“google”獲取結果,而不是搜索Google。

同樣重要的是要認識到圖是自然可加的結構。 很自然地添加有關域實體及其使用新節點和新關係的相互關係方面的事實,即使感覺好像我們正在向數據庫中充斥大量數據。 通常,在寫入時嘗試合併數據元素以保持查詢時的效率是一種不好的做法。 如果我們按照我們要對數據提出的問題進行建模,則將出現域的準確表示。 有了這個數據模型,我們可以相信圖形數據庫在讀取時表現良好。

圖形數據庫即使存儲大量數據也能保持快速查詢時間。 當學習構造我們的圖而不對它們進行規範化時,學會信任我們的圖數據庫非常重要。

8.摘要

圖形數據庫使軟件專業人員可以使用圖形表示問題域,然後在運行時持久保存並查詢該圖形。 我們可以使用圖來清楚地描述問題域; 圖數據庫然後允許我們以在域和數據之間保持高親和力的方式存儲此表示。 此外,圖形建模消除了使用複雜數據管理代碼對數據進行規範化和非規範化的需求。

但是,我們許多人對於使用圖進行建模將是新手。 我們創建的圖形應易於查詢,同時避免混淆實體和操作-可能會丟失有用的領域知識的不良做法。 儘管圖形建模沒有絕對的對與錯,但本章中的指南將幫助您創建可以在許多迭代中滿足系統需求的圖形數據,同時始終與代碼演進保持同步。

瞭解了圖形數據建模之後,您現在可以考慮進行圖形數據庫項目。 在下一章中,我們將研究規劃和交付圖形數據庫解決方案所涉及的內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章