Foreign keys in postgresql can be violated by trigger - postgresql

I've created some tables in postgres, added a foreign key from one table to another and set ON DELETE to CASCADE. Strangely enough, I have some fields that appear to be violating this constraint.
Is this normal behaviour? And if so, is there a way to get the behaviour I want (no violations possible)?
Edit:
I orginaly created the foreign key as part of CREATE TABLE, just using
... REFERENCES product (id) ON UPDATE CASCADE ON DELETE CASCADE
The current code pgAdmin3 gives is
ALTER TABLE cultivar
ADD CONSTRAINT cultivar_id_fkey FOREIGN KEY (id)
REFERENCES product (id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE;
Edit 2:
To Clarify, I have a sneaking suspicion that the constraints are only checked when updates/inserts happen but are then never looked at again. Unfortunately I don't know enough about postgres to find out if this is true or how fields could end up in the database without those checks being run.
If this is the case, is there some way to check all the foreign keys and fix those problems?
Edit 3:
A constraint violation can be caused by a faulty trigger, see below

I tried to create a simple example that shows foreign key constraint being enforced. With this example I prove I'm not allowed to enter data that violates the fk and I prove that if the fk is not in place during insert, and I enable the fk, the fk constraint throws an error telling me data violates the fk. So I'm not seeing how you have data in the table that violates a fk that is in place. I'm on 9.0, but this should not be different on 8.3. If you can show a working example that proves your issue that might help.
--CREATE TABLES--
CREATE TABLE parent
(
parent_id integer NOT NULL,
first_name character varying(50) NOT NULL,
CONSTRAINT pk_parent PRIMARY KEY (parent_id)
)
WITH (
OIDS=FALSE
);
ALTER TABLE parent OWNER TO postgres;
CREATE TABLE child
(
child_id integer NOT NULL,
parent_id integer NOT NULL,
first_name character varying(50) NOT NULL,
CONSTRAINT pk_child PRIMARY KEY (child_id),
CONSTRAINT fk1_child FOREIGN KEY (parent_id)
REFERENCES parent (parent_id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE
)
WITH (
OIDS=FALSE
);
ALTER TABLE child OWNER TO postgres;
--CREATE TABLES--
--INSERT TEST DATA--
INSERT INTO parent(parent_id,first_name)
SELECT 1,'Daddy'
UNION
SELECT 2,'Mommy';
INSERT INTO child(child_id,parent_id,first_name)
SELECT 1,1,'Billy'
UNION
SELECT 2,1,'Jenny'
UNION
SELECT 3,1,'Kimmy'
UNION
SELECT 4,2,'Billy'
UNION
SELECT 5,2,'Jenny'
UNION
SELECT 6,2,'Kimmy';
--INSERT TEST DATA--
--SHOW THE DATA WE HAVE--
select parent.first_name,
child.first_name
from parent
inner join child
on child.parent_id = parent.parent_id
order by parent.first_name, child.first_name asc;
--SHOW THE DATA WE HAVE--
--DELETE PARENT WHO HAS CHILDREN--
BEGIN TRANSACTION;
delete from parent
where parent_id = 1;
--Check to see if any children that were linked to Daddy are still there?
--None there so the cascade delete worked.
select parent.first_name,
child.first_name
from parent
right outer join child
on child.parent_id = parent.parent_id
order by parent.first_name, child.first_name asc;
ROLLBACK TRANSACTION;
--TRY ALLOW NO REFERENTIAL DATA IN--
BEGIN TRANSACTION;
--Get rid of fk constraint so we can insert red headed step child
ALTER TABLE child DROP CONSTRAINT fk1_child;
INSERT INTO child(child_id,parent_id,first_name)
SELECT 7,99999,'Red Headed Step Child';
select parent.first_name,
child.first_name
from parent
right outer join child
on child.parent_id = parent.parent_id
order by parent.first_name, child.first_name asc;
--Will throw FK check violation because parent 99999 doesn't exist in parent table
ALTER TABLE child
ADD CONSTRAINT fk1_child FOREIGN KEY (parent_id)
REFERENCES parent (parent_id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE;
ROLLBACK TRANSACTION;
--TRY ALLOW NO REFERENTIAL DATA IN--
--DROP TABLE parent;
--DROP TABLE child;

Everything I've read so far seems to suggest that constraints are only checked when the data is inserted. (Or when the constraint is created) For example the manual on set constraints.
This makes sense and - if the database works properly - should be good enough. I'm still curious how I managed to circumvent this or if I just read the situation wrong and there was never a real constraint violation to begin with.
Either way, case closed :-/
------- UPDATE --------
There was definitely a constraint violation, caused by a faulty trigger. Here's a script to replicate:
-- Create master table
CREATE TABLE product
(
id INT NOT NULL PRIMARY KEY
);
-- Create second table, referencing the first
CREATE TABLE example
(
id int PRIMARY KEY REFERENCES product (id) ON DELETE CASCADE
);
-- Create a (broken) trigger function
--CREATE LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION delete_product()
RETURNS trigger AS
$BODY$
BEGIN
DELETE FROM product WHERE product.id = OLD.id;
-- This is an error!
RETURN null;
END;
$BODY$
LANGUAGE plpgsql;
-- Add it to the second table
CREATE TRIGGER example_delete
BEFORE DELETE
ON example
FOR EACH ROW
EXECUTE PROCEDURE delete_product();
-- Now lets add a row
INSERT INTO product (id) VALUES (1);
INSERT INTO example (id) VALUES (1);
-- And now lets delete the row
DELETE FROM example WHERE id = 1;
/*
Now if everything is working, this should return two columns:
(pid,eid)=(1,1). However, it returns only the example id, so
(pid,eid)=(0,1). This means the foreign key constraint on the
example table is violated.
*/
SELECT product.id AS pid, example.id AS eid FROM product FULL JOIN example ON product.id = example.id;

Related

tSQLt will not apply misnamed constraints (PK_dbo ....) to fake table

Adding tSQLt tests to an existing production product, so we're not able to alter tables, constraints, etc. Currently all the constraints are labeled like 'PK_dbo.ViolationCategory' when they should be like 'PK_ViolationCategory'
When I run:
EXEC tSQLt.NewTestClass 'AdHocReportFiltersTestConstraint';
GO
CREATE PROCEDURE [AdHocReportFiltersTestConstraint].[Setup]
AS
BEGIN
EXEC tSQLt.FakeTable 'dbo.AdHocReports'
END
GO
CREATE PROCEDURE [AdHocReportFiltersTestConstraint].[test_AdHocReportFilters_Constraint]
AS
BEGIN
DECLARE #Id uniqueidentifier = NEWID()
DECLARE #Name NVARCHAR(Max) = 'Test_Name'
DECLARE #Value NVARCHAR(Max) = 'Test_Value'
DECLARE #AdHocReport_ID uniqueidentifier = NEWID()
INSERT INTO dbo.AdHocReportFilters ([Id], [Name], [Value], [AdHocReport_Id])
VALUES (#Id, #Name, #Value, #AdHocReport_ID)
exec tSQLt.ApplyConstraint 'dbo.AdHocReports', 'PK_dbo.AdHocReportFilters';
END
EXEC tSQLt.RunTestClass 'AdHocReportFiltersTestConstraint';
GO
I receive the error,
(1 row affected)
[AdHocReportFiltersTestConstraint].[test_AdHocReportFilters_Constraint] failed:
(Error) The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_dbo.AdHocReportFilters_dbo.AdHocReports_AdHocReport_Id". The conflict occurred in
database "CR", table "dbo.tSQLt_tempobject_fbc9c8bf09e742929eccae914d5e440d",
column 'Id'.[16,0]{test_AdHocReportFilters_Constraint,11}
Any ideas of how to work around this?
Once I get this working, I will add a second record to violate the PK constraint and catch the error.
Just looking at your code, I see you are doing the following:
Fake the table dbo.AdHocReports
Insert a row into the table dbo.AdHocReportFilters
Apply a PK constraint called "PK_dbo.AdHocReportFilters" to the table dbo.AdHocReports
You will then try and add another row to validate the PK_dbo.AdHocReportFilters constraint
The error you are getting appears to to suggest that you are violating a foreign key on the AdHocReportFilters table - which is expected since that table hasn't been faked.
It is not clear from the test name whether you are trying to validate the behaviour of the primary key or foreign key.
Looking at the steps, I think you may be mixing up the two tables but without more detailed code (i.e. CREATE TABLE) statements, it is difficult for me to help you further.
There are two tables:
dbo.AdHocReports (Id,... (more columns))
dbo.AdHocReportFilters ([Id], [Name], [Value], [AdHocReport_Id] )
AdHocReportFilters.AdHocReport_Id is foriegn key for table dbo.AdHocReportFilters referencing dbo.AdHocReports.Id
When you are inserting insert a row in table AdHocReportFilters, you should make sure AdHocReportFilters.AdHocReport_Id is an id column in table dbo.AdHocReports
But in your SP, #AdHocReport_ID is NEWID(), which is of course a unique value and hence not in table dbo.AdHocReports.Id
Work around,
Disable foreign key contraint
ALTER TABLE dbo.AdHocReportFilters NOCHECK CONSTRAINT FK_dbo.AdHocReportFilters_dbo.AdHocReports_AdHocReport_Id

Before update trigger with referential integrity in oracle 11g

I want to understand what does before update in trigger means.
I have a table called DEPT_MST where DEPT_ID is the primary key. It has 2 rows with DEPT_ID 1 and 2.
Another table EMP has columns EMP_ID as primary key and EMP_DEPT_ID which is a foreign key referencing DEPT_ID of DEPT table.
Now if I add before update trigger on EMP tables EMP_DEPT_ID column which will check if new value for EMP_DEPT_ID is present in master table DEPT if now then will insert new row with new DEPT_ID to DEPT table.
Now if I update EMP_DEPT_ID to 3 where EMP_DEPT_ID is 2 in EMP table it is giving integrity constraint violation error parent not found.
So,
Does this mean that Oracle checks for integrity constraints first and then calls the "before update" trigger?
Then how can we bypass this check and call before update trigger?
What exactly does "before update" mean here?
How can I achieve above result by using triggers and not by using explicit PL SQL block?
Thank you
Non-deferred foreign key constraints are evaluated before triggers are called, yes.
If you can declare the foreign key constraint to be deferrable (which would require dropping and re-creating it if the existing constraint is not deferrable)
ALTER TABLE emp
ADD CONSTRAINT fk_emp_dept (emp_dept_id) REFERENCES dept( dept_id )
INITIALLY DEFERRED DEFERRABLE;
In your application, you can then set the constraint to be deferrable, run your INSERT statement causing the trigger to fire and insert the parent row. Your foreign key constraint will be validated when the transaction commits.
An alternative to defining the constraint to be deferrable would be to rename the emp table to, say, emp_base, create a view named emp and then create an instead of insert trigger on emp that implements the logic of first inserting into dept and then inserting into emp_base.

Set column as primary key if the table doesn't have a primary key

I have a column in db which has 5 columns but no primary key.
One of the columns is named myTable_id and is integer.
I want to check if the table has a primary key column. If it doesn't, then make myTable_id a primary key column and make it identity column. Is there a way to do this?
I tried with this:
ALTER TABLE Persons
DROP CONSTRAINT pk_PersonID
ALTER TABLE Persons
ADD PRIMARY KEY (P_Id)
and I get syntax error in Management studio.
This checks if primary key exists, if not it is created
IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE CONSTRAINT_TYPE = 'PRIMARY KEY' AND TABLE_NAME = 'Persons'
AND TABLE_SCHEMA ='dbo')
BEGIN
ALTER TABLE Persons ADD CONSTRAINT pk_PersonID PRIMARY KEY (P_Id)
END
ELSE
BEGIN
-- Key exists
END
fiddle: http://sqlfiddle.com/#!6/e165d/2
ALTER TABLE Persons
ADD CONSTRAINT pk_PersonID PRIMARY KEY (P_Id)
An IDENTITY constraint can't be added to an existing column, so how you add this needs to be your initial thought. There are two options:
Create a new table including a primary key with identity and drop the existing table
Create a new primary key column with identity and drop the existing 'P_ID' column
There is a third way, which is a better approach for very large tables via the ALTER TABLE...SWITCH statement. See Adding an IDENTITY to an existing column for an example of each. In answer to this question, if the table isn't too large, I recommend running the following:
-- Check that the table/column exist and no primary key is already on the table.
IF COL_LENGTH('PERSONS','P_ID') IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE CONSTRAINT_TYPE = 'PRIMARY KEY' AND TABLE_NAME = 'PERSONS')
-- Add table schema to the WHERE clause above e.g. AND TABLE_SCHEMA ='dbo'
BEGIN
ALTER TABLE PERSONS
ADD P_ID_new int IDENTITY(1, 1)
GO
ALTER TABLE PERSONS
DROP COLUMN P_ID
GO
EXEC sp_rename 'PERSONS.P_ID_new', 'P_ID', 'Column'
GO
ALTER TABLE PERSONS
ADD CONSTRAINT PK_P_ID PRIMARY KEY CLUSTERED (P_ID)
GO
END
Notes:
By explicitly using the CONSTRAINT keyword the primary key constraint is given a particular name rather than depending on SQL Server to auto-assign a name.
Only include CLUSTERED on the PRIMARY KEY if the balance of searches for a particular P_ID and the amount of writing outweighs the benefits of clustering the table by some other index. See Create SQL IDENTITY as PRIMARY KEY.
You can check if primary key exists or not using OBJECTPROPERTY Transact SQL, use 'TableHasPrimaryKey' for the second arguments.
DECLARE #ISHASPRIMARYKEY INT;
SELECT #ISHASPRIMARYKEY = OBJECTPROPERTY(OBJECT_ID('PERSONS'), 'TABLEHASPRIMARYKEY');
IF #ISHASPRIMARYKEY IS NULL
BEGIN
-- generate identity column
ALTER TABLE PERSONS
DROP COLUMN P_ID;
ALTER TABLE PERSONS
ADD P_ID INT IDENTITY(1,1);
-- add primary key
ALTER TABLE PERSONS
ADD CONSTRAINT PK_PERSONID PRIMARY KEY (P_ID);
END;
I don't think you can do that. For making a column into an identity column I think you have to drop the table entirely.

postgres - trigger before transaction commit

I was wondering if it is indirectly possible to have a trigger executed just before the transaction is about to commit? In this trigger, I will do consistency checks and rollback the transaction if required.
For example, I have three tables:
users (id, name)
groups (id, name)
user_in_group (user_id, group_id)
I would like to create a trigger which verifies that a user is always part of a group. No orphan users are allowed. Each time an insert into users occurs, this trigger will verify that a correspondering insert into user_in_group also occured. If not, the transaction will not commit.
This cannot be done using a simple row- or statement- based trigger, since the above scenario requires two separate statements.
The other way around, when a delete from user_in_group happens, can be easily done by a row-based trigger.
Did you look into the CREATE CONSTRAINT TRIGGER, with the DEFERRABLE (INITIALLY DEFERRED) option?
Wouldn't the correct method really be to establish constraints into the database? That seems exactly what constraints are for. Add a foreign key constraint and a not null and it would seem you should be in business.
Now revised for symmetrical constraints:
drop table foousers cascade;
drop table foogroups cascade;
drop table foousergrps cascade;
create table foousers (id int primary key, name text);
create table foogroups (id int primary key, name text);
create table foousergrps (user_id int unique references foousers not null, group_id int unique references foogroups not null);
alter table foogroups add foreign key (id) references foousergrps (group_id) deferrable initially deferred;
alter table foousers add foreign key (id) references foousergrps (user_id) deferrable initially deferred;
begin;
insert into foousers values (0, 'root');
insert into foousers values (1, 'daemon');
insert into foogroups values (0, 'wheel');
insert into foogroups values (1, 'daemon');
insert into foousergrps values (0,0);
insert into foousergrps values (1,1);
commit;
Forbidden:
insert into foousers values (2, 'bad');
insert into foousergrps values (2,2);
Example of (non-deferrable, boo) check function:
create table foousergrps (user_id int unique references foousers not null, group_id int not null);
create function fooorphangroupcheck(int) returns boolean as $$
declare
gid alias for $1;
begin
perform 1 from foousergrps where group_id = gid limit 1;
if NOT FOUND then return false;
end if;
return true;
end;
$$
LANGUAGE 'plpgsql';
alter table foogroups add check (fooorphangroupcheck(id));
Lookind at the doc, there seems to be no such trigger option... so one way to achieve "no orphan users" rule would be to not allow direct insert into users and user_in_group tables. Instead create a view (which combines these tables, ie user_id, user_name, group_id) with a update rule which inserts data into right tables.
Or only allow inserting new users via stored procedure which takes all required data as inpud and thus doesn't allow users withoud group.
BTW, why do you have separate table for user and group relationship? Why not add group_id field into users table with FK / NOT NULL constraint?
From the docs . . .
Triggers can be defined to execute either before or after any INSERT,
UPDATE, or DELETE operation, either once per modified row, or once per
SQL statement.
You can use the sql WITH operator, like this:
WITH insert_user AS (
INSERT INTO users(name) VALUES ('bla-bla-user') RETURNING id
)
INSERT INTO user_in_group(user_id, group_id)
SELECT id, 999 FROM insert_user UNION
SELECT id, 888 FROM insert_user;
SELECT groups (id, name) user_in_group (user_id, group_id)

Setting constraint deferrable doesn't work on PostgreSQL transaction

This is the situation: I have two tables where the one references the other (say, table2 references table1). When creating these tables, I did set the foreign key constraint as DEFERRABLE and the ON UPDATE and ON DELETE clauses as NO ACTION (which is the default).
But still, when running the transaction below, I get the following error.
Transaction:
START TRANSACTION;
SET CONSTRAINTS ALL DEFERRED;
UPDATE table1 SET blah blah;
UPDATE table2 SET blah blah;
COMMIT;
Error:
ERROR: update or delete on table "table1" violates foreign key constraint "table1_column_fkey" on table "table2"
DETAIL: Key (column1)=(blahblah) is still referenced from table "table2".
And table construction:
CREATE TABLE table1(
column1 CHAR(10),
[...]
PRIMARY KEY (column1)
);
CREATE TABLE table2(
primkey CHAR(9),
[...]
column2 CHAR(10) NOT NULL,
PRIMARY KEY(primkey),
FOREIGN KEY(column2) REFERENCES table1(column1) DEFERRABLE
);
What I want to do is to defer the foreign key checking while the transaction is in progress, until it commits. I just can't see why is this error returning and how can I make the transaction work.
The problem was indeed a foreign key constraint violation. I mean, the constraints were indeed deferred within the transaction, but the problem was that at the end of the transaction, after table1 and table2 were updated, the new data were violating a foreign key constraint. I was updating the primary key of a table1 row, which was still being referenced by some table2 rows. These rows I had to update them too, so that the referencing column of table2 rows matched the updated primary key of table1's row. I changed the 'UPDATE' queries within the transaction and the problem got solved.
Sorry to put you into this. The solution was so simple, but that day I coudn't see it.