How to address a multiple purpose column in sql? - sql

Scenario
I need to design a equipment movement from many sources and destinations. I have the folowing sample tables:
CREATE TABLE Area{
Id INT,
Name VARCHAR(50),
//some other fields
}
CREATE TABLE Stowage{
Id Int,
Name VARCHAR(50),
//some other fields
}
CREATE TABLE Movement{
OriginId INT,
DestinationId INT,
}
But I need some kind of movement like:
Origin : Area; Destination: Area
Origin : Area; Destination: Stowage
Origin : Stowage; Destination: Area
Origin : Stowage; Destination: Stowage
But I only have two columns and needs more than one foreign key per column.
Posible solution in mind
Create MovementArea, MovementStowage, MovementStowageArea tables and create its propertly foreigns keys.
Don't create foreigns key for the columns OriginId and DestinationId and fill it as needed.
Final Question
Is there another way to address this in sql or which of the provided solutions is most aceptable for this scenario?

Tricky. You have 4 foreign keys, so I would [naturally] create 4 foreign key columns, as in:
create table movement (
origin_area int,
origin_stowage int,
dest_area int,
dest_stowage int,
constraint fk1 foreign key origin_area references area (id),
constraint fk2 foreign key origin_stowage references stowage (id),
constraint fk3 foreign key dest_area references area (id),
constraint fk4 foreign key dest_stowage references stowage (id),
constraint chk_fk1 check (origin_area is null and origin_stowage is not null
or origin_area is not null and origin_stowage is null),
constraint chk_fk2 check (dest_area is null and dest_stowage is not null
or dest_area is not null and dest_stowage is null)
);
Now, as you see:
There are 4 nullable FK columns.
Each FK column has its corresponding FK constraint.
Also, origin_area and origin_stowage are mutually exclusive. Always one of them is null, while the other points to the other table. This is enforced by the constriaint chk_fk1.
The same can be said for dest_area and dest_stowage. Enforced by chk_fk2.

My first thought is something like this:
Create Table MovementEndpoint
(
ID Int
, Name Varchar(50)
, EndpointType Int -- Area, Stowage, etc
, EndpointDetailID Int -- FK to Area, Stowage, etc
)
Now your movements just go between endpoints, and the MovementEndpoint record lets you get to the Area or Stowage record as needed. The query logic will still be a little tricky, but no more so than your initial design requires.

Related

Sql creating table consisting of keys from other tables

this is probably a simple question but I am quite new to SQL and databases, so I have been following this site: https://www.postgresqltutorial.com/postgresql-foreign-key/ to try and create a table that consist of primary keys from other tables.
Here I have the structure of the database in an excel overview. With colors showing the relations. i am having problems with the One-To-Many tables. As I get the same error every time "ERROR: column "id" referenced in foreign key constraint does not exist
SQL state: 42703".
The SQL query:
DROP TABLE IF EXISTS ingredient_to_unit_relations;
DROP TABLE IF EXISTS ingrediens;
CREATE TABLE ingrediens (
id serial,
name_of_ingredient varchar(255),
price_per_unit int,
PRIMARY KEY (id)
);
CREATE TABLE ingredient_to_unit_relations (
ingredient_relation_id int GENERATED ALWAYS AS IDENTITY,
PRIMARY KEY (ingredient_relation_id),
constraint Fk_ingredient_id
FOREIGN KEY (id)
REFERENCES ingrediens (id)
);
You need to define the column in order to declare it as a foreign key:
CREATE TABLE ingredient_to_unit_relations (
ingredient_relation_id int GENERATED ALWAYS AS IDENTITY,
ingredient_id int,
PRIMARY KEY (ingredient_relation_id),
constraint Fk_ingredient_id FOREIGN KEY (ingredient_id) REFERENCES ingrediens (id)
);
I might recommend some somewhat different naming conventions (I changed the name id in the table above):
CREATE TABLE ingredients (
ingredient_id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name varchar(255),
price_per_unit int
);
CREATE TABLE ingredient_to_unit_relations (
ingredient_relation_id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
ingredient_id int,
CONSTRAINT Fk_ingredient_id FOREIGN KEY (ingredient_id) REFERENCES ingredients (ingredient_id)
);
Notes:
I am a fan of naming primary keys after the table they are in. That way, foreign keys and primary keys usually have the same name (and you can use using if you choose).
Avoid SERIAL. GENERATED ALWAYS AS IDENTITY is now recommended.
You can inline primary key constraints (as well as other constraints).
There is not generally a need to repeat the table name in a column (other than the primary key). So, name instead of name_of_ingredient.
Using int for a monetary column is suspicious. It doesn't allow smaller units. That might work for some currencies but in general I would expect a numeric/decimal type.

