views:

44

answers:

3

I'm curious on how some other people have handled this.

Imagine a system that simply has Products, Purchase Orders, and Purchase Order Lines. Purchase Orders are the parent in a parent-child relationship to Purchase Order Lines. Purchase Order Lines, reference a single Product.

This works happily until you delete a Product that is referenced by a Purchase Order Line. Suddenly, the Line knows its selling 30 of something...but it doesn't know what.

What's a good way to anticipate the deletion of a referenced piece of data like this? I suppose you could just disallow a product to be deleted if any Purchase Order Lines reference it but that sounds...clunky. I imagine its likely that you would keep the Purchase Order in the database for years, essentially welding the product you want to delete into your database.

+3  A: 

Enforce referential integrity. This basically means creating foreign keys between the tables and making sure that nothing "disappears"

You can also use this to cause referenced items to be deleted when the parent is deleted (cascading deletes).

For example you can create a SQL Server table in such a way that if a PurchaseOrder is deleted it's child PurchaseOrderLines are also deleted.

Here is a good article that goes into that.

It doesn't seem clunky to keep this data (to me at least). If you remove it then your purchase order no longer has the meaning that it did when you created it, which is a bad thing. If you are worried about having old data in there you can always create an archive or warehouse database that contains stuff over a year old or something...

Abe Miessler
The proper foreign keys should definitely be created, but I'm not a big fan of using cascading deletes. I prefer to handle that in my own code.
Joe Stefanelli
Interesting, any particular reason?
Abe Miessler
@Abe: Probably just my own personal idiosyncrasy. I just don't like hidden things happening behind the scenes in the DB. And yes, I realize this happens all the time with triggers, computed columns, etc. but cascading deletes just feel wrong to me.
Joe Stefanelli
+2  A: 

For data like this where parts of it have to be kept for an unknown amount of time while other parts will not, you need to take a different approach.

Your Purchase Order Lines (POL) table needs to have all of the columns that the product table has. When a line item is added to the purchase order, copy all of product data into the POL. This includes the name, price, etc. If the product has options, then you'll have to create a corresponding PurchaseOrderLineOptions table.

This is the only real way of insuring that you can recreate the purchase order on demand at any point. It also means that someone can change the pricing, name, description, and other information about the product at anytime without impacting previous orders.

Yes, you end up with a LOT of duplicate information in your line item table..; but that's okay.

For kicks, you might keep the product id in the POL table for referencing back, but you cannot depend on the product table to have any bearing on the paid for product...

Chris Lively
The whole reason for having the foreign key in the Purchase Order Lines table was to directly reference the Product and avoid that duplicate data. BUT what you say makes a lot of sense, makes me feel better hearing it from someone else though that keeping data in that manner is acceptable.
instantmusic
To reduce duplicated data, you can use a separate table to store purchased products, and have that table 100% read-only after the insert. Then you can check to see if a row exists in that table with all of the necessary data before creating a new one. Chris is right about needing to divorce the purchased product's data from the active Products table to ensure the ability to recreate invoices on demand.
Alex Morris
Pretty much every point-of-sale system I've seen uses this way of keeping the order history intact.
Chris Lively
Thanks for your response, I'll use that approach in my orders. I'll definately keep the key to Products as well. If the product isnt deleted, I can use it for sorting(Grabbing all the purchase order lines that use the Product ID). If the product is deleted then meh...no biggie.
instantmusic
+4  A: 

The parent entity should NEVER be deleted or the dependent rows cease to make sense, unless you delete them too. While it is "clunky" to display old records to users as valid selections, it is not clunky to have your database continue to make sense.

To address the clunkiness in the UI, some people create an Inactive column that is set to True when an item is no longer active, so that it can be kept out of dropdown lists in the user interface.

If the value is used in a display field (e.g. a readonly field) the inactive value can be styled in a different way (e.g. strike-through) to reflect its no-longer-active status.

I have StartDate and ExpiryDate columns in all entity tables where the entity can become inactive or where the entity will become active at some point in the future (e.g. a promotional discount).

Tim
Thats an excellent idea in general, many of my tables are represented in my app as Combo Boxes for selection. I can just ignore inactive records when populating them. Thanks!
instantmusic
exactly what we're doing here
DForck42