postgres getting data where two column references same parent id - postgresql

There are 3 table in postgres database
CREATE TABLE tab_name
(
name_id integer NOT NULL,
cust_name character varying NOT NULL, -- contains names like david,jones,athur
CONSTRAINT tab_name_pkey PRIMARY KEY (name_id)
)
CREATE TABLE tab_rel
(
rel_id integer NOT NULL,
rel_desc character varying NOT NULL,-- contains relation description father son, sister brother
CONSTRAINT tab_rel_pkey PRIMARY KEY (rel_id)
)
CREATE TABLE tab_rel_map
(
rel_id integer NOT NULL,
name_id1 integer NOT NULL,
name_id2 integer NOT NULL,
CONSTRAINT tab_rel_map_name_id1_fkey FOREIGN KEY (name_id1)
REFERENCES tab_name (name_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT tab_rel_map_name_id2_fkey FOREIGN KEY (name_id2)
REFERENCES tab_name (name_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT tab_rel_map_rel_id_fkey FOREIGN KEY (rel_id)
REFERENCES tab_rel (rel_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
I am trying to write function which take rel_id as input and should output cust_name respect to name_id1 and name_id2. Because both name_id1 and name_id2 references to same parent id I am not able to get respective name.
rel_id | relation | cust_name1 | cust_name2
------------------------------------------------
1 | Father son | David | Jones

You need to join to the same table twice - here's how you do that using aliases:
select
rm.rel_id,
r.rel_desc as relation,
n1.cust_name as cust_name1,
n2.cust_name as cust_name2
from tab_rel_map rm
join tab_rel r on r.id = rm.rel_id
left join tab_name n1 on n1.name_id = rm.name_id1
left join tab_name n2 on n2.name_id = rm.name_id2
where rm.rel_id = 1;

Related

idiomatic way to atomically create a table that as a record that is associated to other tables

I am coming from graph databases and postgres is still super foreign to me.
I have the following tables
CREATE TYPE runnerenum AS ENUM ('runner');
CREATE TABLE IF NOT EXISTS collections (
collectionid UUID PRIMARY KEY,
name VARCHAR(256) UNIQUE NOT NULL,
runner runnerenum NOT NULL,
runconfig JSONB
);
CREATE TABLE IF NOT EXISTS namedexprs(
namedexprid UUID PRIMARY KEY,
name VARCHAR(256) UNIQUE NOT NULL,
-- exprid UUID NOT NULL REFERENCES expressions(exprid),
collectionid UUID NOT NULL REFERENCES collections(collectionid) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS expressions(
exprid UUID PRIMARY KEY,
ast JSONB NOT NULL,
namedexprid UUID NOT NULL REFERENCES namedexprs(namedexprid) ON DELETE CASCADE
);
My question is what is the idiomatic way to create a collections atomically (while also creating associated expressions and namedexprs). Currently I am executing three separate queries and getting errors because of a foreign key violation.
Example of using DEFERRABLE:
CREATE TABLE parent_tbl (
parent_id integer PRIMARY KEY,
parent_val varchar UNIQUE
);
CREATE TABLE child_tbl (
child_id integer PRIMARY KEY,
parent_fk varchar REFERENCES parent_tbl (parent_val)
ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
child_val varchar
);
\d child_tbl
Table "public.child_tbl"
Column | Type | Collation | Nullable | Default
-----------+-------------------+-----------+----------+---------
child_id | integer | | not null |
parent_fk | character varying | | |
child_val | character varying | | |
Indexes:
"child_tbl_pkey" PRIMARY KEY, btree (child_id)
Foreign-key constraints:
"child_tbl_parent_fk_fkey" FOREIGN KEY (parent_fk) REFERENCES parent_tbl(parent_val) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
BEGIN;
INSERT INTO child_tbl VALUES (1, 'dog', 'cat');
SELECT * FROM child_tbl ;
child_id | parent_fk | child_val
----------+-----------+-----------
1 | dog | cat
(1 row)
SELECT * FROM parent_tbl ;
parent_id | parent_val
-----------+------------
(0 rows)
INSERT INTO parent_tbl VALUES (1, 'dog');
SELECT * FROM parent_tbl ;
parent_id | parent_val
-----------+------------
1 | dog
COMMIT;
The key to using DEFERRABLE is that the individual data entry statements need to be bundled into the same transaction, the BEGIN;/COMMIT;. This allows DEFERRABLE INITIALLY DEFERRED to work as the constraint check is deferred until the end of the transaction. For more ways you can manipulate this see SET CONSTRAINTS.

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.

PostgreSQL - properly change ID of table row

How to change id of some table's row?
Like:
UPDATE table SET id=10 WHERE id=5;
But in way that it would cascade changes to every other table that references this table with that id?
I want to do this, because I need to import data from another database which has most of the same tables, but ids are different. So if ids would match old database, it would be easier to import data correctly.
Suppose you have these two tables:
create table referenced (id integer primary key);
create table referencer (a integer references referenced (id));
Table referencer references table referenced:
=> \d referencer
Table "public.referencer"
Column | Type | Modifiers
--------+---------+-----------
a | integer |
Foreign-key constraints:
"referencer_a_fkey" FOREIGN KEY (a) REFERENCES referenced(id)
Then you insert a value in both:
insert into referenced values (1);
insert into referencer values (1);
select *
from
referenced rd
inner join
referencer rr on rd.id = rr.a
;
id | a
----+---
1 | 1
Now you want to change the reference to on update cascade:
alter table referencer
drop constraint referencer_a_fkey,
add foreign key (a) references referenced (id) on update cascade;
And update it:
update referenced set id = 2;
select *
from
referenced rd
inner join
referencer rr on rd.id = rr.a
;
id | a
----+---
2 | 2
Now you will have another problem in the referenced table primary key if the updated id already exists. But that would make another question.
UPDATE
This is dangerous so backup the db first. It must be done as superuser:
update pg_constraint
set confupdtype = 'c'
where conname in (
select
c.conname
from
pg_constraint c
inner join
pg_class referenced on referenced.oid = c.confrelid
where
referenced.relname = 'referenced'
and
c.contype = 'f'
);
It will change all the foreign key constraints on the referenced table to on update cascade
You will need to change your foreign key and set ON UPDATE action to CASCADE. When you change a value, all associated values will be changed too.
This is an example how to define it:
CREATE TABLE order_items (
product_no integer REFERENCES products ON UPDATE CASCADE,
order_id integer REFERENCES orders ON UPDATE CASCADE,
quantity integer,
PRIMARY KEY (product_no, order_id)
);
For more information see http://www.postgresql.org/docs/current/static/ddl-constraints.html

Postgres: complex CASCADE question - making sure you only delete unique foreign key references?

I've got some linked tables in a Postgres database, as follows:
Table "public.key"
Column | Type | Modifiers
--------+------+-----------
id | text | not null
name | text |
Referenced by:
TABLE "enumeration_value" CONSTRAINT "enumeration_value_key_id_fkey" FOREIGN KEY (key_id) REFERENCES key(id)
Table "public.enumeration_value"
Column | Type | Modifiers
--------+------+-----------
id | text | not null
key_id | text |
Foreign-key constraints:
"enumeration_value_key_id_fkey" FOREIGN KEY (key_id) REFERENCES key(id)
Referenced by:
TABLE "classification_item" CONSTRAINT "classification_item_value_id_fkey" FOREIGN KEY (value_id) REFERENCES enumeration_value(id)
Table "public.classification_item"
Column | Type | Modifiers
----------------+------+-----------
id | text | not null
transaction_id | text |
value_id | text |
Foreign-key constraints:
"classification_item_transaction_id_fkey" FOREIGN KEY (transaction_id) REFERENCES transaction(id)
"classification_item_value_id_fkey" FOREIGN KEY (value_id) REFERENCES enumeration_value(id)
I want to
delete all classification_items associated with a certain transaction
delete all enumeration_values associated with those classification_items
and finally, delete all key items associated with those enumeration_values.
The difficulty is that the key items are NOT unique to enumeration_values associated (via classification_item) with a certain transaction. They get created independently, and can exist across multiple of these transactions.
So I know how to do the second two of these steps, but not the first one:
delete from key where id in (select key_id from enumeration_value where id in (select value_id from "classification_item" where id = (select id from "transaction" where slice_id = (select id from slice where name = 'barnet'))));
# In statement above: help! How do I make sure these keys are ONLY used with these values?
delete from enumeration_value where id in (select value_id from "classification_item" where id = (select id from "transaction" where slice_id = (select id from slice where name = 'barnet')));
delete from classification_item where transaction_id in (select id from "transaction" where slice_id = (select id from slice where name = 'barnet'));
If only postgres had a CASCADE DELETE statement....
If only postgres had a CASCADE DELETE
statement....
PostgreSQL has this option for a long time, as of version 8.0 (5 years ago). Just use them.

Inserting self-referential records in Postgresql

Given the following table in PostgreSQL, how do I insert a record which refers to itself?
CREATE TABLE refers (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
parent_id INTEGER NOT NULL,
FOREIGN KEY (parent_id) REFERENCES refers(id)
);
The examples I'm finding on the Web have been allowed the parent_id to be NULL and then use a trigger to update it. I'd rather update in one shot, if possible.
You can select last_value from the sequence, that is automatically created when you use type serial:
create table test (
id serial primary key,
parent integer not null,
foreign key (parent) references test(id)
);
insert into test values(default, (select last_value from test_id_seq));
insert into test values(default, (select last_value from test_id_seq));
insert into test values(default, (select last_value from test_id_seq));
select * from test;
id | parent
----+--------
1 | 1
2 | 2
3 | 3
(3 rows)
And the following even simpler seems to work as well:
insert into test values(default, lastval());
Though I don't know how this would work when using multiple sequences... I looked it up; lastval() returns the last value returned or set with the last nextval or setval call to any sequence, so the following would get you in trouble:
create table test (
id serial primary key,
foo serial not null,
parent integer not null,
foreign key (parent) references test(id)
);
select setval('test_foo_seq', 100);
insert into test values(default, default, lastval());
ERROR: insert or update on table "test" violates foreign key constraint "test_parent_fkey"
DETAIL: Key (parent)=(101) is not present in table "test".
However the following would be okay:
insert into test values(default, default, currval('test_id_seq'));
select * from test;
id | foo | parent
----+-----+--------
2 | 102 | 2
(1 row)
The main question is - why would you want to insert record which relates to itself?
Schema looks like standard adjacency list - one of methods to implement trees in relational database.
The thing is that in most cases you simply have parent_id NULL for top-level element. This is actually much simpler to handle.