Designing your database schema

Understanding database schema design - a quick guide with examples

Written by

J Edwards

Published on

August 4, 2023

Picture this: You're a developer working on a new project, and everything seems perfect until you realize your data is scattered all over the place and your code can’t access it efficiently! It feels like a never-ending treasure hunt, and you're racing against time to find the right information. But wait, could this very time-comsuming and chaotic situation have been avoided? By planning and organizing, could all of that scattered information have been consolidated?

In this post, we’ll explore the benefits of a well-thought-out / well-designed database schema by answering the following questions:

  • What is a database schema?
  • What relationships should you keep in mind when designing a database schema?
  • What are some best practices for database schema design?

A database schema is like a neat and tidy blueprint that gives structure and order to your data. It’s a framework that provides guidance on how data relates to each other and how you can access it. So when applications come knocking, they can easily fetch, play around, and present the information quickly and efficiently.

Think of a database schema as a filing system, like a huge cabinet with lots of drawers, each containing different categories of files. In this analogy, the cabinet is the whole database, and each drawer is a table within the database. Each file in the drawer is a row in the table, and the labels on the files are the columns.

If your filing system is a mess and labeled incorrectly (i.e. your schema is poorly designed) it can be a headache to find what you need and get the data you want from your database. To avoid this, it's important to create a well-organized schema that can easily store and retrieve data.

So much unorganized data!

Tables are the building blocks of a database schema - they contain specific types of data. For example, in a blog database, you can have separate tables for blog posts, authors, and comments. The columns in the tables define the different attributes and characteristics of the data, such as the title, date, and content of a blog post. Connecting the data in these tables is what makes the blog site functional and user-friendly. It allows users to search, navigate, and interact with the content effectively.

This is because database schemas are really all about correlation and association. One of the most powerful features of a schema is the ability to establish relationships between tables. These relationships allow different pieces of information to be connected, creating a cohesive structure. For example, the blog post table can have a column linking it to a corresponding author, so that each blog post is attributed to a writer.

Let's dive a bit deeper into the world of tables and explore how parent-child relationships play an important role in organizing and efficiently connecting data within databases. A parent and child table are two related tables connected through a foreign key relationship. The parent table contains primary data, and the child table holds information related to the data in the parent table.

For instance, in a blog database, we can have Posts as the parent table and Comments as the child table. Using SQL, it might look something like the following:

  • Parent Table: Posts
    • The Posts table contains information about the blog posts, such as the post title, content, publication date, and the author of the post.
CREATE TABLE posts (
  post_id SERIAL PRIMARY KEY,
  title VARCHAR(200) NOT NULL,
  content TEXT,
  publication_date DATE,
  author_id INT REFERENCES authors(author_id)
);
  • Child Table: Comments
    • The Comments table is a child table related to the Posts table through the post_id column. It stores comments made by users on specific blog posts, along with the name of the commenter and the comment content.
CREATE TABLE comments (
  comment_id SERIAL PRIMARY KEY,
  post_id INT REFERENCES posts(post_id),
  commenter_name VARCHAR(100) NOT NULL,
  comment_content TEXT
);

The Posts table is the parent table and holds the primary data about the blog posts. The Comments table is the child table because it is related to the Posts table through the post_id foreign key. Each record in the Comments table is associated with a specific blog post in the Posts table through a relationship.

This parent-child relationship allows multiple comments to be linked to a single blog post. It helps organize and connect the data in the blog database, so users can view and interact with comments related to each post.

The parent table/ child table is a pretty straight-forward relationship, but database schemas employ other types of relationships. In addition to the parent-child relationship, there are the following:

  • One-to-one relationship:
    • In a one-to-one relationship, each record in one table is associated with exactly one record in another table. This relationship is pretty rare and often used to split data with many columns into two separate tables for clarity or security reasons.
    • Example: Using our blog database as an example, a one-to-one relationship might exist between a User table and a Profile table. Each user has a unique profile, and each profile is associated with only one user.
  • One-to-many relationship:
    • In a one-to-many relationship, each record in one table can be associated with multiple records in another table. However, each record in the second table is related to only one record in the first table.
    • Example: In a one-to-many relationship, a Post table can be related to a Comment table, like we mentioned earlier. Each post can have multiple comments, but each comment belongs to only one post.
  • Many-to-many Relationship:
    • In a many-to-many relationship, each record in one table can be associated with multiple records in another table, and vice versa. This relationship usually requires an intermediary table, called a junction or linking table, to manage the connections between the two main tables.
    • Example: A many-to-many relationship could be established between a Post table and a Tag table through a junction table called PostTags. Each post can have multiple tags, and each tag can be associated with multiple posts.
  • Self-Referencing Relationship:
    • A self-referencing relationship is when a table references itself, creating a recursive relationship or hierarchy within the same table.
    • Example: A self-referencing relationship might occur within the Comment table, where a comment can be a reply to another comment. Each comment can reference the ID of its parent comment in a ParentCommentID column.

