Deleting linked data across tables in PostgreSQL - postgresql

I'm looking for the best way to handle deleting linked data across three PostgreSQL tables (technically more, but it's the same idea).
The three tables are agency, address, and agency_address. An agency can have multiple addresses so the agency_address is just a link table with two columns, agency_id and address_id (defined as their respective foreign keys)
I want to set it up so that when an agency is deleted it will remove the link table row in agency_address and the related address row automagically. (So if an agency is deleted, so are all its addresses)
I can get it to clear the link table, but not sure how to get to the address table.
(Also address is a generic model and will be referenced by others, like a 'site' which will have their own link tables.)

Use foreign keys with ON DELETE CASCADE in the table definition.
ALTER TABLE agency_address
ADD CONSTRAINT agency_address_agency_fkey FOREIGN KEY (agency_id)
REFERENCES agency (agency_id) ON DELETE CASCADE;
It seems uncertain that you should delete addresses automatically, too.
If so, your data model is wrong and addresses should depend on agencies directly, with a foreign key constraint similar to the one above, no n:m linking table necessary.
Edit after more info:
So addresses can be linked to agencies or sites, but never to both at the same time.
The model could work as you have it, but you would have to make sure somehow that an address linked to an agency isn't linked to a site, too.
The foreign key constraint between address and agency_address points in the "wrong" direction, so you cannot simply add another ON DELETE CASCADE. You could implement it with an additional foreign key, but that's tricky. This approach per trigger is much simpler:
CREATE OR REPLACE FUNCTION trg_agency_address_delaft()
RETURNS trigger AS
$BODY$
BEGIN
DELETE FROM address
WHERE address_id = OLD.address_id;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER delaft
AFTER DELETE ON agency_address
FOR EACH ROW EXECUTE PROCEDURE trg_agency_address_delaft();
More about trigger functions and triggers in the manual.

Related

How can a relational database with foreign key constraints ingest data that may be in the wrong order?

The database is ingesting data from a stream, and all the rows needed to satisfy a foreign key constraint may be late or never arrive.
This can likely be accomplished by using another datastore, one without foreign key constraints, and then when all the needed data is available, read into the database which has fk constraints. However, this adds complexity and I'd like to avoid it.
We're working on a solution that creates "placeholder" rows to point the foreign key to. When the real data comes in, the placeholder is replaced with real values. Again, this adds complexity, but it's the best solution we've found so far.
How do people typically solve this problem?
Edit: Some sample data which might help explain the problem:
Let's say we have these tables:
CREATE TABLE order (
id INTEGER NOT NULL,
order_number,
PRIMARY KEY (id),
UNIQUE (order_number)
);
CREATE TABLE line_item (
id INTEGER NOT NULL,
order_number INTEGER REFERENCES order(order_number),
PRIMARY KEY (id)
);
If I insert an order first, not a problem! But let's say I try:
INSERT INTO line_item (order_number) values (123) before order 123 was inserted. This will fail the fk constraint of course. But this might be the order I get the data, since it's reading from a stream that is collecting this data from multiple sources.
Also, to address #philpxy's question, I didn't really find much on this. One thing that was mentioned was deferred constraints. This is a mechanism that waits to do the fk constraints at the end of a transaction. I don't think it's possible to do that in my case however, since these insert statements will be run at random times whenever the data is received.
You have a business workflow problem, because line items of individual orders are coming in before the orders themselves have come in. One workaround, perhaps not ideal, would be to create a before insert trigger which checks, for every incoming insert to the line_item table, whether that order already exists in the order table. If not, then it will first insert the order record before trying the insert on line_item.
CREATE OR REPLACE FUNCTION "public"."fn_insert_order" () RETURNS trigger AS $$
BEGIN
INSERT INTO "order" (order_number)
SELECT NEW.order_number
WHERE NOT EXISTS (SELECT 1 FROM "order" WHERE order_number = NEW.order_number);
RETURN NEW;
END
$$
LANGUAGE 'plpgsql'
# trigger
CREATE TRIGGER "trigger_insert_order"
BEFORE INSERT ON line_item FOR EACH ROW
EXECUTE PROCEDURE fn_insert_order()
Note: I am assuming that the id column of the order table in fact is auto increment, in which case Postgres would automatically assign a value to it when inserting as above. Most likely, this is what you want, as having two id columns which both need to be manually assigned does not make much sense.
You could accomplish that with a BEFORE INSERT trigger on line_item.
In that trigger you query order if a matching item exists, and if not, you insert a dummy row.
That will allow the INSERT to succeed, at the cost of some performance.
To insert rows into order, use
INSERT INTO order ...
ON CONFLICT ON (order_number) DO UPDATE SET
id = EXCLUDED.id;
Updating a primary key is problematic and may lead to conflicts. One way you could get around that is if you use negative ids for artificially generated orders (assuming that the real ids are positive). If you have any references to that primary key, you'd have to define the constraint with ON UPDATE CASCADE.

