當我使用 SQL 來開發 web application 時,最常用的 SQL query 是什麼呢? 其實未必是複雜的 join operation 。最常用的,反而是樸實無華的查看單一或是多個 rows:
(1) SELECT * FROM A
(2) SELECT * FROM A WHERE A.col = b
那麼,這麼簡單的 SQL query ,如果是在 Datomic 的世界,又是怎麼對應呢?我在 datomic 官方的 mbrainz-sample 找出的一段 query 。用了之後,覺得非常容易可以對應上述的兩種情境。sample code 在下方
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; These functions borrowed from https://github.com/Datomic/mbrainz-sample | |
(ns datomic.samples.query | |
(:require [datomic.api :as d])) | |
(defn only | |
"Return the only item from a query result" | |
[query-result] | |
(assert (= 1 (count query-result))) | |
(assert (= 1 (count (first query-result)))) | |
(ffirst query-result)) | |
(defn qe | |
"Returns the single entity returned by a query." | |
[query db & args] | |
(let [res (apply d/q query db args)] | |
(d/entity db (only res)))) | |
;; Given that A.attr is primary key | |
;; Similar usage pattern to SQL expression: `SELECT * FROM A WHERE A.attr = val` | |
(defn find-by | |
"Returns the unique entity identified by attr and val." | |
[db attr val] | |
(qe '[:find ?e | |
:in $ ?attr ?val | |
:where [?e ?attr ?val]] | |
db attr val)) | |
;; similar to `find-by`, but more general because this function allow nil as answer. | |
(defn find-one-by | |
"Given db value and an (attr/val), return the user as EntityMap (datomic.query.EntityMap)" | |
[db attr val] | |
(d/entity db | |
(d/q '[:find ?e . | |
:in $ ?attr ?val | |
:where [?e ?attr ?val]] | |
db attr val))) | |
(defn qes | |
"Returns the entities returned by a query, assuming that | |
all :find results are entity ids." | |
[query db & args] | |
(->> (apply d/q query db args) | |
(mapv (fn [items] | |
(mapv (partial d/entity db) items))))) | |
;; Similar usage pattern to SQL expression: `SELECT * FROM A` | |
(defn find-all-by | |
"Returns all entities possessing attr." | |
[db attr] | |
(qes '[:find ?e | |
:in $ ?attr | |
:where [?e ?attr]] | |
db attr)) |
如果有一個「使用者」的概念,要用資料庫加以建模。使用者有電子郵件、密碼、名字三種屬性。同時,我們需要一個資料庫查詢 (query) ,可以根據電子郵件來查出對應的使用者
1. 用 RDB 來建模的話,這個 sql query 會長成這樣子:
SELECT * FROM user WHERE user.email = "ecoboy@qwerty.com";
2. 用 Datomic 來建模的話,一旦利用上述的 utility functions ,這個 query 就可以用下列的函數產生。
(find-by db :user/email "ecoboy@qwerty.com")
附註:
(a) 在 SQL best practices 裡,因為要考慮效率,往往不建議用 select * 這種 query 直接放在 production code 裡。
(b) 在 Datomic best practices 裡,因為 datomic 的 query 並不需要往返 client-server ,同時 Entity 有 lazy evaluation 的特性,一般而言,推荐的作法是直接取回 Entity ,相當於 SQL 裡的 row 概念。