SQL only allow foreign keys where other column matches

I have two tables,
CREATE TABLE ActivityCodes (
ActivityCodeID INT NOT NULL PRIMARY KEY,
LocationID INT NOT NULL
);
and
CREATE TABLE LocationSettings (
LocationID INT NOT NULL PRIMARY KEY,
DefaultFooActivityCodeID INT,
FOREIGN KEY (DefaultFooActivityCodeID) REFERENCES ActivityCodes(ActivityCodeID)
);
with the foreign key relationship as indicated. Activity codes are valid only for the given LocationID and DefaultFooActivityCodeID in the LocationSettings table should be an ActivityCodeID where ActivityCodes.LocationID == LocationSettings.LocationID. How can I enforce that in SQL? Can it be done with constraints or foreign keys? Is it possible at all?
Edit: Just to add some clarification, this what valid data in these table should look like:
ActivityCodes
ActivityCodeID
LocationID
1
123
2
123
3
456
4
456
LocationSettings
LocationID
DefaultFooActivityCodeID
123
1
456
4
A location can have multiple activity codes. The default activity code for a location must be an activity code for that location. #Charlieface I tried using a composite foreign key as suggested in the answer you linked but I get an error saying LocationID on ActivityCodes is neither unique nor a primary key (I'm using MS SQL Server).
The DDL you provide, do not means what you have describe.
The DDL describe this:
The ActivityCodes are independent of LocationSettings.
The LocationSettings instead depends on an ActivityCodes (one ActivityCodes has many LocationSettings)
The foreign key is well defined with this like FOREIGN KEY (DefaultFooActivityCodeID) REFERENCES ActivityCodes(ActivityCodeID). If you try to insert a LocationSettings without first insert an ActivityCodes it will fail due constraint violation.
A foreign key reference does not have to be a primary key. This allows you to have two foreign key references to the same table, even if they are redundant:
CREATE TABLE ActivityCodes (
ActivityCodeID INT NOT NULL PRIMARY KEY,
LocationID INT NOT NULL,
UNIQUE (LocationID, ActivityCodeID)
);
CREATE TABLE LocationSettings (
LocationID INT NOT NULL PRIMARY KEY,
DefaultFooActivityCodeID INT,
FOREIGN KEY (DefaultFooActivityCodeID) REFERENCES ActivityCodes(ActivityCodeID),
FOREIGN KEY (LocationID, DefaultFooActivityCodeID) REFERENCES ActivityCodes(LocationID, ActivityCodeID)
);
Although this expresses what you want, you will find that this is a bit tricky to maintain in practice. Setting the default requires the following steps:
Insert a location with a NULL default.
Insert a row into ActivityCodes with the default activity.
Update the default in LocationSettings.

SQL: Is creating multiple foreign keys with one statement equivalent to creating them with one statement?

For example let's have the following table definition:
CREATE TABLE table1
(
id INT UNIQUE,
name VARCHAR(100) UNIQUE,
description VARCHAR(100),
PRIMARY KEY (id, name)
);
Now I would like to create another table which would have a foreign key to the above composite primary key. Would the following two statements be equivalent?
1)
CREATE TABLE table2
(
id INT PRIMARY KEY,
table1_id INT,
table1_name VARCHAR(100),
FOREIGN KEY (table1_id) REFERENCES table1(id),
FOREIGN KEY (table1_name) REFERENCES table1(name)
);
2)
CREATE TABLE table2
(
id INT PRIMARY KEY,
table1_id INT,
table1_name VARCHAR(100),
FOREIGN KEY (table1_id, table1_name) REFERENCES table1(id, name),
);
I noticed that behind the scenes Postgre SQL creates two FK db objects in the case of 1) and one FK object in the case of 2). Would everything work the same anyway?
Not at all. A foreign key reference should be to the primary key. In this case you have a composite primary key (although that is probably not needed, see below). Although foreign key references to unique keys are allowed (and some databases even allow foreign key references to any indexed columns), that is not a best practice.
When you use a composite primary key (your second example) you are guaranteeing that id/name are aligned in the first table. When you use separate references (your first example), you do not know that they are aligned, so the id could refer to one row in table1 and the name to another row. I doubt that is your intention.
In any case, repeating redundant data among tables is a bad practice. The better data model is:
CREATE TABLE table1 (
id INT PRIMARY KEY,
name VARCHAR(100) UNIQUE,
description VARCHAR(100),
);
CREATE TABLE table2 (
id INT PRIMARY KEY,
table1_id INT,
FOREIGN KEY (table1_id) REFERENCES table1(id)
);
Then, if you want the corresponding name, look up the name in the first table.
As a note, in Postgres, I would expect the INT to really be SERIAL so the database assigns a unique, increasing value when you insert new rows.
If you actually want two references to table1 then use two id references:
CREATE TABLE table2 (
id INT PRIMARY KEY,
table1_id INT,
table1_id_2 INT,
FOREIGN KEY (table1_id) REFERENCES table1(id),
FOREIGN KEY (table1_id_2) REFERENCES table1(id)
);

