上一章,我們談到了 SQL 查詢的窗口函數 (window function) ,它在 Datalog 沒有直接的對應,但是,我們可以利用自訂聚合函數來做出類似的語意。
仔細想想,窗口函數已經是進階的語法了,但是在 SQL-92 的語法裡,還有一種 HAVING
語法,可以對聚合之後的結果做條件篩選,這種語法又要如何在 Datalog 實現呢?
若有個資料庫內含文章 (post) 和標籤 (tag) 的資料,想要用 SQL 查出:「每個標籤在多少文章裡使用,並且挑出使用次數大於等於三次的標籤,將它們加以輸出。」
我們可以用如下的查詢來達成:
SELECT tag, COUNT(post_id) AS tag_usage
FROM post_tag
GROUP BY tag
HAVING COUNT(post_id) >= 3;
(d/q '[:find ?tag ?n
:where
[(datomic.api/q
(quote [:find ?e (count ?post)
:where [?post :post/tag ?e]]) $)
[[?tag ?n]]]
[(>= ?n 3)]] (db/db))
該怎麼理解這段程式呢?
Day 13 曾經提到過,Datalog 也充許我們使用函數表達式。第一個 :where
子句 [(datomic.api/q ... $) [[?tag ?n]]]
就是函數表達式,它的形式是 [(<fn> <arg1> <arg2> ...) <result-binding>]
。它會把函數運算 (datomic.api/q ...)
算出的結果,綁定到 [[?tag ?n]]
這個變數裡。
由於函數運算使用了子查詢,所以,我們先把子查詢 (sub query) 的部分拉出來解讀。下方的查詢是「找出所有文章與標籤的組合,並且對標籤加以分群後,算出在文章裡出現的次數」。它算出來的結果會是:標籤:出現次數
,這個結果會被綁定到 [[?tag ?n]]
這個在母查詢可以存取的變數裡。
(d/q '[:find ?e (count ?post)
:where [?post :post/tag ?e]]
(db/db))
[(>= ?n 3)]
這個斷言表達式來做條件篩選,就可以做出等價於 HAVING
語法的效果。