How to represent partially nullable foreign keys? - sql

Say that you want to store contact phone numbers, people, and households in a database. Every person belongs to exactly one household. A phone number may be associated with a particular individual in a household, or may be a general number for the household. These relationships are partially expressed in the following Oracle SQL:
CREATE TABLE HOUSEHOLD (
HOUSEHOLD_ID INTEGER PRIMARY KEY
);
CREATE TABLE PERSON (
PERSON_ID INTEGER PRIMARY KEY,
HOUSEHOLD_ID INTEGER NOT NULL,
CONSTRAINT FK_PERSON_HOUSEHOLD
FOREIGN KEY (HOUSEHOLD_ID)
REFERENCES HOUSEHOLD (HOUSEHOLD_ID)
);
CREATE TABLE CONTACT_PHONE (
PHONE_NUMBER CHAR(10) PRIMARY KEY,
HOUSEHOLD_ID INTEGER NOT NULL,
PERSON_ID INTEGER NULL,
CONSTRAINT FK_PHONE_HOUSEHOLD
FOREIGN KEY (HOUSEHOLD_ID)
REFERENCES HOUSEHOLD (HOUSEHOLD_ID),
CONSTRAINT FK_PHONE_PERSON
FOREIGN KEY (PERSON_ID)
REFERENCES PERSON (PERSON_ID)
);
The foreign keys and NULL/NOT NULL constraints ensure that every person belongs to exactly one household, that every contact phone is associated with exactly one household, and that a contact phone may or may not be associated with person. One thing that they do not prevent is a phone number that is associated with one household, and with a person who belongs to a different household. Is there a standard way to express this kind of relationship using database constraints? The example given is for Oracle, but solutions for other database platforms would be welcome, as well.

We want to have a foreign key to the HOUSEHOLD_ID and PERSON_ID columns of the PERSON table, but don't want it to be checked if the PERSON_ID column of the CONTACT_PHONE table is NULL. The solution is to create a virtual/computed column that replicates HOUSEHOLD_ID, but only when PERSON_ID is not NULL, then use it in the foreign key instead of HOUSEHOLD_ID:
CREATE TABLE CONTACT_PHONE (
PHONE_NUMBER CHAR(10) PRIMARY KEY,
HOUSEHOLD_ID INTEGER NOT NULL,
PERSON_ID INTEGER NULL,
PERSON_HOUSEHOLD_ID GENERATED ALWAYS AS (
CAST(DECODE(PERSON_ID, NULL, NULL, HOUSEHOLD_ID) AS INTEGER)
) VIRTUAL,
CONSTRAINT FK_PHONE_HOUSEHOLD
FOREIGN KEY (HOUSEHOLD_ID)
REFERENCES HOUSEHOLD (HOUSEHOLD_ID),
CONSTRAINT FK_PHONE_PERSON
FOREIGN KEY (PERSON_HOUSEHOLD_ID, PERSON_ID)
REFERENCES PERSON (HOUSEHOLD_ID, PERSON_ID)
);
In this way, when PERSON_ID is not NULL, PERSON_HOUSEHOLD_ID will be the same as HOUSEHOLD_ID, and FK_PHONE_PERSON will be checked normally.
But, when PERSON_ID is NULL, PERSON_HOUSEHOLD_ID will also be NULL. Since both of the local columns participating in FK_PHONE_PERSON are NULL, the constraint will not be checked.

Related

How to check for not null value in relationship table in PostgreSQL?

I have these three tables in a PostgreSQL db and as you can see books has an author field (referencing users) and users has a company_id field referencing companies.
create table companies (
id serial primary key,
name varchar not null
);
create table users (
id serial primary key,
email varchar not null,
company_id int,
constraint fk_company
foreign key(company_id)
references companies(id)
);
create table books (
id serial primary key,
title varchar not null,
author_id integer not null,
constraint fk_author
foreign key(author_id)
references users(id)
);
A user may or may not belong to a company, but to create a book the user must have a company reference.
I am wondering if there is a way to implement a CHECK constraint on the author_id column which would ensure that the author has a company reference.
Maybe something like: author_id integer not null CHECK(users.company_id is not null), but of course this doesn't work.
Is there some sort of constraint I can use to check columns in relations?
Also am I even approaching this problem in the right way?
Thanks in advance. :)
Looking at constraints:
A Publisher can have zero or more publications.
An Author/User can have zero or more books Authored.
A Book will have one Publisher & one or more Authors.
Move the company_id constraint to books table. If & when an User/Author[s] publishes a book; They become a new Publishing Company and an entry to Publishing Company.

