給定一張 person 表,有一個欄位 date 表示出生日期,而我們要找出最年輕的一位時,可以用如下的 SQL 查詢。
SELECT MAX(date) AS max_date
FROM person;
如果用 Datalog 來改寫的話:
[:find (max ?date)
:where
[_ :person/date ?date]]
在這個查詢裡,max 就是聚合函數 (aggregate function)。
| 聚合函數 | 返回值的個數 | 備註 |
|---|---|---|
| 平均值 (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 |
內建的聚合函數有兩類:
min, max, sum, avg 等。(min n ?d), (max n ?d), (sample n ?e) 。在這邊的 n 是使用者指定的任意整數,用來描述集合的大小。Datalog 的聚合函數與 SQL 的聚合函數用法,看似很相像卻有小小的不同。總之,許多時候,聚合函數會需要用 :with 子句來加以修飾語意。
用實際例子來說明:
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);
SELECT SUM(count) AS total_count
FROM monsters;
;; =>
total_count
─────────────
6
(1 row)
(def monsters [["Cerberus" 3]
["Medusa" 1]
["Cyclops" 1]
["Chimera" 1]])
(d/q '[:find (sum ?heads) .
:in [[_ ?heads]]]
monsters)
;; => 4
得到的結果卻是 4 。咦,為什麼呢?
(d/q '[:find ?heads
:in [[_ ?heads]]]
monsters)
;; => #{[1] [3]}
抓到原因了!因為上述 Datalog 查詢的語意是:『怪獸的頭的數目有哪些?』而非『所有的怪獸的頭的數目分別是什麼?』
(d/q '[:find (sum ?heads) .
:with ?monster
:in [[?monster ?heads]]]
monsters)
;; => 6
官方文件裡 :with 子句的說明如下:
:with子句在形成查詢結果的基礎集合時考慮了額外的變數,這些額外的變數隨後會被移除,留下由:with變數所限定的、有用的容器 (bag)。
老實說,我覺得有點抽象…。我是透過與 SQL 的比較來理解 :with 子句的:
DISTINCT 的語法可以表現「去除重複」的語意,但是 SQL 的預設語意是「不去除重複值」。SELECT count FROM monsters;
;; =>
count
───────
3
1
1
1
(4 rows)
SELECT DISTINCT count FROM monsters;
;; =>
count
───────
3
1
(2 rows)
:with 子句來做相反方向的修飾。(d/q '[:find ?heads
:in [[_ ?heads]]]
monsters)
;; => #{[1] [3]}
(d/q '[:find ?heads
:with ?monster
:in [[?monster ?heads]]]
monsters)
;; => [[1] [1] [3] [1]]