Officially out now: The TypeDB 3.0 Roadmap >>

TypeDB Learning Center

Modeling Inheritance in the Database


Inheritance hierarchies are one of the cornerstones of object-oriented programming (OOP). However, they are one of the most challenging data structures to model in a database. This resulting mismatch between the application and database models leads to a range of industry-spanning problems, not limited to a single database family.

In this article, we’ll have a look at how to easily model type hierarchies using TypeDB. Due to its declarative schemas and strong type system, TypeDB is built with inheritance and polymorphism as core features. We’ll see how to model hierarchies of entities, relations, and attributes, how capabilities of supertypes are inherited by their subtypes, and how we can query supertypes to retrieve data instances of all corresponding subtypes.

For this exercise, we’ll use the example of a social media graph that captures a number of facts about different entities:

  • Pages of different types: personal profiles, organisation profiles of various kinds, and groups.
  • Posts of different types: text posts, image posts, video posts, live video posts, poll posts, and share posts.
  • The different ways that content can be engaged with: posting, sharing, commenting, reacting, and responding to polls.

Entity hierarchies

Let’s begin by thinking about pages. When a user posts content to the social network, it’s posted on a particular page: their own profile, someone else’s profile, or a group they’re a member of. As such, it seems we have a natural hierarchy, whereby profiles and groups are both kinds of pages. Next, we’ll consider what we mean by a profile, which could be either a user’s personal profile or an organisation’s profile. This gives us a third level to our type hierarchy. Finally, we consider the different types of organisations that can exist: companies, charities, schools, colleges, and universities. We’ll model this with a further two levels: companies, charities, and educational institutes at the fourth level, and schools, colleges, and universities at the fifth.

Here we have use a dotted outline for page and profile. This is because these types are abstract, and so not directly instantiable. Now that we have defined the conceptual entity hierarchy for page types, we can go about implementing it in TypeDB. To define a new type, we use the sub keyword. This defines the new type to be a subtype of an existing type: either one of the three root types (entity, relation, and attribute) or a type we have previously defined. By making use of the latter, we can easily define type hierarchies. In the following code snippet, we define the hierarchy of pages. To make a type abstract, we simply use the abstract keyword.

define
  page sub entity, abstract;
  profile sub page, abstract;
  group sub page;
  person sub profile;
  organisation sub profile;
  company sub organisation;
  charity sub organisation;
  educational-institute sub organisation;
  school sub educational-institute;
  college sub educational-institute;
  university sub educational-institute;

Inheriting attributes from supertypes

With these page types defined, we can now give them some attributes. To give ownership of an attribute to an entity or relation type, we use the owns keyword, as we do in the following code snippet.

define
  page owns name,
    owns bio,
    owns profile-picture;
  profile owns username @key;
  group owns tag;
  organisation owns tag;
  name sub attribute, value string;
  bio sub attribute, value string;
  profile-picture sub attribute, value string;
  username sub attribute, value string;
  tag sub attribute, value string;

Capabilities of types are automatically inherited by their subtypes. As a result, all the subtypes of page also own name, bio, and profile-picture, all the subtypes of profile also own username, and all the subtypes of organisation also own tag. We can now insert data according to these ownerships. In these examples, we’ll store references to media objects (images and videos) as URIs.

insert
  $mario isa person,
    has username "@MarioSantos",
    has name "Mario Santos",
    has bio "Hey there! I'm Mario, a strategist with a passion for the art of planning and problem-solving. When I'm not meticulously organizing elaborate simulations, you can find me diving into a good mystery novel or solving complex puzzles. I love a good game of chess, where strategy and foresight are key—much like in life.",
    has profile-picture "X1fH-ZM9TBrKrmGsNmjQ8mT3OA94Hhbl";
insert
  $kg-group isa group,
    has name "Knowledge Graphs",
    has bio "Welcome to the Knowledge Graphs group! We are a community of data lovers, researchers, and tech aficionados passionate about the world of knowledge graphs. Our mission is to explore, share, and innovate in the realm of connected data, transforming isolated information into insightful networks.",
    has profile-picture "Za_QFPiyCEs5lkO-nMLpQAK4lXOELxyx",
    has tag "#KnowledgeGraphs",
    has tag "#SemanticWeb",
    has tag "#GraphDatabases",
    has tag "#LinkedData",
    has tag "#DataScience",
    has tag "#ArtificialIntelligence",
    has tag "#DataIntegration",
    has tag "#MachineLearning",
    has tag "#Ontology",
    has tag "#DataVisualization";