Design sql tables with list of foreign keys

I want to create an application for rotating pairs in a team every day. I need to store this in the database. Requirments are:
A team should be assigned to one ore more members.
Each team can have multiple tabs and different members allocate in them.(If team consist of 4 members for the particular tab only 3 should be part of it)
Each tab will have a pair of members or list of pairs per day stored.
I have ended up designing something like the example below:
create table if not exists team (
id serial not null primary key,
name text not null
);
create table if not exists member (
id serial not null primary key,
team_id integer references team(id),
nickname text
);
create table if not exists team_tab (
id bigserial not null primary key,
team_id integer references team(id) on delete cascade,
name text not null,
member_ids integer[],
);
create table if not exists team_tab_pairs (
id bigserial not null primary key,
team_tab_id integer not null references team_tab(id) on delete cascade,
tab_date date not null,
pair_ids integer[][],
);
I need an advice and suggestions how could I achieve this without having a list of references ids stored in the 2 tables below.
You need an extra table to design an M:N relationship. This is the case, for example, between "team tab" and "member". In addition to both main entities:
create table member (
id serial not null primary key,
team_id integer references team(id),
nickname text
);
create table team_tab (
id bigserial not null primary key,
team_id integer references team(id) on delete cascade,
name text not null
);
...you'll need to create a table to represent the M:N relationship, as in:
create table team_tab_member (
team_tab_id bigint not null,
member_id int not null,
primary key (team_tab_id, member_id) -- optional depending on the model
);

How can I fix this PostgreSQL foreign key error?

I have created three tables (users, candidates and votes), and when a user create a vote, it is sent to the votes table which has a foreign key candidate from the candidates table, but when I create a new vote, I get this error
ERROR: insert or update on table "votes" violates foreign key constraint "votes_candidate_fkey"
DETAIL: Key (candidate)=(6) is not present in table "candidates".
The candidate table has the candidate with id 6, but when I create a vote I get a foreign key error, how can I solve this, below is the shema
CREATE TABLE IF NOT EXISTS users(
id serial PRIMARY KEY,
first_name VARCHAR (100) NOT NULL,
last_name VARCHAR (100) NOT NULL,
other_name VARCHAR (100) NOT NULL,
email VARCHAR (100) UNIQUE NOT NULL,
password VARCHAR (100) NOT NULL,
phone_Number VARCHAR (100) UNIQUE NOT NULL,
passport_Url VARCHAR (255) NOT NULL,
is_Admin BOOLEAN DEFAULT 'no'
);
CREATE TABLE IF NOT EXISTS candidates(
id serial PRIMARY KEY,
office INTEGER REFERENCES offices(id),
party INTEGER REFERENCES parties(id),
candidate INTEGER UNIQUE REFERENCES users(id)
);
CREATE TABLE IF NOT EXISTS votes(
created_by INTEGER REFERENCES users(id),
office INTEGER REFERENCES offices(id),
candidate INTEGER REFERENCES candidates(id),
created_On DATE NOT NULL DEFAULT NOW(),
PRIMARY KEY (office, created_By)
);
User 6 exists, but you only have five candidates and their ids are 1, 2, 3, 4, and 5.
Your foreign key is to id, not the candidate column:
candidate INTEGER REFERENCES candidates(id),
----------------------------------------^
The appropriate id is 5, for user 6.
You may want to set up the candidates table with the primary key being the user id (that is, candidates are subsets of users). If so remove the serial column:
CREATE TABLE IF NOT EXISTS candidates(
candidate INTEGER PRIMARY KEY REFERENCES users(id),
office INTEGER REFERENCES offices(id),
party INTEGER REFERENCES parties(id)
);
I would recommend this. If you as the database designer are already confused about the difference between a "candidate" and a "user", then no doubt future users of the database will have the same confusion.
The message is very much clear that your candidates tabled does not contain id=6 but you are trying to insert that value into votes tables that's why you got the error, cause it is a foreign key violation
in votes table candidate INTEGER REFERENCES candidates(id) this is relate to candidates tables id column not with candidate column that you thought

