【问题标题】:Find entities whose ref-to-many attribute contains all elements of input查找 ref-to-many 属性包含所有输入元素的实体
【发布时间】:2017-05-04 13:28:45
【问题描述】:

假设我有实体entry 具有引用多属性:entry/groups。我应该如何构建查询来查找其:entry/groups 属性包含所有我输入的外国ID 的实体?

下一个伪代码将更好地说明我的问题:

[2 3] ; having this as input foreign ids

;; and having these entry entities in db
[{:entry/id "A" :entry/groups  [2 3 4]}  
 {:entry/id "B" :entry/groups  [2]}     
 {:entry/id "C" :entry/groups  [2 3]}  
 {:entry/id "D" :entry/groups  [1 2 3]}
 {:entry/id "E" :entry/groups  [2 4]}] 

;; only A, C, D should be pulled

作为 Datomic/Datalog 的新手,我用尽了所有选项,因此不胜感激。谢谢!

【问题讨论】:

    标签: clojure datomic datalog


    【解决方案1】:

    TL;DR

    您正在解决 Datomic 数据日志中“动态连接”的一般问题。

    这里有 3 种策略:

    1. 编写使用 2 个否定和 1 个析取或递归规则的动态 Datalog 查询(见下文)
    2. 生成查询代码(相当于 Alan Thompson 的回答):缺点是动态生成 Datalog 子句的常见缺点,即您不会从 query plan caching 中受益。
    3. 直接使用indexes(EAVT 或 AVET)。

    动态数据记录查询

    Datalog 没有直接的方式来表达动态连接(逻辑 AND / 'for all ...' / set intersection)。但是,您可以在纯 Datalog 中通过组合一个析取(逻辑 OR / 'exists ...' / set union)和两个否定来实现它,即(For all ?g in ?Gs p(?e,?g)) <=> NOT(Exists ?g in ?Gs, such that NOT(p(?e, ?g)))

    在您的情况下,这可以表示为:

    [:find [?entry ...] :in $ ?groups :where
      ;; these 2 clauses are for restricting the set of considered datoms, which is more efficient (and necessary in Datomic's Datalog, which will refuse to scan the whole db)
      ;; NOTE: this imposes ?groups cannot be empty!
      [(first ?groups) ?group0]
      [?entry :entry/groups ?group0]
      ;; here comes the double negation
      (not-join [?entry ?groups]
        [(identity ?groups) [?group ...]]
        (not-join [?entry ?group]
          [?entry :entry/groups ?group]))]
    

    好消息:这可以表示为一个非常通用的 Datalog 规则(我最终可能会添加到 Datofu):

    [(matches-all ?e ?a ?vs)
     [(first ?vs) ?v0]
     [?e ?a ?v0]
     (not-join [?e ?a ?vs]
       [(seq ?vs) [?v ...]]
       (not-join [?e ?a ?v]
         [?e ?a ?v]))]
    

    ... 这意味着您的查询现在可以表示为:

    [:find [?entry ...] :in % $ ?groups :where
     (matches-all ?entry :entry/groups ?groups)]
    

    注意:还有一个使用递归规则的替代实现:

    [[(matches-all ?e ?a ?vs)
      [(seq ?vs)]
      [(first ?vs) ?v]
      [?e ?a ?v]
      [(rest ?vs) ?vs2]
      (matches-all ?e ?a ?vs2)]
     [(matches-all ?e ?a ?vs)
      [(empty? ?vs)]]]
    

    这个的优点是接受一个空的?vs 集合(只要?e?a 在查询中以其他方式绑定)。

    生成查询代码

    生成查询代码的优点是在这种情况下它相对简单,并且它可能使查询执行比更动态的替代方案更有效。在 Datomic 中生成 Datalog 查询的缺点是您可能会失去查询计划缓存的好处;因此,即使您要生成查询,您仍然希望它们尽可能通用(即仅取决于 v 值的数量)

    (defn q-find-having-all-vs 
      [n-vs]
      (let [v-syms (for [i (range n-vs)]
                     (symbol (str "?v" i)))]
        {:find '[[?e ...]]
         :in (into '[$ ?a] v-syms)
         :where 
         (for [?v v-syms]
           ['?e '?a ?v])}))
    
    ;; examples    
    (q-find-having-all-vs 1)
    => {:find [[?e ...]], 
        :in [$ ?a ?v0],
        :where 
        ([?e ?a ?v0])}
    (q-find-having-all-vs 2)
    => {:find [[?e ...]],
        :in [$ ?a ?v0 ?v1], 
        :where
        ([?e ?a ?v0] 
         [?e ?a ?v1])}
    (q-find-having-all-vs 3)
    => {:find [[?e ...]], 
        :in [$ ?a ?v0 ?v1 ?v2], 
        :where 
        ([?e ?a ?v0] 
         [?e ?a ?v1]
         [?e ?a ?v2])}
    
    
    ;; executing the query: note that we're passing the attribute and values!
    (apply d/q (q-find-having-all-vs (count groups))
      db :entry/group groups)
    

    直接使用索引

    我完全不确定上述方法在 Datomic Datalog 的当前实现中的效率如何。如果您的基准测试显示这很慢,您总是可以退回到直接索引访问。

    以下是 Clojure 中使用 AVET 索引的示例:

    (defn find-having-all-vs
      "Given a database value `db`, an attribute identifier `a` and a non-empty seq of entity identifiers `vs`,
      returns a set of entity identifiers for entities which have all the values in `vs` via `a`"
      [db a vs]
      ;; DISCLAIMER: a LOT can be done to improve the efficiency of this code! 
      (apply clojure.set/intersection 
        (for [v vs]
          (into #{} 
            (map :e)
            (d/datoms db :avet a v)))))
    

    【讨论】:

    • 感谢您如此详尽的回答!两个问题:1)如果查询计划缓存不是必需的,或者它被认为是一种不好的做法并导致一些(缓存除外)严重缺陷,是否可以动态生成 Datomic 查询? 2)你如何在你的数据库函数中调用EAVT索引?顺便说一句,我将 Clojure 与 Datomic 一起使用,所以我想我需要致电 datoms function,对吧?
    【解决方案2】:

    您可以从 Tupelo-Datomic 库中查看此 in the James Bond example 的示例。您只需指定 2 个子句,一个用于集合中的每个所需值:

    ; Search for people that match both {:weapon/type :weapon/guile} and {:weapon/type :weapon/gun}
    (let [tuple-set   (td/find :let    [$ (live-db)]
                               :find   [?name]
                               :where  {:person/name ?name :weapon/type :weapon/guile }
                                       {:person/name ?name :weapon/type :weapon/gun } ) ]
      (is (= #{["Dr No"] ["M"]} tuple-set )))
    

    在纯 Datomic 中,它看起来很相似,但使用的是实体 ID:

    [?eid :entry/groups 2]
    [?eid :entry/groups 3]
    

    并且 Datomic 将执行隐式 AND 操作(即两个子句必须匹配;任何多余的条目都将被忽略)。这在逻辑上是一个“连接”操作,即使它是针对两个值查询的同一实体。您可以找到更多信息in the Datomic docs

    【讨论】:

    • 对,所以唯一的方法是使用我的外国 id 中所需的 where 子句动态创建查询?我不能像:in $ ... 一样将它们用作查询的输入?
    • @Twice_Twice 动态创建查询并不是唯一的方法,请看下面我的回答
    猜你喜欢
    • 2014-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-15
    • 2021-11-13
    • 1970-01-01
    • 2021-11-24
    • 1970-01-01
    相关资源
    最近更新 更多