PostgreSQL ensure data integrity for shared primary key - sql

Let's say we want to use globally unique id and find the type of entity using it:
CREATE TABLE identity (
id serial PK NOT NULL,
type IdentityEnum NOT NULL,
UNIQUE (id, type)
);
CREATE TABLE user (
id integer PK NOT NULL REFERENCES identity (id),
type IdentityEnum NOT NULL,
UNIQUE (id, type)
ALTER TABLE user
add constraint identity_fk
foreign key (id, type)
REFERENCES identity (id, type);
);
CREATE TABLE animal (
id integer PK NOT NULL REFERENCES identity (id),
type IdentityEnum NOT NULL,
UNIQUE (id, type)
ALTER TABLE animal
add constraint identity_fk
foreign key (id, type)
REFERENCES identity (id, type);
);
To ensure that user or animal tables will have one to one relation to identity:
it's will not be possible by more that one table to point at the same identity PK
We are forced to add additional type column for each table, add UNIQUE (id, type) and add FK from each table to identity.
Questions:
Is there another way to design database with a shared by multiple tables id without the need for type?
How to do joins if you have only id and don't know the type?

1
You do not need the type columns in any table. The type will appear in a view by the fact that the complementary data are stored into the animal or user table. This is known as an inherited data modeling. The "IDENTITY" table is the father table, while user and animal are children tables.
But to make it working properly you need to add triggers for INSERT / UPDATE that exclude the same ID velue to be present in the two child tables.
2
Use the view

Related

How can I simplify my SQL schema such that I have less tables but retain referential integrity?

Given the below SQL Server schema, how can I combine the CarWindow and HouseWindow tables into one table?
I would like to keep the foreign key constraint to Car if it is a car window, and to House if it is a house window.
I cannot combine the Car and House tables into one table because they are modelling different entities.
DROP TABLE IF EXISTS CarWindow
DROP TABLE IF EXISTS HouseWindow
DROP TABLE IF EXISTS Car
DROP TABLE IF EXISTS House
CREATE TABLE Car
(
Id int IDENTITY PRIMARY KEY,
CarColumn varchar(200)
)
CREATE TABLE House
(
Id int IDENTITY PRIMARY KEY,
HouseColumn_CompletelyDifferent int
)
CREATE TABLE CarWindow
(
IdCar int,
IsBroken bit,
CONSTRAINT fk_CarWindow_Car FOREIGN KEY (IdCar) REFERENCES Car(Id)
)
CREATE TABLE HouseWindow
(
IdHouse int,
IsBroken bit,
CONSTRAINT fk_HouseWindow_House FOREIGN KEY (IdHouse) REFERENCES House(Id)
)
A house presumably has multiple windows, some of which may be broken and some not. Same with a car. So I'm assuming, even though you haven't included it, that HouseWindow actually ought to have a primary key column called Id. Multiple HouseWindows will therefore have multiple Ids - but may share the same IdHouse.
And similarly for the cars and car windows case.
Implement a new table called ItemOfProperty. An item of property can be a car or a house.
CREATE TABLE ItemOfProperty
(
Id integer IDENTITY PRIMARY KEY,
ItemType char(1) NOT NULL,
ItemOfPropertyColumn varchar(200) ,
CONSTRAINT itemofproperty_id_itemtype_key UNIQUE (Id, ItemType),
CONSTRAINT itemofproperty_itemtype_check CHECK (ItemType in ('H', 'C'))
)
Car should have an ItemType attribute which is defaulted to 'C' (for Car), and should have a foreign key to ItemOfProperty:
CREATE TABLE Car
(
Id integer PRIMARY KEY,
ItemType char(1) NOT NULL DEFAULT 'C',
CarColumn varchar(200) ,
CONSTRAINT car_id_itemtype_fkey FOREIGN KEY (Id, ItemType) REFERENCES ItemOfProperty (Id, ItemType) ,
CONSTRAINT car_itemtype_check CHECK (ItemType = 'C')
)
Similarly for House:
CREATE TABLE House
(
Id integer PRIMARY KEY,
ItemType char(1) NOT NULL DEFAULT 'H',
HouseColumn_CompletelyDifferent integer,
CONSTRAINT house_id_itemtype_fkey FOREIGN KEY (Id, ItemType) REFERENCES ItemOfProperty (Id, ItemType),
CONSTRAINT house_itemtype_check CHECK (ItemType = 'H')
)
(Always insert an ItemOfProperty row prior to inserting its corresponding Car row or House row.)
Now, in the same way we've invented a thing called ItemOfProperty which can encompass cars and houses, we can also introduce a table called GenericWindow (which is what you've asked for) which encompasses car windows and house windows.
CarWindow and HouseWindow can reference GenericWindow in much the same way Car and House reference ItemOfProperty. And GenericWindow can reference ItemOfProperty. Because ItemType is used throughout, there is never any danger of a car window belonging to a generic window of the "house" type, nor of a house window belonging to a generic window of the "car" type.
Now you're free to add whatever non-car-specific / non-house-specific window attributes to GenericWindow, while keeping the car-window-specific attributes in CarWindow and the house-window-specific attributes in HouseWindow (EDIT - or ditch the CarWindow and HouseWindow tables entirely if you feel that generic window attributes are all you ever need).
Of course this type of approach could be extended beyond cars and houses, and beyond car windows and house windows. You'd need more tables, and more ItemTypes. But tables ItemOfProperty and GenericWindow would remain at the heart of the design.

