SQL Constraint on join table - sql

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)
);

Related

How to represent partially nullable foreign keys?

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.

How to use two columns in a foreign key constraint

I have two tables:
Article
Subscription
In the Article table I have two columns that make up the primary key: id, sl. In the Subscription table I have a foreign key 'idsl`.
I use this constraint :
constraint FK_idsl
foreign key (idsl) references CSS_SubscriptionGroup(id, sl)
But when I run the query, I getting this error:
Number of referencing columns in foreign key differs from number of referenced columns, table X
In Article Table I have two fields that are the primary key: id,sl. In the Subscription Table I have a foreign key 'idsl`
This design is broken - it is apparent that the composite primary key in Article(id, sl) has been mangled into a single compound foreign key in table Subscription. This isn't a good idea.
Instead, you will need to change the design of table Subscription to include separate columns for both id and sl, of the same type as the Article Table, and then create a composite foreign key consisting of both columns, referencing Article in the same order as the primary key, e.g:
CREATE TABLE Article
(
id INT NOT NULL,
sl VARCHAR(50) NOT NULL,
-- Other Columns
CONSTRAINT PK_Article PRIMARY KEY(id, sl) -- composite primary key
);
CREATE TABLE Subscription
(
-- Other columns
id INT NOT NULL, -- Same type as Article.id
sl VARCHAR(50) NOT NULL, -- Same type as Article.sl
CONSTRAINT FK_Subscription_Article FOREIGN KEY(id, sl)
REFERENCES Article(id, sl) -- Same order as Article PK
);
Edit
One thing to consider here is that by convention a column named table.id or table.tableid should be unique, and is the primary key for the table. However, since table Article requires an additional column sl in the primary key, it implies that id isn't unique.
correct syntax for relation:
CONSTRAINT FK_OtherTable_ParentTable
FOREIGN KEY(OrderId, CompanyId) REFERENCES dbo.ParentTable(OrderId, CompanyId)
You must try like this:
constraint FK_idsl foreign key (id,sl) references CSS_SubscriptionGroup(id,sl)

creating a table with reference to a non primary key column

