Polymorphic Relations in PostgreSQL Using Table Inheritance
Summary
Notes on using PostgreSQL’s Table Interitance using a hypothetical alerting system. The system receives a stream of system metrics and has different types of triggers which are associated with multiple types of subscribers.
Demonstrated with two types of trigger: system load and filesystem usage.
Tables
Example table diagram with triggers and subscribers, where each is associated with a user. The triggers and subscribers are also linked to each other via a many-to-many (junction) table.
The SQL for this schema design:
CREATE TABLE IF NOT EXISTS public.user
(
id UUID PRIMARY KEY NOT NULL DEFAULT gen_random_uuid()
);
-- TRIGGERS
CREATE SCHEMA IF NOT EXISTS triggers;
CREATE TABLE IF NOT EXISTS triggers.base
(
id UUID PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES public.user (id),
active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS triggers."load"
(
one_minute NUMERIC NOT NULL,
five_minute NUMERIC NOT NULL,
fifteen_minute NUMERIC NOT NULL,
PRIMARY KEY (id)
) INHERITS (triggers.base);
CREATE TABLE IF NOT EXISTS triggers.filesystem
(
mount_point TEXT NOT NULL,
usage NUMERIC NOT NULL,
PRIMARY KEY (id),
UNIQUE (user_id, mount_point)
) INHERITS (triggers.base);
-- SUBSCRIBERS
CREATE SCHEMA IF NOT EXISTS subscribers;
CREATE TABLE IF NOT EXISTS subscribers.base
(
id UUID PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES public.user (id),
active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS subscribers.email
(
email TEXT NOT NULL,
PRIMARY KEY (id),
UNIQUE (user_id, email)
) INHERITS (subscribers.base);
CREATE TABLE IF NOT EXISTS subscribers.sms
(
phone_number TEXT NOT NULL,
PRIMARY KEY (id),
UNIQUE (user_id, phone_number)
) INHERITS (subscribers.base);
-- JUNCTION TABLE
CREATE TABLE IF NOT EXISTS public.trigger_to_subscriber
(
trigger_id UUID NOT NULL, -- REFERENCES triggers.base (id),
subscriber_id UUID NOT NULL, -- REFERENCES subscribers.base (id),
PRIMARY KEY (trigger_id, subscriber_id)
);
CREATE INDEX ON public.trigger_to_subscriber (trigger_id);
CREATE INDEX ON public.trigger_to_subscriber (subscriber_id);
Note: Special cases with indexes and foreign-keys.
- Primary keys and unique indexes are defined for each child table.
- The
REFERENCESclauses inpublic.trigger_to_subscriberare commented out. This is because the foreign keys are not inherited by the child tables.
The junction-table can be used between the parent or child tables, but the constraints will not be enforced. - From the PostgreSQL documenation:
A serious limitation of the inheritance feature is that indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children.