Postgres: Add constraint if it doesn't already exist - sql

Does Postgres have any way to say ALTER TABLE foo ADD CONSTRAINT bar ... which will just ignore the command if the constraint already exists, so that it doesn't raise an error?

A possible solution is to simply use DROP IF EXISTS before creating the new constraint.
ALTER TABLE foo DROP CONSTRAINT IF EXISTS bar;
ALTER TABLE foo ADD CONSTRAINT bar ...;
Seems easier than trying to query information_schema or catalogs, but might be slow on huge tables since it always recreates the constraint.
Edit 2015-07-13:
Kev pointed out in his answer that my solution creates a short window when the constraint doesn't exist and is not being enforced. While this is true, you can avoid such a window quite easily by wrapping both statements in a transaction.

This might help, although it may be a bit of a dirty hack:
create or replace function create_constraint_if_not_exists (
t_name text, c_name text, constraint_sql text
)
returns void AS
$$
begin
-- Look for our constraint
if not exists (select constraint_name
from information_schema.constraint_column_usage
where table_name = t_name and constraint_name = c_name) then
execute constraint_sql;
end if;
end;
$$ language 'plpgsql'
Then call with:
SELECT create_constraint_if_not_exists(
'foo',
'bar',
'ALTER TABLE foo ADD CONSTRAINT bar CHECK (foobies < 100);')
Updated:
As per Webmut's answer below suggesting:
ALTER TABLE foo DROP CONSTRAINT IF EXISTS bar;
ALTER TABLE foo ADD CONSTRAINT bar ...;
That's probably fine in your development database, or where you know you can shut out the apps that depend on this database for a maintenance window.
But if this is a lively mission critical 24x7 production environment you don't really want to be dropping constraints willy nilly like this. Even for a few milliseconds there's a short window where you're no longer enforcing your constraint which may allow errant values to slip through. That may have unintended consequences leading to considerable business costs at some point down the road.

You can use an exception handler inside an anonymous DO block to catch the duplicate object error.
DO $$
BEGIN
BEGIN
ALTER TABLE foo ADD CONSTRAINT bar ... ;
EXCEPTION
WHEN duplicate_table THEN -- postgres raises duplicate_table at surprising times. Ex.: for UNIQUE constraints.
WHEN duplicate_object THEN
RAISE NOTICE 'Table constraint foo.bar already exists';
END;
END $$;
http://www.postgresql.org/docs/9.4/static/sql-do.html http://www.postgresql.org/docs/9.4/static/plpgsql-control-structures.html
http://www.postgresql.org/docs/9.4/static/errcodes-appendix.html

you can run query over pg_constraint table to find constraint exists or not.like:
SELECT 1 FROM pg_constraint WHERE conname = 'constraint_name'"

Creating constraints can be an expensive operation on a table containing lots of data so I recommend not dropping constraints only to immediately create them again immediately after - you only want to create that thing once.
I chose to solve this using an anonymous code block, very similar to Mike Stankavich, however unlike Mike (who catches an error) I first check to see if the constraint exists:
DO $$
BEGIN
IF NOT EXISTS ( SELECT constraint_schema
, constraint_name
FROM information_schema.check_constraints
WHERE constraint_schema = 'myschema'
AND constraint_name = 'myconstraintname'
)
THEN
ALTER TABLE myschema.mytable ADD CONSTRAINT myconstraintname CHECK (column <= 100);
END IF;
END$$;

Using information_schema.constraint_column_usage to check for the constraint doesn't work for foreign keys. I use pg_constraint to check for primary keys, foreign keys or unique constraints:
CREATE OR REPLACE FUNCTION add_constraint(t_name text, c_name text, constraint_sql text)
RETURNS void
AS $$
BEGIN
IF NOT EXISTS(
SELECT c.conname
FROM pg_constraint AS c
INNER JOIN pg_class AS t ON c.conrelid = t."oid"
WHERE t.relname = t_name AND c.conname = c_name
) THEN
EXECUTE 'ALTER TABLE ' || t_name || ' ADD CONSTRAINT ' || c_name || ' ' || constraint_sql;
END IF;
END;
$$
LANGUAGE plpgsql;
Examples:
SELECT add_constraint('client_grant_system_scopes', 'client_grant_system_scopes_pk', 'PRIMARY KEY (client_grants_id, tenant, "scope");');
SELECT add_constraint('client_grant_system_scopes', 'client_grant_system_scopes_fk', 'FOREIGN KEY (tenant,"scope") REFERENCES system_scope(tenant,"scope") ON DELETE CASCADE;');
SELECT add_constraint('jwt_assertion_issuers', 'jwt_assertion_issuers_issuer_key', 'UNIQUE (issuer);');

