Designing Data-Intensive Applications — Chap.2 資料模型與查詢語言

Gary Huang
Dublin so code
Published in
27 min readJan 14, 2024
重點筆記

你領500元現金了嗎? 點選我的連結成功開樂天帳戶+登入樂天網銀APP,拿500元現金! 樂天帳戶好康在這 : · 使用行動支付5次,享次月活儲年息1.35%存額無上限 · VIP享每月免費跨提/轉共16次 · 提領日幣手續費優惠8次/月 (推薦序號: JGONGL)
https://www.rakuten-bank.com.tw/s/R775

在本章中,我們將研究一系列用於資料儲存和查詢的通用資料模型(前面列表中的第 2 點)。特別地,我們將比較關係模型,文件模型和少量基於圖形的資料模型。我們還將檢視各種查詢語言並比較它們的用例。在 第三章 中,我們將討論儲存引擎是如何工作的。也就是說,這些資料模型實際上是如何實現的(列表中的第 3 點)。

關係模型與文件模型

NoSQL 的誕生

採用 NoSQL 資料庫的背後有幾個驅動因素,其中包括:

  • 需要比關係資料庫更好的可伸縮性,包括非常大的資料集或非常高的寫入吞吐量
  • 相比商業資料庫產品,免費和開源軟體更受偏愛
  • 關係模型不能很好地支援一些特殊的查詢操作
  • 受挫於關係模型的限制性,渴望一種更具多動態性與表現力的資料模型【5】

物件關係不匹配

​​目前大多數應用程式開發都使用面向物件的程式語言來開發,這導致了對 SQL 資料模型的普遍批評:如果資料儲存在關係表中,那麼需要一個笨拙的轉換層,處於應用程式程式碼中的物件和表,行,列的資料庫模型之間。模型之間的不連貫有時被稱為 阻抗不匹配(impedance mismatch)1

圖 2–1 使用關係型模式來表示領英簡介

例如,圖 2–1 展示瞭如何在關係模式中表示簡歷(一個 LinkedIn 簡介)。整個簡介可以透過一個唯一的識別符號 user_id 來標識。像 first_name 和 last_name 這樣的欄位每個使用者只出現一次,所以可以在 User 表上將其建模為列。但是,大多數人在職業生涯中擁有多於一份的工作,人們可能有不同樣的教育階段和任意數量的聯絡資訊。從使用者到這些專案之間存在一對多的關係,可以用多種方式來表示:

對於一個像簡歷這樣自包含文件的資料結構而言,JSON 表示是非常合適的:請參閱 例 2–1。JSON 比 XML 更簡單。面向文件的資料庫(如 MongoDB 【9】,RethinkDB 【10】,CouchDB 【11】和 Espresso【12】)支援這種資料模型。

例 2-1. 用 JSON 文件表示一個 LinkedIn 簡介

{
"user_id": 251,
"first_name": "Bill",
"last_name": "Gates",
"summary": "Co-chair of the Bill & Melinda Gates... Active blogger.",
"region_id": "us:91",
"industry_id": 131,
"photo_url": "/p/7/000/253/05b/308dd6e.jpg",
"positions": [
{
"job_title": "Co-chair",
"organization": "Bill & Melinda Gates Foundation"
},
{
"job_title": "Co-founder, Chairman",
"organization": "Microsoft"
}
],
"education": [
{
"school_name": "Harvard University",
"start": 1973,
"end": 1975
},
{
"school_name": "Lakeside School, Seattle",
"start": null,
"end": null
}
],
"contact_info": {
"blog": "http://thegatesnotes.com",
"twitter": "http://twitter.com/BillGates"
}
}

有一些開發人員認為 JSON 模型減少了應用程式程式碼和儲存層之間的阻抗不匹配。不過,正如我們將在 第四章 中看到的那樣,JSON 作為資料編碼格式也存在問題。缺乏一個模式往往被認為是一個優勢;我們將在 “文件模型中的模式靈活性” 中討論這個問題。