SQL How to not insert duplicated values

I'm trying to create a procedure that inserts data into a table of registers but i don't want to repeat the second parameter, this is the table
CREATE TABLE Inscription
(
idClass INT references tb_class,
idStudent INT references tb_student,
)
The idea is that a student (idStudent) can register in various classes but not in the same class (idClass), I tried to add a unique constraint in the idStudent column but that only allows a student to register in one single class.
I always suggest that all tables have a numeric primary key. In addition, your foreign key references are not correct. And what you want to do is add a unique constraint.
The exact syntax depends on the database. The following is for SQL Server:
CREATE TABLE Inscriptions (
idInscription int identity(1, 1) primary key
idClass int references tb_classes(idClass),
idStudent int references tb_students(idStudnt)
unique (idClass, idStudent)
);
Notice that I name the tables as the plural of the entity, but the id using the singular.
The Inscriptions table probably wants other columns as well, such as the date/time of the inscription, the method, and other related information.
You are looking to create a constraint on your table that includes both columns idClass and idStudent.
Once that constraint is created, an attempt to insert duplicate class/student will result in an error being raised.
As your table does not seem to include a primary key, you would better make that constraint your primary key.
NB : you did not tell which RDBMS you are using hence cannot give you the exact syntax to use...
Your unique key needs to encompass both idClass and idStudent, so any particular combination cannot repeat itself.

Partial index on value from related table, rather than foreign key?

I'm working on a learning platform where students belong to a team, each of which belongs to a curriculum:
CREATE TABLE teams (
id SERIAL,
name string NOT NULL,
curriculum_id integer NOT NULL
);
CREATE TABLE curricula (
id SERIAL,
name string NOT NULL
);
CREATE UNIQUE INDEX index_curricula_on_name ON curricula USING btree (name);
Curricula have to be unique by name, and while most curricula are allowed to have multiple teams associated to them, one can not. I am trying to add a partial (unique) index on the teams table so as to add a restraint on the curriculum.
I know I can partially constrain the curriculum id itself with...
CREATE UNIQUE INDEX index_teams_on_curriculum_id ON teams USING btree (curriculum_id)
WHERE curriculum_id = 1;
... but this is not viable, as the IDs for the curriculum will vary across environments (dev, staging, etc).
Is there a way to constrain the teams.curriculum_id column by curricula.name instead?
You could implement something like this with a trigger or with a fake immutable function in a CHECK constraint. Both have their weak spots.
But this can also be implemented with pure SQL - only using NOT NULL, CHECK, UNIQUE and FK constraints. No weak spot.
CREATE TABLE curriculum (
curriculum_id serial PRIMARY KEY
, curriculum text UNIQUE NOT NULL
, team_unique boolean UNIQUE NOT NULL
, CONSTRAINT curriculum_team_uni UNIQUE (curriculum_id, team_unique) -- for multicolumn FK
);
CREATE TABLE team (
team_id serial PRIMARY KEY
, team text NOT NULL
, curriculum_id integer NOT NULL
, team_unique boolean NOT NULL
-- , CONSTRAINT fk1 FOREIGN KEY (curriculum_id) REFERENCES curriculum
, CONSTRAINT fk2 FOREIGN KEY (curriculum_id, team_unique)
REFERENCES curriculum (curriculum_id, team_unique)
);
CREATE UNIQUE INDEX team_curriculum_uni_idx ON team (team_unique)
WHERE team_unique;
Add a boolean NOT NULL column to parent and child table and make it UNIQUE in the parent table. So only one row in curriculum can be marked unique - to implement your restrictive requirement:
one can not
A partial unique index team_curriculum_uni_idx enforces only a single reference to it.
If there were multiple unique curriculums (to be referenced once only), we would remove the UNIQUE constraints on curriculum.team_unique and extend the partial unique index on team to (curriculum_id, team_unique).
The FK (fk2) forces to inherit the combination of columns.
This makes it simple to add a UNIQUE constraint to enforce a single team for the unique curriculum.
The default MATCH SIMPLE behavior of Postgres FK constraints only enforces combinations without NULL values. We can either use MATCH FULL or another plain FK (fk1) to enforce only existing curriculum_id. I commented the additional FK since we don't need it in this configuration (both FK columns defined NOT NULL).
SQL Fiddle.
Related:
MATCH FULL vs MATCH SIMPLE in foreign key constraints
CONSTRAINT to check values from a remotely related table (via join etc.)
Disable all constraints and table checks while restoring a dump
Enforcing constraints “two tables away”