We’ve established that the relationships between tables determine how data is organized, accessed, and maintained within the schema. Relationships play a key role in defining the structure and behavior, but this also depends on the database modelling and the type of database model schema your using.

Data modeling progresses from a high-level conceptual understanding of the data to a structured logical representation, and finally, to the specific implementation in a chosen database model schema, tailored to meet the project's needs

The database model schema acts as a visual representation of the database design.

There are several types of database models and database schemas, each with its own way of organizing and structuring data. The following table outlines the different types:

Schema typeDescriptionUse
Flat model schemaData is stored in a single table with rows and columns, similar to a spreadsheet.Schema is simple and easy to implement but may not be suitable for complex data relationships
Hierarchical model schemaOrganizes data in a tree-like structure with parent-child relationships. Each parent can have multiple children, but each child can have only one parent.Schema is useful for representing nested data, such as organizational charts.
Network model schemaFor more complex relationships. Uses a graph-like structure where data items (nodes) are connected to one another through relationships (edges)Suitable for representing many-to-many relationships.
Relational model schemaBased on the concept of tables, rows, and columns. Data is organized in multiple tables, and relationships between tables are established using primary and foreign keysWidely used and allows for flexible data retrieval using SQL.
Entity-relationship model (ER model) schemaRepresents entities, attributes, and relationships between entities.Used to create a conceptual representation of a database before implementing it using a specific schema type.
Star schema / Snowflake schemaOrganizes data into a central fact table and several dimension tables. The Snowflake schema is an extension of the star schema.Used in data warehousing and for dimension tables that are normalized to reduce data redundancy.
Document model schemaStores data as semi-structured documents. Data storage is flexible and dynamic.Used in NoSQL databases.
Key-value model schemaStores data as key-value pairs.Used in NoSQL databases

Designing a database schema is undeniably one of the most critical tasks you’ll undertake in your project! It helps keep things organized, prevents duplication, and makes data retrieval easy. A well-designed schema keeps data consistent and accurate and reduces errors. Plus, it speeds up data access for applications. A good database schema design can make or break your project.

Here are 10 pointers (in no particular order) to consider when designing a database schema:

  1. Uphold data integrity: Set unique labels for each piece of data to avoid mix-ups. Data should be reliable and consistent. By setting rules like primary keys, foreign keys, and unique constraints, you can prevent data errors.
  2. Champion for normalization: Sort similar data into different tables to keep things tidy and organized. Organize data into neat tables and reduce repetition. Data normalization helps eliminate data redundancy, by applying defined rules that divide larger tables into smaller tables, link them using relationships and ensure the data appears consistent across all fields and records
  3. Build relationships: Create good relationships. Connect related tables using foreign keys for easy access.
  4. Think about efficiency: Organize data in a way that you can quickly find the what you need. Use smart tricks like indexes and avoid unnecessary steps.
  5. Design for scalability: Ensure your table can expand to fit more data as your project grows.
  6. Choose simplicity and understandability: Give each column a clear name, so everyone knows what it is. By giving tables and columns meaningful names and following standard rules, you make sure everyone can navigate your database easily.
  7. Focus on flexibility: Design your tables in a way that you can easily add new data or move it around without breaking things.
  8. Prioritize security: Use measures like access controls and encryption to keep your data safe.
  9. Fit and size appropriately: Customize your table for specific tasks. Your design should should fit your application's needs and work hand-in-hand with your application.
  10. Write it all down: Keep good and detailed documentation of your design. It should include information about your design for easy reference and help you maintain your project.

Now that we have an idea of what goes into designing a database schema, let’s apply it to an example. We’ll mix it up and move away from the blog example; let’s see how this can apply to a movie database - something like what we built in our XMDB tutorial (you can also check out this blog post if you want to learn more about how we built XMDB).

