iT邦幫忙

2024 iThome 鐵人賽

DAY 14
0
Software Development

Datomic,內建事件溯源的資料庫。系列 第 14

先從 Datalog 談起 -- part 9 (aggregates and with clause)

  • 分享至 

  • xImage
  •  

給定一張 person 表,有一個欄位 date 表示出生日期,而我們要找出最年輕的一位時,可以用如下的 SQL 查詢。

SELECT MAX(date) AS max_date
FROM person;

如果用 Datalog 來改寫的話:

[:find (max ?date)
 :where
 [_ :person/date ?date]]

在這個查詢裡,max 就是聚合函數 (aggregate function)

Datalog 內建的聚合函數

聚合函數 返回值的個數 備註
平均值 (avg) 1
計數 (count) 1 計算重複值
唯一計數 (count-distinct) 1 只計算唯一值
唯一值 (distinct) n 唯一值的集合
最大值 (max) 1 比較所有類型,不僅限於數字
最大 n 值 (max n) n 返回最多 n 個最大值
中位數 (median) 1
最小值 (min) 1 比較所有類型,不僅限於數字
最小 n 值 (min n) n 返回最多 n 個最小值
隨機 n 值 (rand n) n 隨機選擇最多 n 個,允許重複
樣本 n 值 (sample n) n 隨機樣本最多 n 個,不允許重複
標準差 (stddev) 1
總和 (sum) 1
方差 (variance) 1

內建的聚合函數有兩類:

  1. 傳回單一值的,例如:min, max, sum, avg 等。
  2. 傳回值的集合的,例如:(min n ?d), (max n ?d), (sample n ?e) 。在這邊的 n 是使用者指定的任意整數,用來描述集合的大小。

with 子句

Datalog 的聚合函數與 SQL 的聚合函數用法,看似很相像卻有小小的不同。總之,許多時候,聚合函數會需要用 :with 子句來加以修飾語意。

用實際例子來說明:

  • SQL 資料庫設定
CREATE TABLE monsters (
    name VARCHAR(50) NOT NULL,
    count INT NOT NULL
);

INSERT INTO monsters (name, count)
VALUES 
    ('Cerberus', 3),
    ('Medusa', 1),
    ('Cyclops', 1),
    ('Chimera', 1);
  • 用 SQL 查詢『所有怪獸的頭的總數』
SELECT SUM(count) AS total_count
FROM monsters;

;; => 
 total_count 
─────────────
           6
(1 row)
  • Datalog 的外部資料表設定。
(def monsters [["Cerberus" 3]
               ["Medusa" 1]
               ["Cyclops" 1]
               ["Chimera" 1]])
  • 用 Datalog 查詢『所有怪獸的頭的總數』
(d/q '[:find (sum ?heads) .
       :in [[_ ?heads]]]
     monsters)
     
;; => 4

得到的結果卻是 4 。咦,為什麼呢?

  • 不做加總,先看一下中間運算的結果
(d/q '[:find ?heads
       :in [[_ ?heads]]]
     monsters)
     
;; => #{[1] [3]}

抓到原因了!因為上述 Datalog 查詢的語意是:『怪獸的頭的數目有哪些?』而非『所有的怪獸的頭的數目分別是什麼?』

  • 解決方案,加入 with 子句。
(d/q '[:find (sum ?heads) .
       :with ?monster
       :in [[?monster ?heads]]]
     monsters)
     
;; => 6

如何理解 with 子句

官方文件裡 :with 子句的說明如下:

:with 子句在形成查詢結果的基礎集合時考慮了額外的變數,這些額外的變數隨後會被移除,留下由 :with 變數所限定的、有用的容器 (bag)。

老實說,我覺得有點抽象…。我是透過與 SQL 的比較來理解 :with 子句的:

  • SQL 有一個 DISTINCT 的語法可以表現「去除重複」的語意,但是 SQL 的預設語意是「不去除重複值」。
SELECT count FROM monsters;

;; => 
 count 
───────
     3
     1
     1
     1
(4 rows)

SELECT DISTINCT count FROM monsters;
 
;; => 
 count 
───────
     3
     1
(2 rows)
  • Datalog 的預設語意恰好與 SQL 相反,它的預設是「去除重複值」,所以 Datalog 會需要一個 :with 子句來做相反方向的修飾。
(d/q '[:find ?heads
       :in [[_ ?heads]]]
     monsters)
;; => #{[1] [3]}

(d/q '[:find ?heads
       :with ?monster
       :in [[?monster ?heads]]]
     monsters)
;; => [[1] [1] [3] [1]]

練習題

參考資料

  1. with 子句
  2. Day of Datomic 的 with 子句的範例

其它資源

  1. 歡迎訂閱 PruningSuccess 電子報,主要談論軟體開發、資料處理、資料分析等議題。
  2. 歡迎加入 Clojure 社群

上一篇
先從 Datalog 談起 -- part 8 (predicates and transformation functions)
下一篇
先從 Datalog 談起 -- part 10 (find specs)
系列文
Datomic,內建事件溯源的資料庫。30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言