Chapter Three: Entity types and generalization
Places: identification, abstract supertypes and a first relation
Places
The easiest columns to model are the places: the country, the province and the kabupaten (district) the palm oil comes from, and the country it is imported into. Stated as facts:
-
Each country is identified by its name. The same holds for provinces and kabupaten.
-
Countries, provinces and kabupaten are all places, and every place is one of the three.
Three kinds of place, each identified by its name (underlined), under an abstract supertype place (dashed).
#!test[schema]
define
attribute name, value string;
attribute country_name, sub name;
attribute province_name, sub name;
attribute kabupaten_name, sub name;
entity place @abstract;
entity country,
sub place,
owns country_name @key;
entity province,
sub place,
owns province_name @key;
entity kabupaten,
sub place,
owns kabupaten_name @key;
Reading it line by line:
-
attribute name, value string;declares an attribute type and its value type. -
attribute country_name, sub name;declares a subtype ofname. The three name types are distinct types, so a country’s name cannot be mistaken for a province’s, but a query fornamefinds all of them. ER notation has no way to draw this, so the diagram leaves it out. -
entity place @abstract;declares a type that cannot have instances of its own. -
entity country, sub place, owns country_name @key;declares a subtype ofplacethat owns exactly onecountry_name, which no other country shares.
Keys are identification schemes
@key combines two constraints: every country owns exactly one country_name, and no two countries own the same one.
It is the TypeQL form of a preferred identification scheme.
A type can own several keys, each of which identifies its instances by itself.
Composite keys are not yet available, and are on the TypeDB roadmap.
TypeDB does not use keys to identify instances internally: every instance has an internal identity regardless. A key is a constraint on the data, not the instance’s identity. So types without a natural identifier, such as mills in the next chapter, need no key at all.
The generalization is disjoint and total
Two properties follow without any extra constraints:
-
Disjoint. Every instance has exactly one most-specific type, so nothing can be both a country and a province.
-
Total.
placeis abstract, so every place must be a country, a province or a kabupaten.
Try inserting a bare place:
#!test[write, fail_at=runtime]
insert $p isa place;
The query is rejected before it runs: no type can be inferred for $p, because place is abstract and cannot be instantiated directly.
Places contain places
The places nest: a kabupaten lies in a province, which lies in a country. Stated as facts:
-
A containment relates exactly one parent place to exactly one child place.
-
A country can be a parent.
-
A province can be a parent, and is the child of at most one place.
-
A kabupaten is the child of at most one place.
One relation type, containment, drawn once for each pair of types it connects.
A province plays both of its roles.
#!test[schema]
define
relation containment,
relates parent @card(1),
relates child @card(1);
entity country,
plays containment:parent;
entity province,
plays containment:child @card(0..1), # at most one parent
plays containment:parent;
entity kabupaten,
plays containment:child @card(0..1); # at most one parent
-
relation containment, relates parent, relates childdeclares a relation type and its two roles. -
plays containment:childdeclares which types can fill a role. -
@card(1)on arelatesconstrains how many role players fill that role in each containment: exactly one. -
@card(0..1)on aplaysconstrains how many containments each player plays that role in: at most one.
Running define on a type that already exists, such as country here, adds to its definition.
|
Why not a parent attribute?
In a relational design, containment would typically be a |
Open the Schema page in Studio to see the schema so far.
Challenge
In practice, some administrative areas are redrawn, and a district can for a time be recorded under two provinces. How would you change the model to allow this, and what would you lose?
Answer
Relax the cardinality of the child role.
Because the role already has a cardinality, use redefine rather than define (don’t run this if you are following along, as the rest of the course assumes the original model):
#!test[schema]
redefine
entity kabupaten, plays containment:child @card(0..2);
Use @card(0..) for no upper bound.
You lose the guarantee that the containment hierarchy is a tree, which every query that walks up from a kabupaten to its country currently relies on.