| Lesson 13 | Mandatory and Optional Participation (continued) |
| Objective | Explain and implement optional participation in table relationships |
Participation answers a simple question: “Must an instance on this side of the relationship be linked to something on the other side?”
1 (must participate).0 (may participate, but doesn’t have to).In table terms, optionality affects whether a foreign key may be NULL (in 1:N) and whether the absence of a row in an intersection table is allowed (in M:N).
0..* or 1..*). A 0 means optional; a 1 means mandatory.NULL. Mandatory ⇒ the foreign key is NOT NULL.
Use surrogate keys for entities and intersection tables for the M:N relationships. Optional participation is the default-do not insert rows unless the relationship actually exists.
-- Entities
CREATE TABLE Bars (
bar_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(200) NOT NULL,
address VARCHAR(300),
license_no VARCHAR(50),
CONSTRAINT uq_bars_name UNIQUE (name)
);
CREATE TABLE Beers (
beer_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(150) NOT NULL,
manufacturer VARCHAR(150),
CONSTRAINT uq_beers_name UNIQUE (name)
);
CREATE TABLE Drinkers (
drinker_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(200) NOT NULL,
address VARCHAR(300)
);
-- Intersections (M:N). Optional participation: absence of rows = no relationship.
CREATE TABLE Sells (
bar_id BIGINT NOT NULL,
beer_id BIGINT NOT NULL,
price DECIMAL(7,2) CHECK (price >= 0),
PRIMARY KEY (bar_id, beer_id),
FOREIGN KEY (bar_id) REFERENCES Bars(bar_id),
FOREIGN KEY (beer_id) REFERENCES Beers(beer_id)
);
CREATE TABLE Likes (
drinker_id BIGINT NOT NULL,
beer_id BIGINT NOT NULL,
rating SMALLINT CHECK (rating BETWEEN 1 AND 5),
PRIMARY KEY (drinker_id, beer_id),
FOREIGN KEY (drinker_id) REFERENCES Drinkers(drinker_id),
FOREIGN KEY (beer_id) REFERENCES Beers(beer_id)
);
CREATE TABLE Frequents (
drinker_id BIGINT NOT NULL,
bar_id BIGINT NOT NULL,
since_date DATE,
visits_per_week SMALLINT CHECK (visits_per_week BETWEEN 0 AND 21),
PRIMARY KEY (drinker_id, bar_id),
FOREIGN KEY (drinker_id) REFERENCES Drinkers(drinker_id),
FOREIGN KEY (bar_id) REFERENCES Bars(bar_id)
);
NULL-able.NOT NULL and has a valid parent row.-- Example: Department (1) -- (N) Employee
CREATE TABLE Department (
dept_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(120) NOT NULL UNIQUE
);
-- Optional participation of Employee in Department (employee may be unassigned)
CREATE TABLE Employee_Optional (
emp_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(120) NOT NULL,
dept_id BIGINT NULL, -- optional
FOREIGN KEY (dept_id) REFERENCES Department(dept_id)
);
-- Mandatory participation (every employee must belong to a department)
CREATE TABLE Employee_Mandatory (
emp_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(120) NOT NULL,
dept_id BIGINT NOT NULL, -- mandatory
FOREIGN KEY (dept_id) REFERENCES Department(dept_id)
);
Optionality is modeled by the presence or absence of rows in the intersection table. To make participation mandatory for an entity, enforce “at least one” via constraints, triggers, or periodic checks.
-- Pattern: enforce that each Bar must sell at least one Beer.
-- Option A (transactional trigger - pseudo code):
CREATE TRIGGER bar_must_sell
AFTER INSERT ON Bars
DEFERRABLE INITIALLY DEFERRED
BEGIN
-- At commit, verify each new bar_id appears at least once in Sells.
IF EXISTS (
SELECT 1
FROM Bars b
WHERE NOT EXISTS (SELECT 1 FROM Sells s WHERE s.bar_id = b.bar_id)
AND b.created_in_this_tx = TRUE
) THEN RAISE EXCEPTION 'Each bar must sell at least one beer';
END;
Note: Strict “at least one” constraints are often handled at the application/service layer because enforcing them transactionally can complicate bulk loads and edits.
Use LEFT JOIN to include entities that currently do not participate.
-- Bars and the beers they sell (include bars that currently sell none)
SELECT b.name AS bar, be.name AS beer, s.price
FROM Bars b
LEFT JOIN Sells s ON s.bar_id = b.bar_id
LEFT JOIN Beers be ON be.beer_id = s.beer_id
ORDER BY b.name, be.name;
-- Drinkers with no recorded preferences (optional Likes)
SELECT d.drinker_id, d.name
FROM Drinkers d
LEFT JOIN Likes l ON l.drinker_id = d.drinker_id
WHERE l.drinker_id IS NULL;
-- Count how many bars each drinker frequents (0 allowed)
SELECT d.name, COUNT(f.bar_id) AS bars_count
FROM Drinkers d
LEFT JOIN Frequents f ON f.drinker_id = d.drinker_id
GROUP BY d.name
ORDER BY bars_count DESC;
NULL-able (optional) or NOT NULL (mandatory).NOT NULL.Sells.price).Bars.name, Beers.name).Classify each side of the three relationships (sells, likes, frequents) in the ERD as optional or mandatory given these scenarios:
Explain how your answers change the DDL (NULL vs NOT NULL FKs, presence/absence rules in intersection tables).
Glossary
Sells).