Despite not having declared that person and group own name, bio, and profile-picture, we can insert these attributes on these entities because they inherit the ownership capability from their supertype page. We can also insert tags on groups because we have directly declared that group owns tag. In constrast, we could not insert tags on people, because person neither directly owns nor inherits ownership of tag. The inheritance of capabilities is automatically handled by TypeDB’s strong type system. If we attempted to insert data that did not match the schema, the database would throw an exception.

Querying entity hierarchies

With this data inserted, we can easily query the data via the common supertypes! TypeQL works through declarative pattern matching. When we declare the type of a variable with the isa keyword in a read query, the variable will match instances of both the declared type and its subtypes. For example, the following query will retrieve the names and bios of all pages, regardless of whether they represent people, organisations, or groups.

match
  $page isa page,
    has name $name,
    has bio $bio;
fetch
  $name;
  $bio;
{
    "name": { "value": "Knowledge Graphs", "type": { "label": "name", "root": "attribute", "value_type": "string" } },
    "bio": { "value": "Welcome to the Knowledge Graphs group! We are a community of data lovers, researchers, and tech aficionados passionate about the world of knowledge graphs. Our mission is to explore, share, and innovate in the realm of connected data, transforming isolated information into insightful networks.", "type": { "label": "bio", "root": "attribute", "value_type": "string" } }
}
{
    "name": { "value": "Mario Santos", "type": { "label": "name", "root": "attribute", "value_type": "string" } },
    "bio": { "value": "Hey there! I'm Mario, a strategist with a passion for the art of planning and problem-solving. When I'm not meticulously organizing elaborate simulations, you can find me diving into a good mystery novel or solving complex puzzles. I love a good game of chess, where strategy and foresight are key—much like in life.", "type": { "label": "bio", "root": "attribute", "value_type": "string" } }
}

Defining further hierarchies

We can define as many type hierarchies as our schema needs. Next, we’ll define a hierarchy of posts along with some more attributes. We’ll define six concrete types of post, based on the format of the post’s content:

  • Text posts have a body text only.
  • Image posts have an image and accompanying text.
  • Both video posts and live video posts have a video and accompanying text.
  • Poll posts have a question, one or more answers, and accompanying text.
  • Share posts have another post embedded, and accompanying text.

Additionally, all posts have an ID and a number of tags. As all kinds of post have a body text, we’ll make this attribute a property of the abstract supertype too.

define
  post sub entity, abstract,
    owns id @key,
    owns post-text,
    owns tag;
  text-post sub post;
  image-post sub post,
    owns post-image;
  video-post sub post,
    owns post-video;
  live-video-post sub video-post;
  poll-post sub post,
    owns question,
    owns answer;
  share-post sub post;

id sub attribute, value long;
post-text sub attribute, value string;
post-image sub attribute, value string;
post-video sub attribute, value string;
question sub attribute, value string;
answer sub attribute, value string;

While the embedded content for image, video, and poll posts is stored via attributes, we need a relation to store the reference between a share post and the embedded post it shares.

Defining relations between supertypes

When a user writes a post, it is published on a particular page. This gives us six different combinations of profiles that can publish posts and the pages they can be posted on, assuming they have the necessary permissions:

  • A person posting to a person’s profile (theirs or someone else’s).
  • A person posting to an organisation’s profile.
  • A person posting to a group.
  • An organisation posting to a person’s profile.
  • An organisation posting to an organisation’s profile (theirs or someone else’s).
  • An organisation posting to a group.

When we combine this with the six different kinds of posts as defined above, there are thirty-six possible combinations of poster, page, and post type! In our conceptual model, we could easily express this by using a ternary relation between the abstract supertypes profile, page, and post respectively, which we’ll calls posting. We’ll also introduce a second sharing relation to represent the embedding of one post in another post.

