SQL: How to check if foreign key was really changed - sql

I had FK with wrong constraints and I have to change it:
ALTER TABLE user_login_logout_fact DROP CONSTRAINT user_fk;
ALTER TABLE user_login_logout_fact
ADD CONSTRAINT user_fk FOREIGN KEY (user_id)
REFERENCES uuser (id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
There's no problem with it, but I have to apply this using patch file with checking if it was applied already. So I have to create function like this:
CREATE FUNCTION tryUpgrade(patch varchar) RETURNS integer AS $$
DECLARE testRecord RECORD;
BEGIN
RAISE NOTICE 'checking %', patch;
SELECT INTO testRecord * FROM patchlog where basename = patch;
IF FOUND THEN
RAISE NOTICE 'patch % has already been applied', patch;
RETURN 0;
END IF;
//check if constraints are ok
IF ok THEN
RAISE NOTICE 'upgraded but not noted';
INSERT INTO patchlog VALUES (patch, now());
RETURN 0;
END IF;
SELECT INTO testRecord upgrade(); //this function will alter table
INSERT INTO patchlog VALUES (patch, now());
RAISE NOTICE 'upgraded';
RETURN 1;
END;
$$ LANGUAGE plpgsql;
So, the question is - how to check if FK will use ON DELETE CASCADE but not old NO ACTION?

How to check if FK will use ON DELETE CASCADE but not old NO ACTION?
You can check the confdeltype in the system catalog table pg_constraint. The manual:
Foreign key deletion action code: a = no action, r = restrict, c = cascade, n = set null, d = set default
In PL/pgSQL code use it like:
IF EXISTS (
SELECT FROM pg_constraint
WHERE conname = 'user_fk'
AND conrelid = 'user_login_logout_fact'::regclass
AND confdeltype = 'c'
) THEN
-- all done
ELSE
-- run ALTER TABLE ...
END IF;
Table name can optionally be schema-qualified. See:
How do I speed up counting rows in a PostgreSQL table?
Information schema versus system catalog
As you answered yourself, the same can be achieved by querying the information schema, which adheres to an SQL standard.
However:
Not all major RDBMS implement it. Oracle doesn't, for one.
The information schema is implemented with (sometimes hugely complex) views (not tables), which make look-ups a lot slower than accessing tables in pg_catalog directly. A quick test in a real live db showed factor 10 for the example at hand. I have seem factor 1000 and more.
The aim of using the information schema is often to keep your implementation "portable". But this hardly ever works to begin with. The various RDBMS are just too far from the SQL standard in too many ways.
There is another benefit, though: Postgres does not assert anything concerning the structure of tables in pg_catalog across major versions. By using the information schema, one would be on the safe side across major versions of Postgres.
However, basic structures hardly change. While possible, it is rather unlikely, that a query like this would break in a future version.
See:
How to check if a table exists in a given schema
Related answer on dba.SE discussing "Information schema vs. system catalogs"

Also, second solution whitch is seems to be less postgresql-specific.
Info about foreign keys stores in referential_constraints table:
SELECT * FROM information_schema.referential_constraints
WHERE CAST(constraint_name AS TEXT) LIKE 'user_fk'
AND delete_rule LIKE 'CASCADE'
AND update_rule LIKE 'CASCADE';

Related

Oracle : ORA-04091: table AVIS is mutating, trigger/function may not see it

So, I'm a french student in IT, I've got this work like 4 weeks before, and I really can't get around this problem (I mainly worked on PostGRE and it's way different for me).
I've got those two tables :
link
To resume, I've got one table LIVRES (which is book in French), where there's two attributes :
-"refl", the primary key,
-"note_moy", a calculated attribute, later on that.
And a table AVIS (Rating I guess) with :
-"refl" as part Primary key (the other part is irrelevant), and foreign key references to LIVRES
-"note", with is rating from 0 to 20.
So the idea, is that "note_moy" on LIVRES is the average of all "note" ON AVIS, where the LIVRES.refl = AVIS.refl. So a book has an average rating, and I needed to do a TRIGGER, so every time there's a UPDATE or INSERT successfull, I would use the procedure "Maj_note_moy", who take a "refl", make the AVG of "note" on AVIS, and update the "note_moy" on LIVRES with that AVG.
Here the procedure :
CREATE OR REPLACE PROCEDURE maj_note_moy(vrefl livres.refl%type) IS
v_note_moy livres.note_moy%type;
BEGIN
SELECT AVG(note) INTO v_note_moy
FROM AVIS
WHERE refl = vrefl;
UPDATE Livres SET note_moy = v_note_moy WHERE refl = vrefl;
--RETURN v_note_moy;
END;
/
When I use it with a PLSQL block (like this), it works no problem.
Now, back to the trigger, I try this :
CREATE OR REPLACE TRIGGER trigger_note_moy
AFTER INSERT ON Avis
FOR EACH ROW
BEGIN
Maj_note_moy(:new.refl);
END;
/
And here it is : "Oracle : ORA-04091: table AVIS is mutating, trigger/function may not see it"
I understand what's the problem is, that I trying to do a SELECT on the table AVIS while I insert something into it. But that's what I can't get, I want the trigger after my insert or update, so why is it an issue ? I'm pretty sure I do something like this on PostGRE, and it didn't cause any problems.
So I check, I do saw the COMPOUND TRIGGER, but I really don't understand how it works, nor if it's relevant for my problem.
As #ThorsetnKettner points out in the comments, storing data redundantly via a trigger is rarely a good architectural approach. Embed the calculation in a view or even a materialized view and then you don't have to write code to maintain the data or debug issues when your code has bugs (and in a multi-user system, aggregate values maintained by triggers will almost always have bugs).
Since you're a student, though, I'm guessing that this is part of a homework assignment. If so
CREATE OR REPLACE TRIGGER trigger_note_moy
AFTER INSERT ON Avis
FOR EACH ROW
BEGIN
SELECT AVG(note)
INTO :new.note_moy
FROM AVIS
WHERE refl = :new.refl;
END;
or
CREATE OR REPLACE FUNCTION maj_note_moy(vrefl livres.refl%type)
RETURN livres.note_moy%type
IS
v_note_moy livres.note_moy%type;
BEGIN
SELECT AVG(note) INTO v_note_moy
FROM AVIS
WHERE refl = vrefl;
RETURN v_note_moy;
END;
CREATE OR REPLACE TRIGGER trigger_note_moy
AFTER INSERT ON Avis
FOR EACH ROW
BEGIN
:new.note_moy := maj_note_moy( :new.refl );
END;
would avoid the mutating table exception without needing to use a compound trigger.

