snippetsqlMinor
How can I represent discrete ages in a relational database?
Viewed 0 times
canrepresentagesdiscreterelationaldatabasehow
Problem
I am using PostgreSQL to store data on a Person. I need to store whether each Person is capable of teaching a Child of each age year, from 0-17. The ages are discrete values, and a Person can have any number of the 18 (assuming 0-17 years old) assigned to their account. This will be used in a booking system, and also needs to form part of the search results.
Initially I considered creating 18 boolean fields, but this seems inefficient. Is there a better way to do it? I understand that Postgres supports JSONB, so this is an option, but I am unsure of the implications.
Is there a better way to store this data?
Initially I considered creating 18 boolean fields, but this seems inefficient. Is there a better way to do it? I understand that Postgres supports JSONB, so this is an option, but I am unsure of the implications.
Is there a better way to store this data?
Solution
The ages are discrete values
OK.
a Person can have any number of the 18 (assuming 0-17 years old) assigned to their account.
So it's a many-to-many relationship?
If so, you just decompose your data into third normal form as usual, expressing the cardinality by means of one extra relation.
Example follows. The SQL dialect is not necessarily PostgreSQL as I do not have an installation handy right now.
Schema
Data
Logic
Let us get a report on those who can teach any classes
Result:
Let us get a report of who can teach to whom, if anyone
Result:
Who teach class 3?
Result:
Who can teach classes 7 or 9?
Result:
```
+----------+
OK.
a Person can have any number of the 18 (assuming 0-17 years old) assigned to their account.
So it's a many-to-many relationship?
If so, you just decompose your data into third normal form as usual, expressing the cardinality by means of one extra relation.
Example follows. The SQL dialect is not necessarily PostgreSQL as I do not have an installation handy right now.
Schema
-- persons relation
CREATE TABLE persons (
personId INT NOT NULL PRIMARY KEY,
name TEXT NOT NULL
-- Any other attributes
);
-- classes relation
CREATE TABLE pupilClasses (
age INT NOT NULL PRIMARY KEY,
size ENUM ('small', 'medium', 'large') NOT NULL
);
-- "Tie" relation that expresses the many-to-many cardinality
-- between persons and classes
CREATE TABLE persons_pupilClasses (
personId INT NOT NULL,
age INT NOT NULL,
PRIMARY KEY (personId, age),
FOREIGN KEY (personId) REFERENCES persons (personId)
ON UPDATE CASCADE ON DELETE CASCADE,
FOREIGN KEY (age) REFERENCES pupilClasses (age)
ON UPDATE CASCADE ON DELETE CASCADE
);Data
-- Let us populate with some data
-- A few classes
INSERT INTO pupilClasses (age, size) VALUES (0, 'small');
INSERT INTO pupilClasses (age, size) VALUES (1, 'small');
INSERT INTO pupilClasses (age, size) VALUES (2, 'small');
INSERT INTO pupilClasses (age, size) VALUES (3, 'small');
INSERT INTO pupilClasses (age, size) VALUES (4, 'small');
INSERT INTO pupilClasses (age, size) VALUES (5, 'small');
INSERT INTO pupilClasses (age, size) VALUES (6, 'medium');
INSERT INTO pupilClasses (age, size) VALUES (7, 'medium');
INSERT INTO pupilClasses (age, size) VALUES (8, 'medium');
INSERT INTO pupilClasses (age, size) VALUES (9, 'medium');
INSERT INTO pupilClasses (age, size) VALUES (10, 'medium');
INSERT INTO pupilClasses (age, size) VALUES (11, 'medium');
INSERT INTO pupilClasses (age, size) VALUES (12, 'large');
INSERT INTO pupilClasses (age, size) VALUES (13, 'large');
INSERT INTO pupilClasses (age, size) VALUES (14, 'large');
INSERT INTO pupilClasses (age, size) VALUES (15, 'large');
INSERT INTO pupilClasses (age, size) VALUES (16, 'large');
INSERT INTO pupilClasses (age, size) VALUES (17, 'large');
-- A few persons
INSERT INTO persons (personId, name) VALUES (666, 'Alice');
INSERT INTO persons (personId, name) VALUES (667, 'Bertrand');
INSERT INTO persons (personId, name) VALUES (668, 'Carlos');
-- Who can teach to whom
-- Alice to 0−3 and 7−8 classes
INSERT INTO persons_pupilClasses (personId, age) VALUES (666, 0);
INSERT INTO persons_pupilClasses (personId, age) VALUES (666, 1);
INSERT INTO persons_pupilClasses (personId, age) VALUES (666, 2);
INSERT INTO persons_pupilClasses (personId, age) VALUES (666, 3);
INSERT INTO persons_pupilClasses (personId, age) VALUES (666, 7);
INSERT INTO persons_pupilClasses (personId, age) VALUES (666, 8);
-- Bertrand to 7−9, 13, 16−17 classes
INSERT INTO persons_pupilClasses (personId, age) VALUES (667, 7);
INSERT INTO persons_pupilClasses (personId, age) VALUES (667, 8);
INSERT INTO persons_pupilClasses (personId, age) VALUES (667, 9);
INSERT INTO persons_pupilClasses (personId, age) VALUES (667, 13);
INSERT INTO persons_pupilClasses (personId, age) VALUES (667, 16);
INSERT INTO persons_pupilClasses (personId, age) VALUES (667, 17);
-- Carlos can't teach anyone yet ☹Logic
Let us get a report on those who can teach any classes
SELECT persons.name, GROUP_CONCAT(pupilClasses.age) classes
FROM persons
INNER JOIN persons_pupilClasses
ON persons.personId = persons_pupilClasses.personId
INNER JOIN pupilClasses
ON pupilClasses.age = persons_pupilClasses.age
GROUP BY persons.personId;Result:
+----------+----------------+
| name | classes |
+----------+----------------+
| Alice | 0,1,2,3,7,8 |
| Bertrand | 7,8,9,13,16,17 |
+----------+----------------+Let us get a report of who can teach to whom, if anyone
SELECT persons.name, GROUP_CONCAT(pupilClasses.age) classes
FROM persons
LEFT JOIN persons_pupilClasses
ON persons.personId = persons_pupilClasses.personId
LEFT JOIN pupilClasses
ON pupilClasses.age = persons_pupilClasses.age
GROUP BY persons.personId;Result:
+----------+----------------+
| name | classes |
+----------+----------------+
| Alice | 0,1,2,3,7,8 |
| Bertrand | 7,8,9,13,16,17 |
| Carlos | NULL |
+----------+----------------+Who teach class 3?
SELECT persons.name
FROM persons
INNER JOIN persons_pupilClasses
ON persons.personId = persons_pupilClasses.personId
WHERE persons_pupilClasses.age = 3;Result:
+-------+
| name |
+-------+
| Alice |
+-------+Who can teach classes 7 or 9?
SELECT DISTINCT persons.name
FROM persons
INNER JOIN persons_pupilClasses
ON persons.personId = persons_pupilClasses.personId
WHERE persons_pupilClasses.age IN (7,9);Result:
```
+----------+
Code Snippets
-- persons relation
CREATE TABLE persons (
personId INT NOT NULL PRIMARY KEY,
name TEXT NOT NULL
-- Any other attributes
);
-- classes relation
CREATE TABLE pupilClasses (
age INT NOT NULL PRIMARY KEY,
size ENUM ('small', 'medium', 'large') NOT NULL
);
-- "Tie" relation that expresses the many-to-many cardinality
-- between persons and classes
CREATE TABLE persons_pupilClasses (
personId INT NOT NULL,
age INT NOT NULL,
PRIMARY KEY (personId, age),
FOREIGN KEY (personId) REFERENCES persons (personId)
ON UPDATE CASCADE ON DELETE CASCADE,
FOREIGN KEY (age) REFERENCES pupilClasses (age)
ON UPDATE CASCADE ON DELETE CASCADE
);-- Let us populate with some data
-- A few classes
INSERT INTO pupilClasses (age, size) VALUES (0, 'small');
INSERT INTO pupilClasses (age, size) VALUES (1, 'small');
INSERT INTO pupilClasses (age, size) VALUES (2, 'small');
INSERT INTO pupilClasses (age, size) VALUES (3, 'small');
INSERT INTO pupilClasses (age, size) VALUES (4, 'small');
INSERT INTO pupilClasses (age, size) VALUES (5, 'small');
INSERT INTO pupilClasses (age, size) VALUES (6, 'medium');
INSERT INTO pupilClasses (age, size) VALUES (7, 'medium');
INSERT INTO pupilClasses (age, size) VALUES (8, 'medium');
INSERT INTO pupilClasses (age, size) VALUES (9, 'medium');
INSERT INTO pupilClasses (age, size) VALUES (10, 'medium');
INSERT INTO pupilClasses (age, size) VALUES (11, 'medium');
INSERT INTO pupilClasses (age, size) VALUES (12, 'large');
INSERT INTO pupilClasses (age, size) VALUES (13, 'large');
INSERT INTO pupilClasses (age, size) VALUES (14, 'large');
INSERT INTO pupilClasses (age, size) VALUES (15, 'large');
INSERT INTO pupilClasses (age, size) VALUES (16, 'large');
INSERT INTO pupilClasses (age, size) VALUES (17, 'large');
-- A few persons
INSERT INTO persons (personId, name) VALUES (666, 'Alice');
INSERT INTO persons (personId, name) VALUES (667, 'Bertrand');
INSERT INTO persons (personId, name) VALUES (668, 'Carlos');
-- Who can teach to whom
-- Alice to 0−3 and 7−8 classes
INSERT INTO persons_pupilClasses (personId, age) VALUES (666, 0);
INSERT INTO persons_pupilClasses (personId, age) VALUES (666, 1);
INSERT INTO persons_pupilClasses (personId, age) VALUES (666, 2);
INSERT INTO persons_pupilClasses (personId, age) VALUES (666, 3);
INSERT INTO persons_pupilClasses (personId, age) VALUES (666, 7);
INSERT INTO persons_pupilClasses (personId, age) VALUES (666, 8);
-- Bertrand to 7−9, 13, 16−17 classes
INSERT INTO persons_pupilClasses (personId, age) VALUES (667, 7);
INSERT INTO persons_pupilClasses (personId, age) VALUES (667, 8);
INSERT INTO persons_pupilClasses (personId, age) VALUES (667, 9);
INSERT INTO persons_pupilClasses (personId, age) VALUES (667, 13);
INSERT INTO persons_pupilClasses (personId, age) VALUES (667, 16);
INSERT INTO persons_pupilClasses (personId, age) VALUES (667, 17);
-- Carlos can't teach anyone yet ☹SELECT persons.name, GROUP_CONCAT(pupilClasses.age) classes
FROM persons
INNER JOIN persons_pupilClasses
ON persons.personId = persons_pupilClasses.personId
INNER JOIN pupilClasses
ON pupilClasses.age = persons_pupilClasses.age
GROUP BY persons.personId;+----------+----------------+
| name | classes |
+----------+----------------+
| Alice | 0,1,2,3,7,8 |
| Bertrand | 7,8,9,13,16,17 |
+----------+----------------+SELECT persons.name, GROUP_CONCAT(pupilClasses.age) classes
FROM persons
LEFT JOIN persons_pupilClasses
ON persons.personId = persons_pupilClasses.personId
LEFT JOIN pupilClasses
ON pupilClasses.age = persons_pupilClasses.age
GROUP BY persons.personId;Context
StackExchange Database Administrators Q#135518, answer score: 9
Revisions (0)
No revisions yet.