Querying Basics

Before we continue, let's take a closer look at the data model powering our Pokemon database.

Each Pokemon is stored as an Entity. Here is what Bulbasaur looks like:

{:stat/hp 45, :stat/attack 49, :stat/speed 45, :pokemon/number "001", :stat/sp-defense 65, :stat/defense 49, :pokemon/name "Bulbasaur", :stat/sp-attack 65, :pokemon/type ["Grass" "Poison"]}

Each key is an Attribute (:pokemon/name, :stat/speed, etc) and each value is the Value for that attribute on this entity (Bulbasaur, 45, etc).

In Datomic, every entity is assigned a unique entity ID. Let's find Bulbasaur's:

[:find ?entity-id :where [?entity-id :pokemon/name "Bulbasaur"]]

Now that we have the concept of an entity ID, we can flip the query around and ask: what Attributes does this entity have? We bind the attribute position to a logic variable, then resolve its human-readable name via :db/ident:

[:find ?attribute :where [?entity-id :pokemon/name "Bulbasaur"] [?entity-id ?attribute-id _] [?attribute-id :db/ident ?attribute]]

The underscore _ in the third position is a wildcard, it matches any value but does not bind it to a variable.

Finally, we can pull all attribute-value pairs at once by binding the value position too:

[:find ?attribute ?value :where [?entity-id :pokemon/name "Bulbasaur"] [?entity-id ?attribute-id ?value] [?attribute-id :db/ident ?attribute]]

This works because of Datomic's Universal Schema. Every fact in the database is stored following this Entity-Attribute-Value structure. Attributes are themselves entities, which is why we can traverse from ?attribute-id to :db/ident to get the keyword name.

Try changing "Bulbasaur" to another Pokemon name in the queries above.

Take a look at the EDN file defining all of the Pokemon in our database.

Run the last query with a dual-typed Pokemon like "Charizard" and observe how many attribute-value rows appear — one for each fact stored about that entity.

Previous | Next