datascript

2024-12-20T16:57:10.004419Z

I might have just confused myself, but I thought I remembered this working differently in datomic. How to return all items in a db with an attribute not in a specific value passed as an argument?

(let [db (-> (ds/empty-db)
               (ds/db-with [{:db/id 1 :item/state :active} 
                            {:db/id 2 :item/state :inactive}]))]
    
    ;; works. returns [2]
    (ds/q '[:find [?e ...] 
            :in $ [?state ...] 
            :where
            [?e :item/state]
            (not [?e :item/state ?state])] 
      db
      [:active])


    ; doesn't work, returns [2 1]
    (ds/q '[:find [?e ...] 
            :in $ [?state ...] 
            :where
            [?e :item/state]
            (not [?e :item/state ?state])] 
      db
      [:active :inactive])


    )

đź‘€ 1
Niki 2024-12-21T16:48:14.770939Z

No, Datomic works the same. I wonder why :)

@(d/transact conn 
   [{:db/id "1" :name "Ivan"} 
    {:db/id "2" :name "Oleg"} 
    {:db/id "3" :name "Petr"}])

(d/q '[:find ?e ?n
       :in $ [?name ...] 
       :where
       [?e :name ?n]
       (not [?e :name ?name])] 
  (d/db conn)
  ["Ivan" "Oleg"])
; => #{[17592186045420 "Petr"] [17592186045419 "Oleg"] [17592186045418 "Ivan"]}

Niki 2024-12-21T16:52:35.350509Z

I guess the answer is that it’s interpreted as “not A OR not B”, when it should be “not A AND not B”

Niki 2024-12-21T16:57:01.384859Z

Asked in #datomic https://clojurians.slack.com/archives/C03RZMDSH/p1734800206703189

2024-12-21T16:57:09.370689Z

Ah, yeah, thanks for checking that. And now that you mention it, it does seem like correct behavior https://docs.datomic.com/query/query-data-reference.html#collection-binding > A relation binding is fully general, binding multiple variables positionally to a relation (collection of tuples) passed in. This can be used to ask "or" questions involving variables in the relation binding.

Niki 2024-12-21T16:58:45.966979Z

Not really, becasue OR works:

(d/q '[:find ?e ?n
       :where
       [?e :name ?n]
       (not
         (or
           [?e :name "Ivan"]
           [?e :name "Oleg"]))] 
  (d/db conn))
  
; => #{[17592186045420 "Petr"]}

Niki 2024-12-21T17:00:31.431629Z

As does this:

@(d/transact conn
   [{:db/id "1" :exclude "Ivan"} 
    {:db/id "2" :exclude "Oleg"}])

(d/q '[:find ?e ?n
       :where
       [?e :name ?n]
       (not-join [?e]
         [_ :exclude ?name]
         [?e :name ?name])] 
  (d/db conn))
  
; => #{[17592186045420 "Petr"]}

Niki 2024-12-21T17:00:46.443269Z

So it only doesn’t work if it comes from collection binding

2024-12-21T17:04:49.851559Z

My first interpretation of that quoted line was "run the whole query for each variable passed and return the union of the results", but it's kind of unclear

Niki 2025-01-05T19:04:57.838549Z

Ok I think I have an explanation. Right now ?e and ?state are not connected in your query. So it’s not “find entities that have state”, it’s more like for each possible entity, for each possible state. [?e :item/state] gives you (:inactive 1) (:active 1) (:inactive 2) (:active 2) Then negation does connect them. In particular,

(not [?e :item/state ?state])
gives you (:active 1) (:inactive 2) If you subtract second from the first, you get (:active 2) (:inactive 1) In other words, change [?e :item/state] to [?e :item/state ?state] and you’ll get what you wanted

2025-01-07T19:45:20.453169Z

That all makes sense, and I think is the cause of the problem. Except that I wasn't able to get this working: > In other words, change [?e :item/state] to [?e :item/state ?state] and you’ll get what you wanted Or any of a few variations on that that I've tried (e.g. using a separate input var). Please don't spend any more time on it on my behalf though 🙂 I've since just done the filtering outside of a query and that works.