How to substitute a variable when creating a check constraint?

I need to add a required field for newly added rows. However, it is undesirable to set the default value for old rows due to the large size of the table. I need to provide an automated script that will do this.
I tried this, but it does not work:
do $$
declare
max_id int8;
begin
select max(id) into max_id from transactions;
alter table transactions add constraint check_process_is_assigned check (id <= max_id or process_id is not null);
end $$;
Utility commands like ALTER TABLE do not accept parameters. Only the basic DML commands SELECT, INSERT, UPDATE, DELETE do.
See:
set "VALID UNTIL" value with a calculated timestamp
“ERROR: there is no parameter $1” in “EXECUTE .. USING ..;” statement in plpgsql
Creating user with password from variables in anonymous block
You need dynamic SQL like:
DO
$do$
BEGIN
EXECUTE format(
'ALTER TABLE transactions
ADD CONSTRAINT check_process_is_assigned CHECK (id <= %s OR process_id IS NOT NULL)'
, (SELECT max(id) FROM transactions)
);
END
$do$;
db<>fiddle here
This creates a CHECK constraint based on the current maximum id.
Maybe a NOT VALID constraint would serve better? That is not checked against existing rows:
ALTER TABLE transactions
ADD CONSTRAINT check_process_is_assigned CHECK (process_id IS NOT NULL) NOT VALID;
But you do have to "fix" old rows that get updated in this case. (I.e. assign a value to process_id if it was NULL so far.) See:
Enforce NOT NULL for set of columns with a CHECK constraint only for new rows
Best way to populate a new column in a large table?

How to create sequence if not exists