JSON 表示比 圖 2–1 中的多表模式具有更好的 區域性(locality)。如果在前面的關係型示例中獲取簡介,那需要執行多個查詢(透過 user_id 查詢每個表),或者在 User 表與其下屬表之間混亂地執行多路連線。而在 JSON 表示中,所有相關資訊都在同一個地方,一個查詢就足夠了。

圖 2–2 一對多關係構建了一個樹結構

多對一和多對多的關係

在上一節的 例 2–1 中,region_id 和 industry_id 是以 ID,而不是純字串 “Greater Seattle Area” 和 “Philanthropy” 的形式給出的。為什麼?

如果使用者介面用一個自由文字欄位來輸入區域和行業,那麼將他們儲存為純文字字串是合理的。另一方式是給出地理區域和行業的標準化的列表,並讓使用者從下拉列表或自動填充器中進行選擇,其優勢如下:

  • 各個簡介之間樣式和拼寫統一
  • 避免歧義(例如,如果有幾個同名的城市)
  • 易於更新 — — 名稱只儲存在一個地方,如果需要更改(例如,由於政治事件而改變城市名稱),很容易進行全面更新。
  • 本地化支援 — — 當網站翻譯成其他語言時,標準化的列表可以被本地化,使得地區和行業可以使用使用者的語言來顯示
  • 更好的搜尋 — — 例如,搜尋華盛頓州的慈善家就會匹配這份簡介,因為地區列表可以編碼記錄西雅圖在華盛頓這一事實(從 “Greater Seattle Area” 這個字串中看不出來)

使用 ID 的好處是,ID 對人類沒有任何意義,因而永遠不需要改變:ID 可以保持不變,即使它標識的資訊發生變化。任何對人類有意義的東西都可能需要在將來某個時候改變 — — 如果這些資訊被複制,所有的冗餘副本都需要更新。這會導致寫入開銷,也存在不一致的風險(一些副本被更新了,還有些副本沒有被更新)。去除此類重複是資料庫 規範化(normalization) 的關鍵思想。2

不幸的是,對這些資料進行規範化需要多對一的關係(許多人生活在一個特定的地區,許多人在一個特定的行業工作),這與文件模型不太吻合。在關係資料庫中,透過 ID 來引用其他表中的行是正常的,因為連線很容易。在文件資料庫中,一對多樹結構沒有必要用連線,對連線的支援通常很弱 3

  • 組織和學校作為實體
    在前面的描述中,organization(使用者工作的公司)和 school_name(他們學習的地方)只是字串。也許他們應該是對實體的引用呢?然後,每個組織、學校或大學都可以擁有自己的網頁(標識、新聞提要等)。每個簡歷可以連結到它所提到的組織和學校,並且包括他們的圖示和其他資訊(請參閱 圖 2–3,來自 LinkedIn 的一個例子)。
  • 推薦
    假設你想新增一個新的功能:一個使用者可以為另一個使用者寫一個推薦。在使用者的簡歷上顯示推薦,並附上推薦使用者的姓名和照片。如果推薦人更新他們的照片,那他們寫的任何推薦都需要顯示新的照片。因此,推薦應該擁有作者個人簡介的引用。
圖 2–3 公司名不僅是字串,還是一個指向公司實體的連結(LinkedIn 截圖)

圖 2–4 闡明瞭這些新功能需要如何使用多對多關係。每個虛線矩形內的資料可以分組成一個文件,但是對單位,學校和其他使用者的引用需要表示成引用,並且在查詢時需要連線。

圖 2–4 使用多對多關係擴充套件簡歷

文件資料庫是否在重蹈覆轍?

網狀模型

網狀模型中記錄之間的連結不是外來鍵,而更像程式語言中的指標(同時仍然儲存在磁碟上)。訪問記錄的唯一方法是跟隨從根記錄起沿這些鏈路所形成的路徑。這被稱為 訪問路徑(access path)。

最簡單的情況下,訪問路徑類似遍歷連結串列:從列表頭開始,每次檢視一條記錄,直到找到所需的記錄。但在多對多關係的情況中,數條不同的路徑可以到達相同的記錄,網狀模型的程式設計師必須跟蹤這些不同的訪問路徑。

關係模型

