查找其 ref-to-many 属性包含输入的所有元素的实体
Find entities whose ref-to-many attribute contains all elements of input
假设我的实体 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 中的新手,我用尽了所有选项,因此非常感谢您的帮助。谢谢!
您可以在 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
操作(即两个子句必须匹配;任何多余的条目都将被忽略)。这在逻辑上是一个 "join" 操作,即使它是为两个值查询的同一实体。您可以找到更多信息 in the Datomic docs。
TL;DR
您正在解决 Datomic 的 Datalog 中 'dynamic conjunction' 的一般问题。
此处有 3 个策略:
- 编写一个使用 2 个否定和 1 个析取或递归规则的动态 Datalog 查询(见下文)
- 生成查询代码(相当于 Alan Thompson 的回答):缺点是动态生成 Datalog 子句的常见缺点,即您不会从 query plan caching.
中受益
- 直接使用 indexes(EAVT 或 AVET)。
动态数据日志查询
Datalog 没有直接表达动态连接的方式(逻辑与/'for all ...'/集合交集)。但是,您可以通过组合一个析取(逻辑或/'exists ...'/集合并集)和两个否定,即 (For all ?g in ?Gs p(?e,?g)) <=> NOT(Exists ?g in ?Gs, such that NOT(p(?e, ?g)))
在纯 Datalog 中实现它
在您的情况下,这可以表示为:
[: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]))]
好消息:这可以表示为一个非常通用的数据记录规则(我最终可能会添加到 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)))))
假设我的实体 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 中的新手,我用尽了所有选项,因此非常感谢您的帮助。谢谢!
您可以在 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
操作(即两个子句必须匹配;任何多余的条目都将被忽略)。这在逻辑上是一个 "join" 操作,即使它是为两个值查询的同一实体。您可以找到更多信息 in the Datomic docs。
TL;DR
您正在解决 Datomic 的 Datalog 中 'dynamic conjunction' 的一般问题。
此处有 3 个策略:
- 编写一个使用 2 个否定和 1 个析取或递归规则的动态 Datalog 查询(见下文)
- 生成查询代码(相当于 Alan Thompson 的回答):缺点是动态生成 Datalog 子句的常见缺点,即您不会从 query plan caching. 中受益
- 直接使用 indexes(EAVT 或 AVET)。
动态数据日志查询
Datalog 没有直接表达动态连接的方式(逻辑与/'for all ...'/集合交集)。但是,您可以通过组合一个析取(逻辑或/'exists ...'/集合并集)和两个否定,即 (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]))]
好消息:这可以表示为一个非常通用的数据记录规则(我最终可能会添加到 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)))))