PostgreSQL Inheritance
Introduction
PostgreSQL offers a powerful feature called table inheritance that allows you to create parent-child relationships between tables. This concept is borrowed from object-oriented programming, where a child class can inherit attributes and behavior from a parent class.
In PostgreSQL, a child table can inherit columns from a parent table while adding its own specific columns. This feature can significantly simplify database design when dealing with hierarchical data structures and promote code reuse.
Basic Inheritance Concepts
PostgreSQL's inheritance system lets you define parent-child relationships between tables where a child table inherits all columns from its parent table and can add its own columns.
Here's a simple diagram showing the inheritance concept:
Key points about PostgreSQL inheritance:
- A child table contains all columns from the parent table plus its own specific columns
- You can query data from just the parent table or from the parent and all child tables
- Constraints and indexes are not inherited from the parent table
- Each table is still a fully independent table that can be queried separately
Creating Tables with Inheritance
Let's create a practical example to see inheritance in action. Imagine we're designing a database for a vehicle rental company. We'll create a parent vehicles
table and then specific child tables for different types of vehicles.
Step 1: Create the Parent Table
CREATE TABLE vehicles (
vehicle_id SERIAL PRIMARY KEY,
make VARCHAR(100) NOT NULL,
model VARCHAR(100) NOT NULL,
year INTEGER NOT NULL,
daily_rate DECIMAL(10, 2) NOT NULL,
available BOOLEAN DEFAULT TRUE
);
Step 2: Create Child Tables That Inherit from the Parent
Now let's create child tables for cars and trucks, each with their specific attributes:
-- Cars table inherits from vehicles
CREATE TABLE cars (
passenger_capacity INTEGER NOT NULL,
cargo_volume_cu_ft DECIMAL(5, 2) NOT NULL,
body_style VARCHAR(50) NOT NULL
) INHERITS (vehicles);
-- Trucks table inherits from vehicles
CREATE TABLE trucks (
cargo_capacity_tons DECIMAL(5, 2) NOT NULL,
num_axles INTEGER NOT NULL,
truck_type VARCHAR(50) NOT NULL
) INHERITS (vehicles);
Each child table automatically includes all columns from the parent table (vehicle_id
, make
, model
, year
, daily_rate
, and available
) plus its own specific columns.
Inserting Data into Tables
Let's insert some sample data into our tables:
-- Insert data into cars table
INSERT INTO cars (make, model, year, daily_rate, available, passenger_capacity, cargo_volume_cu_ft, body_style)
VALUES
('Toyota', 'Camry', 2022, 45.00, true, 5, 15.1, 'Sedan'),
('Honda', 'CR-V', 2021, 55.00, true, 5, 39.2, 'SUV'),
('Ford', 'Mustang', 2022, 75.00, false, 4, 13.5, 'Coupe');
-- Insert data into trucks table
INSERT INTO trucks (make, model, year, daily_rate, available, cargo_capacity_tons, num_axles, truck_type)
VALUES
('Ford', 'F-150', 2020, 85.00, true, 0.75, 2, 'Pickup'),
('Chevrolet', 'Silverado', 2021, 95.00, true, 0.85, 2, 'Pickup'),
('Mercedes', 'Actros', 2019, 250.00, false, 20, 5, 'Semi');
Querying with Inheritance
One of the main benefits of inheritance is the ability to query data from all child tables through the parent table.
Querying Just the Parent Table
If you only want to see data from the parent table (without including child table data):
SELECT * FROM ONLY vehicles;
Result:
vehicle_id | make | model | year | daily_rate | available
-----------+------+-------+------+------------+-----------
(0 rows)
This query returns no rows because we haven't inserted any data directly into the parent table.
Querying Parent and All Child Tables
To query from the parent and all child tables:
SELECT vehicle_id, make, model, year, daily_rate, available FROM vehicles;
Result:
vehicle_id | make | model | year | daily_rate | available
-----------+-----------+-----------+------+------------+-----------
1 | Toyota | Camry | 2022 | 45.00 | t
2 | Honda | CR-V | 2021 | 55.00 | t
3 | Ford | Mustang | 2022 | 75.00 | f
4 | Ford | F-150 | 2020 | 85.00 | t
5 | Chevrolet | Silverado | 2021 | 95.00 | t
6 | Mercedes | Actros | 2019 | 250.00 | f
(6 rows)
This query returns all vehicles from both child tables.
Identifying Which Table a Row Comes From
To see which specific table each row belongs to:
SELECT tableoid::regclass AS table_name, vehicle_id, make, model
FROM vehicles
ORDER BY table_name, vehicle_id;
Result:
table_name | vehicle_id | make | model
------------+-----------+-----------+-----------
cars | 1 | Toyota | Camry
cars | 2 | Honda | CR-V
cars | 3 | Ford | Mustang
trucks | 4 | Ford | F-150
trucks | 5 | Chevrolet | Silverado
trucks | 6 | Mercedes | Actros
(6 rows)
Querying Specific Child Tables
You can still query the child tables directly:
-- Query only cars
SELECT vehicle_id, make, model, passenger_capacity, body_style
FROM cars
WHERE available = true;
Result:
vehicle_id | make | model | passenger_capacity | body_style
-----------+--------+-------+--------------------+------------
1 | Toyota | Camry | 5 | Sedan
2 | Honda | CR-V | 5 | SUV
(2 rows)
Modifying Tables with Inheritance
Adding Columns to Parent Table
When you add columns to the parent table, they are automatically inherited by all child tables:
-- Add a column to the parent table
ALTER TABLE vehicles ADD COLUMN last_maintenance_date DATE;
Now both cars
and trucks
tables will have this column.
Adding Constraints
Remember that constraints are not inherited. If you want to add a constraint to both parent and child tables, you need to add it to each table separately:
-- Add a check constraint to vehicles table
ALTER TABLE vehicles ADD CONSTRAINT valid_year CHECK (year >= 2000);
-- Add the same constraint to child tables
ALTER TABLE cars ADD CONSTRAINT valid_year CHECK (year >= 2000);
ALTER TABLE trucks ADD CONSTRAINT valid_year CHECK (year >= 2000);
Updating and Deleting with Inheritance
Updating Data
You can update data across all tables using the parent table:
-- Set all vehicles to unavailable
UPDATE vehicles SET available = false;
This updates rows in both the parent table and all child tables.
Deleting Data
Similarly, you can delete data across all tables:
-- Delete all vehicles made before 2021
DELETE FROM vehicles WHERE year < 2021;
This deletes matching rows from both the parent and all child tables.
Practical Use Cases for Inheritance
PostgreSQL inheritance is particularly useful in several scenarios:
-
Partitioning data: Before PostgreSQL's declarative partitioning, inheritance was commonly used for table partitioning.
-
Content Management Systems: Different content types can share common fields like creation date, author, etc.
-
Product catalogs: All products might share common attributes like price and description, while specific product types have their own attributes.
-
Geographic Information Systems: Different geographic entities can share common spatial attributes.
Let's look at a practical example for a product catalog:
-- Create a base products table
CREATE TABLE products (
product_id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
price DECIMAL(10, 2) NOT NULL,
stock_quantity INTEGER NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Create specific product tables
CREATE TABLE electronics (
warranty_months INTEGER NOT NULL,
power_consumption_watts INTEGER,
manufacturer VARCHAR(100)
) INHERITS (products);
CREATE TABLE clothing (
size VARCHAR(20) NOT NULL,
color VARCHAR(50) NOT NULL,
material VARCHAR(50),
gender VARCHAR(20)
) INHERITS (products);
CREATE TABLE books (
author VARCHAR(100) NOT NULL,
isbn VARCHAR(20) UNIQUE NOT NULL,
pages INTEGER,
publisher VARCHAR(100)
) INHERITS (products);
This structure allows you to:
- Query all products regardless of type using the parent table
- Query specific product types using their respective tables
- Maintain common attributes in one place
Limitations and Considerations
While inheritance is powerful, it has some limitations to be aware of:
-
Constraints are not inherited: You need to define constraints separately for each table.
-
Indexes are not inherited: Each table needs its own indexes.
-
Foreign keys cannot reference the entire hierarchy: You can only reference specific tables.
-
Triggers are not inherited: You need to define triggers separately for each table.
-
Performance considerations: Queries on the parent table need to scan all child tables, which can be slow with many child tables or large datasets.
Inheritance vs. Partitioning
In newer versions of PostgreSQL (10+), declarative partitioning provides a more powerful alternative to inheritance for dividing large tables. Partitioning is preferable when your main goal is to improve query performance on very large tables.
Inheritance is still useful for modeling hierarchical data and when you want the flexibility to query tables both separately and together.
Summary
PostgreSQL inheritance is a powerful feature that:
- Allows tables to inherit columns from parent tables
- Enables hierarchical data modeling
- Facilitates querying data across multiple related tables
- Promotes code reuse in database design
By organizing your tables in inheritance hierarchies, you can create cleaner, more maintainable database designs that better reflect the structure of your data.
Exercises
-
Create a parent table
employees
with common fields likeemployee_id
,name
,hire_date
, andsalary
. Then create child tables for different employee types likemanagers
,developers
, andsales_representatives
with their specific attributes. -
Write a query that lists all employees sorted by salary, including which department they belong to.
-
Add a new column
performance_rating
to the parent table and verify it exists in all child tables. -
Create a query that shows the average salary for each employee type.
Additional Resources
- PostgreSQL Official Documentation on Inheritance
- PostgreSQL Table Partitioning for comparing inheritance with newer partitioning features
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)