PostgreSQL - properly change ID of table row - postgresql

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

Related

Postgres: change primary key in existing table

I have a situation with two tables where one has a foreign key pointing to the other table (simplified) schema:
CREATE TABLE table1 (
name VARCHAR(64) NOT NULL,
PRIMARY KEY(name)
);
CREATE TABLE table2 (
id SERIAL PRIMARY KEY,
table1_name VARCHAR(64) NOT NULL REFERENCES table1(name)
);
Now I regret using the name column as primary key in table1 - and would like to add integer serial key instead. Since I already have data in the database I guess I need to do this carefully. My current plan is as follows:
Drop the foreign key constraint: table2(name) with ALTER TABLE table2 DROP CONSTRAINT table2_table1_name_fkey;
Drop the primary key constraint on table1(name) with ALTER TABLE table1 DROP CONSTRAINT name_pkey;.
Add a unique constraint on table1(name) with ALTER TABLE table1 ADD UNIQUE(name);
Add a automatic primary key to table1 with ALTER TABLE table1 ADD COLUMN ID SERIAL PRIMARY KEY;.
Add a new column table1_id to table2 with ALTER TABLE table2 ADD COLUMN table1_id INT;
Update all rows in table2 - so that the new column (which will be promoted to a foreign key) gets the correct value - as inferred by the previous (still present) foreign key table1_name.
I have completed steps up to an including step 5, but the UPDATE (with JOIN?) required to complete 6 is beyond my SQL paygrade. My current (google based ...) attempt looks like:
UPDATE
table2
SET
table2.table1_id = t1.id
FROM
table1 t1
LEFT JOIN table2 t2
ON t2.table1_name = t1.name;
You do not need JOIN in UPDATE.
UPDATE
table2 t2
SET
table1_id = t1.id
FROM
table1 t1
WHERE
t2.table1_name = t1.name;

How do copy row from one table to another with dependencies?

I have some numbers of tables with foreign key between them. Structure of table is not important. For example, table A is a top level table. Table B and C have foreign key on table A and tables D, E and F and G have foreign key on table B and C resp.
The question is: is there a way for clone one of row from table A
with all depeddencies (row in tables B - G) using SQL?
You didn't provide structure nor data, so you will have to work with my example. The basic idea is just selecting the appropriate FK columns to the respective PK columns as you progress from parent to child.
-- create base tables
create table a (a_id integer, a_col1 text, constraint a_pk primary key (a_id));
create table b (b_id integer, a_id integer, b_col1 text
, constraint b_pk primary key (b_id)
, constraint b_fk_a foreign key (a_id)
references a(a_id)
);
create table g (g_id integer, b_id integer, g_col1 text
, constraint g_pk primary key (g_id)
, constraint g_fk_b foreign key (b_id)
references b(b_id)
);
-------------------------------------------------------------------------------------------------------
-- populate
insert into a (a_id,a_col1) values (11,'ta1'), (12,'ta2');
insert into b (b_id,a_id,b_col1) values (21,11,'tb1'), (22,11,'tb2'), (23,12,'tb3'), (24,12,'tb4');
insert into g (g_id,b_id,g_col1) values (71,21,'tg1'), (72,21,'tg2'), (73,22,'tg3'), (74,22,'tg4')
, (75,23,'tg5'), (76,23,'tg6'), (77,24,'tg7'), (78,24,'tg8');
-------------------------------------------------------------------------------------------------------
-- Soution: clone a_id 12
create table a_clone as
select *
from a
where a_id = 12;
create table b_clone as
select *
from b
where a_id = 12;
create table g_clone as
select *
from g
where b_id in
(select b_id
from b
where a_id = 12
);
-- done
The 'comes to mind' suggestion from Sticky Bit is doable, but it's not straight forward. I believe the above it straight forward.

Use COPY FROM command in PostgreSQL to insert in multiple tables

