Referential integrity over table portion - sql

I'm designing a database and can't figure out how to model referential integrity.
I have the following tables
CREATE TABLE Groups
(
GroupId INT PRIMARY KEY,
GroupName VARCHAR(50)
)
CREATE TABLE GroupMembers
(
GroupId INT NOT NULL,
MemberId INT NOT NULL,
MemberName VARCHAR(50),
CONSTRAINT pk_GroupMember PRIMARY KEY (GroupId, MemberId)
)
CREATE TABLE Missions
(
MissionId INT PRIMARY KEY,
GroupId INT NOT NULL,
MissionName VARCHAR(50)
)
CREATE TABLE MissionRollAssignments
(
MissionId INT NOT NULL,
MemberId INT NOT NULL,
MemberRoll VARCHAR(50) --This will probably become RollId and move details to another table
)
Every mission will have assignments for some/all members of the corresponding group. There will be several missions associated with each group, but only one mission per group is active at a given time.
My question is:
Is it possible to enforce referenciay integrity for roll assignments such that only members
of the corresponding group (given by the MissionId) are selected? I know I can filter this from the GUI, but I'd feel more comfortable if I could create a FK constraint from MissionRollAssignments to GroupMembers while considering the GroupId indicated in the Mission.
A second question would be if you guys think this is a good way to model my domain, or maybe I should try a different approach.
Thanks in advance for any help on this.
Best regards,
Awer

You could put GroupId into MissionRollAssignments and then add two constraints as follows:
ALTER TABLE MissionRollAssignments
ADD CONSTRAINT fk1 FOREIGN KEY (GroupId, Memberid)
REFERENCES GroupMembers (GroupId, Memberid);
ALTER TABLE MissionRollAssignments
ADD CONSTRAINT fk2 FOREIGN KEY (GroupId, MissionId)
REFERENCES Missions (GroupId, MissionId);
To achieve this SQL Server first requires a (redundant) UNIQUE constraint on (GroupId, MissionId) in the Missions table. Other DBMSs are not so strict but SQL Server requires a FOREIGN KEY constraint to match exactly the columns of a uniqueness constraint.

You should use Foreign Keys to reinforce this, eg Mission.GroupId should refer to Group.GroupId.

Is it possible to enforce referenciay integrity for roll assignments such that only members of the corresponding group (given by the MissionId) are selected?
Yes. You need to use identifying relationships to propagate the GroupId all the way down to the bottom of this "diamond-shaped" dependency, similar to this:
Note FK1 and FK2 in front of MissionRollAssignment.GroupId, indicating that foreign keys exist up the both "sides" of this "diamond-shaped" dependency.
As single active mission can be modeled as a foreign key in the opposite direction, in this case as Group {GroupId, ActiveMissionNo} that references the Mission primary key.
Such circular foreign key presents a "chicken-and-egg" problem on a DBMS that doesn't support deferred constraints (which SQL Server doesn't). However, you can just leave Group.ActiveMissionNo NULL-able, so a DBMS that enforces foreign keys in a MATCH SIMPLE fashion (which SQL Server does) will ignore the whole composite foreign key if just one of its fields is NULL. This will allow you to temporarily "disable" the foreign key and break the "chicken-and-egg" cycle when inserting new data.

Related

Efficiently enforcing a 1:1 relationship between two rows with foreign key constraints without creating redundant unique indexes

I have two PostgreSQL tables designed in the following way:
create type content_owner as enum (
'document',
'task'
);
create table content (
id serial not null primary key,
owner content_owner not null,
owner_document_id int references document(id) deferrable initially deferred,
owner_task_id int references task(id) deferrable initially deferred,
-- ...
constraint collab_content_owner_document
check (owner_document_id is null or (owner = 'document' and owner_document_id is not null)),
constraint collab_content_owner_task
check (owner_task_id is null or (owner = 'task' and owner_task_id is not null))
);
create table document (
id serial not null primary key,
content_id int not null references content(id),
-- ...
);
create table task (
id serial not null primary key,
content_id int not null references content(id),
-- ...
);
I want to enforce a 1:1 relationship at the database level for the document<->content relationship and the task<->content relationship.
Adding the following constraints accomplishes that:
alter table collab_content add foreign key (owner_document_id, id) references document (id, content_id) deferrable initially deferred;
alter table collab_content add foreign key (owner_task_id, id) references task (id, content_id) deferrable initially deferred;
alter table document add foreign key (content_id, id) references collab_content (id, owner_document_id);
alter table task add foreign key (content_id, id) references collab_content (id, owner_task_id);
Since I’m saying the ID pair should reference the same ID pair in the other table for both directions. However, this also requires me to create the following indexes:
alter table document add unique (id, content_id);
alter table task add unique (id, content_id);
alter table collab_content add unique (id, owner_document_id);
alter table collab_content add unique (id, owner_task_id);
These indexes feel pretty redundant given that there’s already a primary key on the id columns for these tables. It feels like PostgreSQL should be smart enough to be able to use the existing primary key constraint to make sure the foreign key constraints are met. Ideally I wouldn’t create a second, redundant, index on these tables for the purpose of these foreign key constraints.
Is there a way for me to avoid creating new unique indexes and instead tell PostgreSQL to only lookup the unique ID when resolving the foreign key?
Will PostgreSQL detect that these unique indexes are redundant (because the first column is the primary key) and not materialize a new index on disk for their purpose?
Is there a better way to enforce this constraint?
Two-way linking like this is a recipe for headaches. I recommend avoiding reference cycles if you can. In your case, the simplest way to store this information is to relax the constraint that there cannot be a content without a document or a task. Ask yourself, how might such a situation occur, how else could it be avoided, and what damage might it cause if it happens?
If we can remove that constraint, then we can have a very simple structure where document and task each have a content_id foreign key, and a unique index on it to ensure that no two documents have the same content.
If we can't remove that constraint, then the answers to your questions are:
There is no way to avoid creating those new unique indexes for the foreign keys. Foreign keys must have matching unique indexes.
Postgres will not detect that these indexes are redundant, and they will indeed be materialized and take up space.

