insert several row in order to satisfy a constraint - postgresql

I have two table: deck(id) and card(deck,color,value)
deck have those constraints:
CHECK (fifty_two_cards_deck(id))
PRIMARY KEY (id)
CREATE FUNCTION fifty_two_cards_deck(deck integer) RETURNS boolean
LANGUAGE sql STABLE STRICT
AS $$ SELECT COUNT(*)=52 FROM card WHERE deck=$1 $$;
and card have those constraints:
FOREIGN KEY (deck) REFERENCES deck(id)
PRIMARY KEY (deck, color, value)
How can I insert a new deck?
I tried this:
begin transaction;
INSERT INTO "public"."deck" ("id") VALUES (nextval('deck_id_seq'::regclass));
INSERT INTO "public"."card" ("deck", "color", "value") VALUES ('1', enum_first(null::Suit), enum_first(null::Symbol));
end transaction
(i had edit fifty_two_cards_deck to be a one_card_deck for testing purpose)
but I got this error:
SQL error:
ERROR: new row for relation "deck"
violates check constraint
"fifty_two_cards_deck"
In statement: begin transaction;
INSERT INTO "public"."deck" ("id")
VALUES
(nextval('deck_id_seq'::regclass));
INSERT INTO "public"."card" ("deck",
"color", "value") VALUES ('1',
enum_first(null::Suit),
enum_first(null::Symbol));
end transaction
How can I solve this without removing the constraints?
EDIT: solution
thx to Magnus Hagander I got it working like this (after setting the foreign key deferrable):
begin transaction;
SET CONSTRAINTS ALL DEFERRED;
INSERT INTO "public"."deck-card" ("deck", "position", "color", "value") VALUES (1, 0, enum_first(null::suit), enum_first(null::Symbol));
INSERT INTO "public"."deck" ("id") VALUES (1);
end transaction

It might work if you make the FOREIGN KEY with DEFERRABLE, and then set it to DEFERRED. Then you insert into the "card" table first, and then into "deck". Check constraints execute at the time of insert (thus, well before the entries in "card" exist), and cannot be deferred to transaction end.
But that's not actually going to work around the fact that your constraint is broken and should be removed ;) That CHECK constraint will only check rows going into "deck". But once the row has been inserted there, you will still be able to add more rows to, or delete rows from, the "card" table and the CHECK constraint will not complain - until the next time you try to modify "deck".

Related

Postgres ON CONFLICT DO UPDATE doesn't trigger if other constraints fail

Consider the following SQL:
CREATE TABLE external_item (
id SERIAL PRIMARY KEY,
external_id TEXT UNIQUE,
enabled BOOLEAN NOT NULL CHECK (enabled = false OR external_id IS NOT NULL)
);
INSERT INTO external_item (id, enabled, external_id)
VALUES (1, true, 'ext_id_1');
INSERT INTO external_item (id, enabled)
VALUES (1, true)
ON CONFLICT (id)
DO UPDATE
SET enabled = excluded.enabled;
--> ERROR: new row for relation "external_item" violates check constraint "external_item_check"
--> DETAIL: Failing row contains (1, null, t).
The query fails because it the insert doesn't pass the check constraint. Everything works fine if you omit the CHECK from the table definition. Is there some way to set constraint priority or something so that Postgres would resort to the ON CONFLICT DO UPDATE statement before asserting other checks?

PostgreSQL input bulk data While Skipping Relation Error

Im doing a bulk insert to PostgreSQL using PG_Admin tools, the table field anamnesa_id contain ForeignKey relation to other table pasien_anamnesa,
Is there a way to ignore or skip (which is not exist in other table) the error while inserting all the query? because deleting the error query 1 by 1 is just impossible for this many data (25.000 records),
i've trying:
INSERT INTO "pasien_item" ("id", "anamnesa_id") VALUES (1, 2) ON CONFLICT ON CONSTRAINT pasien_item_pkey DO NOTHING;
resulting error:
ERROR: insert or update on table "pasien_item" violates foreign key constraint "pasien_item_anamnesa_id_dc66b31b_fk_pasien_anamnesa_id"
DETAIL: Key (anamnesa_id)=(2) is not present in table "pasien_anamnesa".
SQL state: 23503
from that error i also tried:
INSERT INTO "pasien_item" ("id", "anamnesa_id") VALUES (1, 2) ON CONFLICT ON CONSTRAINT pasien_item_anamnesa_id_dc66b31b_fk_pasien_anamnesa_id DO NOTHING;
resulting error:
ERROR: constraint in ON CONFLICT clause has no associated index
SQL state: 42809
ON CONFLICT can only deal with unique constraints, not foreign key or check constraints.
You need to rewrite your query to use a SELECT that only returns the rows where the foreign keys exist:
INSERT INTO pasien_item(id, anamnesa_id)
select v.id, v.anamnesa_id
from (
VALUES (1, 2), ...
) v(id, anamnesa_id)
WHERE EXISTS (select *
from pasien_anamnesa pa
where pa.anamnesa_id = v.anamnesa_id)
ON CONFLICT ON CONSTRAINT pasien_item_pkey DO NOTHING;
Online example

Postgres ignore insert error and carry on