Consider a movie database with entities such as Movies, Actors, and Directors, and their respective attributes:

  • Identify entities and attributes:
    • Movies entity: Attributes like movie_id (unique identifier), title, release_year, genre, and rating.
    • Actors entity: Attributes like actor_id (unique identifier), full_name, birth_date, and nationality.
    • Directors entity: Attributes like director_id (unique identifier), full_name, birth_date, and nationality.
CREATE TABLE movies (
  movie_id SERIAL PRIMARY KEY,
  title VARCHAR(200) NOT NULL,
  release_year INT,
  genre VARCHAR(100),
  rating NUMERIC(3, 1)
);
 
CREATE TABLE actors (
  actor_id SERIAL PRIMARY KEY,
  full_name VARCHAR(100) NOT NULL,
  birth_date DATE,
  nationality VARCHAR(50)
);
 
CREATE TABLE directors (
  director_id SERIAL PRIMARY KEY,
  full_name VARCHAR(100) NOT NULL,
  birth_date DATE,
  nationality VARCHAR(50)
);
  • Normalize data: Avoid redundancy. For example, if an actor stars in multiple movies, store the actor details only once in a actors table and use the movie_actor table to represent their associations.

  • Establish relationships: Create relationships between Movies, Actors, and Directors to represent their associations.

    • Many-to-many relationship: A movie can have multiple actors, and an actor can be associated with multiple movies.
    CREATE TABLE movie_actor (
      movie_id INT REFERENCES movies(movie_id),
      actor_id INT REFERENCES actors(actor_id),
      PRIMARY KEY (movie_id, actor_id)
    );
    • One-to-many relationship: A director can direct multiple movies, but each movie has only one director.
    ALTER TABLE movies
    ADD COLUMN director_id INT REFERENCES directors(director_id);
  • Indexing: Create indexes on frequently searched columns to optimize query performance. For instance, create an index on the title column in the movies table for faster movie title searches.

CREATE INDEX idx_movie_title ON movies(title);
  • Use constraints: Apply constraints to maintain data integrity. For example, enforce a NOT NULL constraint on the title column in the movies table, so that a value has to be applied.
ALTER TABLE movies
ADD CONSTRAINT chk_movie_title_not_null CHECK (title IS NOT NULL);
  • Review and refine: Review and refine the database schema based on user feedback and evolving data requirements. A new film comes out? Add it in a way that complies and conforms to the rules you established.

Mastering the art of schema design is no easy task, but it’s definitely worth it. Remember these best practices to guide you along the way:

  • Know your data: Take the time to understand your data inside out. Identify what data you'll store and how it relates to each other. This deep understanding lays the foundation for a robust schema design.
  • Choose the right model: Not all models fit every situation. Be familiar with different database models and choose the one that aligns best with your data needs. Whether it's a flat model or a relational model, the right fit will make all the difference.
  • Unique keys for the win: Assign unique keys to each table. These keys act like special IDs for your data, ensuring that no two records are the same. This uniqueness is essential for data integrity and efficient data retrieval.
  • Build strong relationships: Just like friendships, relationships between tables are vital! Connect related data using foreign keys to keep everything cohesive and easily accessible. This way, your database will work harmoniously, and data retrieval will be a breeze.
  • Test, tweak, and repeat: Take your schema for a test drive with real data. Fine-tune as needed to optimize performance. Testing and tweaking ensure your schema runs smoothly and efficiently, making your data management journey a success!

Do you have any valuable tips or suggestions for designing effective database schemas? If there's anything we missed or if you want some extra guidance, don't hesitate to reach out to us; we’d love to hear what you think. If you have any feedback you can join us on Discord or follow us on Twitter.

Start free, pay as you grow

Xata provides the best free plan in the industry. It is production ready by default and doesn't pause or cool-down. Take your time to build your business and upgrade when you're ready to scale.

Free plan includes
  • 10 database branches
  • High availability
  • Point-in-time recovery
  • 15 GB data storage
  • 15 GB search engine storage
  • 2 GB file attachments
  • 250 AI queries per month
Free plan includes
  • 10 database branches
  • High availability
  • Point-in-time recovery
  • 15 GB data storage
  • 15 GB search engine storage
  • 2 GB file attachments
  • 250 AI queries per month