How to establish foreign key relationship

I got three tables.
User
Project
WorkFlow
In workflow ProjectId, UserId together should never repeat. Thats my
requirement.I mean the combination should never repeat.
And the ProjectId should be present in the Project table and UserId
should be present in the User table.
This is the requirement.
Steps i tried :
I made ProjectId, UserId as composite key in workFlow. But cant be able to maintain foreign key since two columns are not available in single table.
How to resolve this.
I am open to change my design also, since this is the initial stage of my development.
Main reuirement is
One table to store project (project table) related informations and
the other one(workFlow) hold the record which project is assigned to
which user.
Foreign keys do not control uniqueness; they only control referential integrity. For uniqueness, you need unique constraints:
create table dbo.Workflow (
Id int identity(1,1) primary key,
ProjectId int not null,
UserId int not null,
foreign key (ProjectId) references dbo.Project (Id),
foreign key (UserId) references dbo.[User] (Id),
unique (UserId, ProjectId)
);
EDIT: If you don't need a surrogate key in this table, and don't care much about its possible children, you can simplify the structure by switching from surrogate primary key to the natural one. With table becoming more narrow, it will increase performance in high load scenarios by reducing its disk footprint:
create table dbo.Workflow (
ProjectId int not null,
UserId int not null,
primary key (UserId, ProjectId)
foreign key (ProjectId) references dbo.Project (Id),
foreign key (UserId) references dbo.[User] (Id),
);
And yes, constraints should be uniquely named, it will make schema comparisons and updates much easier.

SQL Foreign key abrevation

Are these T-SQL declarations equals?
CREATE TABLE Person
(
ID INT PRIMARY KEY,
NAME VARCHAR(60)
)
CREATE TABLE Dog
(
CHIP_ID INT PRIMARY KEY,
OWNER_ID INT REFERENCES Person(ID)
)
and
CREATE TABLE Person
(
ID INT PRIMARY KEY,
NAME VARCHAR(60)
)
CREATE TABLE Dog
(
CHIP_ID INT PRIMARY KEY,
OWNER_ID INT,
FOREIGN KEY(OWNER_ID) REFERENCES Person(ID)
)
I'm talking of course about the foreign key, I'm not sure if I have to specify it is a foreign key or not.
Thank you.
Yes, the DBMS see both as the same. But humans can many times miss important details when the code is cryptic. In fact, my preference is this:
CREATE TABLE Person(
ID INT not null,
Name VARCHAR(60) not null,
constraint PK_Person primary key( ID )
);
CREATE TABLE Dog(
ID INT not null,
OwnerID INT,
constraint PK_Dog primary key( CHIP_ID ),
constraint FK_Dog_Owner foreign key( OWNER_ID ) REFERENCES Person( ID )
);
Using the constraint clause not only defines the primary and foreign keys, but allow us to give them a meaningful name. And the surrogate key of each table should be named "ID". Any foreign keys in other tables will expand that name according to its context (RoleID). As you have in the Dog table with OwnerID. Another table with a FK to the same Person table may name it GroomerID or whatever else shows the role that person plays in the context of the table.
Also, as you can see, I prefer CamelCase with SQL Server, leaving OWNER_ID for Oracle.
Some even go so far as to place either NULL or NOT NULL after each column definition. But I find that adds clutter and doesn't really supply information even a beginning SQL developer doesn't already know. So I only supply NOT NULL when appropriate and let the default carry. Actually, in the later versions of Oracle and SQL Server, the NOT NULL for the primary key field is optional as the primary key is going to be defined as NOT NULL no matter what.
Long ago there seemed to be an informal contest to see who could cram the most operations into the fewest words or even characters. Just stay far away from that kind of thinking. But do make everything you write meaningful.
In general, use every opportunity to add meaningful information to the code. The computer doesn't care. Write to the other developers who will follow you.
Both T-SQL will create the foreign key you need. However, I believe the second approach where the code explicitely states "FOREIGN KEY..." is a good contribution to keep easy-maintenance and clean code for future software engineer understanding.