Take advantage of regclass to reduce verbosity, increase performance, and avoid errors related to table naming clashes between schemas:
DO $$ BEGIN
IF NOT EXISTS (SELECT FROM pg_constraint
WHERE conrelid = 'foo'::regclass AND conname = 'bar') THEN
ALTER TABLE foo ADD CONSTRAINT bar...;
END IF;
END $$;
This will also work for tables in other schemas, e.g.:
DO $$ BEGIN
IF NOT EXISTS (SELECT FROM pg_constraint
WHERE conrelid = 's.foo'::regclass AND conname = 'bar') THEN
ALTER TABLE s.foo ADD CONSTRAINT bar...;
END IF;
END $$;

In psql You can use metacommand \gexec for run generated query.
SELECT 'ALTER TABLE xx ADD CONSTRAINT abc' WHERE not EXISTS (SELECT True FROM pg_constraint WHERE conname = 'abc') \gexec

For me those solutions didn't work because the constraint was a primary key.
This one worked for me:
ALTER TABLE <table.name> DROP CONSTRAINT IF EXISTS <constraint.name> CASCADE;

Considering all the above mentioned answers , the below approach help if you just want to check if a constraint exist in the table in which you are trying to insert and raise a notice if there happens to be one
DO
$$ BEGIN
IF NOT EXISTS (select constraint_name
from information_schema.table_constraints
where table_schema='schame_name' and upper(table_name) =
upper('table_name') and upper(constraint_name) = upper('constraint_name'))
THEN
ALTER TABLE TABLE_NAME ADD CONSTRAINT CONTRAINT_NAME..... ;
ELSE raise NOTICE 'Constraint CONTRAINT_NAME already exists in Table TABLE_NAME';
END IF;
END
$$;

Don't know why so many lines of code ?
-- SELECT "Column1", "Column2", "Column3" , count(star) FROM dbo."MyTable" GROUP BY "Column1" , "Column2" , "Column3" HAVING count(*) > 1;
alter table dbo."MyTable" drop constraint if exists "MyConstraint_Name" ;
ALTER TABLE dbo."MyTable" ADD CONSTRAINT "MyConstraint_Name" UNIQUE("Column1", "Column3", "Column2");

Related

Can I alter the constraints of one table by using the constraints of another table?

