SQL constraints on update problems - sql

I am currently studying SQL and I had a question about a constraint that I must create for an exercise.
Given 2 table:
CREATE TABLE A (
a1 integer PRIMARY KEY,
a2 integer CHECK (a2>10))
CREATE TABLE B (
b1 integer PRIMARY KEY, b2 integer))
and a many to many relationship table between A and B
CREATE TABLE R (
rID integer PRIMARY KEY,
a1ID integer REFERENCES A (a1),
b1ID integer REFERENCES B (b1))
I want to create a constrain on table B such that: when a new instance of B is added, there should be at least 5 and at most 10 instances of A Being related to B.
This is how I did it:
1) a trigger on insert into B:
CREATE triggerA BEFORE INSERT ON B
EXECUTE PROCEDURE upCons();
2) create a function that check the number of instance of A related to B:
here is my problem, can I just count the number of instance of R by doing this?
CREATE FUNCTION upCons() RETURN trigger AS $$
DECLARE x integer;
BEGIN
SELECT count(rID) INTO x FROM R;
IF (x<5 OR x>10) THEN RAISE EXCEPTION 'insert condition not met';
END IF;
END; LANGUAGE 'plpgsql';
OR I do the same by counting the instances of A in the relationship.
Is this a good approach to the problem? Should I do it differently and is this correct to begin with

I want to create a constrain on table B such that: when a new instance
of B is added, there should be at least 5 and at most 10 instances of
A Being related to B.
This constraint seems unreasonable (or perhaps even impossible), because at the time of inserting an entry into B (or A), there cannot be any relations already referencing the new entry. Referencing an entry (using a foreign key constraint as you do) requires the entry itself to already exist in the correct table. And according to your trigger, if you don't have enough relations in R, you cannot insert anything into B. Therefore you have effectively made your tables B and R read-only, since table B insertions are denied by your trigger and table R insertions by the foreign key constraint that requires any reference to table B to already exist in table B.

IF (x<5 AND x>10) THEN RAISE EXCEPTION 'insert condition not met';
This is supposed to be OR instead of AND, right?

Related

How to inherit generated column in Postgres

I have 3 tables as follows:
CREATE TABLE A (
ID integer PRIMARY KEY generated always as identity,
data integer
);
CREATE TABLE B (
other_data integer
) INHERITS (A);
CREATE TABLE C (
other_other_data integer
) INHERITS (A);
My intent is to have unique id for tables B and C so they don't mix up. When trying this approach and inserting data into B (insert into B (other_data) values (1)) i get the following error:
ERROR: null value in column "id" of relation "B" violates not-null constraint
DETAIL: Failing row contains (null)
I suspect this approach is not the correct way to make 2 tables share unique ids. And Postgres documentation really sucks. I tried using serial for a long time. Only to find out that buried in the wiki is a warning to not use them.
-edit: added the insert statement

How to insert multiple rows into table B, and update table A's null foreign keys with the new IDs?

I've found a million things sounding kind of similar on StackOverflow, but not my case exactly. I'll simplify as much as possible:
I have two tables as follows:
CREATE TABLE B (id uuid PRIMARY KEY);
CREATE TABLE A (id uuid PRIMARY KEY, b_id uuid REFERENCES b);
There are some NULL values in A.b_id. I am trying to create a migration that does the following:
For every row in A with no b_id, create a new row in B, and assign its id to A.b_id.
How can I accomplish this in one query?
Assuming you want a distinct entry in b for every row with a missing UUID in a:
WITH upd AS (
UPDATE a
SET b_id = gen_random_uuid()
WHERE b_id IS NULL
RETURNING b_id
)
INSERT INTO b (id)
SELECT b_id FROM upd;
db<>fiddle here
This works because it's a single command, and the FK reference is only enforced at the end of the command.
See:
SET CONSTRAINTS ALL DEFERRED not working as expected
Constraint defined DEFERRABLE INITIALLY IMMEDIATE is still DEFERRED?

how to set a constraint that sets null on only one of the fields in the composite foreign key when the parent record is deleted?