How to work around error "Delete Prevented by referential constraint" in DB2?

So the problem I have is in my task provided to us by the Professor we are to
create tables
insert records to each table.
update and delete (minimum of 1 record) from each table
using a DB2 Script that is following the old standard where COLLECTIONS are created instead of SCHEMAS
steps 1 and 2 are done. the updates are done. my deletes are giving me a hard time. an example would be this.
CREATE TABLE UMALIK8.CAMPUS (
CAMPUS_ID VARCHAR (10) NOT NULL,
CAMPUS_NAME VARCHAR (30) NOT NULL,
MANAGER_NUM VARCHAR (10) NOT NULL,
CONSTRAINT UMALIK8.CAMPUS_PK PRIMARY KEY (CAMPUS_ID),
CONSTRAINT UMALIK8.CAMPUS_FK FOREIGN KEY (MANAGER_NUM)
REFERENCES UMALIK8.MANAGER(MANAGER_NUM)
ON DELETE CASCADE);
INSERT INTO UMALIK8.CAMPUS (CAMPUS_ID, CAMPUS_NAME, MANAGER_NUM)
VALUES ('King', 'King Campus', 'M021386');
DELETE FROM UMALIK8.CAMPUS
WHERE CAMPUS_ID = 'King';
so when I try to delete it, it says delete prevented by referential constraint "roomassign_fk" which doesn't make sense to me because the roomassign table is like 3 or 4 tables AFTER the campus table, the campus is the parent table, and the manager number is from the manager table and the parent table for manager table is Employee table....all throughout the delete script im getting referential errors and I don't know why. Even in my adult table but my adult table has no foreign keys, its only got a primary key on its own, and its got a bunch of child tables....
Now the order of my script is
Tables,
Inserts,
Updates,
Deletes
all separated from each other in one long script
any idea how to fix this? what am i doing wrong?
your help is greatly appreciated, thanks!
As discussed on the comments with the OP turns out that the issue is about a trigger on the table CAMPUS. As the OP asked I'm putting this as an answer.
Is it possible to exist on this table UMALIK8.CAMPUS a trigger which is inserting registries in a table that has an FK to it?
What I mean with a trigger is that if your table has an after insert trigger that would mean something like this: you run the insert command on CAMPUS, after the insert happens the DB2 will call the trigger and insert in a ROOM (i think that is the name of other table given the FK name) one registry which will be linked (by FK) to the one you just inserted on CAMPUS, then if you try to delete the registry on CAMPUS the referential constraint "roomassign_fk" will happen because you have a child registry that is linked to the one in CAMPUS

How to maintain record history on table with one-to-many relationships?

I have a "services" table for detailing services that we provide. Among the data that needs recording are several small one-to-many relationships (all with a foreign key constraint to the service_id) such as:
service_owners -- user_ids responsible for delivery of service
service_tags -- e.g. IT, Records Management, Finance
customer_categories -- ENUM value
provider_categories -- ENUM value
software_used -- self-explanatory
The problem I have is that I want to keep a history of updates to a service, for which I'm using an update trigger on the table, that performs an insert into a history table matching the original columns. However, if a normalized approach to the above data is used, with separate tables and foreign keys for each one-to-many relationship, any update on these tables will not be recognised in the history of the service.
Does anyone have any suggestions? It seems like I need to store child keys in the service table to maintain the integrity of the service history. Is a delimited text field a valid approach here or, as I am using postgreSQL, perhaps arrays are also a valid option? These feel somewhat dirty though!
Thanks.
If your table is:
create table T (
ix int identity primary key,
val nvarchar(50)
)
And your history table is:
create table THistory (
ix int identity primary key,
val nvarchar(50),
updateType char(1), -- C=Create, U=Update or D=Delete
updateTime datetime,
updateUsername sysname
)
Then you just need to put an update trigger on all tables of interest. You can then find out what the state of any/all of the tables were at any point in history, to determine what the relationships were at that time.
I'd avoid using arrays in any database whenever possible.
I don't like updates for the exact reason you are saying here...you lose information as it's over written. My answer is quite simple...don't update. Not sure if you're at a point where this can be implemented...but if you can I'd recommend using the main table itself to store historical (no need for a second set of history tables).
Add a column to your main header table called 'active'. This can be a character or a bit (0 is off and 1 is on). Then it's a bit of trigger magic...when an update is preformed, you insert a row into the table identical to the record being over-written with a status of '0' (or inactive) and then update the existing row (this process keeps the ID column on the active record the same, the newly inserted record is the inactive one with a new ID).
This way no data is ever lost (admittedly you are storing quite a few rows...) and the history can easily be viewed with a select where active = 0.
The pain here is if you are working on something already implemented...every existing query that hits this table will need to be updated to include a check for the active column. Makes this solution very easy to implement if you are designing a new system, but a pain if it's a long standing application. Unfortunately existing reports will include both off and on records (without throwing an error) until you can modify the where clause

