Postgresql conditional check - postgresql

I've a users table:
id
type
merchant_id
agent_id
...
I want to add a check constraint using the following conditions:
if type == 'MERCHANT' then merchant_id is not null
if type == 'AGENT' then agent_id is not null
How this constraint is implemented?
Update:
I forgot to mention an extra requirement. the user can only have an agent_id or merchant_id.

You may add the following check constraints to the create table statement:
CREATE TABLE users (
id INTEGER,
type VARCHAR(55),
merchant_id INTEGER,
agent_id INTEGER,
...,
CHECK ((type <> 'MERCHANT' OR merchant_id IS NOT NULL) AND
(type <> 'AGENT' OR agent_id IS NOT NULL))
)

You can check constraints in CREATE TABLE command:
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id SERIAL PRIMARY KEY,
type VARCHAR (50),
merchant_id INT (50),
CONSTRAINT if_attribute_then_field_is_not_null
CHECK ( (NOT attribute) OR (field IS NOT NULL) )
);
OR in Alter Table command:
ALTER TABLE users
ADD CONSTRAINT if_attribute_then_field_is_not_null
CHECK ( (NOT attribute) OR (field IS NOT NULL) )
);

Related

CREATE TABLE T1 ... LIKE ... creating table with cluster key if source table defined with cluster key in SNowflake

It is mentioned in documents that the snowflake does not propagate cluster key from the source table if the table is created with 'CREATE TABLE .... LIKE.....';
From Documentation :
• An existing Clustering key is not propagated when a table is created using CREATE TABLE … LIKE.
CREATE
OR replace TABLE
CUST_1GB_CL ***cluster BY (C_CUSTKEY)*** (
C_CUSTKEY NUMBER(38, 0) NOT NULL
,C_NAME VARCHAR(25) NOT NULL
,C_ADDRESS VARCHAR(40) NOT NULL
,C_NATIONKEY NUMBER(38, 0) NOT NULL
,C_PHONE VARCHAR(15) NOT NULL
,C_ACCTBAL NUMBER(12, 2) NOT NULL
,C_MKTSEGMENT VARCHAR(10)
,C_COMMENT VARCHAR(117)
);
create table CUST_1GB_CL3 like CUST_1GB_CL;
GET_DDL('TABLE', 'CUST_1GB_CL3')
CREATE
OR replace TABLE
CUST_1GB_CL3 ***cluster BY (C_CUSTKEY)*** (
C_CUSTKEY NUMBER(38, 0) NOT NULL
,C_NAME VARCHAR(25) NOT NULL
,C_ADDRESS VARCHAR(40) NOT NULL
,C_NATIONKEY NUMBER(38, 0) NOT NULL
,C_PHONE VARCHAR(15) NOT NULL
,C_ACCTBAL NUMBER(12, 2) NOT NULL
,C_MKTSEGMENT VARCHAR(10)
,C_COMMENT VARCHAR(117)
);
This is strange. I think following up with Snowflake on this is best course of action.
If you want you can drop the clustering key in your new table as follows:
ALTER TABLE IF EXISTS CUST_1GB_CL3 DROP CLUSTERING KEY;

How to select rows that fail exclusion constraint

I have a table with rows in it (source) that I am trying to insert into another table (target). The target has an exclusion constraint in place. However, when I do this, some of the rows fail the exclusion constraint. I would like to be able to select these rows in the source, that fail the exclusion constraint. Is this possible?
create table target(
id bigint primary key
,external_data_source_id bigint not null
,external_id text not null
,external_id_domain_id bigint not null
,internal_id bigint not null
,valid_period tstzrange not null
,EXCLUDE USING gist (external_data_source_id with = , external_id_domain_id with =, internal_id with =, external_id with =, valid_period WITH &&)
);
create table source(
id bigint primary key
,external_data_source_id bigint not null
,external_id text not null
,external_id_domain_id bigint not null
,internal_id bigint not null
,valid_period tstzrange not null
);
insert into source
select 1,1,'text',1,1,tstzrange('2000-01-01','2001-01-01');
insert into source
select 2,1,'text',1,1,tstzrange('2000-01-01','2001-01-01');
insert into source
select 1,'text',1,1,tstzrange('2002-01-01','2004-01-01');
insert into target
select * from source;
gives
Error: ERROR: conflicting key value violates exclusion constraint "target_external_data_source_id_external_id_domain_id_inter_excl"
Detail: Key (external_data_source_id, external_id_domain_id, internal_id, external_id, valid_period)=(1, 1, 1, text, ["2000-01-01 00:00:00+01","2001-01-01 00:00:00+01")) conflicts with existing key (external_data_source_id, external_id_domain_id, internal_id, external_id, valid_period)=(1, 1, 1, text, ["2000-01-01 00:00:00+01","2001-01-01 00:00:00+01")).
SQLState: 23P01
ErrorCode: 0
I would like to select the rows in source that fail this exclusion constraint.
You can use condition from the exclusion constraint in an exists query:
select s1.*
from source s1
where exists (select *
from source s2
where (s2.external_data_source_id, s2.external_id_domain_id,
s2.internal_id, s2.external_id)
= (s1.external_data_source_id, s1.external_id_domain_id,
s1.internal_id, s1.external_id)
and s1.valid_period && s2.valid_period
and s1.id <> s2.id
);
Online example: https://rextester.com/PDOE78609

