【问题标题】:Equivalent of SQL "limit" clause in DatomicDatomic 中 SQL “limit”子句的等价物
【发布时间】:2014-11-27 03:22:48
【问题描述】:

标题说明了一切,但假设我有一个简单的查询如下:

(q '[:find ?c ?n :where [?c :my-thing/its-attribute ?n]]
   (d/db conn))

反对类似的架构

[{:db/id (d/tempid :db.part/db)
  :db/ident :my-thing/its-attribute
  :db/valueType :db.type/string
  :db/doc "My thing's attribute"
  :db/cardinality :db.cardinality/one
  :db.install/_attribute :db.part/db}]

如果查询匹配所有内容(例如,100M 条目),则返回的结果将很大。如果我只想要其中的几个,那么最好的方法是什么?

【问题讨论】:

  • 您可以使用pull expression 指定限制。
  • @BenKamphaus 看起来很有希望。因为pull 需要一个实体参数,这是否意味着我需要建立一些层次结构,其中我想要查询(和限制)的所有事物都是其他事物的属性(pull 的最后一个参数)?目前,我的最小架构中只有一种属性。
  • 只需使用 take(如果需要从中间开始,则使用 drop/take)
  • @GuillermoWinkler 只要您的查询不返回大量集合,它就可以正常工作——如果我理解正确,查询结果并不懒惰。
  • @JohnJ limit in pull 用于限制基数许多属性的返回值。读这个有点太快了。对于针对简单查询的惰性访问,针对正确索引的datoms 可能比查询更适合。

标签: clojure datomic


【解决方案1】:

使用 rand(允许重复)和 sample(仅不同)的两个随机名称

(d/q '[:find [(rand 2 ?name) (sample 2 ?name)]
       :where [_ :artist/name ?name]]
     db)

这个例子来自day-of-datomic github repo。

【讨论】:

  • 这确实是最简单、最地道的答案,应该接受。
【解决方案2】:

对于“所有属性和值对,已排序”的简单情况,将 take 与 seek-datoms 一起使用是您的最佳选择。以下示例使用mbrainz 示例数据库:

(def conn (d/connect "datomic:sql://mbrainz-1968-1973?jdbc:postgresql://localhost:5432/datomic?user=datomic&password=datomic"))

(->> (d/seek-datoms (d/db conn) :avet :artist/sortName "Bea")
     (take 20))

然后返回:

(#datom[17592186050196 81 "Beach Boys, The" 13194139539089 true] #datom[17592186047857 81 "Beatles, The" 13194139536749 true] #datom[17592186048553 81 "Beau Brummels, The" 13194139537425 true] #datom[17592186049043 81 "Beaver & Krause" 13194139537919 true] #datom[17592186046205 81 "Beaver, Paul" 13194139535085 true] #datom[17592186046692 81 "Beck, Bogert & Appice" 13194139535579 true] #datom[17592186046886 81 "Beck, Jeff" 13194139535761 true] #datom[17592186047111 81 "Beck, Jeff Group" 13194139535995 true] #datom[17592186046486 81 "Bedford, David" 13194139535371 true] #datom[17592186046992 81 "Bee Gees" 13194139535865 true] #datom[17592186045876 81 "Beethoven, Ludwig van" 13194139534747 true] #datom[17592186048427 81 "Beggars Opera" 13194139537321 true] #datom[17592186047091 81 "Beginning of the End, The" 13194139535969 true] #datom[17592186045945 81 "Belafonte, Harry" 13194139534825 true] #datom[17592186047485 81 "Bell, Archie & Drells, The" 13194139536359 true] #datom[17592186045915 81 "Bell, Carey" 13194139534799 true] #datom[17592186046324 81 "Bell, Vinnie" 13194139535215 true] #datom[17592186047164 81 "Bell, William" 13194139536047 true] #datom[17592186047652 81 "Belle, Marie-Paule" 13194139536541 true] #datom[17592186046496 81 "Bellou, Sotiria" 13194139535371 true])

当然,你可以映射一个fn来美化输出或者添加更多属性等等,比如这个例子:

(let [db (d/db conn)]
  (->> (d/seek-datoms db :avet :artist/sortName "Bea")
       (take 20)
       (map #(merge {:artist/name (:v %)
                     :artist/type (-> (d/pull db [{:artist/type [:db/ident]}] (:e %))
                                      :artist/type
                                      :db/ident)}))))

返回:

({:artist/name "Beach Boys, The" :artist/type :artist.type/group} {:artist/name "Beatles, The" :artist/type :artist.type/group} {:artist/name "Beau Brummels, The" :artist/type :artist.type/group} {:artist/name "Beaver & Krause" :artist/type :artist.type/group} {:artist/name "Beaver, Paul" :artist/type :artist.type/person} {:artist/name "Beck, Bogert & Appice" :artist/type :artist.type/group} {:artist/name "Beck, Jeff" :artist/type :artist.type/person} {:artist/name "Beck, Jeff Group" :artist/type :artist.type/group} {:artist/name "Bedford, David" :artist/type :artist.type/person} {:artist/name "Bee Gees" :artist/type :artist.type/group} {:artist/name "Beethoven, Ludwig van" :artist/type :artist.type/person} {:artist/name "Beggars Opera" :artist/type :artist.type/group} {:artist/name "Beginning of the End, The" :artist/type :artist.type/group} {:artist/name "Belafonte, Harry" :artist/type :artist.type/person} {:artist/name "Bell, Archie & Drells, The" :artist/type :artist.type/group} {:artist/name "Bell, Carey" :artist/type :artist.type/person} {:artist/name "Bell, Vinnie" :artist/type :artist.type/person} {:artist/name "Bell, William" :artist/type :artist.type/person} {:artist/name "Belle, Marie-Paule" :artist/type :artist.type/person} {:artist/name "Bellou, Sotiria" :artist/type :artist.type/person})

