PostgreSQL - Multiple constraints - postgresql

I want to add several CHECK CONSTRAINTS to a PostgreSQL 13 table. In natural language logic is : if a field contain a defined value, an other field must be filled. I have several scenarios to combine. When I add just one constraint it's ok, but when I want to accumulate them, CONSTRAINTS aren't respected and row can't be inserted.
Here is my table:
CREATE TABLE IF NOT EXISTS demo_table
(
uuid uuid NOT NULL DEFAULT uuid_generate_v4(),
id integer NOT NULL DEFAULT nextval('demo_table_id_seq'::regclass),
thematic character varying COLLATE pg_catalog."default",
field_a character varying COLLATE pg_catalog."default",
field_b character varying COLLATE pg_catalog."default",
CONSTRAINT demo_table_pkey PRIMARY KEY (uuid),
CONSTRAINT field_a_check CHECK (thematic::text ~~ 'A'::text AND field_a IS NOT NULL),
CONSTRAINT field_b_check CHECK (thematic::text ~~ 'B'::text AND field_b IS NOT NULL)
)
My expected logic is : when thematic like 'A', field_a can't be NULL or when thematic like 'B' field_b can't be NULL. With this settings I can't add rows because my CONSTRAINTS definitions never check both conditions (field_a IS NOT NULL and field_b IS NOT NULL).
I tried to define an unique CONSTRAINT as suggested in this post, but CHECK CONSTRAINT isn't respected either because parenthesis who isolate conditions aren't saved in the definition.
CREATE TABLE IF NOT EXISTS demo_table
(
uuid uuid NOT NULL DEFAULT uuid_generate_v4(),
id integer NOT NULL DEFAULT nextval('demo_table_id_seq'::regclass),
thematic character varying COLLATE pg_catalog."default",
field_a character varying COLLATE pg_catalog."default",
field_b character varying COLLATE pg_catalog."default",
CONSTRAINT demo_table_pkey PRIMARY KEY (uuid),
CONSTRAINT field_a_b_check CHECK (thematic::text ~~ 'A'::text AND field_a IS NOT NULL OR thematic::text ~~ 'B'::text AND field_b IS NOT NULL)
)
How to combine multiple CONSTRAINTS like (IF ... ) OR (IF ... ) OR (IF ...) ?

The problem with your approach is that your constraints are not full.
For example:
CONSTRAINT field_a_check CHECK (thematic::text ~~ 'A'::text AND field_a IS NOT NULL),
The constraint says "the record is ok if thematic contains 'A' and field_a is not empty". That means the record is not OK otherwise (if it does not contain 'A'). If you appended your checks with "OK otherwise" you could have several of them - no problem:
CONSTRAINT field_a_check CHECK (thematic::text ~~ 'A'::text AND field_a IS NOT NULL OR NOT thematic::text ~~ 'A'::text)
As to why the parenthesis are removed - it's because they are not needed. The AND operator has priority over OR, so the expressions are the same with or without parenthesis.
You are welcome to check the solution at db<>fiddle

You can do like this.
alter table table_1
add constraint ck_only_one check ((col1 is null and col2 is not null) or (col2 is null and col1 is not null));
Separation with parenthesis is to be given for better segregation.

Related

Postgres - how to bulk insert table with foreign keys

I am looking to do a bulk insert into my postgreSQL database.
database is not yet live
postgreSQL 13
I have a temporary staging table which I bulk inserted data
TABLE public.temp_inverter_location
(
id integer ,
inverter_num_in_sld integer,
lift_requirements character varying,
geo_location_id integer NOT NULL (foreign key references geo_location.id),
location_name character varying,
project_info_id integer NOT NULL (foreign key references project_info.id)
)
I am trying to populate the two foreign key columns temp_inverter_location.geo_location_id and temp_inverter_location.project_info_id.
The two referenced tables are referenced by their id columns:
geo_location
CREATE TABLE public.geo_location
(
id integer,
country character varying(50) COLLATE pg_catalog."default",
region character varying(50) COLLATE pg_catalog."default",
city character varying(100) COLLATE pg_catalog."default",
location_name character varying COLLATE pg_catalog."default",
)
and
project_info
CREATE TABLE public.project_info
(
id integer
operation_name character varying,
project_num character varying(10),
grafana_site_num character varying(10)
)
I want to populate the correct foreign keys into the columns temp_inverter_location.geo_location_id and temp_inverter_location.project_info_id.
I am trying to use INSERT INTO SELECT to populate temp_inverter_location.geo_location_id with a JOIN that matches geo_location.location_name and temp_inverter_location.name.
I have tried this query however inverter_location.geo_location_id remains blank:
INSERT INTO temp_inverter_location(geo_location_id) SELECT geo_location.id FROM geo_location INNER JOIN temp_inverter_location ON geo_location.location_name=temp_inverter_location.location_name;
Please let me know if more info is needed, thanks!
I was able to resolve this issue using update referencing another table.
Basically, I updated the geo_location_id column using
UPDATE temp_inverter_location SET geo_location_id = geo_location.id FROM geo_location WHERE geo_location.location_name = temp_inverter_location.location_name;
and updated the project_info_id using
UPDATE load_table SET project_info_id = project_info.id FROM project_info WHERE project_info.operation_name = load_table.location_name;
It seems to have worked.

check constraint being printed without parenthesis

