給定一張 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]]