trydatomic

Chapters

Cardinality Many

Most attributes hold a single value per entity: :db.cardinality/one. But :pokemon/type is declared :db.cardinality/many in Modeling data, meaning an entity can have multiple values for the same attribute.

The consequence for queries is important: binding a cardinality/many attribute produces one result row per value. Let's see it in action. Run this query and notice how Bulbasaur appears twice:

Query
[:find ?name ?type :where [?e :pokemon/name ?name] [?e :pokemon/type ?type] [(= ?name "Bulbasaur")]]

Bulbasaur has two type values (Grass and Poison) so it produces two rows. A pure Fire Pokemon like Charmander produces only one.

Finding dual-type Pokemon

We can use the multi-row behaviour to our advantage. By joining :pokemon/type to itself under two different variable names, we get all pairs of types on the same entity. Filtering with not= keeps only the pairs where the two values differ, that is, entities that carry at least two distinct types:

Query
[:find ?name :where [?e :pokemon/name ?name] [?e :pokemon/type ?type1] [?e :pokemon/type ?type2] [(not= ?type1 ?type2)]]

Because :find removes duplicate rows, each name appears only once even though many (?type1, ?type2) combinations matched.

Counting values with aggregation

We can count how many type values each Pokemon has by using the count aggregate in :find. Aggregates get their own chapter later. For now, just observe that count collapses all the type rows for one entity into a single number:

Query
[:find ?name (count ?type) :where [?e :pokemon/name ?name] [?e :pokemon/type ?type]]

Pokemon with a count of 2 are dual-typed. Those with 1 are single-typed.

TRY

Modify the dual-type query to also return ?type1 and ?type2 in :find to see which type combinations appear. Or change "Bulbasaur" in the first query to "Charmander" and compare the number of rows.

You can now
  • Explain why a cardinality-many attribute produces one result row per value.
  • Join an attribute to itself to find entities that have more than one distinct value for it.
  • Use count to collapse those per-value rows into a single number per entity.