注意:要将seek-datomsdatoms:avet 一起使用,必须对相关属性进行索引。

【讨论】:

  • 听起来很有趣,下次我使用 Datomic 时会尝试一下。
【解决方案3】:

这个答案有点像@adamneilson 和原始问题上的 cmets 的汇编。我试图完成与 OP 相同的事情,但无法在这里找到我的答案,所以希望这会对某人有所帮助。

我的用例是通过分页提取 10 万条记录。简单地使用take/drop 绝对不可行,因为它需要很长时间(几十秒)。

我的解决方法是首先获取所需的实体 ID,在该集合上执行 take/drop,然后使用 entity 映射它们。这是我的最终代码:

(defn eid->entity
  [eid]
  (into {} (d/touch (d/entity (d/db (get-conn)) eid))))

(defn find-eids
  [attr value limit offset]
  (let [query '[:find ?eid
                :in $ ?attr ?value
                :where [?eid ?attr ?value]]
        db (d/db (get-conn))
        result (drop offset (sort (d/q query db attr value)))]
    (map first (take limit result))))

(map eid->entity (find-eids :attr-name "value" 10 10)

这对我受过 SQL 训练的大脑来说感觉非常错误,但我认为这是直接的方式。而且它并不是很慢 - 100k 条记录大约需要 500 毫秒,这对我来说已经足够了。

【讨论】:

    【解决方案4】:

    您尝试过使用get-some 吗?

    发件人:http://docs.datomic.com/query.html

    吃点东西

    get-some 函数接受一个数据库、实体和一个或多个 基数一属性,返回实体 id 的元组和 实体拥有的第一个属性的值。

    [(get-some $ ?person :person/customer-id :person/email) ?identifier]
    

    -- 编辑以回复评论--

    您也可以尝试进行查询以选择低于特定数量的实体。

    user> (defn example-take-query [n]
            (into '[:find ?e :where [?e :age ?a]]
                  [[`(~'> ~n ~'?e)]]))
    #'user/example-take-query
    user> (example-take-query 3)
    [:find ?e :where [?e :age ?a] [(> 3 ?e)]]
    user> (example-take-query 10)
    [:find ?e :where [?e :age ?a] [(> 10 ?e)]]
    

    【讨论】:

    • 像 Clojure 的 some 核心函数一样,它似乎只返回一个匹配的元组,而不是 N。
    • 这也不是我想要的——这是一种解决方法(充其量),我想要的是正确的成语。
    【解决方案5】:

    在使用查询映射时,可以使用键 :limit 来实现相当于 SQL 的“限制”子句。

     (d/q {:query '[:find ?c ?n :where [?c :my-thing/its-attribute ?n]] 
           :offset 1
           :limit 10
           :args [(d/db conn)]})
    

    您可以在 datomic 客户端 api 文档中阅读更多相关信息:

    https://docs.datomic.com/client-api/datomic.client.api.html

    或关于查询映射语法:

    https://docs.datomic.com/on-prem/query.html#timeout

    【讨论】:

      【解决方案6】:

      不久前我也有类似的需求,我将 LIMIT 的 clojure MySql 方法放在了一个集合中:

      (defmacro limit 
      "Pagination mimicking the MySql LIMIT" 
      ([coll start-from quantity]
          `(take ~quantity (drop ~start-from ~coll)))
      ([coll quantity]
          `(limit ~coll 0 ~quantity)))
      

      repl 上的简单示例用法:

      user=>  (defmacro limit 
        #_=>     "Pagination mimicking the MySql LIMIT" 
        #_=>     ([coll start-from quantity]
        #_=>     `(take ~quantity (drop ~start-from ~coll)))
        #_=>     ([coll quantity]
        #_=>         `(limit ~coll 0 ~quantity)))
      #'user/limit
      user=> (def hundred (take 100 (iterate inc 0))) ;; define a collection
      #'user/hundred
      user=> (limit hundred 25) ;; get the first 25 from the collection
      (0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24)
      user=> (limit hundred 25 25) ;; get the next 25
      (25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49)
      

      不确定它是否能准确回答您的问题,但它可能会有一些用处。

      【讨论】:

      • 该宏与普通函数一样好(或更好)。
      猜你喜欢
      • 2012-05-10
      • 1970-01-01
      • 2012-02-19
      • 2011-08-09
      • 2014-06-01
      • 2017-01-15
      • 1970-01-01
      • 1970-01-01
      • 2010-09-29
      相关资源
      最近更新 更多