Okay correction - I spoke too soon(!) - thanks to help from @jarohen I can present the “correlated not exists” workaround:
(->
(unify
(join (->
(unify
(rel [{:o-id "o2"} {:o-id "o3"}] [o-id])
(from :ts [{:xt/id t-id} o-ids])
(unnest {o-id o-ids}))
(aggregate {:matched-t-ids (array-agg t-id)}))
[matched-t-ids])
(from :vs [{:xt/id vpid} {:t-ids vp-t-ids}])
(where (not (exists? (->
(rel [{}] [])
(unnest {:matched-t-id $matched-t-ids})
(where (not (exists? (->
(rel [{}] [])
(unnest {:vp-t-id $vp-t-ids})
(where (= $matched-t-id vp-t-id)))
{:args [matched-t-id {:vp-t-ids $vp-t-ids}]}))))
{:args [matched-t-ids vp-t-ids]}))))
(return vpid))
Given the data:
[:put-docs :ts
{:xt/id "t1" :o-ids ["o1" "o2"]}
{:xt/id "t2" :o-ids ["o2" "o3"]}
{:xt/id "t3" :o-ids ["o4" "o4"]}
{:xt/id "t4" :o-ids ["o1" "o5"]}
{:xt/id "t5" :o-ids ["o9" "o8"]}]
[:put-docs :vs
{:xt/id "v1" :t-ids ["t1" "t4"]}
{:xt/id "v2" :t-ids ["t1" "t2" "t3"]}
{:xt/id "v3" :t-ids ["t1" "t3"]}
{:xt/id "v4" :t-ids ["t1" "t4" "t2"]}]
Produces:
{:vpid "v2"}
{:vpid "v4"}
You can test it out on the highly experimental XT fiddle: https://fiddle.xtdb.com/
<@
or equivalent would undoubtedly be nicer, but hopefully that workaround at least demonstrates the compositional powers of XTQL.