Cannot add Foreign Key on tables in DashDB / DB2 on Bluemix

When I create a table in DashDB (DB2) on Bluemix like this:
CREATE TABLE DEPARTMENT (
depname CHAR (10) UNIQUE NOT NULL ,
phone INTEGER
) ;
ALTER TABLE DEPARTMENT ADD CONSTRAINT DEPARTMENT_PK PRIMARY KEY ( depname ) ;
CREATE TABLE EMPLOYEE (
"EmpNr" NUMERIC (3) UNIQUE NOT NULL ,
empname CHAR (20) ,
depname CHAR (10) ,
EMPLOYEE2_title CHAR (20)
);
ALTER TABLE EMPLOYEE ADD CONSTRAINT EMPLOYEE_PK PRIMARY KEY ( "EmpNr" ) ;
ALTER TABLE EMPLOYEE ADD CONSTRAINT EMPLOYEE_DEPARTMENT_FK FOREIGN KEY (depname ) REFERENCES DEPARTMENT ( depname ) ;
Bluemix disallows adding a Foreign Key constraint for this table type.
When you look at the documentation for dashDB (not DB2) you will notice that foreign keys can be created. However, a table by default is created column-organized. Only non-enforced referential constraints are supported. In your example you would need to add NOT ENFORCED to your statement:
ALTER TABLE EMPLOYEE
ADD CONSTRAINT EMPLOYEE_DEPARTMENT_FK FOREIGN KEY (depname )
REFERENCES DEPARTMENT ( depname ) NOT ENFORCED;
By default on CREATE a DashDB table on Bluemix is 'organized by column'...
https://www-01.ibm.com/support/knowledgecenter/SSEPGG_10.5.0/com.ibm.db2.luw.admin.dbobj.doc/doc/c0060592.html
This will also disallow adding Foreign Key constraints for this table type.
To add FKs add ORGANIZE BY ROW to your CREATE TABLE statement:
CREATE TABLE DEPARTMENT (
depname CHAR (10) UNIQUE NOT NULL ,
phone INTEGER
) ORGANIZE BY ROW;
ALTER TABLE DEPARTMENT
ADD CONSTRAINT DEPARTMENT_PK PRIMARY KEY ( depname ) ;
CREATE TABLE EMPLOYEE (
"EmpNr" NUMERIC (3) UNIQUE NOT NULL ,
empname CHAR (20) ,
depname CHAR (10) ,
EMPLOYEE2_title CHAR (20)
) ORGANIZE BY ROW;
ALTER TABLE EMPLOYEE
ADD CONSTRAINT EMPLOYEE_PK PRIMARY KEY ( "EmpNr" ) ;
ALTER TABLE EMPLOYEE
ADD CONSTRAINT EMPLOYEE_DEPARTMENT_FK FOREIGN KEY (depname )
REFERENCES DEPARTMENT ( depname ) ;

"polymorphism" for FOREIGN KEY constraints