I have this DDL:
CREATE TABLE checkout_value (
id BIGSERIAL PRIMARY KEY,
start_value INTEGER,
end_value INTEGER,
);
With an e-commerce in mind, I want to save several ranges of possible values, where future rules will be applied at checkout. examples:
values until $20
values from $400
between $30 and $300
This way, I want to allow one null value, but if both are not null, start_value should be smaller than end_value.
I though about triggers, but I'm trying to do this using a check constraint, this way:
CREATE TABLE checkout_value (
id BIGSERIAL PRIMARY KEY,
start_value INTEGER,
end_value INTEGER,
CHECK
(
(start_value IS NOT NULL AND end_value IS NULL)
OR
(start_value IS NULL AND end_value IS NOT NULL)
OR
(start_value IS NOT NULL AND end_value IS NOT NULL AND end_value > start_value)
)
);
this works! but when I run \d checkout_value, it prints without any parenthesis:
Check constraints:
"checkout_value_check" CHECK (start_value IS NOT NULL AND end_value IS NULL OR start_value IS NULL AND end_value IS NOT NULL OR start_value IS NOT NULL AND end_value IS NOT NULL AND end_value > start_value)
which, without parenthesis, would lead to an unwanted rule. Is this a bug at printing the details of the table? Is there an easier way to apply while documenting these rules in a more explicit way?
AND binds stronger than OR, so both versions are equivalent. PostgreSQL doesn't store the string, but the parsed expression.

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

Postgres Update table id with sequence

I am trying to connect a sequence for user table to auto incremental value for id field.
I created following sequence,
CREATE SEQUENCE "USER_MGMT"."USER_SEQ"
INCREMENT 1
START 1000
MINVALUE 1000
MAXVALUE 99999999
CACHE 1;
ALTER SEQUENCE "USER_MGMT"."USER_SEQ"
OWNER TO postgres;
following is my table,
-- Table: "USER_MGMT"."USER"
-- DROP TABLE "USER_MGMT"."USER";
CREATE TABLE "USER_MGMT"."USER"
(
"USER_ID" bigint NOT NULL,
"FIRST_NAME" character varying(50) COLLATE pg_catalog."default" NOT NULL,
"LAST_NAME" character varying(50) COLLATE pg_catalog."default" NOT NULL,
"EMAIL_ID" character varying(100) COLLATE pg_catalog."default" NOT NULL,
"DESK_NUMBER" bigint,
"MOBILE_NUMBER" bigint,
"IS_ACTIVE" boolean NOT NULL DEFAULT true,
"CREATED_BY" character varying(100) COLLATE pg_catalog."default",
"MODIFIED_BY" character varying(100) COLLATE pg_catalog."default",
"DATE_CREATED" timestamp without time zone NOT NULL,
"DATE_MODIFIED" timestamp without time zone,
CONSTRAINT "USER_ID_PK" PRIMARY KEY ("USER_ID"),
CONSTRAINT "EMAIL_ID_UK" UNIQUE ("EMAIL_ID"),
CONSTRAINT "MOBILE_NUMBER_UK" UNIQUE ("MOBILE_NUMBER"),
CONSTRAINT "USER_ID_UK" UNIQUE ("USER_ID")
)
WITH (
OIDS = FALSE
)
TABLESPACE pg_default;
ALTER TABLE "USER_MGMT"."USER"
OWNER to postgres;
I want to connect this sequence to USER_ID column, so it will be auto incremented.
Table name and fields should be in upper case,
I am trying to execute the following query, but its not working
ALTER TABLE USER_MGMT.USER ALTER COLUMN USER_ID SET DEFAULT nextval('USER_MGMT.USER_SEQ');
It says the following error message in console.
ERROR: schema "user_mgmt" does not exist
********** Error **********
That is because when you use double quotes then you are creating case sensitive object identifier or to be more precise - this object will have identifier with exact case as given in the query during creation. If you do not double quote them, then they are converted to lower case.
So what you need is to either stop using double quotes, create objects in lower case or use double quotes in your alter query:
ALTER TABLE "USER_MGMT"."USER" ALTER COLUMN "USER_ID" SET DEFAULT nextval('"USER_MGMT"."USER_SEQ"');

Insert into PERSOANE table return violates check constraint but shouldn't

This is my creation script:
CREATE TABLE PERSOANE (
idPers numeric (5)
CONSTRAINT pk_persoane PRIMARY KEY,
NumePren varchar (30)
CONSTRAINT ck_nume CHECK (NumePren=LTRIM(INITCAP(NumePren))),
Loc varchar (30)
CONSTRAINT nn_loc NOT NULL
CONSTRAINT ck_loc CHECK (Loc=LTRIM(INITCAP(Loc))),
Jud varchar (25)
CONSTRAINT nn_jud NOT NULL
CONSTRAINT ck_jud CHECK (Jud=LTRIM(INITCAP(Jud))),
Tel numeric (10)
CONSTRAINT nn_tel NOT NULL,
E_mail varchar(254)
CONSTRAINT nn_e_mail NOT NULL
CONSTRAINT ck_e_mail CHECK (E_mail = LTRIM(E_mail))
);
When I try to insert values in the table I got error: new row for relation "persoane" violates check constraint "ck_jud" Here is my insert script:
INSERT INTO PERSOANE VALUES (11111, 'slimi marius', 'oras', 'judet', 0752361507, 'simic#yahoo.com');
Anyone has any suggestion how to fix this problem?
initcap() will change the first character to uppercase. So your check constraints on the columns numepren, loc and jud require you to enter values where the first character of every word is in uppercase. In the value 'jude' the first character is lowercase, so the check constraint is violated. You need to use 'Jude' instead. This is also true for the other columns you have defined:
INSERT INTO PERSOANE
(idpers, numepren, loc, jud, tel, e_mail)
VALUES
(11111, 'Slimi Marius', 'Oras', 'Judet', 0752361507, 'simic#yahoo.com');