This conceptual posting relation with thirty-six possible variants is just as easy to express in TypeDB. We can define it in our schema using the relates keyword to define new roles, which we define to be played by our entity types using the plays keyword. We’ll similarly define the sharing relation.

define
  posting sub relation,
    relates author,
    relates page,
    relates post;
  sharing sub relation,
    relates author,
    relates original-post,
    relates share-post;
  profile plays posting:author,
     plays sharing:author;
  page plays posting:page;
  post plays posting:post,
    plays sharing:original-post;
  share-post plays sharing:share-post;

We saw earlier that owns statements are inherited down type hierarchies, and the same is true of plays statements. Now we can insert some posting and sharing relations with any permitted combination of roleplayer types. Once again, the type system enforces semantic correctness.

match
  $mario isa person, has username "@MarioSantos";
  $kg-group isa group, has name "Knowledge Graphs";
insert
  $post isa video-post,
    has id 1,
    has post-text "Hey everyone! I put together a quick tutorial on how to build a basic knowledge graph using open-source tools. It’s a step-by-step guide perfect for beginners. Let me know if you have any questions or need further clarification!",
    has post-video "eH8ilqtekYJEB1J5_TkPo-QyFcIoCVvQ",
    has tag "#Tutorial",
    has tag "#LinkedData",
    has tag "#OpenSource";
  (author: $mario, page: $kg-group, post: $post) isa posting;
match
  $mario isa person, has username "@MarioSantos";
  $kg-group isa group, has name "Knowledge Graphs";
insert
  $post isa poll-post,
    has id 2,
    has post-text "I'm curious to know which tools you all prefer for creating knowledge graphs. Let's see what the community thinks!",
    has question "Favorite tool for creating knowledge graphs?",
    has answer "TypeDB",
    has answer "Neo4j",
    has answer "GraphDB",
    has answer "RDF4J",
    has answer "Other (comment below)",
    has tag "#Poll",
    has tag "#GraphTools";
  (author: $mario, page: $kg-group, post: $post) isa posting;
match
  $mario isa person, has username "@MarioSantos";
  $original-post isa post, has id 1;
insert
  $post isa share-post,
    has id 3,
    has post-text "Hey friends! I've put together a step-by-step video tutorial perfect for anyone looking to dive into the world of knowledge graphs. If you've ever been curious about how to visualize complex data relationships and enhance your data analysis, this is for you.",
    has tag "#KnowledgeGraphs",
    has tag "#DataScience",
    has tag "#Tutorial",
    has tag "#JoinTheCommunity";
  (author: $mario, page: $mario, post: $post) isa posting;
  (author: $mario, original-post: $original-post, share-post: $post) isa sharing;

We can then query the text of all posts of any kind by querying the three supertypes profile, page, and post, as in the following query, where we retrieve the details of any post that has the “#Tutorial” tag.

match
  $author isa profile, has username $username;
  $page isa page, has name $page-name;
  $post isa post,
    has tag "#Tutorial",
    has post-text $post-text;
  (author: $author, page: $page, post: $post) isa posting;
fetch
  $username;
  $page-name;
  $post-text;
{
    "username": { "value": "@MarioSantos", "type": { "label": "username", "root": "attribute", "value_type": "string" } },
    "page-name": { "value": "Knowledge Graphs", "type": { "label": "name", "root": "attribute", "value_type": "string" } },
    "post-text": { "value": "Hey everyone! I put together a quick tutorial on how to build a basic knowledge graph using open-source tools. It’s a step-by-step guide perfect for beginners. Let me know if you have any questions or need further clarification!", "type": { "label": "post-text", "root": "attribute", "value_type": "string" } }
}
{
    "username": { "value": "@MarioSantos", "type": { "label": "username", "root": "attribute", "value_type": "string" } },
    "page-name": { "value": "Mario Santos", "type": { "label": "name", "root": "attribute", "value_type": "string" } },
    "post-text": { "value": "Hey friends! I've put together a step-by-step video tutorial perfect for anyone looking to dive into the world of knowledge graphs. If you've ever been curious about how to visualize complex data relationships and enhance your data analysis, this is for you.", "type": { "label": "post-text", "root": "attribute", "value_type": "string" } }
}

