Search code examples
clojuredatomic

Datomic not returning the correct "min" result when retrieving entity ID in result tuple


I've got this simple schema and data:

(def product-offer-schema
  [{:db/ident :product-offer/product
    :db/valueType :db.type/ref
    :db/cardinality :db.cardinality/one}
   {:db/ident :product-offer/vendor
    :db/valueType :db.type/ref
    :db/cardinality :db.cardinality/one}
   {:db/ident :product-offer/price
    :db/valueType :db.type/long
    :db/cardinality :db.cardinality/one}
   {:db/ident :product-offer/stock-quantity
    :db/valueType :db.type/long
    :db/cardinality :db.cardinality/one}
  ])
(d/transact conn product-offer-schema)

(d/transact conn
  [{:db/ident :vendor/Alice}
   {:db/ident :vendor/Bob}
   {:db/ident :product/BunnyBoots}
   {:db/ident :product/Gum}
  ])
(d/transact conn
  [{:product-offer/vendor  :vendor/Alice
    :product-offer/product :product/BunnyBoots
    :product-offer/price   9981 ;; $99.81
    :product-offer/stock-quantity 78
   }

   {:product-offer/vendor  :vendor/Alice
    :product-offer/product :product/Gum
    :product-offer/price   200 ;; $2.00
    :product-offer/stock-quantity 500
   }

   {:product-offer/vendor  :vendor/Bob
    :product-offer/product :product/BunnyBoots
    :product-offer/price   9000 ;; $90.00
    :product-offer/stock-quantity 15
   }
  ])

When I retrieve the cheapest bunny boots, only retrieving the price, I get the expected result (9000):

(def cheapest-boots-q '[:find (min ?p) .
                        :where
                        [?e :product-offer/product :product/BunnyBoots]
                        [?e :product-offer/price ?p]
                       ])
(d/q cheapest-boots-q db)
;; => 9000

However, when I want to get the entity ID along with the price, it gives me the higher-priced boots:

(def db (d/db conn))
(def cheapest-boots-q '[:find [?e (min ?p)]
                        :where
                        [?e :product-offer/product :product/BunnyBoots]
                        [?e :product-offer/price ?p]
                       ])
(d/q cheapest-boots-q db)
;; => [17592186045423 9981]

I tried adding :with but that gives me an error:

(def cheapest-boots-q '[:find [?e (min ?p)]
                        :with ?e
                        :where
                        [?e :product-offer/product :product/BunnyBoots]
                        [?e :product-offer/price ?p]
                       ])
(d/q cheapest-boots-q db)
;; => =>  Execution error (ArrayIndexOutOfBoundsException) at datomic.datalog/fn$project (datalog.clj:503).

What am I doing wrong?


Solution

  • As a commenter kind of pointed out, ?e isn't bound in any way to the (min ?p) expression, so it's not defined what you'll get there, beyond a product entity id of some sort.

    What you actually want to do is unify those values somehow as part of the query, and not perform aggregation on the results, for example:

    (d/q '[:find [?e ?p]
           :where
           [?e :product-offer/product :product/BunnyBoots]
           [?e :product-offer/price ?p]
           [(min ?p)]] 
         db)
    

    You can see that the min clause is part of the query, and as such will take part in the unification on the result, giving you what you want.