在關係資料庫中,查詢最佳化器自動決定查詢的哪些部分以哪個順序執行,以及使用哪些索引。這些選擇實際上是 “訪問路徑”,但最大的區別在於它們是由查詢最佳化器自動生成的,而不是由程式設計師生成,所以我們很少需要考慮它們。

但是,在表示多對一和多對多的關係時,關係資料庫和文件資料庫並沒有根本的不同:在這兩種情況下,相關專案都被一個唯一的識別符號引用,這個識別符號在關係模型中被稱為 外來鍵,在文件模型中稱為 文件引用【9】。該識別符號在讀取時透過連線或後續查詢來解析。迄今為止,文件資料庫沒有走 CODASYL 的老路。

關係型資料庫與文件資料庫在今日的對比

哪種資料模型更有助於簡化應用程式碼?

如果應用程式中的資料具有類似文件的結構(即,一對多關係樹,通常一次性載入整個樹),那麼使用文件模型可能是一個好主意。將類似文件的結構分解成多個表(如 圖 2–1 中的 positions、education 和 contact_info)的關係技術可能導致繁瑣的模式和不必要的複雜的應用程式程式碼。

文件模型有一定的侷限性:例如,不能直接引用文件中的巢狀的專案,而是需要說 “使用者 251 的位置列表中的第二項”(很像層次模型中的訪問路徑)。但是,只要檔案巢狀不太深,這通常不是問題。

文件資料庫對連線的糟糕支援可能是個問題,也可能不是問題,這取決於應用程式。例如,如果某分析型應用程式使用一個文件資料庫來記錄何時何地發生了何事,那麼多對多關係可能永遠也用不上。【19】。

但如果你的應用程式確實會用到多對多關係,那麼文件模型就沒有那麼誘人了。儘管可以透過反規範化來消除對連線的需求,但這需要應用程式程式碼來做額外的工作以確保資料一致性。儘管應用程式程式碼可以透過向資料庫發出多個請求的方式來模擬連線,但這也將複雜性轉移到應用程式中,而且通常也會比由資料庫內的專用程式碼更慢。在這種情況下,使用文件模型可能會導致更複雜的應用程式碼與更差的效能【15】。

我們沒有辦法說哪種資料模型更有助於簡化應用程式碼,因為它取決於資料項之間的關係種類。對高度關聯的資料而言,文件模型是極其糟糕的,關係模型是可以接受的,而選用圖形模型(請參閱 “圖資料模型”)是最自然的。

文件模型中的模式靈活性

大多數文件資料庫以及關係資料庫中的 JSON 支援都不會強制文件中的資料採用何種模式。關係資料庫的 XML 支援通常帶有可選的模式驗證。沒有模式意味著可以將任意的鍵和值新增到文件中,並且當讀取時,客戶端無法保證文件可能包含的欄位。

文件資料庫有時稱為 無模式(schemaless),但這具有誤導性,因為讀取資料的程式碼通常假定某種結構 — — 即存在隱式模式,但不由資料庫強制執行【20】。一個更精確的術語是 讀時模式(即 schema-on-read,資料的結構是隱含的,只有在資料被讀取時才被解釋),相應的是 寫時模式(即 schema-on-write,傳統的關係資料庫方法中,模式明確,且資料庫確保所有的資料都符合其模式)【21】。

讀時模式類似於程式語言中的動態(執行時)型別檢查,而寫時模式類似於靜態(編譯時)型別檢查。就像靜態和動態型別檢查的相對優點具有很大的爭議性一樣【22】,資料庫中模式的強制性是一個具有爭議的話題,一般來說沒有正確或錯誤的答案。

在應用程式想要改變其資料格式的情況下,這些方法之間的區別尤其明顯。例如,假設你把每個使用者的全名儲存在一個欄位中,而現在想分別儲存名字和姓氏【23】。在文件資料庫中,只需開始寫入具有新欄位的新文件,並在應用程式中使用程式碼來處理讀取舊文件的情況。例如:

if (user && user.name && !user.first_name) {
// Documents written before Dec 8, 2013 don't have first_name
user.first_name = user.name.split(" ")[0];
}