Condition for some column in foreign key

There is table A with columns (Id, BId). BId is foreign key to table B. B has columns (Id, Type).
CREATE TABLE [A] (
[Id] INT IDENTITY CONSTRAINT [PK_A_Id] PRIMARY KEY,
[BId] INT NOT NULL CONSTRAINT [FK_A_B] REFERENCES [B](Id)
)
GO
CREATE TABLE [B] (
[Id] INT IDENTITY CONSTRAINT [PK_B_Id] PRIMARY KEY,
[Type] INT NOT NULL
)
GO
So, it is very simple scheme, but I want to add condition for foreign keys like "type should be 0". It should be something like
CONSTRAINT [FK_A_B] REFERENCES [B](Id) WHERE [B].[Type] = 0
How to use UNIQUE keyword or smth else correctly to realize it?
What you are trying to achieve is not a task for FOREIGN KEY. In FK you can't specify additional conditions on a target table. If you have such need, usually it means that your DB is not normalized. It looks like table [B] stores several data entities in one table, and Type column determines what entity it is. If you broke normalization rules, declarative integrity means like FK don't work for you. From now on you have to control integrity on your own. You can do this in application logic or create triggers (procedural integrity). In any case there will be no foreign key constraint in DB.
It would be nice if this could be done using a filtered unique index. But, SQL Server doesn't allow that. So here is another idea:
Start by defining a (redundant) unique constraint on:
CREATE TABLE [B] (
[Id] INT IDENTITY CONSTRAINT [PK_B_Id] PRIMARY KEY,
[Type] INT NOT NULL,
UNIQUE (ID, Type)
)
GO
Now, I don't think you can define the constraint as:
CONSTRAINT [FK_A_B] (ID, 0) REFERENCES [B](Id, Type)
But, I think you can trick it by doing:
_Type as 0,
CONSTRAINT [FK_A_B] (ID, _Type) REFERENCES [B](Id, Type)
That is, add a computed column and use that for the constraint.

Duplicate Primary Keys?

Looking at an example from Java Persistence with Hibernate, there are 2 tables:
create table USERS (
USER_ID bigint not null primary key,
USERNAME varchar(15) not null unique,
NAME varchar(50) not null,
...
)
create table BILLING_DETAILS (
BILLING_DETAILS_ID bigint not null primary key,
ACCOUNT_NUMBER VARCHAR(10) not null unique,
ACCOUNT_NAME VARCHAR(50) not null,
ACCOUNT_TYPE VARCHAR(2) not null,
USER_ID bigint foreign key references USERS
)
The book mentions that the following indicates a many-to-one relationship:
USER_ID bigint foreign key references USERS
Next, it says that the following SQL statements show one-to-one associations:
USER_ID bigint unique foreign key references USERS
BILLING_DETAILS_ID bigint primary key foreign key references USERS
As I understand, the first statement means that USER_ID of the BILLING_DETAILS table will be unique and reference the primary_key of the USERS table, i.e. references USERS.
The second makes the BILLING_DETAILS_ID the primary_key and reference the USERS's primary key.
If we used both of these one-to-one relationships, then we'd have duplicate fields: USER_ID and BILLING_DETAILS_ID since they effectively are the same data?
They aren't necessarily the same data in the absence of an additional CHECK() constraint that requires user ID and billing details ID to be equal.
I have not read the book, but as a database professional, I'd consider that implementation of a one-to-one relationship to be in error. A unique constraint on billing_details.user_id, along with its obvious foreign key constraint, is sufficient to guarantee a one-to-one relationship between the two tables. (That kind of relationship doesn't make much sense in the real world, though, when you think about what billing details means.)
From a database viewpoint, letting billing_details.user_id be nullable seems questionable, too.
Later . . .
I just thought of an alternative explanation of what the book says. When it says
USER_ID bigint unique foreign key references USERS
BILLING_DETAILS_ID bigint primary key foreign key references USERS
it's describing two different ways to implement a one-to-one relationship. It's not saying you should use both statements to implement one one-to-one relationship.
But this, too, is an error, in that USER_ID isn't declared to be NOT NULL.
BILLING_DETAILS_ID is practically the primary key, which means it cannot be duplicated. However, USER_ID is NOT, you can have duplicated USER_ID in the table BILLING_DETAILS, it is treated as a data which should have a reference in the table USER.
the relationship between BILLING_DETAILS and USER is one(USER)-to-many(BILLING_DETAILS)