Master the art of data modeling and learn to create efficient, normalized database structures.
Understanding database normalization principles:
Exploring different types of table relationships:
Implementing relationships using Knex:
Data normalization is the process of organizing a database to reduce redundancy and improve data integrity through a series of rules called normal forms.
Example of normalizing data:
-- Unnormalized data (violations of normal forms) CREATE TABLE orders_unnormalized ( order_id INTEGER, customer_name TEXT, customer_email TEXT, customer_address TEXT, product_id INTEGER, product_name TEXT, product_category TEXT, product_price REAL, quantity INTEGER, order_date DATE ); -- Normalized to 3NF (multiple related tables) CREATE TABLE customers ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE NOT NULL, address TEXT ); CREATE TABLE products ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, category TEXT, price REAL NOT NULL ); CREATE TABLE orders ( id INTEGER PRIMARY KEY, customer_id INTEGER NOT NULL, order_date DATE NOT NULL, FOREIGN KEY (customer_id) REFERENCES customers(id) ); CREATE TABLE order_items ( order_id INTEGER, product_id INTEGER, quantity INTEGER NOT NULL, PRIMARY KEY (order_id, product_id), FOREIGN KEY (order_id) REFERENCES orders(id), FOREIGN KEY (product_id) REFERENCES products(id) );
Understanding different types of table relationships is crucial for effective data modeling. Each relationship type serves specific purposes in representing real-world connections between entities.
Examples of relationship implementations:
-- One-to-One: User and User Profile CREATE TABLE users ( id INTEGER PRIMARY KEY, username TEXT UNIQUE NOT NULL, email TEXT UNIQUE NOT NULL ); CREATE TABLE user_profiles ( user_id INTEGER PRIMARY KEY, bio TEXT, avatar_url TEXT, FOREIGN KEY (user_id) REFERENCES users(id) ); -- One-to-Many: Department and Employees CREATE TABLE departments ( id INTEGER PRIMARY KEY, name TEXT NOT NULL ); CREATE TABLE employees ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, department_id INTEGER, FOREIGN KEY (department_id) REFERENCES departments(id) ); -- Many-to-Many: Students and Courses CREATE TABLE students ( id INTEGER PRIMARY KEY, name TEXT NOT NULL ); CREATE TABLE courses ( id INTEGER PRIMARY KEY, title TEXT NOT NULL ); CREATE TABLE enrollments ( student_id INTEGER, course_id INTEGER, enrollment_date DATE NOT NULL, PRIMARY KEY (student_id, course_id), FOREIGN KEY (student_id) REFERENCES students(id), FOREIGN KEY (course_id) REFERENCES courses(id) );
Knex.js provides a powerful and expressive API for creating and managing table relationships in your migrations.
table.foreign()
- Creates a foreign key constrainttable.references()
- Specifies the referenced columntable.inTable()
- Specifies the referenced tableonDelete()
- Action when referenced row is deletedonUpdate()
- Action when referenced key is updatedExample of creating relationships with Knex.js:
// One-to-One relationship: User and Profile exports.up = function(knex) { return knex.schema .createTable('users', table => { table.increments('id'); table.string('username').notNullable().unique(); table.string('email').notNullable().unique(); }) .createTable('profiles', table => { // Primary key is also a foreign key table.integer('user_id').primary(); table.text('bio'); table.string('avatar_url'); // Define foreign key relationship table.foreign('user_id') .references('id') .inTable('users') .onDelete('CASCADE'); }); }; // One-to-Many relationship: Departments and Employees exports.up = function(knex) { return knex.schema .createTable('departments', table => { table.increments('id'); table.string('name').notNullable(); }) .createTable('employees', table => { table.increments('id'); table.string('name').notNullable(); // Foreign key in the "many" table table.integer('department_id') .references('id') .inTable('departments') .onDelete('SET NULL'); }); }; // Many-to-Many relationship: Students and Courses exports.up = function(knex) { return knex.schema .createTable('students', table => { table.increments('id'); table.string('name').notNullable(); }) .createTable('courses', table => { table.increments('id'); table.string('title').notNullable(); }) .createTable('enrollments', table => { // Composite primary key table.integer('student_id') .notNullable() .references('id') .inTable('students') .onDelete('CASCADE'); table.integer('course_id') .notNullable() .references('id') .inTable('courses') .onDelete('CASCADE'); table.date('enrollment_date').notNullable(); // Define the composite primary key table.primary(['student_id', 'course_id']); }); };
In this module's guided project, you will:
After completing this module, you'll be ready for the Sprint Challenge, where you'll: