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:
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:
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.
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:
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:
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.
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].
- Exclude rows with
not, and know every variable inside it must already be bound outside. - Use
not-joinwhen the negation needs a variable of its own, like anot=check on a self-joined attribute. - Use
missing?to keep only the entities that lack a given attribute entirely.