Adding multiple foreign keys in column

Is there a way to store multiple values in a column that has a foreign key constraint?
Let's say I have a states table and a project table. Project table has a foreign key constraint with states table. Now we are implementing the same project in three different states. How can I select multiple states?
Sample
Create table states (
Stateid int identity primary key,
State varchar(100)
);
Projects Table
Create table projects (
ProjId int primary key identity,
ProjTitle varchar (100),
Budget decimal,
);
How can I insert multiple values in projects states table?
Based on TPHE answer lets me create another table called projectstates
Create table projectstates(
projStatid int identity primary key,
stateid int,
ProjId int
constraint fk_ProjId foreign key (ProjId) references Projects(ProjId),
constraint fk_stateid foreign key (stateid) references states(StateId)
);
Now how can i insert data in ProjectStates while adding project to the project table?
The best way I've found to do this is to create the second table with no foreign key constraint at first. Then you can populate both tables with the data, then introduce the constraint afterwards - assuming the data in the tables complies with the constraint.
Also, if a many to many relationship exists, add in a mapping table to allow this.
It would break some basic rules of database design to add multiple values. You should create a new table that has a one to many relationship with the states table (each state can have multiple values in the new table) and contains a column for the associated project IDs (also with a one-to-many relationship). Then you would join from the states table to the new table and then to the projects table or vice versa. More info on how and why to design databases in this way.

SQL database (Firebird): problem with a foreign key

My SQL database (Firebird) has a table named tTransaction. It contains two columns, senderFK and receiverFK. There are three other tables, tBufferStock, tFacility and tOutsideLocation.
A sender or a receiver can be either a buffer stock, our own facility or a location outside.
My problem is that I don't know how to let senderFK or receiverFK reference the right table.
I've thought of a new table between sender and the three possible senders with an ID, a number between 1 and 3 for the table and the referenced ID within this table, but actually that doesn't solve the problem. Any ideas?
Norbert
What you're trying to do cannot be done in SQL. You cannot reference up to three different tables with a single FK.
What you need to do would be:
create additional columns senderBufferstockFK, senderFacilityFK, and senderOutsideLocationFK
connect those to the appropriate tables
have a check constraint (if supported) or a trigger or some other mechanism on your main table to make sure only one of those three has a value at any given time
This would mean, at any given time, only one of the three "fk" column could have a value on it, but each FK column would be a specific FK to a specific table.
You could put this directly into the table you're talking about, or you could externalize this into a separate table and from your main table just reference that "intermediary" table, and from there have these three FK
YourTable.SenderFK --> Intermediary.PK
Intermediary.SenderBufferstockFK --> tBufferstock.ID
Intermediary.SenderFacilityFK --> tFacility.ID
Intermediary.SenderOutsideLocationFK --> tOutsideLocation.ID
Or you can just drop the FK-relationship, but that's definitely NOT a good idea!
Marc
Try the following schema:
tSenderReceiver (type INT, id INT, PRIMARY KEY (type, id))
tTransaction (id INT PRIMARY KEY, senderType INT, senderId INT, receiverType INT, receiverID INT,
FOREIGN KEY (senderType, senderID) REFERENCES tSenderReceiver,
FOREIGN KEY (receiverType, receiverID) REFERENCES tSenderReceiver
)
tBufferStock (type INT, id INT,
CHECK (type = 1),
PRIMARY KEY (type, id),
FOREIGN KEY (type, id) REFERENCES tSenderReceiver
)
tFacility (type INT, id INT,
CHECK (type = 2),
PRIMARY KEY (type, id),
FOREIGN KEY (type, id) REFERENCES tSenderReceiver
)
tOutsideLocation (type INT, id INT,
CHECK (type = 3),
PRIMARY KEY (type, id),
FOREIGN KEY (type, id) REFERENCES tSenderReceiver
)
SQL does not support a foreign key of the form "either this column in table X or that column in table Y". You can:
Refactor your database so that all three possible foreign key tables are combined into one, possibly called tCounterParty. This is definitely appropriate if the structure of those tables is identical or very similar. If they are not similar you can still take this approach and use three other tables, linked to tCounterParty, to hold the varying information.
Move your referential integrity from a foreign key into a trigger, if supported by your database.
can't you use 3 columns for sender and 3 for receiver? so you'll have bufferSenderFK, facilitySenderFK and facilitySenderFK. for a single transaction, 1 column can be used and other two will be null.