# Neo4j練習
延續上一篇對資料庫的介紹,在本章節中筆者將練習操作圖形資料庫以更深地體驗NoSQL的彈性與便捷性。而選擇Neo4j作為初學入門主要是因為它易讀性高,且圖像資料庫的特性與我們人類平常直觀地思考不同事物之間的關係較相似。例如,當要表示家庭成員間的關係時,通常會繪製家庭樹、族譜等,透過符號(姓名、稱呼、大頭貼)代表家庭成員,再透過不同符號間的線條描述不同家庭成員之間的關係;而Neo4j同理可以將資料如製作家庭樹般視覺化介面。

# 簡介
首先,在圖形資料庫中,一個節點(node)可以儲存任何使用者定義的東西,而線條(edges)則代表節點與節點間的關係。Neo4j是圖形資料庫的一種,為native graph database,使用Cypher查詢語言來執行如下圖的模型。其簡單語法為:圓括號內==一個節點,短橫線+中括號內+短橫線+> == 關係。更方便的是!每一個節點和關係線也都可以儲存它的特性。

:small_red_triangle:Native graph database model

:small_red_triangle:Cypher語法示意圖
>*上圖皆截自於 https://youtu.be/T6L9EoBy8Zk?si=eJKRzJF-43hPPxnV*
# 跟著Laith Academy做練習
接下來,筆者將跟著下方的youtube影片教學一同實作,最後再嘗試創建出屬於自己的資料庫。
{%youtube 8jNPelugC2s?si=1MrMdMZ3xJs_YhcS%}
## 一、下載安裝neo4j
neo4j有提供線上的平台進行操作(https://neo4j.com/),讀者可以自行註冊使用。不過,筆者就照著影片的步驟下載應用程式:🔗 https://neo4j.com/download/
---
**1.進入官網點擊下載。**

**2.填寫基本資料**(用於產生軟體的金鑰),填寫完再點擊下載。
**3.** 上一步完,網頁會自動跳出下載,然後顯示如下的頁面。須要**先將金鑰複製至剪貼簿再關閉網頁**。如果不放心的話,也可以先保留頁面。

### ⚠注意事項:
須注意您的電腦若是使用windows系統,應該要將[powershell更新到最新版本](https://learn.microsoft.com/zh-tw/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.3),且所屬路徑與neo4j要在同一顆硬碟中。不然可能出現以下的錯誤訊息。

4.下載完會跳出軟體註冊,這時可以把**貼上第3步複製的金鑰**,再點擊Activate。

---
## 二、建立檔案
**1.** 在目錄最上方是所有檔案,點選「➕ New」→「Create project」

建立後的檔案名稱可以更改,接著點選「➕ Add」

**2.** 選擇「Local DBMS」→更改資料庫名稱→設定密碼→點擊「✅Create」

接著會出現以下畫面,點擊「Start」啟動資料庫!
如圖,新建的資料庫已經啟動成功了!

**3.** 點擊最上方的「Open」,我們就可以開始做各種嘗試啦( •̀ ω •́ )✧
---
## 三、查詢節點Querying for Nodes
跟著影片我們來到[18:30](https://youtu.be/8jNPelugC2s?si=xQxMBxDSUxBzSUex&t=1110)-Querying for Nodes的部分。我們會先使用Laith已經寫好的Cypher Code作為練習,創建的資料庫是以NBA各個球隊、所屬球員、教練及他們之間的關係組成。
🔗 https://github.com/harblaith7/Neo4j-Crash-Course/blob/main/01-initial-data.cypher
**新增資料於資料庫中**
開啟Neo4j Browser,將Git-hub裡的code全部複製貼上。

結果如下:
### **1.使用MATCH作為搜尋指令**
* 輸入"Match"指令,後面接圓括號內可以放入任何變數,就會開始獲取你所指定的數據。
* 換行:SHIFT+Enter。
* 輸入"Return"指令,後面接變數,即會將搜尋的內容回饋輸出。
```
neo4j$ Match(player:PLAYER)
Return(player)
```

💡試著點擊每個node看看他們所含該的特性如何?點擊箭頭又代表著什麼呢?
>在這裡的示範僅搜尋了"n",除了將各個節點都顯示出來之外,每個點之間的關係也一併顯示了。從右側的Overview可以看到,目前的25個標籤中,有12個屬PLAYER球員,7個COACH教練,6個TEAM球隊,以及5種關係:COACHES指導、TEAMMATES隊友、PLAYED_AGAINST敵對、COACHES_FOR為(某隊)輔導、PLAYS_FOR為(某隊)上場打球。

### **2.在MATCH中增加篩選條件**
在上一步中,我們輸入一個變數"n",以輸出所有資料。同理我們可以設定一個變數"player"(亦可省略,重點是要有冒號),來搜尋標籤為"PLAYER"的資料。
```neo4j$
Match(player:PLAYER)
Return(player)
```

### **3.利用MATCH中找出各節點的特性Properties**
如下所示,僅需要在變數後加入".某特性"即可找出符合變數所有的特性;若要連續搜尋,只要用逗點隔開就好。搜尋結果因為只是資料,所以最終會以表格呈現。
```
Match(player:PLAYER)
Return player.name, player.weight //找出球員姓名、體重
```

如果想要更改表格上的欄位名稱,也可以用"AS"重新命名。
```
Match(player:PLAYER)
Return player.name AS 球員名稱, player.age AS 年齡, player.weight AS 體重
```

### **4.找出具有特定特性的節點**
舉例來說,我們要在player變數中找出年齡為22歲的所有球員,從上表可以知道,我們應該要得到"Ja Morant"和"Luka Doncic"這兩名。
* **使用WHERE**
這裡就跟英文文法中的關係副詞一樣,用"where = 某特定特性"來搜尋。
```
Match(player:PLAYER)
WHERE player.age = 22 //其中為22歲的球員
Return player
```

* **使用大括號{}**
在Match指令中,用大括號"{特性:某特定}"包住符合篩選條件的內容。
```
Match(player:PLAYER {age:22})
Return player
```

同理,也可以由逗點隔開,繼續連續添加篩選條件。
```
Match(player:PLAYER {age:22, height:2.01})
Return player
```

### **利用WHERE做算術篩選條件**
除了直接性地搜尋某個特定的特性,也可以透過WHERE與各種運算符號來設定符合篩選條件所有范圍的資料。例如,我想搜尋年齡為22歲以外的球員,即可使用"<>"。
```
match(player:PLAYER)
where player.age <> 22
return(player)
```

範例如下:
| 功能 | 除此之外| 大/少於|四則運算|邏輯運算|
| -----| -----| ----------------- | --- | -------- |
| function | except | more or less than |arithmetic|logic|
| code | <> | > or < |+ - * / |AND etc.|
| 例子 | 年齡22以外| 大於2公尺/小於2公尺 |計算球員BMI|年齡大於22歲且身高等於或高於2公尺|
| 程式 | ```player.age <> 22``` | ```player.height > 2``` ```player.height < 2``` |```(player.weight/(player.height^2)) > 25```|```player.age>22 AND player.height >= 2``` |
影片中還有其他很有趣的搜尋方式,在此就不一一列舉了,以下挑了筆者覺得最酷的搜尋方式:球員&隊伍一起看!
```
match (player:PLAYER), (team:TEAM)
where player.age>22 AND player.height >= 2
return player, team
```

🤩一下子就能找到年齡大於22歲,且身高等於或高於2公尺的球員,以及他們所屬的球隊了✨✨
---
## 四、查詢關係Querying for Relationships
那如果我們想知道有某隊伍中有哪些球員,或是特定球員是給哪一位教練輔導等,有關於關係的資料又要怎麼搜尋呢?如簡介所述:
>短橫線+中括號內+短橫線+> 即代表某關係
> -[relationship]->
那針對查詢關係,筆者著重學習以下幾點:
### **1. 具方向性**:
標示時要注意箭頭方向要正確。
```
match (player:PLAYER)-[:PLAYS_FOR]-> (team:TEAM) //獲取球員 「為」球隊「上場的」
where team.name = "LA Lakers" //其中球隊為"湖人隊"
return player //找出這些球員
```

🔺為湖人隊上場的球員們
### **2.一次查尋多個**:
這部分和節點一樣,只需註意用逗點隔開。
```
match (player:PLAYER)-[:PLAYS_FOR]-> (team:TEAM) //獲取球員 「為」球隊「上場的」
where team.name = "LA Lakers" OR team.name = "Brooklyn Nets" //其中球隊為"湖人隊"或者是"布魯克林"
return player, team //找出這些球員及球隊
```

🔺為湖人隊或布魯克林隊上場的球員們
### **3.找出具有特定特性的關係**:
如節點,關係也可以有它的特性。如:LeBron James為湖人隊上場打球,他與球隊有僱傭關係(employment),這段關係的特性可能就包含:薪資、合同簽約期限等。
```
match (player:PLAYER)-[employment:PLAYS_FOR]-> (team:TEAM) //獲取為球隊上場的球員,並定義此關係為"employment"
where employment.salary>=40000000 //其中僱傭關係中的薪水特性salary大於或等於400萬
return player, team //找出符合的球員、球隊
```

🔺這個資料庫中有5個球員薪水>=400萬(幾乎每個球隊都有一名sport star)
### **4.找出節點間非直接關聯的關係**:
這種關係通常指要找出多重特性的關係。如:我想知道LeBron James的隊友中有哪一位薪水高於300萬?這個問題牽扯到2個關係:
1. 他與他隊友之間的「隊友關係」
2. 他隊友與湖人隊之間的「僱傭關係」
```
match (雷霸龍:PLAYER {name:"LeBron James"})-[:TEAMMATES]-> (teammate:PLAYER) //獲取與標籤為"LeBron James"是「隊友關係」的球員
match (teammate)-[employment:PLAYS_FOR]-> (team:TEAM) //獲取球員與球隊的「僱傭關係」
where employment.salary>=30000000 //其中僱傭關係的「薪水」特性>= 300萬
return teammate, 雷霸龍, team //找出球員,雷霸龍和球隊
```

🔺Russell和Anthony都是LeBron James在湖人隊中薪水大於300萬的隊友
諸如此類的查詢關係方式相較之下稍微有些複雜,不過沿用之前對於MATCH及WHERE的語法,很快就能上手理解了。
---
## 五、資料整合Aggregating Data
我們目前已經知道資料庫運用的最基本功能——查詢,現在我們來繼續學習如何將所查詢的資料做整合。以下3點只是筆者跟著Laith的示範,更多的功能可以參考Neo4j的官方教學:***Controlling Query Processing***, https://neo4j.com/developer/cypher/controlling-query-processing/#aggregate-count
### 1.利用COUNT計算查詢結果
這個用法與python類似,會回傳符合條件的節點\關係的特性。例如,在這個資料庫中,若想知道每個球員的上場次數,我們可以利用COUNT計算每個球員的「敵對關係」的次數和。
```
match (球員:PLAYER)-[上場次數:PLAYED_AGAINST]-> (:TEAM) //獲取球員與球隊為敵對關係
return 球員.name, COUNT(上場次數) //找出球員名與相對應上場次數的總和數值
```

🔺每個球員相對應的上場次數
| 球員. name | COUNT(上場次數) |
| ----------------------- | --------------- |
| "Tobias Harris" | 3 |
| "Ja Morant" | 3 |
| "Kevin Durant" | 3 |
| "James Harden" | 3 |
| "Joel Embiid" | 3 |
| "Anthony Davis" | 4 |
| "Russell Westbrook" | 4 |
| "LeBron James" | 4 |
| "Giannis Antetokounmpo" | 2 |
| "Khris Middleton" | 2 |
| "Kristaps Porzingis" | 3 |
| "Luka Doncic" | 3 |
同樣地,這個方式與WHERE都可以搭配。如下,我想知道哪些隊伍有幾位球員的年齡大於26歲,就可以得到有4個隊伍:湖人隊、布魯克林、密爾瓦基公鹿、費城76人,分別有3、2、1、2個球員符合查詢條件的資料表。
```
MATCH(player:PLAYER)-[:PLAYS_FOR]->(team:TEAM) //獲取球隊與球員的僱傭關係
where player.age > 26 //其中球員的年齡大於26歲
return team.name , count (player) //找出球隊名稱與相對應球員的總和數
```

🔺每隊年齡大於26歲的人數
### 2.利用AVG計算節點/關係中的特性平均值
如題,舉例來說,我想了解每個隊伍給它們球員的平均薪資為何,就可以計算並統整出來。
```
match (球員:PLAYER)-[僱傭關係:PLAYS_FOR]-> (team:TEAM) //獲取球員與球隊的僱傭關係
return team.name, AVG(僱傭關係.salary) //找出球隊名稱,薪資的平均值
```

### 3.為整合的資料排序、選取
承上點,如果想把以上各隊的資訊做排序,將薪資由大到小排列 *(DESC)* ,可以先將結果定義為 *(AS)* 一個變數,再對此變數進行排序 *(ORDER BY)*。
```
match (球員:PLAYER)-[僱傭關係:PLAYS_FOR]-> (team:TEAM) //獲取球員與球隊的僱傭關係
return team.name, AVG(僱傭關係.salary) AS 平均薪資 //找出球隊名稱,薪資的平均值並定義為"平均薪資
ORDER BY 平均薪資 DESC //將平均薪資以遞減方式排列
```

🔺每個球隊的球員平均薪資
若想進一步,只想選取平均薪資最高的前三名隊伍,則可以用LIMIT設定。
```
match (球員:PLAYER)-[僱傭關係:PLAYS_FOR]-> (team:TEAM)
return team.name, AVG(僱傭關係.salary) AS 平均薪資
ORDER BY 平均薪資 DESC
LIMIT 3 //限制3個回傳值
```

🔺平均薪資排前三名的球隊
---
## 六、刪除節點和關係Deleting Nodes and Relationships
### 1.使用DELETE刪除節點
如上所述,neo4j的資料存於每一個節點中,而每個節點或許與其他節點有關係連接。因此若想直接使用delete可能會發生如圖示的錯誤——因為這個節點還有相接連的關係存在。

🔺無法刪除的錯誤訊息
那麼最簡捷的方式即是使用 **"DETACH"**,把與節點相關的內容分離,然後再刪除所有與獲取資料有關的關係。
```
MATCH (player {name:"Ja Morant"})
DETACH DELETE player
```

🔺已成功刪除1個節點和5個關係
### 2.使用DELETE刪除關係
一樣是用match獲取想刪掉的關係 *(rel)*,再用delete刪除。例如,Frank教練要退休了:

🔺已成功刪除1個關係
---
## 七、創建節點和關係Creating Nodes and Relationships
### 1.使用CREATE建立節點
用CREATE作為創建的指令,而圓括號內每個冒號與其接著的內容,即是不同的節點屬性(承接上面的例子,即是:球隊、教練、球員)。
那筆者想建立SPY×FAMILY這部動漫中出場角色的資料庫,假設先從男主角——洛伊德·佛傑開始,我想依性別作為節點屬性,而節點特性則有:姓名、代號、髮色。
```
CREATE (佛傑:MALE {name:"Loid Forger", code:"黃昏", hair_color:"Gold"}) //建立MALE的節點屬性,其中姓名為"Loid Forger", 代號為"黃昏", 髮色為"Gold";此變數為佛傑
RETURN 佛傑 //回傳佛傑
```

🔺洛伊德·佛傑的節點
### 2.使用CREATE建立關係
承接上一點,若要直接為佛傑建立一條關係,勢必需要另一個節點被創建才能完成。但若已經有另一節點存在,單純想要建立關係的話,一樣是用CREATE使 (:節點屬性 {:特性"指定特定節點"})-**[:關係屬性 {關係特性:"特性內容"} ]**->(:節點屬性 {:特性"指定特定節點"})。【若想與原本就有的節點創建關係,[見這裡!](####3.新增安妮亞與原本佛傑的關係)】
```
CREATE (安妮亞:FEMALE {name:"Anya Forger", code:"實驗體007", hair_color:"Pink"})-[:Family {meet:"adopted"}]->(佛傑:MALE {name:"Loid Forger"})
/*建立節點屬性為"FEMALE",其中姓名為"Anya Forger", 代號為"實驗體007", 髮色為"Pink";此變數為安妮亞。
另它與節點名稱為"Loid Forger"的"MALE"節點,建立Family屬性的關係,其中這條關係的meet特性為"adopted"。*/
RETURN 安妮亞, 佛傑
```

🔺創建安妮亞被洛伊德·佛傑領養的關係
*註:這裡的關係其實有誤喔:-] 因為adopted是「領養···」,那如何改正就接下來看最後一點吧!*
---
## 八、更新節點和關係Updating Nodes and Relationships
### 1.使用SET更新節點
承上,假設我想要將佛傑的節點更新2處:
1.**更新(增加)節點屬性**為:SPY
2.**更新節點特性**,另增.height為1.87,則程式碼如下:
```
MATCH(character:MALE)
WHERE ID(character) = 0 //要注意這個ID是程式自己定義的
SET character.height = 1.87, character:SPY
RETURN character
```
這裡可以看到有個比較特別的是:
* 使用ID搜尋特定節點—用WHERE ID(參數)= ID數值,如此以來就不用特別打一大串 ```:MALE {name:"Loid Forger"}``` 就能找到佛傑的節點了。

🔺洛伊德·佛傑如今不只有男性屬性還有間諜屬性!
### 2.使用SET更新關係
筆者寫到這裡發現目前的資料庫有兩個問題:
1. 當初與安妮亞連接關係的Loid Forger並不是原本已另填屬性的佛傑!
2. 安妮亞與佛傑的關係meet特性應該為adopted by

🔺Anya Forger節點應該與綠色的Loid Forger相連!
解決方法白白種,這裡只是展示其中之一:
#### 1. 先用MATCH把那一條 ```:Family```關係找出來,再Delete那條關係 *(rel)*。
```
Match (安妮亞 {name:"Anya Forger"})-[rel:Family]->(male:MALE {name:"Loid Forger"})
Delete rel
return 安妮亞, male
```

🔺刪除錯誤關係
> 錯誤回顧:為什麼會多出一個節點呢?
>Ans. 因為當初指令最開頭為CREATE,所以不僅創建了Anya Forger的節點、family的關係,還有屬性:MALE name="Loid Forger"的新節點!
#### 2. 把多餘的"Loid Forger"節點刪除,要注意用ID去找出多的那個節點,不然會因為兩個節點都是MALE屬性,且name特性相同而誤刪到原本想保留的節點了!
```
MATCH(多的節點:MALE {name:"Loid Forger"})
WHERE ID(多的節點) = 19 //要記得確認多的節點的ID哦!
delete 多的節點
```

🔺刪除多餘的佛傑節點
#### 3.新增安妮亞與原本佛傑的關係
原來!如果只是創建兩個已存有之間的關係,只需要**提前先為這兩個節點訂定參數**,在這裡分別是:*(佛傑)*、*(安妮亞)*。之後,再使用CREATE直接創建關係:
**CREATE 已有節點-[:關係屬性 {關係特性:內容}]->已有節點**
```
MATCH(佛傑:MALE {name:"Loid Forger"}), (安妮亞:FEMALE {name:"Anya Forger"})
CREATE (安妮亞)-[:Family {meet:"adopted by"}]->(佛傑)
Return 安妮亞, 佛傑
```

🔺新增安妮亞與原本佛傑的關係
#### 4.更新關係的特性
與更新節點類似,先用MATCH獲取關係,為此關係定義參數,再用SET更改其中的特性。
```
MATCH (安妮亞:FEMALE {name:"Anya Forger"})-[family:Family]->(佛傑:MALE {name:"Loid Forger"})
SET family.meet = "founded by"
Return 安妮亞, 佛傑
```

🔺改變了安妮亞與佛傑的meet關係特性為"founded by"(翻譯:被發現)
# 小結
透過neo4j的簡單練習,學會了如何使用Cypher語言來進行查詢、整合、刪除及創建更新。雖然還有一些小細節沒有涵蓋到,不過在走完Laith的影片步驟,並試著自己練習後,確實能感受到NoSQL的便捷性。而有關Neo4j的更多教學,可以參照它們的youtube頻道:https://youtube.com/@neo4j?si=U6L8Aot1WeVQ5QGk 。