I had to drop a table and remake it using an archive. In the process, I lost the table's constraints--things like the primary key--triggers, indices, and more. I have, however, the same table on a different DB, which has all the appropriate constraints.
I have tried adding the constraints, triggers, and indices manually, but there are just too many.
I was wondering if I could do something like:
alter table t73
modify col_n....col_n+1
using (select constraints from t73#otherdb)
No, that won't work.
What you could do is to use some GUI (like TOAD or SQL Developer), find table t73, have a look at its Script which contains all commands (CREATE TABLE, CREATE INDEX, CREATE CONSTRAINT, ...) and copy/paste the ones you need and execute them in your current database.
That would be quick.
If you want to do it right (you know, pretending you know what you're doing, just like I do), then see DBMS_METADATA.GET_DDL and extract those commands from the database.
The final result should be the same.
this is below example how you can use dbms_metadata.get_ddl oracle package
create table EX_EMPLOYEe ( id number(5) null, name varchar2(100))
/
alter table ex_Employee add constraint PK_EX_EMPLOYEE primary key (id)
/
alter table ex_Employee add constraint FK_EX_EMPLOYEE foreign key (id)
references ex_Employee1 (id)
/
create table EX_EMPLOYEe1 ( id number(5) null, name varchar2(100))
/
alter table ex_Employee1 add constraint PK_EX_EMPLOYEE1 primary key (id)
alter table SYS_PARAM_KEY_LABEL
add constraint FK1_SYS_PARAM_KEY_LABEL foreign key (KEY_GROUP_ID)
references SYS_PARAM_KEY_GROUP (KEY_GROUP_ID);
/
CREATE INDEX IDX_EX_EMPLOYEe on ex_employee(name)
/
Create or replace PROCEDURE P_EX_EMPLOYEe as
begin
select id from ex_employee where rownum=1;
end;
/
CREATE OR REPLACE TRIGGER TRG_EX_EMPLOYEe AFTER DELETE ON EX_EMPLOYEe
FOR EACH ROW
BEGIN
DELETE FROM ex_employee1 WHERE id = :OLD.ID;
END;
/
select to_char( dbms_metadata.get_ddl('CONSTRAINT', c.constraint_name)) from user_constraints c where table_name='EX_EMPLOYEE'
and c.constraint_type='P'
union
select to_char( dbms_metadata.get_ddl('REF_CONSTRAINT', c.constraint_name)) from user_constraints c where table_name='EX_EMPLOYEE'
and c.constraint_type='R'
union
select to_char( dbms_metadata.get_ddl('INDEX', c.index_name)) from user_indexes c where table_name='EX_EMPLOYEE'
union
select to_char( dbms_metadata.get_ddl('PROCEDURE', d.name)) from user_dependencies d where d.referenced_name='EX_EMPLOYEE'
and d.type='PROCEDURE'
union
select to_char( dbms_metadata.get_ddl('TRIGGER', d.name)) from user_dependencies d where d.referenced_name='EX_EMPLOYEE'
and d.type='TRIGGER'

Is it possible to create a cross relationship constraint in postgresql? [duplicate]

I would like to add a constraint that will check values from related table.
I have 3 tables:
CREATE TABLE somethink_usr_rel (
user_id BIGINT NOT NULL,
stomethink_id BIGINT NOT NULL
);
CREATE TABLE usr (
id BIGINT NOT NULL,
role_id BIGINT NOT NULL
);
CREATE TABLE role (
id BIGINT NOT NULL,
type BIGINT NOT NULL
);
(If you want me to put constraint with FK let me know.)
I want to add a constraint to somethink_usr_rel that checks type in role ("two tables away"), e.g.:
ALTER TABLE somethink_usr_rel
ADD CONSTRAINT CH_sm_usr_type_check
CHECK (usr.role.type = 'SOME_ENUM');
I tried to do this with JOINs but didn't succeed. Any idea how to achieve it?
CHECK constraints cannot currently reference other tables. The manual:
Currently, CHECK expressions cannot contain subqueries nor refer to
variables other than columns of the current row.
One way is to use a trigger like demonstrated by #Wolph.
A clean solution without triggers: add redundant columns and include them in FOREIGN KEY constraints, which are the first choice to enforce referential integrity. Related answer on dba.SE with detailed instructions:
Enforcing constraints “two tables away”
Another option would be to "fake" an IMMUTABLE function doing the check and use that in a CHECK constraint. Postgres will allow this, but be aware of possible caveats. Best make that a NOT VALID constraint. See:
Disable all constraints and table checks while restoring a dump
A CHECK constraint is not an option if you need joins. You can create a trigger which raises an error instead.
Have a look at this example: http://www.postgresql.org/docs/9.1/static/plpgsql-trigger.html#PLPGSQL-TRIGGER-EXAMPLE
CREATE TABLE emp (
empname text,
salary integer,
last_date timestamp,
last_user text
);
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Check that empname and salary are given
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname cannot be null';
END IF;
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% cannot have null salary', NEW.empname;
END IF;
-- Who works for us when she must pay for it?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
END IF;
-- Remember who changed the payroll when
NEW.last_date := current_timestamp;
NEW.last_user := current_user;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
...i did it so (nazwa=user name, firma = company name) :
CREATE TABLE users
(
id bigserial CONSTRAINT firstkey PRIMARY KEY,
nazwa character varying(20),
firma character varying(50)
);
CREATE TABLE test
(
id bigserial CONSTRAINT firstkey PRIMARY KEY,
firma character varying(50),
towar character varying(20),
nazwisko character varying(20)
);
ALTER TABLE public.test ENABLE ROW LEVEL SECURITY;
CREATE OR REPLACE FUNCTION whoIAM3() RETURNS varchar(50) as $$
declare
result varchar(50);
BEGIN
select into result users.firma from users where users.nazwa = current_user;
return result;
END;
$$ LANGUAGE plpgsql;
CREATE POLICY user_policy ON public.test
USING (firma = whoIAM3());
CREATE FUNCTION test_trigger_function()
RETURNS trigger AS $$
BEGIN
NEW.firma:=whoIam3();
return NEW;
END
$$ LANGUAGE 'plpgsql'
CREATE TRIGGER test_trigger_insert BEFORE INSERT ON test FOR EACH ROW EXECUTE PROCEDURE test_trigger_function();

Multiple constraints in table: How to get all violations?

I have a table in Oracle with several constraints. When I insert a new record and not all constraints are valid, then Oracle raise only the "first" error. How to get all violations of my record?
CREATE TABLE A_TABLE_TEST (
COL_1 NUMBER NOT NULL,
COL_2 NUMBER NOT NULL,
COL_3 NUMBER NOT NULL,
COL_4 NUMBER NOT NULL
);
INSERT INTO A_TABLE_TEST values (1,null,null,2);
ORA-01400: cannot insert NULL into ("USER_4_8483C"."A_TABLE_TEST"."COL_2")
I would like to get something like this:
Column COL_2: cannot insert NULL
Column COL_3: cannot insert NULL
This would be also sufficient:
Column COL_2: not valid
Column COL_3: not valid
Of course I could write a trigger and check each column individually, but I like to prefer constraints rather than triggers, they are easier to maintain and don't require manually written code.
Any idea?
There no straightforward way to report all possible constraint violations. Because when Oracle stumble on first violation of a constraint, no further evaluation is possible, statement fails, unless that constraint is deferred one or the log errors clause has been included in the DML statement. But it should be noted that log errors clause won't be able to catch all possible constraint violations, just records first one.
As one of the possible ways is to:
create exceptions table. It can be done by executing ora_home/rdbms/admin/utlexpt.sql script. The table's structure is pretty simple;
disable all table constraints;
execute DMLs;
enable all constraints with exceptions into <<exception table name>> clause. If you executed utlexpt.sql script, the name of the table exceptions are going to be stored would be exceptions.
Test table:
create table t1(
col1 number not null,
col2 number not null,
col3 number not null,
col4 number not null
);
Try to execute an insert statement:
insert into t1(col1, col2, col3, col4)
values(1, null, 2, null);
Error report -
SQL Error: ORA-01400: cannot insert NULL into ("HR"."T1"."COL2")
Disable all table's constraints:
alter table T1 disable constraint SYS_C009951;
alter table T1 disable constraint SYS_C009950;
alter table T1 disable constraint SYS_C009953;
alter table T1 disable constraint SYS_C009952;
Try to execute the previously failed insert statement again:
insert into t1(col1, col2, col3, col4)
values(1, null, 2, null);
1 rows inserted.
commit;
Now, enable table's constraints and store exceptions, if there are any, in the exceptions table:
alter table T1 enable constraint SYS_C009951 exceptions into exceptions;
alter table T1 enable constraint SYS_C009950 exceptions into exceptions;
alter table T1 enable constraint SYS_C009953 exceptions into exceptions;
alter table T1 enable constraint SYS_C009952 exceptions into exceptions;
Check the exceptions table:
column row_id format a30;
column owner format a7;
column table_name format a10;
column constraint format a12;
select *
from exceptions
ROW_ID OWNER TABLE_NAME CONSTRAINT
------------------------------ ------- ------- ------------
AAAWmUAAJAAAF6WAAA HR T1 SYS_C009951
AAAWmUAAJAAAF6WAAA HR T1 SYS_C009953
Two constraints have been violated. To find out column names, simply refer to user_cons_columns data dictionary view:
column table_name format a10;
column column_name format a7;
column row_id format a20;
select e.table_name
, t.COLUMN_NAME
, e.ROW_ID
from user_cons_columns t
join exceptions e
on (e.constraint = t.constraint_name)
TABLE_NAME COLUMN_NAME ROW_ID
---------- ---------- --------------------
T1 COL2 AAAWmUAAJAAAF6WAAA
T1 COL4 AAAWmUAAJAAAF6WAAA
The above query gives us column names, and rowids of problematic records. Having rowids at hand, there should be no problem to find those records that cause constraint violation, fix them, and re-enable constraints once again.
Here is the script that has been used to generate alter table statements for enabling and disabling constraints:
column cons_disable format a50
column cons_enable format a72
select 'alter table ' || t.table_name || ' disable constraint '||
t.constraint_name || ';' as cons_disable
, 'alter table ' || t.table_name || ' enable constraint '||
t.constraint_name || ' exceptions into exceptions;' as cons_enable
from user_constraints t
where t.table_name = 'T1'
order by t.constraint_type
You would have to implement a before-insert trigger to loop through all the conditions that you care about.
Think about the situation from the database's perspective. When you do an insert, the database can basically do two things: complete the insert successfully or fail for some reason (typically a constraint violation).
The database wants to proceed as quickly as possibly and not do unnecessary work. Once it has found the first complaint violation, it knows that the record is not going into the database. So, the engine wisely returns an error and stops checking further constraints. There is no reason for the engine to get the full list of violations.
In the meantime I found a lean solution using deferred constraints:
CREATE TABLE A_TABLE_TEST (
COL_1 NUMBER NOT NULL DEFERRABLE INITIALLY DEFERRED,
COL_2 NUMBER NOT NULL DEFERRABLE INITIALLY DEFERRED,
COL_3 NUMBER NOT NULL DEFERRABLE INITIALLY DEFERRED,
COL_4 NUMBER NOT NULL DEFERRABLE INITIALLY DEFERRED
);
INSERT INTO A_TABLE_TEST values (1,null,null,2);
DECLARE
CHECK_CONSTRAINT_VIOLATED EXCEPTION;
PRAGMA EXCEPTION_INIT(CHECK_CONSTRAINT_VIOLATED, -2290);
REF_CONSTRAINT_VIOLATED EXCEPTION;
PRAGMA EXCEPTION_INIT(REF_CONSTRAINT_VIOLATED , -2292);
CURSOR CheckConstraints IS
SELECT TABLE_NAME, CONSTRAINT_NAME, COLUMN_NAME
FROM USER_CONSTRAINTS
JOIN USER_CONS_COLUMNS USING (TABLE_NAME, CONSTRAINT_NAME)
WHERE TABLE_NAME = 'A_TABLE_TEST'
AND DEFERRED = 'DEFERRED'
AND STATUS = 'ENABLED';
BEGIN
FOR aCon IN CheckConstraints LOOP
BEGIN
EXECUTE IMMEDIATE 'SET CONSTRAINT '||aCon.CONSTRAINT_NAME||' IMMEDIATE';
EXCEPTION
WHEN CHECK_CONSTRAINT_VIOLATED OR REF_CONSTRAINT_VIOLATED THEN
DBMS_OUTPUT.PUT_LINE('Constraint '||aCon.CONSTRAINT_NAME||' at Column '||aCon.COLUMN_NAME||' violated');
END;
END LOOP;
END;
It works with any check constraint (not only NOT NULL). Checking FOREIGN KEY Constraint should work as well.
Add/Modify/Delete of constraints does not require any further maintenance.

Generate SQL to update primary key

I want to change a primary key and all table rows which reference to this value.
# table master
master_id|name
===============
foo|bar
# table detail
detail_id|master_id|name
========================
1234|foo|blu
If I give a script or function
table=master, value-old=foo, value-new=abc
I want to create a SQL snippet that executes updates on all tables which refere to table "master":
update detail set master_id=value-new where master_id=value-new;
.....
With the help of introspection, this should be possible.
I use postgres.
Update
The problem is, that there are many tables which have a foreign-key to the table "master". I want a way to automatically update all tables which have a foreign-key to master table.
The easiest way to deal with primary key changes - by far - is to ALTER your referring foreign key constraints to be ON UPDATE CASCADE.
You are then free to update the primary key values, and the changes will cascade to child tables. It can be a very slow process due to all the random I/O, but it will work.
You do need to watch out not to violate uniqueness constraints on the primary key column during the process.
A fiddlier but faster way is to add a new UNIQUE column for the new PK, populate it, add new columns to all the referring tables that point to the new PK, drop the old FK constraints and columns, then finally drop the old PK.
If you need to change PK you could use DEFFERED CONSTRAINTS:
SET CONSTRAINTS sets the behavior of constraint checking within the current transaction. IMMEDIATE constraints are checked at the end of each statement. DEFERRED constraints are not checked until transaction commit. Each constraint has its own IMMEDIATE or DEFERRED mode.
Data preparation:
CREATE TABLE master(master_id VARCHAR(10) PRIMARY KEY, name VARCHAR(10));
INSERT INTO master(master_id, name) VALUES ('foo', 'bar');
CREATE TABLE detail(detail_id INT PRIMARY KEY, master_id VARCHAR(10)
,name VARCHAR(10)
,CONSTRAINT fk_det_mas FOREIGN KEY (master_id) REFERENCES master(master_id));
INSERT INTO detail(detail_id, master_id, name) VALUES (1234,'foo','blu');
In normal situtation if you try to change master detail you will end up with error:
update detail set master_id='foo2' where master_id='foo';
-- ERROR: insert or update on table "detail" violates foreign key
-- constraint "fk_det_mas"
-- DETAIL: Key (master_id)=(foo2) is not present in table "master"
update master set master_id='foo2' where master_id='foo';
-- ERROR: update or delete on table "master" violates foreign key
-- constraint "fk_det_mas" on table "detail"
-- DETAIL: Key (master_id)=(foo) is still referenced from table "detail".
But if you change FK resolution to deffered, there is no problem:
ALTER TABLE detail DROP CONSTRAINT fk_det_mas ;
ALTER TABLE detail ADD CONSTRAINT fk_det_mas FOREIGN KEY (master_id)
REFERENCES master(master_id) DEFERRABLE;
BEGIN TRANSACTION;
SET CONSTRAINTS ALL DEFERRED;
UPDATE master set master_id='foo2' where master_id = 'foo';
UPDATE detail set master_id='foo2' where master_id = 'foo';
COMMIT;
DBFiddle Demo
Please note that you could do many things inside transaction, but during COMMIT all referential integrity checks have to hold.
EDIT
If you want to automate this process you could use dynamic SQL and metadata tables. Here Proof of Concept for one FK column:
CREATE TABLE master(master_id VARCHAR(10) PRIMARY KEY, name VARCHAR(10));
INSERT INTO master(master_id, name)
VALUES ('foo', 'bar');
CREATE TABLE detail(detail_id INT PRIMARY KEY, master_id VARCHAR(10),
name VARCHAR(10)
,CONSTRAINT fk_det_mas FOREIGN KEY (master_id)
REFERENCES master(master_id)DEFERRABLE ) ;
INSERT INTO detail(detail_id, master_id, name) VALUES (1234,'foo','blu');
CREATE TABLE detail_second(detail_id INT PRIMARY KEY, name VARCHAR(10),
master_id_second_name VARCHAR(10)
,CONSTRAINT fk_det_mas_2 FOREIGN KEY (master_id_second_name)
REFERENCES master(master_id)DEFERRABLE ) ;
INSERT INTO detail_second(detail_id, master_id_second_name, name)
VALUES (1234,'foo','blu');
And code:
BEGIN TRANSACTION;
SET CONSTRAINTS ALL DEFERRED;
DO $$
DECLARE
old_pk TEXT = 'foo';
new_pk TEXT = 'foo2';
table_name TEXT = 'master';
BEGIN
-- update childs
EXECUTE (select
string_agg(FORMAT('UPDATE %s SET %s = ''%s'' WHERE %s =''%s'' ;'
,c.relname,pa.attname, new_pk,pa.attname, old_pk),CHR(13)) AS sql
from pg_constraint pc
join pg_class c on pc.conrelid = c.oid
join pg_attribute pa ON pc.conkey[1] = pa.attnum
and pa.attrelid = pc.conrelid
join pg_attribute pa2 ON pc.confkey[1] = pa2.attnum
and pa2.attrelid = table_name::regclass
where pc.contype = 'f');
-- update parent
EXECUTE ( SELECT FORMAT('UPDATE %s SET %s = ''%s'' WHERE %s =''%s'';'
,c.relname,pa.attname, new_pk,pa.attname, old_pk)
FROM pg_constraint pc
join pg_class c on pc.conrelid = c.oid
join pg_attribute pa ON pc.conkey[1] = pa.attnum
and pa.attrelid = pc.conrelid
WHERE pc.contype IN ('p','u')
AND conrelid = table_name::regclass
);
END
$$;
COMMIT;
DBFiddle Demo 2
EDIT 2:
I tried it, but it does not work. It would be nice, if the script could show the SQL. This is enough. After looking at the generated SQL I can execute it if psql -f
have you tried it? It did not work for me.
Yes, I have tried it. Just check above live demo links.
I prepare the same demo with more debug info:
values before
executed SQL
values after
Please make sure that FKs are defined as DEFFERED.
DBFiddle 2 with debug info
LAST EDIT
Then I wanted to see the sql instead of executing it. I removed "perform" from your fiddle, but then I get an error. See: http://dbfiddle.uk/?rdbms=postgres_10&fiddle=b9431c8608e54b4c42b5dbd145aa1458
If you only want to get SQL code you could create function:
CREATE FUNCTION generate_update_sql(table_name VARCHAR(100), old_pk VARCHAR(100), new_pk VARCHAR(100))
RETURNS TEXT
AS
$$
BEGIN
RETURN
-- update childs
(SELECT
string_agg(FORMAT('UPDATE %s SET %s = ''%s'' WHERE %s =''%s'' ;', c.relname,pa.attname, new_pk,pa.attname, old_pk),CHR(13)) AS sql
FROM pg_constraint pc
JOIN pg_class c on pc.conrelid = c.oid
JOIN pg_attribute pa ON pc.conkey[1] = pa.attnum and pa.attrelid = pc.conrelid
JOIN pg_attribute pa2 ON pc.confkey[1] = pa2.attnum and pa2.attrelid = table_name::regclass
WHERE pc.contype = 'f') || CHR(13) ||
-- update parent
(SELECT FORMAT('UPDATE %s SET %s = ''%s'' WHERE %s =''%s'';', c.relname,pa.attname, new_pk,pa.attname, old_pk)
FROM pg_constraint pc
JOIN pg_class c on pc.conrelid = c.oid
JOIN pg_attribute pa ON pc.conkey[1] = pa.attnum and pa.attrelid = pc.conrelid
WHERE pc.contype IN ('p','u')
AND conrelid = table_name::regclass)
;
END
$$ LANGUAGE plpgsql;
And execution:
SELECT generate_update_sql('master', 'foo', 'foo');
UPDATE detail SET master_id = 'foo' WHERE master_id ='foo' ;
UPDATE detail_second SET master_id_second_name = 'foo'
WHERE master_id_second_name ='foo' ;
UPDATE master SET master_id = 'foo' WHERE master_id ='foo';
DBFiddle Function Demo
Of course there is a place for improvement for example handling identifiers like "table with space in name" and so on.
I found a dirty solution: in psql the command \d master_table show the relevant information. With some text magic, it is possible to extract the needed information:
echo "UPDATE master_table SET id='NEW' WHERE id='OLD';" > tmp/foreign-keys.txt
psql -c '\d master_table' | grep -P 'TABLE.*CONSTRAINT.*FOREIGN KEY' \
>> tmp/foreign-keys.txt
reprec '.*TABLE ("[^"]*") CONSTRAINT[^(]*\(([^)]*)\).*' \
"UPDATE \1 set \2='NEW' WHERE \2='OLD';" \
tmp/foreign-keys.txt
psql -1 -f tmp/foreign-keys.txt
Result:
UPDATE "master_table" SET id='NEW' WHERE id='OLD';
UPDATE "other_table" SET master_id='NEW' WHERE master_id='OLD';
...
But better solutions are welcome.
I dont think you can update the Primary key. One possible work around is that you can remove the primary key constraint from the table column. Then update the column value.
Updating the primary key can lead you to some serious problems. But if you still want to do it.
Please refer this Thread.(kevchadders has given a solution.)

Drop Unique Constraint on Table Column without Knowing the Constraint Name

In Oracle 10g, how can I drop a unique constraint on a column without knowing the name of the constraint (e.g. a system generated name, which won't necessarily be the same across database instances)? Dropping and recreating the table isn't an option. Is it possible?
You can retrieve the constraint's name with:
SELECT CONSTRAINT_NAME
FROM USER_CONSTRAINTS WHERE TABLE_NAME = 'xxx'
AND CONSTRAINT_TYPE = 'U'
You can for instance create a stored procedure that executes the previous sql, stores its result in a variable and uses this variable in ALTER TABLE DROP CONSTRAINT
EDIT: e.g.:
BEGIN
FOR r IN (
SELECT TABLE_NAME, CONSTRAINT_NAME
FROM USER_CONSTRAINTS WHERE TABLE_NAME = 'xxx'
AND CONSTRAINT_TYPE = 'U') LOOP
EXECUTE IMMEDIATE REPLACE(REPLACE(
'ALTER TABLE #TABLE# DROP CONSTRAINT #CON#'
,'#TABLE#',r.TABLE_NAME)
,'#CON#',r.CONSTRAINT_NAME);
END LOOP;
END;