I have 2 postgres tables.
table one:
|id|user_id|master_team_id|role_id
table two:
|uuid|user_id|master_team_id|number
master_team_id in table two can be null.
user_id and master_id foreign key references the user_id and
master_team_id in table one.
in order for master_team_id in table two to not be null, the user_id
and master_team_id combo must exist in table one.
how do i add a constraint that sets null on only master_team_id in the composite key(user_id, master_team_id) in table two when the referenced row in table one is deleted?
in the FK constraint specify ON DELETE SET NULL
https://www.techonthenet.com/sql_server/foreign_keys/foreign_delete_null.php
Side Note: I would suggest using adding a new column to table two called "TableOneID" that way you can know if the matching record exists or not.
You can't do that yet.
What a coincidence. The ability to do that was committed yesterday, and (if all goes according to plan) will be included in v15, which is not due out for nearly a year.
You can use a trigger, which would look like this:
create table p (c1 int, c2 int, unique (c1,c2));
create table c (c1 int, c2 int, r double precision);
alter table c add constraint fk99999 foreign key (c1,c2) references p (c1,c2);
create function update_c() returns trigger language plpgsql as $$
BEGIN
update c set c2=null where c1=old.c1 and c2=old.c2;
return old;
END; $$;
create trigger sdjflsfd before delete ON p for each row execute function update_c();

How to create a Postgres column indicating if a row is referenced

I have a schema similar to
CREATE TABLE A(
id INTEGER PRIMARY KEY NOT NULL,
has_ref BOOLEAN NOT NULL
);
CREATE TABLE B(
f_id INTEGER REFERENCES A (id)
);
CREATE TABLE C(
f_id INTEGER REFERENCES A (id)
);
http://sqlfiddle.com/#!17/224c86
has_ref is supposed to be TRUE if the row in A has a reference, and FALSE if not. There should only be at most one reference to each row in A.
Ideally, I would like this to happen automatically and be enforced by the database, so that e.g. if a row in B or C is deleted, has_ref would change back to FALSE in the referenced row.
I could not find a way to do this with triggers or constraints.
Rather than storing a boolean, store an integer column refcount.
Then add triggers on b and c that add or subtract from these values whenever a referencing row is added, removed or modified.
You can use a query to define it:
select a.*
(exists (select 1 from b where b.f_id = a.id) or
exists (select 1 from c where c.f_id = a.id)
) as has_ref
from a;
If you want to encapsulate this logic, you can create a view that calculates it on the fly.

PostgreSQL - Constraint Based on Column in Another Table

I have two tables, one called ballots and one called votes. Ballots stores a list of strings representing options that people can vote for:
CREATE TABLE IF NOT EXISTS Polls (
id SERIAL PRIMARY KEY,
options text[]
);
Votes stores votes that users have made (where a vote is an integer representing the index of the option they voted for):
CREATE TABLE IF NOT EXISTS Votes (
id SERIAL PRIMARY KEY,
poll_id integer references Polls(id),
value integer NOT NULL ,
cast_by integer NOT NULL
);
I want to ensure that whenever a row is created in the Votes table, the value of 'value' is in the range [0,length(options)) for the corresponding row in Polls (by corresponding, I mean the row where the poll_id's match).
Is there any kind of check or foreign key constraint I can implement to accomplish this? Or do I need some kind of trigger? If so, what would that trigger look like and would there be performance concerns? Would it be just as performant to just manually query for the corresponding poll using a SELECT statement and then assert that 'value' is valid before inserting into Votes table?
In your requirement you can not use Check Constraint because it can refer the column of the same table.
You can refer the Official Manual for the same.
So, here you should use Trigger on BEFORE INSERT event of your Votes Table or you can use function/procedure(depend upon your version of PostgreSQL) for your insert operation where you can check the value before insert and raise exception if the condition not satisfied.
USING Trigger:
create or replace function id_exist() returns trigger as
$$
begin
if new.value<0 or new.value>=(select array_length(options,1) from polls where id=new.poll_id) then
raise exception 'value is not in range';
end if;
return new;
end;
$$
language plpgsql
CREATE TRIGGER check_value BEFORE INSERT ON votes
FOR EACH ROW EXECUTE PROCEDURE id_exist();
DEMO
I would suggest that you modify your data model to have a table, PollOptions:
CREATE TABLE IF NOT EXISTS PollOptions (
PollOptionsId SERIAL PRIMARY KEY, -- should use generated always as identity
PollId INT NOT NULL, REFERENCES Polls(id),
OptionNumber int,
Option text,
UNIQUE (PollId, Option)
);
Then your Votes table should have a foreign key reference to PollOptions. You can use either PollOptionId or (PollId, Option).
No triggers or special functions are needed if you set up the data correctly.