I tried to use code from Check if sequence exists in Postgres (plpgsql).
To create sequence if it does not exists. Running this code two times causes an exception:
sequence ... already exists.
How to create sequence only if it does not exist?
If the sequence does not exist, no message should be written and no error should occur so I cannot use the stored procedure in the other answer to this question since it writes message to log file every time if sequence exists.
do $$
begin
SET search_path = '';
IF not EXISTS (SELECT * FROM pg_class
WHERE relkind = 'S'
AND oid::regclass::text = 'firma1.' || quote_ident('myseq'))
THEN
SET search_path = firma1,public;
create sequence myseq;
END IF;
SET search_path = firma1,public;
end$$;
select nextval('myseq')::int as nr;
Postgres 9.5 or later
IF NOT EXISTS was added to CREATE SEQUENCE in Postgres 9.5. That's the simple solution now:
CREATE SEQUENCE IF NOT EXISTS myschema.myseq;
But consider details of the outdated answer anyway ...
And you know about serial or IDENTITY columns, right?
Auto increment table column
Postgres 9.4 or older
Sequences share the namespace with several other table-like objects. The manual:
The sequence name must be distinct from the name of any other
sequence, table, index, view, or foreign table in the same schema.
Bold emphasis mine. So there are three cases:
Name does not exist. -> Create sequence.
Sequence with the same name exists. -> Do nothing? Any output? Any logging?
Other conflicting object with the same name exists. -> Do something? Any output? Any logging?
Specify what to do in either case. A DO statement could look like this:
DO
$do$
DECLARE
_kind "char";
BEGIN
SELECT relkind
FROM pg_class
WHERE oid = 'myschema.myseq'::regclass -- sequence name, optionally schema-qualified
INTO _kind;
IF NOT FOUND THEN -- name is free
CREATE SEQUENCE myschema.myseq;
ELSIF _kind = 'S' THEN -- sequence exists
-- do nothing?
ELSE -- object name exists for different kind
-- do something!
END IF;
END
$do$;
Object types (relkind) in pg_class according to the manual:
r = ordinary table
i = index
S = sequence
v = view
m = materialized view
c = composite type
t = TOAST table
f = foreign table
Related:
How to check if a table exists in a given schema
I went a different route: just catch the exception:
DO
$$
BEGIN
CREATE SEQUENCE myseq;
EXCEPTION WHEN duplicate_table THEN
-- do nothing, it's already there
END
$$ LANGUAGE plpgsql;
One nice benefit to this is that you don't need to worry about what your current schema is.
If you don't need to preserve the potentially existing sequence, you could just drop it and then recreate it:
DROP SEQUENCE IF EXISTS id_seq;
CREATE SEQUENCE id_seq;
Postgres doesn't have CREATE SEQUENCE IF NOT EXISTS and if the table has default value using the sequence if you just drop the sequence, you might get error:
ERROR: cannot drop sequence (sequence_name) because other objects depend on it SQL state: 2BP01
For me, this one can help:
ALTER TABLE <tablename> ALTER COLUMN id DROP DEFAULT;
DROP SEQUENCE IF EXISTS <sequence_name>;
CREATE sequence <sequence_name>;
The information about sequences can be retrieved from information_schema.sequences (reference)
Try something like this (untested):
...
IF not EXISTS (SELECT * FROM information_schema.sequences
WHERE sequence_schema = 'firma1' AND sequence_name = 'myseq') THEN
...
I have a function to clean all tables in my database application at any time. It is build dynamically, but the essence is that it deletes all data from each table and resets the sequence.
This is the code to reset the sequence of one of the tables:
perform relname from pg_statio_all_sequences where relname = 'privileges_id_seq';
if found then
select setval ('privileges_id_seq',1, false) into i_result;
end if;
Hope this helps,
Loek
I am using postgres 8.4, I see that you use 9.2. Could make a difference where the information is stored.

Creating a Trigger To Delete Records

