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

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.

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 create SQL constraint on primary key to make sure it could only be referenced once?

How do I add constraint to guard that a primary key could only be referenced once?(It could be referenced in two tables)
Each reference should have a unique value out of the primary key.
Table A
----------------------
id
1
2
3
4
Table B
----------------------
id a_id (foreign key to table A.id)
1 2
2 3
Table C
----------------------
id a_id (foreign key to table A.id)
1 1
I want something to happen to give error when try to insert a_id = 2 into table C as its used in table B already.
You can use an INSERT, UPDATE trigger on each of the child tables to ensure that the PK of the parent table that is about to be inserted or updated does not already exist in the other child table.
What you are trying to do requires another table D, that will help unify the references to A.
Table D will contain its own primary key ( Id ), a reference to table A with a UNIQUE constraint on it (call it AId ), and a third column (called "RowType") to indicate to which of the child tables (B or C) the row corresponds. You can make this column to be of type int, and assign value "0" for B and "1" for C, for example.
Then in table B you add a foreign key to D.Id, AND another column "BRowType" as foreign key to D.RowType; then you define a constraint on this column, so it can only have the value '0' ( or whatever value you have decided to correspond to this table).
For table C your constraint will limit the values to '1'.
Or course, in order to insert a record into B or C you first need to create a record in D. But once you have a record in B that references a record in D, which in turn links to a record in A, you will no longer be able to create a record in C for the same line in A - because of the UNIQUE constraint on D.AId AND the constraint on C.BRowType.
If I understand the question correctly, it sounds like you need to add a unique constraint on the column of each table that references your primary key.
For example:
Table A
----------------------
id (primary key)
1
2
3
Table B
----------------------
id a_id (foreign key to table A.id)
1 2
2 3
Set the a_id column to be UNIQUE and that way you can ensure that the primary key from Table A is not used twice. You would do that in each table which references A.id
If you want to avoid using triggers, you could create a table X with id and a unique constraint on it.
In each transaction in which you insert a record into B or C you have to insert into X as well. Both insertions will only be possible if not yet in the other table.

SQL constraints on update problems

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?

SQL query to combine existence check and condition check

I have 2 tables, call A and B. A has a foreign key on B. call them A_ID and B_ID respectively. But the constraint not enforced in the design. I am not supposed to change the schema. I need to delete entries from table A based on 2 conditions.
1)If table B doesn't contain A_ID
2)If some condition on B is met.
I have formed a query something like this. But I dont think its optimal. Is there a better way of doing this?
delete from A where A_ID not in (select B_ID from B where status='x' )
or A_ID not in (select B_ID from B)
You could use not exists to delete rows without a matching entry in table B. This one treats status = 'x' as if no match was found, i.e. it will delete those rows:
delete A
where not exists
(
select *
from B
where B.B_ID = A.A_ID
and status <> 'x'
)
JustABitOfCode and UltraCommit told about omitting one part
furthermore, if it's a foreign key, you can say to keep deleting unwanted A in definition:
CREATE TABLE A
(
uniqeidentifire A_ID
, FOREIGN KEY (A_ID) REFERENCES B(B_ID) ON DELETE CASCADE
);
This will Automatically delete each A that does not have a B match
and this is more efficient
As just explained from JustABitOfCode, please remove the condition:
(select B_ID from B where status='x')
because it is redundant: the result set of the previous select, is a SUBSET of the result set of the following select:
(select B_ID from B)