I have two tables Users & Stores
Users table primary key is combination of address & phone number (users enroll through a web page and I don't want the same user to enroll twice), the userId column is serial but not a primary key
In Stores table the column of ownerID is it's userID from the users table - but since it's not primary key the reference can not be set (although it's serial)
how can I achieve this result?
Referenced column need not to be a primary key.
A foreign key can reference columns that either are a primary key OR a unique constraint.
This can be done in this way:
CREATE TABLE Users(
address varchar(100),
phone_number varchar(20),
userid serial,
constraint pk primary key (address, phone_number ),
constraint userid_unq unique (userid)
);
create table Stores(
storeid int primary key,
ownerID integer,
constraint b_fk foreign key (ownerID)
references Users(userid)
);
You should make userid primary key, so you could reference it easily in foreign keys. To eliminate duplication of phone and address, you can define a unique constraint or unique index for those columns.

violated - parent key not found error

I have the following error appearing:
INSERT INTO GroupMembers VALUES ('Goldfrat', 'Simon Palm')
*
ERROR at line 1:
ORA-02291: integrity constraint (SHAHA1.IAM_IS_GROUP_FK) violated - parent key
not found
The constraint in the GroupMembers table is:
CONSTRAINT iam_is_group_fk FOREIGN KEY(is_group) REFERENCES Members(group_name)
The Members Table looks like this:
CREATE TABLE Members (
group_name VARCHAR2(40),
CONSTRAINT g_id_pk PRIMARY KEY(group_name),
CONSTRAINT m_group_name_fk FOREIGN KEY(group_name) REFERENCES Artist(artistic_name));
All of the tables are created fine until it comes to creating the GroupMembers table. Anyone have any ideas? I've been scratching for quite a while.
The problem is that
CONSTRAINT iam_is_group_fk FOREIGN KEY(is_group) REFERENCES Members(group_name); references the table Members on the group_name field.
This means that the field is_group on the table GroupMembers must have an identical value on the table Members and group_name field.
In my opinion, this is bad practice.
First of all, you should have a primary key field on the table GroupMembers. You should not store the names of the group members in the table GroupMembers, but their corresponding id from the table Members.
Also, the table Members should look something like this:
CREATE TABLE Members (
member_id NUMBER PRIMARY KEY
member_name VARCHAR2(40),
CONSTRAINT g_id_pk PRIMARY KEY(member_id),
CONSTRAINT m_group_name_fk FOREIGN KEY(group_name) REFERENCES Artist(artistic_name));
Then on the table GroupMembers, I suppose you want to associate some members to their set of groups and back, so you should do something like this:
CREATE TABLE GroupMembers (
member_id NUMBER,
group_id NUMBER
)
CONSTRAINT iam_is_member_fk FOREIGN KEY(member_id) REFERENCES Members(member_id);
CONSTRAINT iam_is_member_fk FOREIGN KEY(group_id) REFERENCES Groups(group_id);
Supposing that you have a table Groups containing all the group details, with the primary key stored as number , and name group_id.
Always remember that each table must have a primary key. It is good practice for this key to be a number.
So by having member_id in Members, group_id in Groups, you can create a many-to-many relationship in GroupMembers. Also, put a unique index on this table so you don't have duplicates ( the same member associated with the same id several times ).
Look at this example linking users to roles. It is the same case:
error is
you have to use same column name which is defined in reference table
i.e
CONSTRAINT m_group_name_fk FOREIGN KEY(group_name) REFERENCES Artist(group_name)
That group_name should be define as primary key in Artist Table.
Order in which you are insert is the reason for the error.
Members(group_name) does not contain Goldfrat. If this is not true then it's not there in the table Artists.

what is FOREIGN KEY is for?

let's say i have 2 tables:
Department(depNum)
Worker(id,deptNum) - key should be id
now i want dept to reference an existing value in Department.
so i write in create table:
CREATE TABLE Worker(
id integer primary key,
dept integer references Department);
my question is, i've seen in many examples that you also put foreign key with the references statement. i don't understand what is primary key for.
does it mean that dept will be also a key on Worker?
thank you
From Wikipedia:
A primary key is a combination of columns which uniquely specify a
row. It is a special case of unique keys. . . . Primary keys were
added to the SQL standard mainly as a convenience to the application
programmer.
You cannot reference a record in a table without a primary key. A foreign key lets you reference a record in another table within an individual record. This foreign key is usually referencing the primary key in the foreign table.
This post has a lot of great information. In particular, check out the highest ranked answer for a bullet list of do's and do not's.
What's wrong with foreign keys?
This post gives a pretty decent explanation, given the poster's
original example:
What will these foreign keys do?
Let's say that each worker can only work in one department at any one time.
So each department has its own unique ID. This is the department's primary key because two departments should never have the same id.
Now, each individual worker must be tracked so they are also assigned their own unique ID. This is their primary key. You need to link the worker to the department that they work in and since they can only work in one department at a time, you can have their department as a foreign key. The foreign key in the worker table is linked to the ID of the department table.
This has more information: http://www.1keydata.com/sql/sql-foreign-key.html
You have two tables:
PLAYER,
primary key (unique) PK_player_id
player_name
foreignt key to TEAM.team_id FK_team_id
TEAM
primary key (unique) PK_team_id
team_name
Every PLAYER is in exact one TEAM.
The PLAYER has a FOREIGN-KEY to the TEAM (FK_team_id). Also you can delete the TEAM, which will delete all player in it cascading (if configured).
Now you can't create a player without an existing TEAM, because the database ensures this.
EDIT:
Didn't you ask for the foreign key?
The primary key is one or more than one column, which will identify on datarow within your database. If jou want to create a foreign key, you have to use a column or more than one column) which is unique.
In my example, there is a unique key (the primary key) for every table, because the name may change. To identify the 'target' of the foreign key, it has to be unique. so it is liklye to use the prmary key of the second table. (TEAM.PK_team_id)
I am not clear, the requirement should be
Department(dept)
Worker(id,dept) - key should be id
which means dept is the primary key in Department and foreign key in worker.
the foreign key is not unique in worker table but it is unique in Department Table.
The worker table cannot have some unknown department which is not defined in the Department.
Did I make sense ?
To ensure the integrity of the tables, not allowing you to enter values in the table (Worker) without referencing an existing row (at Department)
According to the SQL-92 Standard:
A foreign key (FK) may reference either a PRIMARY KEY or a UNIQUE CONSTRAINT. In the case of a PRIMARY KEY, the referenced columns may be omitted from the foreign key declaration e.g. the following three are all valid:
CREATE TABLE Department ( Department INTEGER NOT NULL PRIMARY KEY, ...);
CREATE TABLE Worker (dept INTEGER REFERENCES Department, ...);
CREATE TABLE Department ( Department INTEGER NOT NULL PRIMARY KEY, ...);
CREATE TABLE Worker (dept INTEGER REFERENCES Department (dept), ...);
CREATE TABLE Department ( Department INTEGER NOT NULL UNIQUE, ...);
CREATE TABLE Worker (dept INTEGER REFERENCES Department (dept), ...);
The following is not valid:
CREATE TABLE Department ( Department INTEGER NOT NULL UNIQUE, ...);
CREATE TABLE Worker (dept INTEGER REFERENCES Department, ...);
...because the referenced columns involved in the foreign key must be declared.
When declaring a simple (single-column) FK in-line the FOREIGN KEY keywords are omitted as above.
A composite (multiple-column) cannot be declared in-line and a simple FK need not be declared in-line: in these cases, the referencing column(s) AND the FOREIGN KEY keywords are required (the rules for the referenced columns remain the same as stated earlier) e.g. here are just a few examples:
CREATE TABLE Department ( Department INTEGER NOT NULL PRIMARY KEY, ...);
CREATE TABLE Worker (dept INTEGER, FOREIGN KEY (dept) REFERENCES Department, ...);
CREATE TABLE Department ( Department INTEGER NOT NULL UNIQUE, ...);
CREATE TABLE Worker (dept INTEGER, FOREIGN KEY (dept) REFERENCES Department (dept), ...);
CREATE TABLE DepartmentHistory
(
dept INTEGER NOT NULL,
dt DATE NOT NULL,
PRIMARY KEY (dt, dept),
...
);
CREATE TABLE Worker
(
dept INTEGER NOT NULL,
dept_dt DATE NOT NULL,
FOREIGN KEY (dept_dt, dept) REFERENCES DepartmentHistory,
...
);