I have a table which we can call decks. Decks has an id used as its primary key along with some other attributes. The cards table contains a foreign key reference to the deck id and has a primary key of cardid as well. Another table exists called answers where its foreign key is the cardid.
So in order to delete from decks, the database requires I delete from answers first, then cards, and then finally from decks.
I would like to create a trigger which takes care of the first and second delete so that I only have to specify a delete statement from the decks table to completely destroy a deck.
Below is an example PostgreSQL trigger I've found, but I am not sure if its even possible to do what I am asking as I can find no examples online of anyone creating a trigger this way.
CREATE OR REPLACE FUNCTION autoCalculate() RETURNS TRIGGER AS $$
BEGIN
IF NEW.wins < 0 THEN
RAISE EXCEPTION 'Wins cannot be negative';
END IF;
IF(OLD.wins <> NEW.wins_ OR (OLD.losses <> NEW.losses) THEN
NEW.Winning_Percentage := calc_winning_percentage(NEW.Wins, NEW.Losses);
END IF
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER update_winning_percentage ON standings;
CREATE TRIGGER update_winning_percentage BEFORE INSERT OR UPDATE ON standings
FOR EACH ROW EXECUTE PROCEDURE autoCalculate();
If anyone has knowledge to do this, if they could nudge me in the right direction or provide an example of how to do this I'd be grateful!
On your foreign key definitions use ON DELETE CASCADE and it will take care of this for you
In Postgres 8.2 you can specify On Delete Casacde when you define your foreign key relationships.
http://www.postgresql.org/docs/8.2/static/ddl-constraints.html

PL/SQL DB triggers - Make insertion fail if a given condition is not met

I have a small test database I'm using for learning SQL.
There's a Duel table, which contains two Pilot foreign keys (the duelists). I want to check if the duelists haven't already "met" before insertion.
Pseudocode:
before insertion on Duel
for each row already in the table
if ((new_row.numpilot1 = old_row.numpilot1 and new_row.numpilot2 = old_row.numpilot2) OR
(new_row.numpilot1 = old_row.numpilot2 and new_row.numpilot2 = old_row.numpilot1)
)
insertion fails
One other alternative would be
tempnum integer;
select numpilot1 into tempnum from duel
where (:NEW.numpilot1 = numpilot1 and :NEW.numpilot2 = numpilot2) OR
(:NEW.numpilot1 = numpilot2 and :NEW.numpilot2 = numpilot1);
if tempnum == null
fail insertion
What is the PL/SQL (Oracle DBMS) version of this?
Normally, you wouldn't use a trigger for this sort of requirement. Instead, you'd create a couple of constraints on the table. I would suggest a unique constraint on (numpilot1, numpilot2) along with a check constraint that ensures that numpilot1 < numpilot2.
ALTER TABLE duel
ADD CONSTRAINT unique_pilot_combination
UNIQUE( numpilot1, numpilot2 );
ALTER TABLE duel
ADD CONSTRAINT chk_pilot1_lt_pilot2
CHECK( numpilot1_fk < numpilot2_fk );
If you wanted to do this sort of thing in a trigger, it would be quite a bit more complicated. In general, a row-level trigger on DUEL cannot query the DUEL table-- doing so would create a mutating table exception. You would need to create a collection in a package, a before statement trigger that initializes the collection, a row-level trigger that inserts the new pilot keys into the collection, and an after statement trigger that reads the data in the collection and does the validation. That's quite a few moving pieces to manage in addition to the potential performance hit. If you're really stuck with the trigger solution, however, there is an example of using the three trigger solution to work around mutating table exceptions on Tim Hall's site.
You could use a function-based index:
create unique index duel_uk on duel
( least(numpilot1, numpilot2), greatest(numpilot1, numpilot2));
The answer you are clearly seeking is a trigger like this:
create trigger duel_trg
before insert on duel
for each row
declare
dummy number;
begin
select count(*)
into dummy
from duel
where (numpilot1 = :new.numpilot1 and numpilot2 = :new.numpilot2)
or (numpilot1 = :new.numpilot2 and numpilot2 = :new.numpilot1);
if dummy > 0 then
raise_application_error(-20001,'You lose');
end if;
end;
However, that will fail to ensure integrity in a multi-user (or multi-session) environment, as this can happen:
User1> insert into duel (numpilot1, numpilot2) values (1,2);
-- trigger checks, all seems OK
User2> insert into duel (numpilot1, numpilot2) values (1,2);
-- trigger checks, all seems OK (can't see User1's new row
-- as it hasn't been committed)
User1> commit;
User2> commit;
Result: corrupt database. So while this trigger may satisfy the teacher, it is a bad solution and constraints should be used instead (preferably Justin's solution rather than mine!)