Is my query correct when I set primary key for 3 columns in a table?

In my case, I have only 1 candidate may go with 1 job at the time so they are must be 2 primary key.
Then, a column is as JobApplicationId use for the table CandidateDetail as a foreign key.
Is that correct when I decide to set these 3 columns above as primary key or there are other ways to address my problem here?
CREATE TABLE Candidate(
CandidateId int identity primary key,
FullName nvarchar(50)
)
CREATE TABLE Job(
JobId int identity primary key,
JobTitle nvarchar(50)
)
CREATE TABLE JobApplication(
JobApplicationId int identity,
JobId int,
CandidateId int,
CreatedDate datetime,
primary key(JobApplicationId, JobId, CandidateId)
)
CREATE TABLE CandidateDetail(
CandidateDetailId int identity primary key,
JobApplicationId int,
[Description] nvarchar(300)
)
ALTER TABLE JobApplication ADD CONSTRAINT fk_JobApplication_Job FOREIGN KEY (JobId) REFERENCES Job(JobId)
ALTER TABLE JobApplication ADD CONSTRAINT fk_JobApplication_Candidate FOREIGN KEY (CandidateId) REFERENCES Candidate(CandidateId)
ALTER TABLE CandidateDetail ADD CONSTRAINT fk_CandidateDetail_JobApplication FOREIGN KEY (JobApplicationId) REFERENCES JobApplication(JobApplicationId)
Instead of a primary key with three columns you could just have JobApplicationId as the primary key and a unique constraint on JobId, CandidateId.
Otherwise, two rows with JobApplicationId=1, JobId=1, CandidateId=1 and JobApplicationId=2, JobId=1, CandidateId=1 would still be valid in terms of your current primary key approach, but would be invalid in terms of the business case.
From both a performance and usability perspective, a compound primary key can be a hassle and can create performance issues. Personally, I would choose JobApplicationId as the primary key (because this is an identity column and will be unique for each record). Then, if you need to constrain the table so that JobId and CandidateId are always unique (not allowing more than 1 record for any given candidate and the job they've applied for) then I would use a compound Unique Constraint.
However, I would suggest that you evaluate those requirements more closely because what if a candidate applies for the same position in a different time frame? It might stand to reason that having the same candidate applied to the same job more than once in that table might be valid data.

Subtype disjointness

I've looked through the forums, but I can't seem to find exactly what I'm looking for. I have a supertype, employee, and three subtypes that reference employee's primary key, ID. The subtypes of employee must be disjoint. My problem arises in that I don't understand where to place a constraint to make this happen.
CREATE TABLE Employee(
ID INT,
PRIMARY KEY(ID));
CREATE TABLE Manager(
ID INT,
Salary INT NOT NULL,
PRIMARY KEY(ID),
FOREIGN KEY(ID) REFERENCES Employee(ID));
CREATE TABLE Server(
ID INT,
Tips INT NOT NULL,
PRIMARY KEY(ID),
FOREIGN KEY(ID) REFERENCES Employee(ID));
CREATE TABLE Hostess(
ID INT,
hourly_sal INT NOT NULL,
PRIMARY KEY(ID),
FOREIGN KEY(ID) REFERENCES Employee(ID));
I thought to create the constraint via a view of intersecting values, then a constraint limiting the view's entries to null only, as shown below:
CREATE VIEW EMPLOYEE_DISJOINT AS
((SELECT ID FROM Server)INTERSECT (SELECT ID FROM Hostess))
UNION
((SELECT ID FROM Hostess) INTERSECT (SELECT ID FROM Manager))
UNION
((SELECT ID FROM Server) INTERSECT (SELECT ID FROM Manager));
ALTER VIEW EMPLOYEE_DISJOINT
ADD CONSTRAINT disjoint CHECK(ID = NULL);
Seeking to create a constraint on the view requiring all of the primary keys in Employee to be unique to one and only one subtype of employee. Is there a better way to do this? While this method seems like it ought to work, I get the following error:
ADD CONSTRAINT disjoint CHECK(ID = NULL)
*
ERROR at line 2:
ORA-00922: missing or invalid option
Please help or point me in the direction of somewhere I may find it! Thanks a ton!
You could use a materialized view and add the CHECK (ID=NULL) constraint, but a simpler method might be just to have a discriminator column on the Employee table, e.g. employee_type with valid values ('Manager', 'Server', 'Hostess').