There is this field in a table:
room_id INT NOT NULL CONSTRAINT room_id_ref_room REFERENCES room
I have three 2 tables for two kinds of rooms: standard_room and family_room
How to do something like this:
room_id INT NOT NULL CONSTRAINT room_id_ref_room REFERENCES standard_room or family_room
I mean, room_id should reference either standard_room or family_room.
Is it possible to do so?
Here is the pattern I've been using.
CREATE TABLE room (
room_id serial primary key,
room_type VARCHAR not null,
CHECK CONSTRAINT room_type in ("standard_room","family_room"),
UNIQUE (room_id, room_type)
);
CREATE_TABLE standard_room (
room_id integer primary key,
room_type VARCHAR not null default "standard_room",
FOREIGN KEY (room_id, room_type) REFERENCES room (room_id, room_type),
CHECK CONSTRAINT room_type = "standard_room"
);
CREATE_TABLE family_room (
room_id integer primary key,
room_type VARCHAR not null default "family_room",
FOREIGN KEY (room_id, room_type) REFERENCES room (room_id, room_type),
CHECK CONSTRAINT room_type = "family_room"
);
That is, the 'subclasses' point at the super-class, by way of a type descriminator column (such that the pointed to base class is of the correct type, and that primary key of the super class is the same as the child classes.
Here's the same SQL from the accepted answer that works for PostGres 12.8. There's a few issues not only the CREATE_TABLE syntax mistake:
CREATE TABLE room (
room_id serial primary key,
room_type VARCHAR not null,
CONSTRAINT room_in_scope CHECK (room_type in ('standard_room','family_room')),
CONSTRAINT unique_room_type_combo UNIQUE (room_id, room_type)
);
CREATE TABLE standard_room (
room_id integer primary key,
room_type VARCHAR not null default 'standard_room',
CONSTRAINT roomid_std_roomtype_fk FOREIGN KEY (room_id, room_type) REFERENCES public."room" (room_id, room_type),
CONSTRAINT std_room_constraint CHECK (room_type = 'standard_room')
);
CREATE TABLE family_room (
room_id integer primary key,
room_type VARCHAR not null default 'family_room',
CONSTRAINT roomid_fam_roomtype_fk FOREIGN KEY (room_id, room_type) REFERENCES "room" (room_id, room_type),
CONSTRAINT fam_room_constraint CHECK (room_type = 'family_room')
);
NOTE: The SQL above uses constraints to enforce the child room_type values default to the parent tables' room_type values: 'standard_room' or 'family_room'.
PROBLEM: Since the child tables Primary Key's expect either the standard and family room Primary Key that means you can't insert more than one record in thsee two child tables.
insert into room (room_type) VALUES ('standard_room'); //Works
insert into room (room_type) values ('family_room'); //Works
insert into standard_room (room_id,pictureAttachment) VALUES (1,'Before Paint'); //Works
insert into standard_room (room_id,pictureAttachment) VALUES (1,'After Paint'); //Fails
insert into standard_room (room_id,pictureAttachment) VALUES (1,'With Furniture');
insert into family_room (room_id,pictureAttachment) VALUES (2, 'Beofre Kids'); //Works
insert into family_room (room_id,pictureAttachment) VALUES (2,'With Kids'); //Fails
To make the tables accept > 1 row you have to remove the Primary Keys from the 'standard_room' and 'family_room' tables which is BAD database design.
Despite 26 upvotes I will ping OP about this as I can see the answer was typed free hand.
Alternate Solutions
For smallish tables with less than a handful of variations a simple alterative is a single table with Bool columns for different table Primary Key fields.
Single Table "Room"
Id
IsStandardRoom
IsFamilyRoom
Desc
Dimensions
1
True
False
Double Bed, BIR
3 x 4
2
False
True
3 Set Lounge
5.5 x 7
SELECT * FROM Room WHERE IsStdRoom = true;
At the end of the day, in a relational database it's not very common to be adding Room Types when it involves creating the necessary related database tables using DDL commands (CREATE, ALTER, DROP).
A typical future proof database design allowing for more Tables would look something like this:
Multi Many-To-Many Table "Room"
Id
TableName
TableId
1
Std
8544
2
Fam
236
3
Std
4351
Either Standard or Family:
select * from standard_room sr where sr.room_id in
(select TableId from room where TableName = 'Std');
select * from family_room fr where fr.room_id in
(select id from room where TableName = 'Fam');
Or both:
select * from standard_room sr where sr.room_id in
(select TableId from room where TableName = 'Std')
UNION
select * from family_room fr where fr.room_id in
(select id from room where TableName = 'Fam');
Sample SQL to demo Polymorphic fields:
If you want to have different Data Types in the polymorphic foreign key fields then you can use this solution. Table r1 stores a TEXT column, r2 stores a TEXT[] Array column and r3 a POLYGON column:
CREATE OR REPLACE FUNCTION null_zero(anyelement)
RETURNS INTEGER
LANGUAGE SQL
AS $$
SELECT CASE WHEN $1 IS NULL THEN 0 ELSE 1 END;
$$;
CREATE TABLE r1 (
r1_id SERIAL PRIMARY KEY
, r1_text TEXT
);
INSERT INTO r1 (r1_text)
VALUES ('foo bar'); --TEXT
CREATE TABLE r2 (
r2_id SERIAL PRIMARY KEY
, r2_text_array TEXT[]
);
INSERT INTO r2 (r2_text_array)
VALUES ('{"baz","blurf"}'); --TEXT[] ARRAY
CREATE TABLE r3 (
r3_id SERIAL PRIMARY KEY
, r3_poly POLYGON
);
INSERT INTO r3 (r3_poly)
VALUES ( '((1,2),(3,4),(5,6),(7,8))' ); --POLYGON
CREATE TABLE flex_key_shadow (
flex_key_shadow_id SERIAL PRIMARY KEY
, r1_id INTEGER REFERENCES r1(r1_id)
, r2_id INTEGER REFERENCES r2(r2_id)
, r3_id INTEGER REFERENCES r3(r3_id)
);
ALTER TABLE flex_key_shadow ADD CONSTRAINT only_one_r
CHECK(
null_zero(r1_id)
+ null_zero(r2_id)
+ null_zero(r3_id)
= 1)
;
CREATE VIEW flex_key AS
SELECT
flex_key_shadow_id as Id
, CASE
WHEN r1_id IS NOT NULL THEN 'r1'
WHEN r2_id IS NOT NULL THEN 'r2'
WHEN r3_id IS NOT NULL THEN 'r3'
ELSE 'wtf?!?'
END AS "TableName"
, CASE
WHEN r1_id IS NOT NULL THEN r1_id
WHEN r2_id IS NOT NULL THEN r2_id
WHEN r3_id IS NOT NULL THEN r3_id
ELSE NULL
END AS "TableId"
FROM flex_key_shadow
;
INSERT INTO public.flex_key_shadow (r1_id,r2_id,r3_id) VALUES
(1,NULL,NULL),
(NULL,1,NULL),
(NULL,NULL,1);
SELECT * FROM flex_key;

How to add a foreign key constraint to same table using ALTER TABLE in PostgreSQL

To create table I use:
CREATE TABLE category
(
cat_id serial NOT NULL,
cat_name character varying NOT NULL,
parent_id integer NOT NULL,
CONSTRAINT cat_id PRIMARY KEY (cat_id)
)
WITH (
OIDS=FALSE
);
ALTER TABLE category
OWNER TO pgsql;
parent_id is a id to another category. Now I have a problem: how to cascade delete record with its children? I need to set parent_id as foreign key to cat_id.
I try this:
ALTER TABLE category
ADD CONSTRAINT cat_cat_id_fkey FOREIGN KEY (parent_id)
REFERENCES category (cat_id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE
But it falls with:
ERROR: insert or update on table "category" violates foreign key constraint "cat_cat_id_fkey"
DETAIL: Key (parent_id)=(0) is not present in table "category".
The problem you have - what would be the parent_id of a category at the top of the hierarchy?
If it will be null - it will break the NOT NULL constratint.
If it will be some arbitrary number like 0 - it will break the foreign key (like in your example).
The common solution - drop the NOT NULL constratint on the parent_id and set parent_id to null for top categories.
-- create some fake data for testing
--
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
CREATE TABLE category
(
cat_id serial NOT NULL,
cat_name character varying NOT NULL,
parent_id integer NOT NULL,
CONSTRAINT cat_id PRIMARY KEY (cat_id)
);
INSERT INTO category(cat_name,parent_id)
SELECT 'Name_' || gs::text
, gs % 3
FROM generate_series(0,9) gs
;
-- find the records with the non-existing parents
SELECT ca.parent_id , COUNT(*)
FROM category ca
WHERE NOT EXISTS (
SELECT *
FROM category nx
WHERE nx.cat_id = ca.parent_id
)
GROUP BY ca.parent_id
;
-- if all is well: proceed
-- make parent pointer nullable
ALTER TABLE category
ALTER COLUMN parent_id DROP NOT NULL
;
-- set non-existing parent pointers to NULL
UPDATE category ca
SET parent_id = NULL
WHERE NOT EXISTS (
SELECT *
FROM category nx
WHERE nx.cat_id = ca.parent_id
)
;
-- Finally, add the FK constraint
ALTER TABLE category
ADD CONSTRAINT cat_cat_id_fkey FOREIGN KEY (parent_id)
REFERENCES category (cat_id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE
;
This is quite simple.
Here the foreign key parent_id refers to cat_id.
Here a record with parent_id=0 exists but not a record with cat_id=0.