SQL Constraint on join table

I have four tables in a PostgreSQL database:
Company
User (with a foreign key column company_id)
Location (with a foreign key column company_id)
UserLocations (an association table, with foreign key columns user_id and location_id)
Essentially:
a company has many users and locations
a user belongs to a company and has many locations
a location belongs to a company and has many users
I want to know if there is a way for the database to constrain entries in the UserLocations association table such that the referenced user and location must have an identical company_id value. I don't want a user from company A to have a location from company B.
I can check this at my application layer (rails) but would be interested in making this a hard database level constraint if the option exists.
One way you can accomplish this is with foreign key references and redundancy.
So, the UserLocations table would have a UserId, LocationId, and CompanyId. It would then have the following foreign key relationships:
foreign key (UserId, CompanyId) references Users(UserId, CompanyId)
foreign key (LocationId, CompanyId) references Locations(LocationId, CompanyId)
Of course, you have to declare Users(UserId, CompanyId) and Locations(LocationId, CompanyId) as unique keys for the reference. This is a bit redundant, but it does guarantee the matching to company without creating triggers.
Overlapping foreign key constraints are your friend.
create table company (
company_id integer primary key
);
-- Reserved words include "user". Better to use identifiers that
-- are not reserved words.
create table "user" (
user_id integer primary key,
company_id integer not null references company (company_id),
-- Unique constraint lets this be the target of a foreign key reference.
unique (user_id, company_id)
);
create table location (
location_id integer primary key,
company_id integer not null references company (company_id),
unique (location_id, company_id)
);
create table user_location (
user_id integer not null,
location_id integer not null,
company_id integer not null,
-- Not sure what your primary key is here.
-- These foreign keys overlap on the column "company_id", so there
-- can be only one value for it.
foreign key (user_id, company_id) references "user" (user_id, company_id),
foreign key (location_id, company_id) references location (location_id, company_id)
);

Foreign key with many-to-many containment constraint