另一方面,在 “靜態型別” 資料庫模式中,通常會執行以下 遷移(migration) 操作:

ALTER TABLE users ADD COLUMN first_name text;
UPDATE users SET first_name = split_part(name, ' ', 1); -- PostgreSQL
UPDATE users SET first_name = substring_index(name, ' ', 1); -- MySQL

模式變更的速度很慢,而且要求停運。它的這種壞名譽並不是完全應得的:大多數關係資料庫系統可在幾毫秒內執行 ALTER TABLE 語句。MySQL 是一個值得注意的例外,它執行 ALTER TABLE 時會複製整個表,這可能意味著在更改一個大型表時會花費幾分鐘甚至幾個小時的停機時間,儘管存在各種工具來解決這個限制【24,25,26】。

大型表上執行 UPDATE 語句在任何資料庫上都可能會很慢,因為每一行都需要重寫。要是不可接受的話,應用程式可以將 first_name 設定為預設值 NULL,並在讀取時再填充,就像使用文件資料庫一樣。

查詢的資料區域性

區域性僅僅適用於同時需要文件絕大部分內容的情況。資料庫通常需要載入整個文件,即使只訪問其中的一小部分,這對於大型文件來說是很浪費的。更新文件時,通常需要整個重寫。只有不改變文件大小的修改才可以容易地原地執行。因此,通常建議保持相對小的文件,並避免增加文件大小的寫入【9】。這些效能限制大大減少了文件資料庫的實用場景。

文件和關係資料庫的融合

隨著時間的推移,關係資料庫和文件資料庫似乎變得越來越相似,這是一件好事:資料模型相互補充 5,如果一個數據庫能夠處理類似文件的資料,並能夠對其執行關係查詢,那麼應用程式就可以使用最符合其需求的功能組合。

關係模型和文件模型的混合是未來資料庫一條很好的路線。

資料查詢語言

許多常用的程式語言是命令式的。例如,給定一個動物物種的列表,返回列表中的鯊魚可以這樣寫:

function getSharks() {
var sharks = [];
for (var i = 0; i < animals.length; i++) {
if (animals[i].family === "Sharks") {
sharks.push(animals[i]);
}
}
return sharks;
}

定義 SQL 時,它緊密地遵循關係代數的結構:

SELECT * FROM animals WHERE family ='Sharks';

在宣告式查詢語言(如 SQL 或關係代數)中,你只需指定所需資料的模式 — 結果必須符合哪些條件,以及如何將資料轉換(例如,排序,分組和集合) — 但不是如何實現這一目標。資料庫系統的查詢最佳化器決定使用哪些索引和哪些連線方法,以及以何種順序執行查詢的各個部分。

Web 上的宣告式查詢

現在想讓當前所選頁面的標題具有一個藍色的背景,以便在視覺上突出顯示。使用 CSS 實現起來非常簡單:

li.selected > p {
background-color: blue;
}

想象一下,必須使用命令式方法的情況會是如何。在 Javascript 中,使用 文件物件模型(DOM) API,其結果可能如下所示:

var liElements = document.getElementsByTagName("li");
for (var i = 0; i < liElements.length; i++) {
if (liElements[i].className === "selected") {
var children = liElements[i].childNodes;
for (var j = 0; j < children.length; j++) {
var child = children[j];
if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "P") {
child.setAttribute("style", "background-color: blue");
}
}
}
}

在 Web 瀏覽器中,使用宣告式 CSS 樣式比使用 JavaScript 命令式地操作樣式要好得多。類似地,在資料庫中,使用像 SQL 這樣的宣告式查詢語言比使用命令式查詢 API 要好得多 6

MapReduce查詢

在 PostgreSQL 中,你可以像這樣表述這個查詢:

SELECT
date_trunc('month', observation_timestamp) AS observation_month,
sum(num_animals) AS total_animals
FROM observations
WHERE family = 'Sharks'
GROUP BY observation_month;

同樣的查詢用 MongoDB 的 MapReduce 功能可以按如下來表述:

