一般情況下,我們使用資料庫查找事物間的聯繫的時候,只需要短程關係的查詢(兩層以內的關聯)。當需要進行更長程的,更廣範圍的關係查詢時,就需要圖資料庫的功能。
而隨著社交、電商、金融、零售、物聯網等行業的快速發展,現實世界的事物之間織起了一張巨大複雜的關係網,傳統資料庫面對這樣複雜關係往往束手無策。因此,圖資料庫應運而生。
圖資料庫(Graph database)指的是以圖數據結構的形式來存儲和查詢數據的資料庫。
從 http://db-engines.com/en/ranking 可以發現,Neo4j 是目前用的最多的圖資料庫,世界資料庫排行榜上排名21位。
Neo4J屬於原生圖資料庫,其使用的存儲後端專門為圖結構數據的存儲和管理進行定製和優化的,在圖上互相關聯的節點在資料庫中的物理地址也指向彼此,因此更能發揮出圖結構形式數據的優勢。
知識圖譜中,知識的組織形式採用的就是圖結構,所以非常適合用neo4j進行存儲。
圖資料庫的優勢在於:
- 性能上,對長程關係的查詢速度快
- 擅於發現隱藏的關係,例如通過判斷圖上兩點之間有沒有走的通的路徑,就可以發現事物間的關聯
數據存儲形式
neo4j的數據存儲形式 主要是 節點(node)和 邊(edge) 來組織數據。node可以代表知識圖譜中的實體,edge可以用來代表實體間的關係,關係可以有方向,兩端對應開始節點和結束節點。
另外,可以在node上加一個或多個標籤(Node Label)表示實體的分類,以及一個鍵值對集合來表示該實體除了關係屬性之外的一些額外屬性。關係也可以附帶額外的屬性。
查詢語言cypher
neo4j採用自己設計的查詢語言cypher,其特點和sql有很多相似的地方。match、where、return是最常用到的關鍵詞:
- match: 相當於 sql中的select,用來說明查詢匹配的數據模式(或者說圖模式)
- where: 用來限制node或者關係中部分屬性的屬性值,從而返回我們想要的數據
- return: 返回節點或者關係
安裝neo4j
這裡我們使用docker安裝neo4j,安裝命令行如下:
docker run -d --name=Neo4j\\
--publish=7474:7474 --publish=7687:7687 \\
--volume=$HOME/neo4j/data:/data --volume=$HOME/neo4j/import:/import\\
neo4j
根據配置參數,我們將容器內的7474埠掛載到外部宿主機的7474埠,並設置好文件夾的映射關係,注意/import文件夾下放的是將要導入資料庫的csv文件。
接著在瀏覽器中打開 「 http://localhost:7474/ 」,就可以訪問Neo4j管理介面了。
neo4j網頁管理介面
我們通過一個例子來說明如何運用neo4j資料庫。
1. 導入數據
我們這裡有兩個csv文件如下圖,左邊的nodes_companies.csv是一部分公司節點,右邊的edges_director_duration.csv是這些公司互相之間的服務關係。
nodes_companies.csv文件和edges_director_duration.csv
把這兩個文件放到neo4j根目錄下的import文件夾內,使用LOAD…AS row語句讀取,表示將csv文件按行讀取,每行的變量名為row。再使用MERGE指令創建節點,將csv文件的第一列數據與第二列數據匯總為一個結點內的兩條屬性信息。
LOAD CSV WITH HEADERS FROM "file:///nodes_companies.csv" AS row
MERGE (c:Company {companyId:row.companyId, companyName:row.name})
這裡提一下cypher中兩個用於創建新的數據的兩個關鍵詞: create 和 merge
merge:在資料庫中可以匹配到模式相同的數據就返回,沒有則創建一條這樣的數據(有則返回,沒有則創建)
create: 無論如何,都會創建一條新的數據
上面再LOAD文件時使用merge可以避免導入完全重複的數據。
導入公司節點
通過第二個csv文件的START_ID和END_ID欄位為第一個csv文件的company之間建立聯繫,即不斷遍歷第二個文件的每一行,根據START_ID和END_ID使用where找到圖中相應節點,並為它們添加相應的服務(INTERLOCK)關係,添加關係屬性為weight。
LOAD CSV WITH HEADERS FROM "file:///edges_director_duration.csv" AS row
match (c1:company), (c2:company)
where row.START_ID = c1.id and row.END_ID = c2.id
create (c1)-[r:INTERLOCK{weight:row.years_served}]->(c2)
注意在cypher語句里,節點是用()括起來表示,關係則用 [] 括起來表示。
導入公司關係
2.創建關係
這裡我們嘗試自己創建一條新的關係,比如在id = 281 和 id = 879 的兩個節點間創建一條標籤為「INTERLOCK」的關係。
先match和where鎖定 id = 281 和 id = 879的兩個公司節點,然後用create創建他們之間的關係,並添加特定關係屬性信息(例如weight為10)。
cypher語句如下:
MATCH (c1:company),(c2:company)
WHERE c1.id = 「281」 AND c2.id = 「879」 CREATE (c1)-[r:INTERLOCK{weight:10}]->(c2) RETURN (c1)-[r]-(c2)
這條語句的意思是,匹配類別標籤為company,id分別等於281和879的兩個公司節點,設置變量名為c1和c2,在他們之間創建關係,關係變量名為r,這裡 ()-[]-() 代表無向邊,()-[]->() 代表有向邊。
返回結果 (c1)-[r]-(c2) 匹配到的子圖如下所示:
創建新的關係
3.比較複雜的查詢
下面這條語句會把所有公司中,指向其他公司的連接關係數超過75條的公司全部找出來。用空括號()代表任一節點,函數count() 計算關係的數量。
MATCH (c:company)-[r:INTERLOCK]->() WITH c, count(r) as relaNum WHERE relaNum>=75 RETURN c,relaNum
4.最短路徑查詢
neo4j還還內置實現了一套圖搜索算法,並提供了相關函數接口,比如你想查詢兩個節點之間的最短路徑,就可以用下面的查詢語句:
- shortestPath():返回兩節點間的最短路徑
match (c1:company), (c2:company), p=shortestPath((c1)-[r:INTERLOCK*..10]->(c2))
where c1.id <> c2.id
return p,length(p) order by length(p) desc limit 1000
直接調用函數shortestPath,傳入的參數為選定的關係,選取任意兩個節點,<>表示id不相等,因為查找的兩個點不能是同一個點,*..10表示10度以內的所有關係,返回降序排序的長度,限制在1000個防止內存溢出)
- allshortestpaths():返回兩節點間所有的最短路徑
MATCH (c1:company), (c2:company), p = allshortestpaths((c1)-[r:INTERLOCK*]-(c2))
WHERE c1.id <> c2.id
RETURN
extract(n in nodes(p)|n.name) as Nodes,
length(p) as pathLength,
reduce(s=0, e in relationships(p)| s + toInt(e.weight)) as pathDist
LIMIT 1000
allshortestpaths函數返回結果
語句中的pathLength是路徑的邊數(第一句return),pathDist是路徑上所有帶weight邊的加權總和(第二句return)。