Quick question that I'm having trouble putting into search terms:
Suppose I have a many-to-many relationship between players and teams:
CREATE TABLE players (
id bigserial primary key,
name text not null
);
CREATE TABLE teams (
id bigserial primary key,
name text not null,
team_captain_id bigint not null references players(id)
);
CREATE TABLE team_players (
id bigserial primary key,
player_id bigint not null,
team_id bigint not null
);
ALTER TABLE team_players ADD CONSTRAINT uq_team_players UNIQUE (player_id,team_id);
Now, each team is required to have a team captain, who is also a player. But I want to enforce that the team captain is also a member of that team (or, semantically equivalent, that the team captain is not redundantly in the join table)
Is there a standard way to model this? I can think of several ways that would actually get the job done, but I'm wondering if there's a standard, elegant way of doing it.
Thanks!
EDIT: Although it would be nice to have the captain a required field, I would also be content with the following condition: If the team has at least 1 member, then a captain is defined for it.
EDIT 2: OK, here's an attempt for clarification. Pardon the unnecessary "id" columns.
CREATE TABLE players (
id bigserial primary key,
name text not null
);
CREATE TABLE teams (
id bigserial primary key,
name text not null
);
CREATE TABLE leaderships (
id bigserial primary key,
team_id bigint not null references teams(id),
captain_id bigint not null references players(id),
-- Make a key.
UNIQUE (team_id,captain_id),
-- Only one leadership per team.
UNIQUE (team_id)
);
CREATE TABLE team_players (
id bigserial primary key,
team_id bigint not null,
captain_id bigint not null,
player_id bigint not null,
-- One entry per player.
UNIQUE (team_id,captain_id,player_id),
-- Valid reference to a leadership.
FOREIGN KEY (team_id,captain_id) references leaderships(team_id,captain_id),
-- Not the captain.
CHECK (player_id <> captain_id)
);
You need to learn about database design.
Find fill-in-the-(named-)blank statements that describe your application. Each statement gets a table. A table holds the rows that make a true statement.
// [player_id] is a player
player(player_id)
// [team_id] is a team
team(team_id)
// player [player_id] plays for team [team_id]
team_players(team_id,player_id)
Turns out you don't need a player_team_id. The team_players (player_id,team_id) pairs are 1:1 with them so you can use those instead. On the other hand team-player contracts are 1:1 with them so they might have a role.
Every team_players player_id is a player player_id (since every team player is a player). We say that via a FOREIGN KEY delaration (and the DBMS enforces it):
FOREIGN KEY (team_id) REFERENCES team (team_id)
FOREIGN KEY (player_id) REFERENCES player (player_id)
It's true that team_players (player_id,team_id) is unique. But more than that is true. No contained subrow is unique. This matters to database design.
A unique subrow is a "superkey". A unique subrow containing no smaller unique subrow is a "key". Use KEY for that. Any superset of key columns is unique. But SQL requires that the target of a FOREIGN KEY be explictly declared so. Use UNIQUE for that. Traditionally in SQL you pick one key as PRIMARY KEY. This matters to some SQL functionality. (Technically, in SQL KEY means UNIQUE and PRIMARY KEY means UNIQUE NON NULL. Ie SQL does not enforce no-smaller-contained-unique-subrow.)
KEY (team_id,player_id)
(If you also had a team_player_id in team_players it too would be a KEY, typically the PK.)
Some players are captains. It's 1:1. So both team_id and player_id are unique.
// [player_id] captains [team_id]
team_captains(team_id,player_id)
FOREIGN KEY (team_id) REFERENCES team (team_id)
FOREIGN KEY (player_id) REFERENCES player (player_id)
KEY (team_id)
KEY (player_id)
A team-captain pair must appear as a team-player pair.
FOREIGN KEY (team_id,player_id) REFERENCES team_players (team_id,player_id)
Your thoughts on redundancy re captains is admirable. It is true that there is a sense in which it is redundant to have the database record that a person is a team's captain and that they are on a given team.
-- instead of having team_players(team_id,player_id)
-- team_players team_players FK now to here
// player [player_id] is a non-captain on team [team_id]
team_non_captains(team_id,player_id)
FOREIGN KEY (team_id) REFERENCES team (team_id)
FOREIGN KEY (player_id) REFERENCES player (player_id)
KEY (team_id,player_id)
However, every time you wanted the players on a team you'd have to say:
-- now team_player =
// player [player_id] is a non-captain on team [team_id]
// OR player [player_id] is captain of team [teamm_id]
select * from team_non_captains UNION select * from team_captains
It turns out it is probably worse to have one "redundant" row per captain than it is to have one "redundant" union operation (and "redundant" human parsing of a sub-expression) per query involving a whole team. Just make the most straightforward statements.
(Avoid nulls in an initial design. They complicate table meanings and query meanings. Especially query meanings because SQL does not evaluate expressions involving nulls in a way that means means anything in particular in terms of the meanings of tables in a query, let alone "not known" or "not applicable". One uses them as an engineering tradeoff which you must learn to judge.)
The simplest possible design, a real-world solution could possible involve more complexity.
a player is part of exactly one team so player.team_id is functionally dependent on player.id, and points to teams.id
a team has one captain which should be present in the players table
The captain should be part of the team, which requires an additional constraint teams.{team_id,captain_id} -> players.{team_id,id}
CREATE TABLE players
( id bigserial primary key
, team_id INTEGER NOT NULL
, name text not null
, UNIQUE (team_id, id) -- we need this because the FK requires it
);
CREATE TABLE teams
( id bigserial primary key
, captain_id bigint not null references players(id)
, name text not null
);
ALTER TABLE players
ADD FOREIGN KEY (team_id )
REFERENCES teams(id)
;
-- captain must be part of the team
ALTER TABLE teams
ADD FOREIGN KEY (id, captain_id )
REFERENCES players( team_id, id)
;
UPDATE it appears a player can take part of more than one team, so a N:M junction-table will be needed:
CREATE TABLE players
( player_id bigserial PRIMARY KEY
, name text not null
);
CREATE TABLE teams
( team_id bigserial PRIMARY KEY
, captain_id bigint
, name text not null
);
CREATE TABLE players_teams
(player_id INTEGER NOT NULL REFERENCES players(player_id)
, team_id INTEGER NOT NULL REFERENCES teams(team_id)
, PRIMARY KEY (player_id,team_id)
);
ALTER TABLE teams
ADD FOREIGN KEY (team_id,captain_id)
REFERENCES players_teams(team_id,player_id)
;