Attribute hierarchies

What if we want to retrieve all content associated with these tutorial posts, instead of just the post text? We can achieve this by reorganising our attributes into a hierarchy, which will extend an abstract payload attribute type.

define
  payload sub attribute, abstract, value string;
  text-payload sub payload, abstract;
  media-payload sub payload, abstract;
  image-payload sub payload, abstract;
  video-payload sub payload, abstract;
  bio sub text-payload;
  post-text sub text-payload;
  question sub text-payload;
  answer sub text-payload;
  post-image sub image-payload;
  profile-picture sub image-payload;
  post-video sub video-payload;

Now we can modify our previous query to leverage this hierarchy, this time retrieving all payloads for tutorial posts instead of just the body texts.

match
  $author isa profile, has name $username;
  $page isa page, has name $page-name;
  $post isa post, has tag "#Tutorial";
  (author: $author, page: $page, post: $post) isa posting;
fetch
  $username;
  $page-name;
  $post: payload;
{
    "username": { "value": "Mario Santos", "type": { "label": "name", "root": "attribute", "value_type": "string" } },
    "page-name": { "value": "Knowledge Graphs", "type": { "label": "name", "root": "attribute", "value_type": "string" } },
    "post": {
        "type": { "label": "video-post", "root": "entity" },
        "payload": [
            { "value": "Hey everyone! I put together a quick tutorial on how to build a basic knowledge graph using open-source tools. It’s a step-by-step guide perfect for beginners. Let me know if you have any questions or need further clarification!", "type": { "label": "post-text", "root": "attribute", "value_type": "string" } },
            { "value": "eH8ilqtekYJEB1J5_TkPo-QyFcIoCVvQ", "type": { "label": "post-video", "root": "attribute", "value_type": "string" } }
        ]
    }
}
{
    "username": { "value": "Mario Santos", "type": { "label": "name", "root": "attribute", "value_type": "string" } },
    "page-name": { "value": "Mario Santos", "type": { "label": "name", "root": "attribute", "value_type": "string" } },
    "post": {
        "type": { "label": "share-post", "root": "entity" },
        "payload": [ { "value": "Hey friends! I've put together a step-by-step video tutorial perfect for anyone looking to dive into the world of knowledge graphs. If you've ever been curious about how to visualize complex data relationships and enhance your data analysis, this is for you.", "type": { "label": "post-text", "root": "attribute", "value_type": "string" } } ]
    }
}

Relation hierarchies

Finally, let’s see how can use relation type hierarchies in our schemas. We’ll begin by defining three new relation types: commenting, reaction, and response (for recording responses to poll posts). The commenting relation is a ternary relation type, but both posts comments can play the parent role, because a comment can reply either directly to a post or to a comment on that post (at any depth in the comment tree). Similarly, both posts and comments can be reacted to. Both reaction and response are binary relations.

If we examine these new relation types, along with the two previous ones, posting and sharing, we notice that all these relations describe some form of engagement with content: posting on a page, commenting on a post, reacting to a comment, etc. So, it makes sense that we should group all of these relation types into a type hierarchy. We’ll redesign our hierarchy so that all five of these types are now subtypes of an abstract supertype called content-engagement, which relates an author (of the engagement) and content (that is engaged with). While the author of any content engagement is always a profile (either personal or an organisation), the permitted type of content depends on the specific subtype of relation!

Overriding roles on relation subtypes

The possible content types are different for each engagement type:

  • Posting is an engagement with the page posted on.
  • Sharing is an engagement with the shared post.
  • Commenting is an engagement with the comment’s parent (a post or another comment).
  • Reacting is an engagement with the reaction’s parent (a post or a comment).
  • Responding is an engagement with a poll.

To encode this in our schema, we override the content role of the supertype by using the as keyword, and it is not directly played by any type! This allows us to ensure that only the correct entities can play these roles in each subtype. Meanwhile, the author role is not overridden, so a profile entity can play this role in any content-engagement.