db.observations.mapReduce(function map() {
var year = this.observationTimestamp.getFullYear();
var month = this.observationTimestamp.getMonth() + 1;
emit(year + "-" + month, this.numAnimals);
},
function reduce(key, values) {
return Array.sum(values);
},
{
query: {
family: "Sharks"
},
out: "monthlySharkReport"
});

對每個文件都會呼叫一次 map 函式,結果將是 emit(“1995–12”,3) 和 emit(“1995–12”,4)。隨後,以 reduce(“1995–12”,[3,4]) 呼叫 reduce 函式,將返回 7。

map 和 reduce 函式在功能上有所限制:它們必須是 純 函式,這意味著它們只使用傳遞給它們的資料作為輸入,它們不能執行額外的資料庫查詢,也不能有任何副作用。這些限制允許資料庫以任何順序執行任何功能,並在失敗時重新執行它們。然而,map 和 reduce 函式仍然是強大的:它們可以解析字串、呼叫庫函式、執行計算等等。

對每個文件都會呼叫一次 map 函式,結果將是 emit(“1995–12”,3) 和 emit(“1995–12”,4)。隨後,以 reduce(“1995–12”,[3,4]) 呼叫 reduce 函式,將返回 7。

map 和 reduce 函式在功能上有所限制:它們必須是 純 函式,這意味著它們只使用傳遞給它們的資料作為輸入,它們不能執行額外的資料庫查詢,也不能有任何副作用。這些限制允許資料庫以任何順序執行任何功能,並在失敗時重新執行它們。然而,map 和 reduce 函式仍然是強大的:它們可以解析字串、呼叫庫函式、執行計算等等。

能夠在查詢中使用 JavaScript 程式碼是高階查詢的一個重要特性,但這不限於 MapReduce,一些 SQL 資料庫也可以用 JavaScript 函式進行擴充套件【34】。

圖資料模型

一個圖由兩種物件組成:頂點(vertices,也稱為 節點,即 nodes,或 實體,即 entities),和 邊(edges,也稱為 關係,即 relationships,或 弧,即 arcs)。多種資料可以被建模為一個圖形。典型的例子包括:

  • 社交圖譜
    頂點是人,邊指示哪些人彼此認識。
  • 網路圖譜
    頂點是網頁,邊緣表示指向其他頁面的 HTML 連結。
  • 公路或鐵路網路
    頂點是交叉路口,邊線代表它們之間的道路或鐵路線。

在本節中,我們將使用 圖 2–5 所示的示例。它可以從社交網路或系譜資料庫中獲得:它顯示了兩個人,來自愛達荷州的 Lucy 和來自法國 Beaune 的 Alain。他們已婚,住在倫敦。

圖 2–5 圖資料結構示例(框代表頂點,箭頭代表邊)

屬性圖

在屬性圖模型中,每個頂點(vertex)包括:

  • 唯一的識別符號
  • 一組出邊(outgoing edges)
  • 一組入邊(ingoing edges)
  • 一組屬性(鍵值對)

每條邊(edge)包括:

  • 唯一識別符號
  • 邊的起點(尾部頂點,即 tail vertex)
  • 邊的終點(頭部頂點,即 head vertex)
  • 描述兩個頂點之間關係型別的標籤
  • 一組屬性(鍵值對)

例 2–2 使用關係模式來表示屬性圖

CREATE TABLE vertices (
vertex_id INTEGER PRIMARY KEY,
properties JSON
);
CREATE TABLE edges (
edge_id INTEGER PRIMARY KEY,
tail_vertex INTEGER REFERENCES vertices (vertex_id),
head_vertex INTEGER REFERENCES vertices (vertex_id),
label TEXT,
properties JSON
);
CREATE INDEX edges_tails ON edges (tail_vertex);
CREATE INDEX edges_heads ON edges (head_vertex);

關於這個模型的一些重要方面是:

  1. 任何頂點都可以有一條邊連線到任何其他頂點。沒有模式限制哪種事物可不可以關聯。
  2. 給定任何頂點,可以高效地找到它的入邊和出邊,從而遍歷圖,即沿著一系列頂點的路徑前後移動(這就是為什麼 例 2–2tail_vertexhead_vertex 列上都有索引的原因)。
  3. 透過對不同型別的關係使用不同的標籤,可以在一個圖中儲存幾種不同的資訊,同時仍然保持一個清晰的資料模型。