I have a PostgeresDB with the following constraint:
CONSTRAINT "Car_Data_3PM_pkey" PRIMARY KEY ("F_ID", "Date"),
CONSTRAINT "Car_Data_3PM_F_ID_fkey" FOREIGN KEY ("F_ID")
REFERENCES "Bike_Data" ("F_ID") MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
When I try to insert multiple values using:
INSERT INTO "Car_Data_3PM" ("F_ID","Date","Price_Type","O","H","L","LT","EQ","V","NAD") VALUES (38,'2016-10-02 08:19:40.056679','x',0,0,0,112.145,0,0,112.145),(14,'2016-10-02 08:19:40.056679','x',0,0,0,5476,0,0,5476),(13,'2016-10-02
I get this error:
ERROR: insert or update on table "Car_Data_3PM" violates foreign key
constraint "Car_Data_3PM_F_ID_fkey" SQL state: 23503 Detail: Key
(F_ID)=(38) is not present in table "Bike_Data".
NO ROWS are inserted.
How can I make Postgres ONLY miss out the rows where the constraint is an issue? i.e Insert most of them?
You can't make Postgres ignore the values, but you can rewrite your statement to not insert those rows:
INSERT INTO "Car_Data_3PM" ("F_ID","Date","Price_Type","O","H","L","LT","EQ","V","NAD")
select *
from (
VALUES
(38,'2016-10-02 08:19:40.056679','x',0,0,0,112.145,0,0,112.145),
(14,'2016-10-02 08:19:40.056679','x',0,0,0,5476,0,0,5476),
... -- all other rows
) as x (id, date, price_type, o, h, l, lt, eq, v nad)
where exists (select 1
from "Bike_Data" bd
where bd."F_ID" = x .id)
One way is to write a trigger that filters out the bad values, like this:
CREATE FUNCTION car_insert_filter() RETURNS trigger
LANGUAGE plpgsql AS
$$BEGIN
IF EXISTS(SELECT 1 FROM "Bike_Data" WHERE "F_ID" = NEW."F_ID")
THEN
RETURN NEW;
ELSE
RAISE NOTICE 'Skipping row with "F_ID"=% and "Date"=%',
NEW."F_ID", NEW."Date";
RETURN NULL;
END IF;
END;$$;
CREATE TRIGGER car_insert_filter
BEFORE INSERT ON "Car_Data_3PM" FOR EACH ROW
EXECUTE PROCEDURE car_insert_filter();

How to have parent record data available when a child one is deleted through cascade

Consider the following two tables:
CREATE TABLE public.parent
(
id bigint NOT NULL DEFAULT nextval('parent_id_seq'::regclass),
CONSTRAINT pk_parent PRIMARY KEY (id)
);
CREATE TABLE public.child
(
child_id bigint NOT NULL DEFAULT nextval('child_child_id_seq'::regclass),
parent_id bigint NOT NULL,
CONSTRAINT pk_child PRIMARY KEY (child_id),
CONSTRAINT inx_parent FOREIGN KEY (parent_id)
REFERENCES public.parent (id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE INDEX fki_child
ON public.child
USING btree
(parent_id);
CREATE TRIGGER child_trg
BEFORE DELETE
ON public.child
FOR EACH ROW
EXECUTE PROCEDURE public.trg();
And the trg is defined as:
CREATE OR REPLACE FUNCTION public.trg()
RETURNS trigger AS
$BODY$BEGIN
INSERT INTO temp
SELECT p.id
FROM parent p
WHERE
p.id = OLD.parent_id;
return OLD;
END;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
To sum up what is happening, there're two tables with a simple parent-child relationship and a cascade on it. There's also a trigger defined on child listening to deletion. I need to access parent's data, in the trigger, when the child's records are deleted due to cascade on parent-child relation. But I can not since they are already deleted! Does anyone have any idea how?
One solution would be to create a BEFORE DELETE trigger on parent instead, which can see all data.
CREATE OR REPLACE FUNCTION public.trg_parent()
RETURNS trigger AS
$func$
BEGIN
INSERT INTO some_tbl (id) -- use target list !!
VALUES (OLD.parent_id);
RETURN OLD;
END
$func$ LANGUAGE plpgsql;
CREATE TRIGGER parent_trg
BEFORE DELETE ON public.parent
FOR EACH ROW EXECUTE PROCEDURE public.trg_parent();

Using triggers to maintain linking table

I'm considering employing triggers for maintaining linking table. However my initial approach fails due to foreign key constraint violation. Is there any way to solve the issue without disabling constraints?
CREATE TABLE foo (
id SERIAL PRIMARY KEY,
data TEXT
);
CREATE TABLE bar (
id SERIAL PRIMARY KEY,
data TEXT
);
CREATE TABLE foo_bar_link (
foo_id INT NOT NULL REFERENCES foo(id),
bar_id INT NOT NULL REFERENCES bar(id),
UNIQUE (foo_id, bar_id)
);
CREATE OR REPLACE FUNCTION maintain_link()
RETURNS TRIGGER AS
$maintain_link$
DECLARE
bar_id INT;
BEGIN
INSERT INTO bar (data) VALUES ('not_important_for_this_example_bar_data') RETURNING id INTO bar_id;
INSERT INTO foo_bar_link (foo_id, bar_id) VALUES (NEW.id, bar_id);
RETURN NEW;
END;
$maintain_link$
LANGUAGE plpgsql;
CREATE TRIGGER maintain_link BEFORE INSERT ON foo
FOR EACH ROW EXECUTE PROCEDURE maintain_link();
Here is sqlfiddle.
Use AFTER insert, since using BEFORE insert fails because your parent row in foo doesn't exist yet.