How to temporarily disable all constraints of a schema in postgresql? - sql

I need to temporarily disable all constraints and triggers of a schema in order to anonymize data in several tables. As there are dependencies between tables, I prefer to disable everything and once the anonymization treatment is over I can enable all constraints and triggers one more time.
I tried SET FOREIGN_KEY_CHECKS=0; and I got this error:
ERROR: unrecognized configuration parameter "foreign_key_checks"
SQL state: 42704
I've been reading a lot about that and some people say this is not possible.
Do you know a way to do that?
Thank you!

To disable foreign keys and deferrable unique and primary key constraints for a table, you can use
ALTER TABLE ... DISABLE TRIGGER ALL;
To disable all such constraints for the duration of a database session, you can
SET session_replication_role = replica;
Both tricks won't work for non-deferrable constraints and check constraints.

I found this solution,
I created a temporal table to keep all constraints definition:
CREATE TEMPORARY TABLE temp_constraints AS
SELECT conname constraintname, conrelid::regclass tablename, pg_get_constraintdef(oid) definition, contype
FROM pg_catalog.pg_constraint;
Then I drop all constraints:
DO $$
DECLARE constraint_name TEXT;
DECLARE constraint_table TEXT;
BEGIN
FOR constraint_name, constraint_table IN
SELECT constraintname , tablename FROM temp_constraints ORDER BY contype DESC
LOOP
EXECUTE 'ALTER TABLE ' || constraint_table || ' DROP CONSTRAINT IF EXISTS ' || constraint_name || ' CASCADE;';
END LOOP;
END $$;
And after anonymizing data I restore all constraints using the definitions in the temporal table and I drop the temporal table:
DO $$
DECLARE constraint_table TEXT;
DECLARE constraint_definition TEXT;
BEGIN
FOR constraint_table, constraint_definition IN
SELECT tablename, definition FROM temp_constraints ORDER BY contype DESC
LOOP
EXECUTE 'ALTER TABLE ' || constraint_table || ' ADD ' || constraint_definition || ';';
END LOOP;
DROP TABLE IF EXISTS temp_constraints;
END $$;
I hope this can help someone else. Thank you!

Related

How to delete every table in a specific schema in postgres?

How do I delete all the tables I have in a specific schema? Only the tables in the schema should be deleted.
I already have all the table names that I fetched with the code below, but how do delete all those tables?
The following is some psycopg2 code, and below that is the SQL generated
writeCon.execute("SELECT table_name FROM information_schema.tables WHERE table_schema='mySchema'")
SELECT table_name FROM information_schema.tables WHERE table_schema='mySchema'
You can use an anonymous code block for that.
WARNING: This code is playing with DROP TABLE statements, and they are really mean if you make a mistake ;) The CASCADE option drops all depending objects as well. Use it with care!
DO $$
DECLARE
row record;
BEGIN
FOR row IN SELECT * FROM pg_tables WHERE schemaname = 'mySchema'
LOOP
EXECUTE 'DROP TABLE mySchema.' || quote_ident(row.tablename) || ' CASCADE';
END LOOP;
END;
$$;
In case you want to drop everything in your schema, including wrappers, sequences, etc., consider dropping the schema itself and creating it again:
DROP SCHEMA mySchema CASCADE;
CREATE SCHEMA mySchema;
For a single-line command, you can use psql and its \gexec functionality:
SELECT format('DROP TABLE %I.%I', table_schema, table_name)
FROM information_schema.tables
WHERE table_schema= 'mySchema';\gexec
That will run the query and execute each result string as SQL command.

How to change Null constraint for all columns ?

I have some big tables (30+ columns) with NOT NULL constraints. I would like to change all those constraints to NULL. To do it for a single column I can use
ALTER TABLE <your table> MODIFY <column name> NULL;
Is there a way to do it for all columns in one request ? Or should I copy/paste this line for all columns (><) ?
Is there a way to do it for all columns in one request ?
Yes. By (ab)using EXECUTE IMMEDIATE in PL/SQL. Loop through all the columns by querying USER_TAB_COLUMNS view.
For example,
FOR i IN
( SELECT * FROM user_tab_columns WHERE table_name = '<TABLE_NAME>' AND NULLABLE='N'
)
LOOP
EXECUTE IMMEDIATE 'ALTER TABLE <TABLE_NAME> MODIFY i.COLUMN_NAME NULL';
END LOOP;
In my opinion, by the time you would write the PL/SQL block, you could do it much quickly by using a good text editor. In pure SQL you just need 30 queries for 30 columns.
For a single table you can issue a single alter table command to set the listed columns to allow null, which is a little more efficient than running one at a time, but you still have to list every column.
alter table ...
modify (
col1 null,
col1 null,
col3 null);
If you were applying not null constraints then this would be more worthwhile, as they require a scan of the table to ensure that no nulls are present, and (I think) an exclusive table lock.
You can query user_tab_cols and combine it with a FOR cursor & EXECUTE IMMEDIATE to modify all not null columns - the PL/SQL block for doing like that would look like so:
DECLARE
v_sql_statement VARCHAR2(2000);
BEGIN
FOR table_recs IN (SELECT table_name, column_name
FROM user_tab_cols
WHERE nullable = 'N') LOOP
v_sql_statement :=
'ALTER TABLE ' || table_recs.table_name || ' MODIFY ' || table_recs.column_name || ' NULL';
EXECUTE IMMEDIATE v_sql_statement;
END LOOP;
END;
If you want to do it for all columns in a database instead of the ones in current schema, you can replace user_tab_cols and put in dba_tab_cols; I'd just run the query in the FOR to ensure that the columns being fetched are indeed the correct ones to be modified

