Datalog help: Only if every matched item

Hi All!

I was hoping to have some help making a xtdb/datalog query easier to read.

The goal is to get a list of users for whom all transactions were successful. I am able to model this with subqueries, comparing a user’s transactions to a user’s successful transactions. This looks like this:

(require '[xtdb.api :as xt])

;; create a few entities
(def entities [{:xt/id "transaction-1"
                :user/id "user-1"
                :transaction/amount 300
                :transaction/success true}
               {:xt/id "transaction-2"
                :user/id "user-1"
                :transaction/amount 200
                :transaction/success true}
               {:xt/id "transaction-3"
                :user/id "user-2"
                :transaction/amount 100
                :transaction/success false}
               {:xt/id "transaction-4"
                :user/id "user-2"
                :transaction/amount 100
                :transaction/success true}])


(defonce node (xt/start-node {}))

(xt/submit-tx node (map (fn [doc] [::xt/put doc])
                        entities))

;; find the users for which all transactions were successful
(xt/q (xt/db node) '{:find [user-id]
                     :where [[transaction :user/id user-id]
                             [(q {:find [transaction]
                                  :in [user-id]
                                  :where [[transaction :user/id user-id]]}
                                 user-id)
                              all-transactions]
                             [(q {:find [transaction]
                                  :in [user-id]
                                  :where [[transaction :user/id user-id]
                                          [transaction :transaction/success true]]}
                                 user-id)
                              successful-transactions]
                             [(= all-transactions successful-transactions)]]})

For which the results are:

 #{["user-1"]} 

Is there a cleaner way to do this? Thanks in advance for any help!

1 Like

Hey @nikonikoniko - interesting problem :slight_smile:

I think this version should be close to optimal:

{:find [user-id]
 :where [[_ :user/id user-id]
         [(q {:find [transaction]
              :in [user-id]
              :limit 1
              :where [[transaction :user/id user-id]
                      [transaction :transaction/success false]]}
             user-id)
          unsuccessful-transactions]
         [(empty? unsuccessful-transactions)]]}

Thank you!

This seems obvious in retrospect.

1 Like