define
content-engagement sub relation, abstract,
    relates author,
    relates content;
  posting sub content-engagement,
    relates page as content,
    relates post;
  sharing sub content-engagement,
    relates original-post as content,
    relates share-post;
  commenting sub content-engagement,
    relates parent as content,
    relates comment;
  reaction sub content-engagement,
    relates parent as content,
    owns emoji;
  response sub content-engagement,
    relates poll as content,
    owns answer;
  profile plays content-engagement:author;
  page plays posting:page;
  post plays posting:post,
    plays sharing:original-post,
    plays commenting:parent,
    plays reaction:parent;
  share-post plays sharing:share-post;
  comment sub entity,
    owns comment-text,
    owns tag,
    plays commenting:parent,
    plays commenting:comment,
    plays reaction:parent;
  poll-post plays response:poll;
  emoji sub attribute, value string, regex "^(like|love|funny|surprise|sad|angry)$";
  comment-text sub text-payload;

Querying relation hierarchies

Let’s see how the hierarchy of content-engagement relations expands the querying power of the model. First, we’ll insert some more data.

Additional data
match
  $mario isa person, has username "@MarioSantos";
  $post-1 isa post, has id 1;
  $post-2 isa post, has id 2;
insert
  $gabriel isa person,
    has username "@GabrielMedina",
    has name "Gabriel Medina",
    has bio "Hey everyone! I'm Gabriel, a tech enthusiast and gadget lover with a knack for all things electronic. When I’m not deep into coding or setting up intricate surveillance systems, you’ll find me tinkering with the latest tech toys or building custom gadgets. I'm a huge fan of sci-fi movies and novels—anything that sparks the imagination and showcases futuristic technology.",
    has profile-picture "2hmsCfjdpypYlVoecyxp9Ahc2mHiCjMS";
  $post isa image-post,
    has id 4,
    has post-text "Just got my hands on the latest Raspberry Pi 5 and I’m already planning some cool projects with it. Thinking about setting up a home automation system. Anyone else playing around with this little powerhouse? Share your projects or ideas! #TechGeek #RaspberryPi #HomeAutomation",
    has post-image "3xh7fmYzHT8vAhuuRGp4wb80NTXGtb6k";
  (author: $gabriel, page: $gabriel, post: $post) isa posting;
  $comment-1 isa comment,
    has comment-text "@MarioSantos, your knowledge graph tutorial was top-notch! It got me thinking about how we can integrate some of those concepts into our next project. Also, if anyone here hasn’t checked it out yet, I highly recommend it. Great work, Mario!",
    has tag "#KnowledgeGraphs",
    has tag "#DataScience",
    has tag "#Learning",
    has tag "@MarioSantos";
  (author: $gabriel, parent: $post-1, comment: $comment-1) isa commenting;
  $comment-2 isa comment,
    has comment-text "Thanks, Gabriel! I'm glad you found the tutorial helpful. Integrating some of those concepts into our next project sounds like a fantastic idea. Let's discuss it further — your expertise with tech and gadgets will be invaluable. Excited to see what we can create together!",
    has tag "#Collaboration",
    has tag "#Innovation";
  (author: $mario, parent: $comment-1, comment: $comment-2) isa commenting;
  $comment-3 isa comment,
    has comment-text "Definitely check out ArangoDB! It's a multi-model database that handles graphs really well and offers great performance.",
    has tag "#ArangoDB",
    has tag "#GraphDatabases";
  (author: $gabriel, parent: $post-2, comment: $comment-3) isa commenting;
  (author: $gabriel, parent: $post-1) isa reaction,
    has emoji "like";
  (author: $mario, parent: $comment-1) isa reaction,
    has emoji "love";
  (author: $mario, parent: $comment-3) isa reaction,
    has emoji "like";
  (author: $mario, poll: $post-2) isa response,
    has answer "TypeDB",
    has answer "Neo4j";
  (author: $gabriel, poll: $post-2) isa response,
    has answer "TypeDB",
    has answer "Other (comment below)";

Now we can run the following query, which will list the posts in the Knowledge Graphs group, and for each post, show which users have engaged and how.

match
  $kg-group isa page, has name "Knowledge Graphs";
  (page: $kg-group, post: $post) isa posting;
  $post isa! $post-type, has post-text $post-text;