How do I alter a CHECK constraint on Firebird?

I have a Firebird table like this:
CREATE TABLE events (
event VARCHAR(6) NOT NULL
CHECK (event IN ('deploy', 'revert', 'fail')),
change_id CHAR(40) NOT NULL,
change VARCHAR(512) NOT NULL
);
Now I need to add another value to the IN() list in the CHECK constraint. How do I do that?
Things I've tried so far:
Updating the value in RDB$TRIGGERS.RDB$TRIGGER_SOURCE:
UPDATE RDB$TRIGGERS
SET RDB$TRIGGER_SOURCE = 'CHECK (event IN (''deploy'', ''revert'', ''fail'', ''merge''))'
WHERE RDB$TRIGGER_SOURCE = 'CHECK (event IN (''deploy'', ''revert'', ''fail''))';
Does not seem to work, as the trigger is compiled in RDB$TRIGGERS.RDB$TRIGGER_BLR.
Creating a new table with a new check, copying the data over, dropping the old table and renaming the new table. However, it seems that one cannot rename a Firebird table, so I can't make the new table have the same name as the old one.
I suspect updating RDB$TRIGGERS is the way to go (idk!), if only I could get Firebird to recompile the code. But maybe there's a better way?
You need to drop and the re-create the check constraint.
As you didn't specify a name for your constraint, Firebird created one, so you first need to find that name:
select trim(cc.rdb$constraint_name), trg.rdb$trigger_source
from rdb$relation_constraints rc
join rdb$check_constraints cc on rc.rdb$constraint_name = cc.rdb$constraint_name
join rdb$triggers trg on cc.rdb$trigger_name = trg.rdb$trigger_name
where rc.rdb$relation_name = 'EVENTS'
and rc.rdb$constraint_type = 'CHECK'
and trg.rdb$trigger_type = 1;
I just added the trigger source for informational reasons.
Once you have the name, you can drop it, e.g.
alter table events drop constraint integ_27;
and then add the new constraint:
alter table events
add constraint check_event_type
CHECK (event IN ('deploy', 'revert', 'fail', 'merge'));
In the future you don't need to look for the constraint name because you already it.
Here's how to do it dynamically:
SET AUTOddl OFF;
SET TERM ^;
EXECUTE BLOCK AS
DECLARE trig VARCHAR(64);
BEGIN
SELECT TRIM(cc.rdb$constraint_name)
FROM rdb$relation_constraints rc
JOIN rdb$check_constraints cc ON rc.rdb$constraint_name = cc.rdb$constraint_name
JOIN rdb$triggers trg ON cc.rdb$trigger_name = trg.rdb$trigger_name
WHERE rc.rdb$relation_name = 'EVENTS'
AND rc.rdb$constraint_type = 'CHECK'
AND trg.rdb$trigger_type = 1
INTO trig;
EXECUTE STATEMENT 'ALTER TABLE EVENTS DROP CONSTRAINT ' || trig;
END^
SET TERM ;^
COMMIT;
ALTER TABLE events ADD CONSTRAINT check_event_type CHECK (
event IN ('deploy', 'revert', 'fail', 'merge')
);
COMMIT;
I had to disable AUTOddl and put in explicit commits or else I got a deadlock on the ALTER TABLE ADD CONSTRAINT statement.
Here's how to do it dynamically:
EXECUTE BLOCK RETURNS (STMT VARCHAR(1000)) AS
BEGIN
SELECT TRIM(R.RDB$CONSTRAINT_NAME)
FROM RDB$RELATION_CONSTRAINTS R
WHERE R.RDB$RELATION_NAME = 'TABLE_NAME'
AND UPPER(R.RDB$CONSTRAINT_TYPE) = UPPER('PRIMARY KEY')
INTO :STMT;
IF (:STMT IS NOT NULL) THEN
BEGIN
EXECUTE STATEMENT 'ALTER TABLE TABLE_NAME DROP CONSTRAINT ' || :STMT || ';';
EXECUTE STATEMENT 'ALTER TABLE TABLE_NAME ADD CONSTRAINT ' || :STMT || ' PRIMARY KEY (FIELD1, FIELD2, FIELD3);';
END
ELSE
BEGIN
EXECUTE STATEMENT 'ALTER TABLE FIELD1 ADD CONSTRAINT PK_PRIMARY_NAME PRIMARY KEY (FIELD1, FIELD2, FIELD3);';
END
END;

Oracle PL SQL disable all constraints of database tables

