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;
Related
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!
I have two queries for disabling all constraints, but they don't seem to be working.
first one disables foreign keys:
select 'alter table '||table_name||' disable constraint '||constraint_name||';'
from user_constraints
where constraint_type ='R'
and status = 'ENABLED';
and the second disables everything else:
select 'alter table '||table_name||' disable constraint '||constraint_name||';'
from user_constraints
where status = 'ENABLED';
Now when I check the constraints with SELECT * FROM USER_CONSTRAINTS
I can see that they all are still 'ENABLED'.
Why is this?
I tried commit after running the queries but to no avail.
My goal is to disable constraints from all tables with those queries.
As per my comment above, it isn't sufficient to run the 2 queries, you then need to run all the alter table statements that these have generated. However you could do it all at once using PL/SQL. I have combined the 2 queries into one, using order by to process the foreign keys (constraint_type = 'R') first:
begin
for r in
( select 'alter table '||table_name||' disable constraint '||constraint_name as statement
from user_constraints
where status = 'ENABLED'
order by case constraint_type when 'R' then 1 else 2 end
)
loop
execute immediate r.statement;
end loop;
end;
So I have many tables in a db and I want to add two new columns for them.
For example, I have the columns "created_at" and "modified_at" and I want to create the columns "client_created_at" and "client_modified_at"
and at the same time populate these new columns with the values of "created_at" and "modified_at" of each table.
I imagine and have tried something like this:
ALTER TABLE patients, folders, auscultations, auscultations_notes, folder_ausc_association
ADD COLUMN client_created_at bigint, client_modified_at bigint;
UPDATE patients, folders, auscultations, auscultations_notes, folder_ausc_association
SET client_created_at = created_at, client_modified_at = modified_at
I'm not sure about how to structure it, any help would be appreciated!
In addition to the solution from Laurenz Albe, you could create an anonymous code block to do this job. Such a query can be very handy when you have many tables and don't want to create one statement per table.
DO $$
DECLARE
row record;
BEGIN
FOR row IN SELECT * FROM pg_tables WHERE schemaname = 'public'
LOOP
EXECUTE 'ALTER TABLE public.' || quote_ident(row.tablename) || ' ADD COLUMN client_created_at bigint, ADD COLUMN client_modified_at bigint;';
EXECUTE 'UPDATE ' || quote_ident(row.tablename) || ' SET client_created_at = created_at, client_modified_at = modified_at;';
END LOOP;
END;
$$;
Note: This code block adds the columns you want into all tables in the schema public - use it with care! You can adapt it to the tables you need by changing this query in the block:
SELECT * FROM pg_tables WHERE schemaname = 'public'
You'll have to use a statement per table for each of your two statements.
Define a maintenance window, and then perform for each table:
ALTER TABLE patients
ADD client_created_at bigint, client_modified_at bigint;
UPDATE patients
SET client_created_at = created_at, client_modified_at = modified_at;
ALTER TABLE patients
ALTER client_created_at SET NOT NULL,
ALTER client_created_at DEFAULT extract(epoch FROM current_timestamp),
ALTER client_modified_at SET NOT NULL,
ALTER client_modified_at DEFAULT extract(epoch FROM current_timestamp);
Use a different DEFAULT if you have different needs.
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.
Can anyone please tell me whats going on here...
CREATE TRIGGER afr_alt_trig AFTER ALTER ON SCHEMA
cols ora_name_list_t;
BEGIN
IF (ora_dict_obj_type = 'TABLE') THEN
select column_name bulk collect into cols from user_tab_cols where table_name = ora_dict_obj_name;
FOR i IN cols.first .. cols.last LOOP
dbms_output.put_line(cols(i));
END LOOP;
END IF;
END;
The above trigger is expected to display the list of all the columns in a table after the ALTER TABLE command is executed.
It works just fine when I ALTER a table with column(s) without any constraints.
Ex: Alter Table tab_xxx ADD(nucol char);--FINE
However, if I add a new column with a constraint like
Ex: ALTER TALE tab_xxx ADD(n number NOT NULL);
then this new column is not immediately reflected in the USER_TAB_COLS DD view and the FOR loop above to display the names of all the columns DOESN'T show this NEW column added.
Thanks already.