PostgreSQL ON INSERT CASCADE

I've got two tables - one is Product and one is ProductSearchResult.
Whenever someone tries to Insert a SearchResult with a product that is not listed in the Product table the foreign key constrain is violattet, hence i get an error.
I would like to know how i could get my database to automatically create that missing Product in the Product Table (Just the ProductID, all other attributes can be left blank)
Is there such thing as CASCADE ON INSERT? If there is, i was not able not get it working.
Rules are getting executed after the Insert, so because we get an Error beforehand there are useless if you USE an "DO ALSO". If you use "DO INSTEAD" and add the INSERT Command at the End you end up with endless recursion.
I reckon a Trigger is the way to go - but all my attempts to write one failed.
Any recommendations?
The Table Structure:
CREATE TABLE Product (
ID char(10) PRIMARY KEY,
Title varchar(150),
Manufacturer varchar(80),
Category smallint,
FOREIGN KEY(Category) REFERENCES Category(ID) ON DELETE CASCADE);
CREATE TABLE ProductSearchResult (
SearchTermID smallint NOT NULL,
ProductID char(10) NOT NULL,
DateFirstListed date NOT NULL DEFAULT current_date,
DateLastListed date NOT NULL DEFAULT current_date,
PRIMARY KEY (SearchTermID,ProductID),
FOREIGN KEY (SearchTermID) REFERENCES SearchTerm(ID) ON DELETE CASCADE,
FOREIGN KEY (ProductID) REFERENCES Product ON DELETE CASCADE);
Yes, triggers are the way to go. But before you can start to use triggers in plpgsql, you
have to enable the language. As user postgres, run the command createlang with the proper parameters.
Once you've done that, you have to
Write function in plpgsql
create a trigger to invoke that function
See example 39-3 for a basic example.
Note that a function body in Postgres is a string, with a special quoting mechanism: 2 dollar signs with an optional word in between them, as the quotes. (The word allows you to quote other similar quotes.)
Also note that you can reuse a trigger procedure for multiple tables, as long as they have the columns your procedure uses.
So the function has to
check if the value of NEW.ProductID exists in the ProductSearchResult table, with a select statement (you ought to be able to use SELECT count(*) ... INTO someint, or SELECT EXISTS(...) INTO somebool)
if not, insert a new row in that table
If you still get stuck, come back here.
In any case (rules OR triggers) the insert needs to create a new key (and new values for the attributes) in the products table. In most cases, this implies that a (serial,sequence) surrogate primary key should be used in the products table, and that the "real world" product_id ("product number") should default to NULL, and be degraded to a candidate key.
BTW: a rule can be used, rules just are tricky to implement correctly for N:1 relations (they need the same kind of EXISTS-logic as in Bart's answer above).
Maybe cascading on INSERT is not such a good idea after all. What do you want to happen if someone inserts a ProductSearchResult record for a not-existing product? [IMO a FK is always a domain; you cannot just extend a domain just by referring to a not-existant value for it; that would make the FK constraint meaningless]

foreign key constraint in sql

I have 2 tables in sql server with primary keys set to identity. They are related and work fine.
I then created a form in vb 2008 and tried inserting some values into my database the respective primary keys work but the primary key in the parent table wont show up in the child table.I did create a relationship in vb using ado.net and all the details of my table are defineed in the data table. For example
cust tables (custid,name,..)
book table(bookid,bookname,..,custid)
in vb my insert statement is something like Insert into cust(name) values(#name)
insert into book(bookname) values(#bookname). I do not include the id columns as they auto generate in the database(tables).
My question is that how do i get to insert the custid in the book table when the data is stored back into the tavles in my database.
Please advice with an example as im not half as good as you guys.
Kind Regards
You have to know which customer you want to associate with the book before INSERTing the book. If you don't know before hand, you can't. So somewhere in your Form there should be a way to select a customer. Then when you create a book, you grab that customer's ID and insert it along with the other book info.
You don't actually say that you created a foreign key constraint between the two tables!
You need to:
Ensure that you create an explicit foreign key on the BOOK table to point to a customer in the CUST table.
First insert the customer.
Then find out what the customer's auto-generated ID was. That value is in ##IDENTITY. Store it somewhere e.g. #CUSTID.
Insert the book, specifying #CUSTID as the customer's ID.