I try to disable all constraint of all database tables. The database is named "database_test". The performance is not a problem.
I wrote a pl/sql script which give me all constraints with the table.
My problem is : when I run the script SQL developper say me "ORA-00972: identifier is too long". But I use only the fields already defined in the database.
CREATE OR REPLACE PROCEDURE DISPLAY_CONSTRAINT_DATABASE AS
BEGIN
FOR i IN (SELECT DISTINCT OWNER, OBJECT_NAME FROM ALL_OBJECTS WHERE OBJECT_TYPE = 'TABLE' AND OWNER = 'DB_NAME')
LOOP
FOR j IN (SELECT CONSTRAINT_NAME FROM ALL_CONSTRAINTS WHERE owner = i.OWNER AND table_name = i.OBJECT_NAME AND CONSTRAINT_TYPE='R')
LOOP
dbms_utility.exec_ddl_statement('alter table "DB_NAME.' || i.OBJECT_NAME || ' DISABLE CONSTRAINT ' || j.CONSTRAINT_NAME);
END LOOP;
END LOOP;
END DISPLAY_CONSTRAINT_DATABASE;
Your double quotes are wrong. You are generating SQL statements like this:
alter table "DB_NAME.FOOBAR disable constraint some_constraint;
Which misses the second double quote (my guess is that you probably wanted to put the second quote after the table name which would have been wrong as well).
You need to put each part of the identifier in quotes, not the whole thing:
alter table "DB_NAME"."FOOBAR" disable constraint some_constraint;
I also don't see the necessity to use dbms_sql:
execute immediate 'alter table "DB_NAME"."' || i.OBJECT_NAME || '" DISABLE CONSTRAINT ' || j.CONSTRAINT_NAME;
To avoid having to repeat the owner, I would actually change the statement to:
execute immediate 'alter table "'||j.owner||'"."' || i.OBJECT_NAME || '" DISABLE CONSTRAINT ' || j.CONSTRAINT_NAME;
Thus you only need to "hard-code" the owner name once in the procedure.

Oracle: Modify primary/foreignkeys and constrains inside a TRIGGER

we have an issue with the folowing trigger where we have to disable certain constraints to add the primary key of a table:
create or replace trigger TRG_NAMENSAENDERUNG_MA
after update of vname, nname on mitarbeiter
referencing new as new old as old
for each row
declare
initialien char(2);
benutzernr int;
benutzername_neu char(5);
benutzername_alt char(5);
begin
/*...
Code sets corect values to all variables.
...*/
/* the following is suposed to disable the two constraints*/
for i in (select fk_session_log_ben_name, SESSION_LOGGING FROM USER_CONSTRAINTS) loop
execute immediate 'ALTER TABLE'||i.session_logging||' DISABLE CONSTRAINT '||i.fk_session_log_ben_name||'';
end loop;
for i in (select fk_geraetekto_ben_name, GERAETEKONTO FROM USER_CONSTRAINTS) loop
execute immediate 'ALTER TABLE'||i.geraetekonto||' DISABLE CONSTRAINT '||i.fk_geraetekto_ben_name||'';
end loop;
/*Update statements which can only work without the constraints!!: */
UPDATE BENUTZERKONTO SET BENUTZERNAME = benutzername_neu WHERE BENUTZERNAME = benutzername_alt;
UPDATE SESSION_LOGGING SET BENUTZERNAME = benutzername_neu WHERE BENUTZERNAME = benutzername_alt;
UPDATE GERAETEKONTO SET BENUTZERNAME = benutzername_neu WHERE BENUTZERNAME = benutzername_alt;
/*Supposed to re-enable the constraints. */
for i in (select fk_session_log_ben_name, SESSION_LOGGING FROM USER_CONSTRAINTS) loop
execute immediate 'ALTER TABLE'||i.session_logging||' ENABLE CONSTRAINT '||i.fk_session_log_ben_name||'';
end loop;
for i in (select fk_geraetekto_ben_name, GERAETEKONTO FROM USER_CONSTRAINTS) loop
execute immediate 'ALTER TABLE'||i.geraetekonto||' ENABLE CONSTRAINT '||i.fk_geraetekto_ben_name||'';
end loop;
end TRG_NAMENSAENDERUNG_MA;
It throws the error that SESSION_LOGGING would be an "invalid identifier". It is typed right though and we copied the syntax from the example from the Oracale page.
What's the easiest way to achieve what we want?
Thanks in advance!
Here's the invalid identifier, these columns do not exist in USER_CONSTRAINTS. The full error message should have included the line number and given a hint at the meaning.
select fk_session_log_ben_name, SESSION_LOGGING FROM USER_CONSTRAINTS
The code should probably be this:
select constraint_name fk_session_log_ben_name, table_name SESSION_LOGGING
FROM USER_CONSTRAINTS;
My first suggestion is to redesign the schema so that it's no need to change the PK values (especially by users). PK must be surrogate. There must be serious reasons for a decision like yours. Hope you don't need such reasons :) The redesign mentioned, methinks, would be less expensive than struggling with the problem you cast. With changing PK values you'll probably face such problems again and again as the application is evolved.