fetch
  $post-type;
  $post-text;
  engagements: {
    match
      (author: $author, content: $post) isa! $engagement-type;
      $author has username $username;
    fetch
      $username;
      $engagement-type;
  };
{
    "post-type": { "label": "video-post", "root": "entity" },
    "post-text": { "value": "Hey everyone! I put together a quick tutorial on how to build a basic knowledge graph using open-source tools. It’s a step-by-step guide perfect for beginners. Let me know if you have any questions or need further clarification!", "type": { "label": "post-text", "root": "attribute", "value_type": "string" } },
    "engagements": [
        {
            "username": { "value": "@MarioSantos", "type": { "label": "username", "root": "attribute", "value_type": "string" } },
            "engagement-type": { "label": "sharing", "root": "relation" }
        },
        {
            "username": { "value": "@GabrielMedina", "type": { "label": "username", "root": "attribute", "value_type": "string" } },
            "engagement-type": { "label": "commenting", "root": "relation" }
        },
        {
            "username": { "value": "@GabrielMedina", "type": { "label": "username", "root": "attribute", "value_type": "string" } },
            "engagement-type": { "label": "reaction", "root": "relation" }
        }
    ]
}
{
    "post-type": { "label": "poll-post", "root": "entity" },
    "post-text": { "value": "I'm curious to know which tools you all prefer for creating knowledge graphs. Let's see what the community thinks!", "type": { "label": "post-text", "root": "attribute", "value_type": "string" } },
    "engagements": [
        {
            "username": { "value": "@GabrielMedina", "type": { "label": "username", "root": "attribute", "value_type": "string" } },
            "engagement-type": { "label": "response", "root": "relation" }
        },
        {
            "username": { "value": "@MarioSantos", "type": { "label": "username", "root": "attribute", "value_type": "string" } },
            "engagement-type": { "label": "response", "root": "relation" }
        },
        {
            "username": { "value": "@GabrielMedina", "type": { "label": "username", "root": "attribute", "value_type": "string" } },
            "engagement-type": { "label": "commenting", "root": "relation" }
        }
    ]
}

Despite the different types of content engagement, and the many different types of content and authors of engagements, we can easily query them in a declarative manner by querying the supertypes of each hierarchy! There is no need to enumerate all of the different subtypes in our query, and it ends up reading close to natural language.

Summary

Most databases are not built to model polymorphism, causing problems when trying to describe inheritance hierarchies. While it is possible to emulate hierarchies by using certain modelling techniques, for example class-table inheritance in relational databases and using multiple node labels in graph databases, these approaches are largely hacks rather than real solutions. Employing these techniques can lead to a wide range of problems when maintaining the database. This has led to the use of ORMs to manage inheritance, but they come with their own problems: they do not offer the full expressivity of a native database query language, often cannot generate data models and queries with optimal performance, are typically tied to specific database technologies, and introduce an additional layer of complexity and overhead to database architecture.

TypeDB is built with inheritance and polymorphism as first-class features, so we can easily work with type hierarchies directly in the database, allowing us to:

  • Build hierarchies of entity, relation, and attribute types.
  • Assign attributes to entity and relation supertypes that are inherited by their subtypes.
  • Have the database validate inserted data against the schema for semantic correctness.
  • Query supertypes declaratively to retrieve instances of all subtypes.
  • Override roles in subtypes of relations, allowing the specialisation of capabilities.
  • Query relation supertypes declaratively to retrieve entities that play roles in all their subtypes, in any combination.

There is no need for ORMs or specialised toolings: these features are built in to all distributions of TypeDB, leading to simpler architectures and more expressive queries.

Share this article

TypeDB Newsletter

Stay up to date with the latest TypeDB announcements and events.

Subscribe to Newsletter

Further Learning

TypeDB's Core Features

Explore TypeDB's core features in detail, including validated polymorphic data models and declarative querying, and learn about their impact on database engineering.

Read article

Cracks in SQL

Polymorphism is a typical characteristic of almost all data sets, and every database necessarily has approaches to modeling it, some better than others.

Read article

Crash course

A concise overview of the main aspects of TypeDB and TypeQL, with multiple pathways based on previous database experience.

Read docs

Feedback