Cypher 查詢語言

例 2–3 將圖 2–5 中的資料子集表示為 Cypher 查詢

CREATE
(NAmerica:Location {name:'North America', type:'continent'}),
(USA:Location {name:'United States', type:'country' }),
(Idaho:Location {name:'Idaho', type:'state' }),
(Lucy:Person {name:'Lucy' }),
(Idaho) -[:WITHIN]-> (USA) -[:WITHIN]-> (NAmerica),
(Lucy) -[:BORN_IN]-> (Idaho)

當 圖 2–5 的所有頂點和邊被新增到資料庫後,讓我們提些有趣的問題:例如,找到所有從美國移民到歐洲的人的名字。更確切地說,這裡我們想要找到符合下面條件的所有頂點,並且返回這些頂點的 name 屬性:該頂點擁有一條連到美國任一位置的 BORN_IN 邊,和一條連到歐洲的任一位置的 LIVING_IN 邊。

例 2–4 展示瞭如何在 Cypher 中表達這個查詢。在 MATCH 子句中使用相同的箭頭符號來查詢圖中的模式:(person) -[:BORN_IN]-> () 可以匹配 BORN_IN 邊的任意兩個頂點。該邊的尾節點被綁定了變數 person,頭節點則未被繫結。

例 2–4 查詢所有從美國移民到歐洲的人的 Cypher 查詢:

MATCH
(person) -[:BORN_IN]-> () -[:WITHIN*0..]-> (us:Location {name:'United States'}),
(person) -[:LIVES_IN]-> () -[:WITHIN*0..]-> (eu:Location {name:'Europe'})
RETURN person.name

查詢按如下來解讀:

找到滿足以下兩個條件的所有頂點(稱之為 person 頂點):

  • person 頂點擁有一條到某個頂點的 BORN_IN 出邊。從那個頂點開始,沿著一系列 WITHIN 出邊最終到達一個型別為 Locationname 屬性為 United States 的頂點。
  • person 頂點還擁有一條 LIVES_IN 出邊。沿著這條邊,可以透過一系列 WITHIN 出邊最終到達一個型別為 Locationname 屬性為 Europe 的頂點。

對於這樣的 Person 頂點,返回其 name 屬性。

SQL 中的圖查詢

自 SQL:1999,查詢可變長度遍歷路徑的思想可以使用稱為 遞迴公用表表達式(WITH RECURSIVE 語法)的東西來表示。例 2–5 顯示了同樣的查詢 — 查詢從美國移民到歐洲的人的姓名 — 在 SQL 使用這種技術(PostgreSQL、IBM DB2、Oracle 和 SQL Server 均支援)來表述。但是,與 Cypher 相比,其語法非常笨拙。

例 2–5 與示例 2–4 同樣的查詢,在 SQL 中使用遞迴公用表表達式表示

WITH RECURSIVE
-- in_usa 包含所有的美國境內的位置 ID
in_usa(vertex_id) AS (
SELECT vertex_id FROM vertices WHERE properties ->> 'name' = 'United States'
UNION
SELECT edges.tail_vertex FROM edges
JOIN in_usa ON edges.head_vertex = in_usa.vertex_id
WHERE edges.label = 'within'
),
-- in_europe 包含所有的歐洲境內的位置 ID
in_europe(vertex_id) AS (
SELECT vertex_id FROM vertices WHERE properties ->> 'name' = 'Europe'
UNION
SELECT edges.tail_vertex FROM edges
JOIN in_europe ON edges.head_vertex = in_europe.vertex_id
WHERE edges.label = 'within' ),
  -- born_in_usa 包含了所有型別為 Person,且出生在美國的頂點
