5

Database relationship cycles smell like bad database design. Below is a situation in which I think it can not be prevented:

  • a Company has Locations (City)
  • a Company has Products (Big Mac)

  • Products are/are not available on Locations (No Bacon Burger in Saudia Arabia)

The current design would allow you to offer a Product that doesn't belong to this Company on a Location that does belong to this Company.

Company
1 - McDonalds
2 - Burger King

Location
1 - New York, building 1 - McDonalds (1)
2 - Amsterdam, building 2 - Burger King (2)

Product
1 - Big Mac - McDonalds (1)

ProductLocation
1 - Big Mac (1) - Amsterdam, building 2 (2)

McDonalds sells Big Macs, Burger King doesn't, but it seems their building does :)
It becomes worse when we add relationships to Product that are also Location dependent.

What can I do to prevent the cycle?
How do I ensure database data integrity?

Yvo
  • 18,681
  • 11
  • 71
  • 90
  • 1
    "It becomes worse when we add relationships to Product that are also Location dependent." It would be even trickier if they're also be company dependent. XYZ Burgers could bribe a Saudi official and be entitled to sell BLTs in touristy places. :-) – Denis de Bernardy May 25 '11 at 10:12

5 Answers5

8

Cyclic dependencies are not automatically "bad database design". From a conceptual modelling point of view if such a dependency accurately represents what you are trying to model then it isn't "wrong".

Unfortunately the limitations of SQL often make it hard or impossible to enforce constraints that are cyclical. In SQL you will usually have to compromise by breaking the constraint in some way or by implementing the rule in procedural code rather than through database constraints.

nvogel
  • 24,981
  • 1
  • 44
  • 82
4

If we start with Location, Company and Product as independent entities -- as I think you tried to:

enter image description here

create table ProductAtLocation (
      CompanyID  integer
    , LocationID integer
    , ProductID  integer
);

alter table ProductAtLocation
    add constraint pk_ProdLoc  primary key (CompanyID, LocationID, ProductID)
  , add constraint fk1_ProdLoc foreign key (CompanyID, LocationID) references CompanyLocation (CompanyID, LocationID)
  , add constraint fk2_ProdLoc foreign key (CompanyID, ProductID)  references CompanyProduct  (CompanyID, ProductID)
;

And if the Product is a dependent entity (depends on company):

enter image description here

Damir Sudarevic
  • 21,891
  • 3
  • 47
  • 71
  • Technically you are offering the solution to my question, therefor I've marked your answer as THE answer. From a practical point of view, I choose to keep the cycles in my db design (as depicted by dportas, cycles aren't necessarily bad). Thank you! – Yvo May 25 '11 at 15:23
2

What you really need as a SQL "assertion". However unfortunately no current DBMS supports these. The assertion would be something like:

assertion product_location_check
check (not exists (select null
                   from   company_product_location cpl
                   where  not exists
                   ( select null
                     from   company_products cp
                     join   company_locations cl on c1.company_id = cp.company_id
                     and    cp.product_id = cpl.product_id
                     and    cl.location_id = cpl.location_id
                     and    cp.company_id = cpl.company_id
                   )
                  )
      );

In the absence of these, another possibility is set up the keys such that the rule can be checked:

create table company_products
( company_id references companies
, product_id ...
, primary key (company_id, product_id)
);

create table company_locations
( company_id references companies
, location_id ...
, primary key (company_id, location_id)
);

create table company_product_locations
( company_id ...
, product_id ...
, location_id ...
, primary key (company_id, product_id, location_id)
, foreign key (company_id, product_id) references company_products)
, foreign key (company_id, location_id) references company_locations)
);

This ensures that each company_product_locations references a product and a location associated with the same company.

Yet another possibility for complex constraints is to use materialized views. I have blogged about this in the context of Oracle here.

Tony Andrews
  • 129,880
  • 21
  • 220
  • 259
  • +1. Anyone who wants to learn more about "how to enforce complex constraints" (SQL ASSERTIONs, that is), should also (and especially) read "Applied Mathematics for Database Professionals", chapter 11. – Erwin Smout May 25 '11 at 12:40
0

I disagree - this statement is incorrect:

The current design would allow you to offer a Product that doesn't belong to this Company

If a Product does not belong to a Company, then it won't have a foreign key to that Company. A Company may have many Products, but a Product can only belong to one company. That's a one-to-many relationship.

As for Product-Location, that sounds like a many-to-many relationship: a Product can be offered at many Locations, and a Location can sell many Products. You need a Product_Location JOIN table.

UPDATE:

The records you added only clarify the issue. A location is more than a building; McDonalds and Burger King might be in the same building, but they aren't in the same location in that building. Your Location table will need additional columns besides the street address. My comments still stand. Burger King will not be able to sell a Big Mac if you design this properly. You don't have it right yet; hence your confusion.

duffymo
  • 305,152
  • 44
  • 369
  • 561
  • I've added a few database records to clarify the problem. – Yvo May 25 '11 at 10:17
  • "Building 1" describes the location where the sales occur (the level of detail is irrelevant). Additional columns in the location table aren't going to add in database integrity. – Yvo May 25 '11 at 12:29
0

Part of the problem is that both McDonald's and Burger King sell products called "hamburger" and "cheeseburger" and (I think) "double cheeseburger". So the information you're storing in ProductLocation is incomplete.

Product
--
Big Mac    McDonald's
Hamburger  McDonald's
Hamburger  Burger King

ProductLocation
Big Mac    McDonald's   New York, building 1
Hamburger  McDonald's   New York, building 1
Hamburger  Burger King  Amsterdam, building 2

And duffymo is right when he says "A location is more than a building."

Here's one way to implement these constraints. I dropped the id numbers, because they tend to hide what's really happening.

create table company (
  co_name varchar(15) primary key
);

insert into company values 
('McDonald''s'),
('Burger King');

create table location (
  loc_name varchar(30) primary key,
  co_name varchar(15) not null references company (co_name),
  unique (loc_name, co_name)
);

insert into location values 
('New York, building 1', 'McDonald''s'),
('Amsterdam, building 2', 'Burger King');

create table product (
  co_name varchar(15) not null references company (co_name),
  product_name varchar(15) not null,
  primary key (co_name, product_name)
);

insert into product values
('McDonald''s', 'Big Mac'),
('McDonald''s', 'Hamburger'),
('McDonald''s', 'Cheeseburger'),
('Burger King', 'Hamburger'),
('Burger King', 'Cheeseburger');

create table product_location (
  loc_name varchar(30) not null references location (loc_name),
  co_name varchar(15) not null,
  product_name varchar(15) not null,
  foreign key (co_name, product_name) references product (co_name, product_name),
  foreign key (loc_name, co_name) references location (loc_name, co_name),
  primary key (loc_name, co_name, product_name)
);

insert into product_location values 
('Amsterdam, building 2', 'Burger King', 'Cheeseburger');

Note the overlapping foreign keys in product_location. Overlapping foreign keys guarantee that the company identified with the location and the company identified with the product are the same company. Now the following INSERT will fail with a foreign key constraint violation.

insert into product_location values 
('Amsterdam, building 2', 'McDonald''s', 'Cheeseburger');
Mike Sherrill 'Cat Recall'
  • 91,602
  • 17
  • 122
  • 185