I'm trying to use the performance of COPY FROM command in PostgreSQL to get all data of 1 table of a CSV file (CSV -> table1) and I need to insert other data, but, in a new table. I will need of a primary key of first table to put as a foreign key in second table.
Example:
I need to insert 1,000,000 of names in table1 and 500,000 of names in table2, but, all names in table2 reference to 1 tuple in table1.
CREATE TABLE table1 (
table1Id bigserial NOT NULL,
Name varchar(100) NULL,
CONSTRAINT table1Id PRIMARY KEY (table1Id)
);
CREATE TABLE table2 (
table2Id bigserial NOT NULL,
Other_name varchar(100) NOT NULL
table1_table1Id int8 NOT NULL,
CONSTRAINT table2_pk PRIMARY KEY (table2Id)
);
Command COPY does not allow table manipulations while copying data (such as look up to other table for fetching proper foreign keys to insert). To insert into table2 ids for corresponding rows from table1 you need to drop NOT NULL constraint for that field, COPY data and then UPDATE that fields separately.
Assuming table1 and table2 tables can be joined by table1.Name = table2.Other_name, the code is:
Before COPY:
ALTER TABLE table2 ALTER COLUMN table1_table1Id DROP NOT NULL;
After COPY:
UPDATE table2 SET table2.table1_table1Id = table1.table1Id
FROM table1
WHERE table1.Name = table2.Other_name;
ALTER TABLE table2 ALTER COLUMN table1_table1Id SET NOT NULL;

How to leave a row in the table with value of foreign key in, if a row with a foreign key is deleted?

There is table1:
CREATE TABLE table1
(
id serial NOT NULL,
CONSTRAINT pk_table1_id PRIMARY KEY (id),
)
There is table2:
CREATE TABLE table2
(
id serial NOT NULL,
fk_1 integer,
CONSTRAINT fk_to_table1 FOREIGN KEY (fk_1)
REFERENCES table1 (id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE NO ACTION
NOT VALID
)
When I try to delete some row from table1 I got exception:
UPDATE or DELETE in the table "table1" violates the foreign key constraint "fk_to_table1" of the table "table2"
Is there some way to leave(save) a row in the table2 with same value in fk_1 (id from table1) if a row with a foreign key from table1 is deleted?
Example:
Table1:
id
1
2
3
Teble2:
id fk_1
1 | 1
2 | 1
3 | 3
4 | 2
What I want to see after delete first row from table1:
Table1:
id
2
3
Teble2:
id fk_1
1 | 1
2 | 1
3 | 3
4 | 2
You can do that using ON DELETE SET NULL:
CREATE TABLE table2
(
id int NOT NULL,
fk_1 integer,
CONSTRAINT fk_to_table1 FOREIGN KEY (fk_1)
REFERENCES table1 (id)
ON UPDATE CASCADE
ON DELETE SET NULL
);
insert into table1 values (1), (2);
insert into table2 values (1,1), (2,1), (3,2), (4,2);
Then if you run:
delete from table1
where id = 1;
Table2 will look like this:
id | fk_1
---+-----
1 |
2 |
3 | 2
4 | 2
Online example: http://rextester.com/NPUG18060
The short answer is: you cannot.
the purpose of a foreign key is to verify that whenever that attribute has a value, the value is in the referenced table (except when the value is NULL---NULL does not need to exist in the table).
Two suggestions:
Solution 1. Keep the tuple in Table2, do not delete it. Instead, add a boolean attribute that determines is the tuple has been deleted.
Specifically for your problem. Add an attribute deleted to the table.
Set if to default to false. And when the tuple is deleted
create a trigger to set this attribute to true instead. This is cumbersome
and error prone.
Solution 2 (this is my preferred solution). You can use a third table that is managed via triggers.
Every time a tuple in the table2 is added, a tuple to the table3 is
added. Table3 will only have one attribute (the one you want to have
the foreign key referenced to). Table3 will be the set of all values of the attribute that have been part of Table2. The trigger to add to Table3 should use upsert, in case the attribute being added has been seen before.
Table1 will then have the foreign key constraint pointing to Table3.

postgres getting data where two column references same parent id

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;