born_in_usa(vertex_id) AS (
SELECT edges.tail_vertex FROM edges
JOIN in_usa ON edges.head_vertex = in_usa.vertex_id
WHERE edges.label = 'born_in' ),
-- lives_in_europe 包含了所有型別為 Person,且居住在歐洲的頂點。
lives_in_europe(vertex_id) AS (
SELECT edges.tail_vertex FROM edges
JOIN in_europe ON edges.head_vertex = in_europe.vertex_id
WHERE edges.label = 'lives_in')
SELECT vertices.properties ->> 'name'
FROM vertices
JOIN born_in_usa ON vertices.vertex_id = born_in_usa.vertex_id
JOIN lives_in_europe ON vertices.vertex_id = lives_in_europe.vertex_id;

三元組儲存和 SPARQL

在三元組儲存中,所有資訊都以非常簡單的三部分表示形式儲存(主語,謂語,賓語)。例如,三元組 (吉姆, 喜歡, 香蕉) 中,吉姆 是主語,喜歡 是謂語(動詞),香蕉 是物件。

例 2–6 圖 2–5 中的資料子集,表示為 Turtle 三元組

@prefix : <urn:example:>.
_:lucy a :Person.
_:lucy :name "Lucy".
_:lucy :bornIn _:idaho.
_:idaho a :Location.
_:idaho :name "Idaho".
_:idaho :type "state".
_:idaho :within _:usa.
_:usa a :Location
_:usa :name "United States"
_:usa :type "country".
_:usa :within _:namerica.
_:namerica a :Location
_:namerica :name "North America"
_:namerica :type :"continent"

RDF 資料模型

與之前相同的查詢 — — 查詢從美國移民到歐洲的人 — — 使用 SPARQL 比使用 Cypher 甚至更為簡潔(請參閱 例 2–9)。

例 2–8 用 RDF/XML 語法表示例 2–7 的資料

<rdf:RDF xmlns="urn:example:"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<Location rdf:nodeID="idaho">
<name>Idaho</name>
<type>state</type>
<within>
<Location rdf:nodeID="usa">
<name>United States</name>
<type>country</type>
<within>
<Location rdf:nodeID="namerica">
<name>North America</name>
<type>continent</type>
</Location>
</within>
</Location>
</within>
</Location>
<Person rdf:nodeID="lucy">
<name>Lucy</name>
<bornIn rdf:nodeID="idaho"/>
</Person>
</rdf:RDF>

基礎:Datalog

Datalog 是比 SPARQL、Cypher 更古老的語言,在 20 世紀 80 年代被學者廣泛研究【44,45,46】。它在軟體工程師中不太知名,但是它是重要的,因為它為以後的查詢語言提供了基礎。

實踐中,Datalog 在有限的幾個資料系統中使用:例如,它是 Datomic 【40】的查詢語言,Cascalog 【47】是一種用於查詢 Hadoop 大資料集的 Datalog 實現 8。

Datalog 的資料模型類似於三元組模式,但進行了一點泛化。把三元組寫成 謂語(主語,賓語),而不是寫三元語(主語,謂語,賓語)。例 2–10 顯示瞭如何用 Datalog 寫入我們的例子中的資料。

例 2–10 用 Datalog 來表示圖 2–5 中的資料子集

name(namerica, 'North America').
type(namerica, continent).
name(usa, 'United States').
type(usa, country).
within(usa, namerica).
name(idaho, 'Idaho').
type(idaho, state).
within(idaho, usa).
name(lucy, 'Lucy').
born_in(lucy, idaho).

本章小結

每個資料模型都具有各自的查詢語言或框架,我們討論了幾個例子:SQL,MapReduce,MongoDB 的聚合管道,Cypher,SPARQL 和 Datalog。我們也談到了 CSS 和 XSL/XPath,它們不是資料庫查詢語言,而包含有趣的相似之處。

參考翻譯:https://github.com/Vonng/ddia/blob/master/zh-tw/ch2.md

日本越後湯澤滑雪 石打丸 Gala 輕井澤 Tani Yuuki 菅原圭 — Cheers / FunkyGary / Japan — Echigo-Yuzawa

--

--

Gary Huang
Dublin so code

Self-taught in programming, currently working as a web developer and also a online course instructor, helping self-taught programmers find employment.