trydatomic

Chapters

Negation

Sometimes you want to exclude results rather than include them. Datomic provides three tools for this: not, not-join, and missing?.

not

not excludes any entity for which the inner pattern matches. Variables already bound in the outer query are automatically joined, but that's the only kind of variable a plain not can use. Every variable inside it must already be bound outside; there's no way to introduce a new one that stays local to the negation. Let's find Pokemon that are not Grass type:

Query
[:find ?name :where [?e :pokemon/name ?name] (not [?e :pokemon/type "Grass"])]

Because :pokemon/type is :db.cardinality/many, this excludes Pokemon that have any Grass entry, so Bulbasaur (Grass/Poison) is excluded. Try changing "Grass" to "Fire" or "Water".

not-join

Here we want to find single-type Pokemon: those for which there is no second, different type value. It reuses the not= comparison from Cardinality many. The check needs ?other, a variable that only makes sense inside the negation:

Query
[:find ?name ?type :where [?e :pokemon/name ?name] [?e :pokemon/type ?type] (not-join [?e ?type] [?e :pokemon/type ?other] [(not= ?type ?other)])]

For each ?name/?type pair, not-join checks whether a different type value ?other exists on the same entity. If it does, the row is dropped, leaving only single-type Pokemon.

TRY

Before running the next thing, predict: change not-join [?e ?type] above to plain not and run it. Datomic refuses: ?other not bound in not clause. Every variable inside a plain not must already be bound outside it: ?other is new, so there's nowhere for it to come from. not-join solves exactly this problem: it lets you introduce fresh variables inside the negation, as long as you declare explicitly which outer variables to join.

missing?

missing? checks that an entity has no value for a given attribute. Its arguments are the database binding $, the entity variable, and the attribute keyword.

In our Pokemon database, only a few have a :pokemon/category, with the value :legendary:

Query
[:find ?name :where [?e :pokemon/category :legendary] [?e :pokemon/name ?name]]

In Datomic any entity can have any attribute, so for every other Pokemon that fact simply doesn't exist: they are missing the attribute. Where get-else in Function clauses kept those Pokemon and gave them a default, missing? keeps only them. Let's find all non-legendary Pokemon:

Query
[:find ?name :where [?e :pokemon/name ?name] [(missing? $ ?e :pokemon/category)]]

This returns all 147 Pokemon that lack the :pokemon/category attribute. Try adding a stat like [?e :stat/hp ?hp] and including ?hp in :find. missing? only filters; the rest of the query works normally.

TRY

Modify the not query at the top to exclude a different type, like "Ice" or "Dragon". Or flip the missing? demo to find legendary Pokemon by replacing missing? with a data pattern [?e :pokemon/category :legendary].

You can now
  • Exclude rows with not, and know every variable inside it must already be bound outside.
  • Use not-join when the negation needs a variable of its own, like a not= check on a self-joined attribute.
  • Use missing? to keep only the entities that lack a given attribute entirely.