Unique key with Empty values - sql

This is the schema for the table, here defined a unique constraint with job_category_id, screening_type_id, test_id, sex.
CREATE TABLE job_profile
(
profile_id numeric(5,0) NOT NULL,
job_category_id numeric(5,0),
test_id numeric(5,0),
sex character(1),
default_yn character(1),
screening_type_id numeric(5,0),
CONSTRAINT job_profile_pkey PRIMARY KEY (profile_id),
CONSTRAINT fk_jobprofile_jobcate FOREIGN KEY (job_category_id)
REFERENCES job_category_mast (job_category_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fk_jobprofile_test FOREIGN KEY (test_id)
REFERENCES test_mast (test_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fk_prof_screentype FOREIGN KEY (screening_type_id)
REFERENCES screening_type_mast (screening_type_id) MATCH SIMPLE
ON UPDATE RESTRICT ON DELETE RESTRICT,
CONSTRAINT uk_job_test_sex_screening UNIQUE (job_category_id, screening_type_id, test_id, sex)
)
WITH (
OIDS=FALSE
);
ALTER TABLE job_profile
OWNER TO cdcis;
GRANT ALL ON TABLE job_profile TO cdcis;
GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE job_profile TO cdcis_app;
If one field is empty then the unique constraint fails here.
How to add constraint so that one empty value is accepted, so it will be unique.
Can handle this scenario in the application using JPA?

According to the documentation:
Null values are not considered equal
So unique constraint won't work this way.
You have 2 options:
Create a trigger that will manually check the data integrity and deny changes if the table contains more than one empty value.
Set default value to 0 and NOT NULL constraint on these columns. In that case, you will be able to have only one row containing empty (zero) value.
Update:
As Abelisto suggested, it can be easily done with functional indexes.
CREATE UNIQUE INDEX uix_job_test_sex_screening on job_profile(coalesce(job_category_id, -1), screening_type_id, test_id, sex);

Related

Change primary key from simple to composite with new column

I am trying to change the primary key of one of my tables from a simple to a composite key, where the composite should be composed of the old pk column and a newly created one.
I followed along this answer from a very similar question: https://stackoverflow.com/a/27832197/1948454
It almost works, except that there is no value set in the dependant table for the new column.
Here's the situation:
Suppose I have a table for a catalog, and a table for catalog entries. Before:
-- DDL Catalog
CREATE TABLE public.Catalog (
name_ VARCHAR(255) NOT NULL,
foo_ VARCHAR(255) NULL,
CONSTRAINT Catalog_pkey PRIMARY KEY (name_)
);
-- DDL CatalogEntry
CREATE TABLE public.CatalogEntry (
pricekey_ VARCHAR(255) NOT NULL,
pricekeyroot_ VARCHAR(255) NOT NULL,
catalog_name_ VARCHAR(255) NULL,
bar_ VARCHAR(255) NULL,
CONSTRAINT CatalogEntry_pkey PRIMARY KEY (pricekey_, pricekeyroot_)
);
-- public.CatalogEntry FOREIGN KEYs
ALTER TABLE public.CatalogEntry ADD CONSTRAINT CatalogEntry_catalog_name__fkey FOREIGN KEY (catalog_name_) REFERENCES Catalog(name_) ON DELETE CASCADE;
So CatalogEntry.catalog_name references to Catalog.name_.
Now I need to add another column version_ to the Catalog table, indicating a version of some catalog. This means I will have to create a new composite pk composed of name_ and version_. Here's my script:
-- UPDATE script
-- add the new version column and set all values to default of 1
ALTER TABLE Catalog ADD version_ INTEGER;
UPDATE Catalog SET version_ = 1;
ALTER TABLE Catalog ALTER column version_ SET not null;
-- update primary key and foreign key
BEGIN;
-- first, drop fkey constraint on CatalogEntry
ALTER TABLE CatalogEntry DROP CONSTRAINT CatalogEntry_catalog_name__fkey;
-- then, update Catalog primary key
ALTER TABLE Catalog DROP CONSTRAINT Catalog_pkey,
ADD CONSTRAINT Catalog_uni_name UNIQUE (name_),
ADD PRIMARY KEY (name_, version_);
-- now add new foreign key again to CatalogEntry
ALTER TABLE CatalogEntry ADD catalog_version_ INTEGER;
ALTER TABLE CatalogEntry
ADD CONSTRAINT CatalogEntry_catalog_name__catalog__fkey FOREIGN KEY (catalog_name_, catalog_version_)
references Catalog(name_, version_ ) ON DELETE CASCADE;
COMMIT;
-- finally, remove unique constraint on name since it is not needed anymore
ALTER TABLE Catalog DROP CONSTRAINT Catalog_uni_name;
After performing these steps, the primary and foreign key appear to be set correctly - but the value of CatalogEntry.catalog_version_ is null. The corresponding value of Catalog.version_ is set correctly to 1.
Where is my mistake? Do I also have to set CatalogEntry.catalog_version_ manually to 1? I would have assumed that it would be set automatically.
The value of CatalogEntry.catalog_version_ doesn't magically get set just because you define a foreign key constraint.
What effectively happened is that no row in CatalogEntry references a row in Catalog. The reason is that the default for foreign key constraints is MATCH SIMPLE, see the documentation:
MATCH SIMPLE allows any of the foreign key columns to be null; if any of them are null, the row is not required to have a match in the referenced table.
You should create the foreign key constraint as MATCH FULL so that either all or none of the columns must be NULL. Then you would have received an error creating the foreign key.
Solution: update CatalogEntry and set the column to 1 there as well, then define the foreign key with MATCH FULL.

Postgresql, references to unique constraint

I'm running PostgreSQL 9.4 and have the following table:
CREATE TABLE user_cars (
user_id SERIAL REFERENCES users (id) ON DELETE CASCADE,
car CHARACTER VARYING(255) NOT NULL,
CONSTRAINT test UNIQUE (user_id, car)
);
The table allows a user to have multiple cars, but only use the car name once. But other users may have the same car name.
I would like to have another table with references to the unique constraint test, and have tried stuff like:
CREATE TABLE mappings (
other_id CHARACTER(9) REFERENCES other (id) ON DELETE CASCADE,
user_cars REFERENCES user_cards (test) ON DELETE CASCADE
);
But that fails "obviously". I would like to make sure that other_id only have a single references to a user_car entry.
So to explain, how can I in table mappings have a references to test from table user_cars.
This is the thing that fails currently:
user_cars REFERENCES user_cards (test) ON DELETE CASCADE
Don't use composite foreign key references, if you can avoid it. Just add a unique id to the table:
CREATE TABLE user_cars (
user_car_id serial primary key,
user_id int REFERENCES users (id) ON DELETE CASCADE,
car CHARACTER VARYING(255) NOT NULL,
CONSTRAINT test UNIQUE (user_id, car)
);
Then mappings is simply:
CREATE TABLE mappings (
mapping_id serial primary key,
user_car_id int references user_cars(user_car_id) on delete cascade,
other_id CHARACTER(9) REFERENCES other (id) ON DELETE CASCADE,
);
If car should be unique, add UNIQUE constrain only on car column.
If user should be unique, add UNIQUE constrain only on user column.
If you add UNIQUE constrain on combination, then there will be duplicate values in the table.
UPDATE:
You can add multiple constraints on single column. With Foreign key add UNIQUE constraint as well on user_cars column in mapping table.

Postgresql: Changing Action For Foreign Key Constraint

I have a simple table like below.
create table chemlab.rule_header (
id serial PRIMARY KEY,
name varchar(50),
grade varchar(20),
class_tag varchar(20), --tag added to sammple if match
parent_id int REFERENCES chemlab.rule_header(id) DEFAULT NULL,
unique( grade, class_tag )
)
But afterwards, I found that I need to add ON DELETE action, the default is NO ACTION. I couldn't figure out how to change the action.
Now I have to DROP & ADD
ALTER table chemlab.rule_header
DROP CONSTRAINT rule_header_parent_id_fkey ;
ALTER TABLE rule_header
ADD CONSTRAINT rule_header_parent_id_fkey
FOREIGN KEY (parent_id) REFERENCES chemlab.rule_header(id) ON DELETE RESTRICT;
So what is the correct syntax to alter an action on foreign key constraint ?
Well, this not directly altering FOREIGN KEY constraint, and there are DROP and ADD still, though this is only one statement:
ALTER table chemlab.rule_header
DROP CONSTRAINT rule_header_parent_id_fkey,
ADD CONSTRAINT rule_header_parent_id_fkey
FOREIGN KEY (parent_id) REFERENCES chemlab.rule_header(id) ON DELETE RESTRICT;
Take a look at the documentation at https://www.postgresql.org/docs/current/sql-altertable.html. There are options to alter a few things about a constraint (like DEFERRABLE) but not for changing the action, as I understand you need.

How to replace compound primary key in table?

I have following table:
CREATE TABLE "PostViews"
(
"createdAt" timestamp with time zone NOT NULL,
"updatedAt" timestamp with time zone NOT NULL,
"PostId" integer NOT NULL,
"UserId" integer NOT NULL,
CONSTRAINT "PostViews_pkey" PRIMARY KEY ("PostId", "UserId"),
CONSTRAINT "PostViews_PostId_fkey" FOREIGN KEY ("PostId")
REFERENCES "Posts" (id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT "PostViews_UserId_fkey" FOREIGN KEY ("UserId")
REFERENCES "Users" (id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE
)
It is join-through table which links many Users to many Posts with compound pk PostId,UserId. What I have to do is to drop uniqueness from PostId,UserId in order to allow to store more than one PostView per post per user.
Should I remove pk and add index for PostId,UserId (or add index first and then drop pk)?
Or Should I add id serial column, make it pk and then drop compound pk?
You do not explain the updatedAt column purpose so I would drop it, rename the createdAt as viewedAt and make it part of the PK:
constraint PostViews_pkey PRIMARY KEY (PostId, UserId, viewedAt)
The chance of the user viewing the post twice at the exact same time is insignificant. If you want to provide for that case then wrap the client insertion code in a try/catch and retry in case of a PK exception.
Do not use double quotes for identifiers. It will be a real pain forever.

How to add a foreign key referring to itself in SQL Server 2008?

I have not seen any clear, concise examples of this anywhere online.
With an existing table, how do I add a foreign key which references this table? For example:
CREATE TABLE dbo.Projects(
ProjectsID INT IDENTITY(1,1) PRIMARY KEY,
Name varchar(50)
);
How would I write a command to add a foreign key which references the same table? Can I do this in a single SQL command?
I'll show you several equivalent ways of declaring such a foreign key constraint. (This answer is intentionally repetitive to help you recognise the simple patterns for declaring constraints.)
Example: This is what we would like to end up with:
Case 1: The column holding the foreign keys already exists, but the foreign key relationship has not been declared / is not enforced yet:
In that case, run this statement:
ALTER TABLE Employee
ADD FOREIGN KEY (ManagerId) REFERENCES Employee (Id);
Case 2: The table exists, but it does not yet have the foreign key column:
ALTER TABLE Employee
ADD ManagerId INT, -- add the column; everything else is the same as with case 1
FOREIGN KEY (ManagerId) REFERENCES Employee (Id);
or more succinctly:
ALTER TABLE Employee
ADD ManagerId INT REFERENCES Employee (Id);
Case 3: The table does not exist yet.
CREATE TABLE Employee -- create the table; everything else is the same as with case 1
(
Id INT NOT NULL PRIMARY KEY,
ManagerId INT
);
ALTER TABLE Employee
ADD FOREIGN KEY (ManagerId) REFERENCES Employee (Id);
or, declare the constraint inline, as part of the table creation:
CREATE TABLE Employee
(
Id INT NOT NULL PRIMARY KEY,
ManagerId INT,
FOREIGN KEY (ManagerId) REFERENCES Employee (Id)
);
or even more succinctly:
CREATE TABLE Employee
(
Id INT NOT NULL PRIMARY KEY,
ManagerId INT REFERENCES Employee (Id)
);
P.S. regarding constraint naming: Up until the previous revision of this answer, the more verbose SQL examples contained CONSTRAINT <ConstraintName> clauses for giving unique names to the foreign key constraints. After a comment by #ypercube I've decided to drop these clauses from the examples, for two reasons: Naming a constraint is an orthogonal issue to (i.e. independent from) putting the constraint in place. And having the naming out of the way allows us to focus on the the actual adding of the constraints.
In short, in order to name a constraint, precede any mention of e.g. PRIMARY KEY, REFERENCES, or FOREIGN KEY with CONSTRAINT <ConstraintName>. The way I name foreign key constraints is <TableName>_FK_<ColumnName>. I name primary key constraints in the same way, only with PK instead of FK. (Natural and other alternate keys would get the name prefix AK.)
You can add the column and constraint in one operation
ALTER TABLE dbo.Projects ADD
parentId INT NULL,
CONSTRAINT FK FOREIGN KEY(parentid) REFERENCES dbo.Projects
Optionally you could specify the PK column in brackets after the referenced table name but it is not needed here.
If the table already exists: Assuming you don't already have a column to store this data. If you do then skip this step.
ALTER TABLE [dbo].[project]
ADD [fkProjectsId] INT;
GO
ALTER TABLE [dbo].[projects]
ADD CONSTRAINT [FK_Projects_ProjectsId] FOREIGN KEY ([fkProjectsId]) REFERENCES [dbo].[Projects] ([ProjectsID])
GO