Thread: Foreign Key normalization question

Foreign Key normalization question

From
Matthew Wilson
Date:
I'm building an app that has a customers table, a locations table, a
products table, and a product_locations table.

They make a diamond shape.

The locations table and the products table each have a customer_id
column that links back to the customers table.

Then the product_locations table table has just two columns: a
location_id column and a product_id column, each linking back to the
appropriate table.

I want to write a constraint or a trigger or something else that makes
sure that before a (location_id, product_id) tuple is inserted into the
product_locations table, the system verifies that the product links to
the same customer as the location.

How do I do this?

Thanks in advance.

Matt


Re: Foreign Key normalization question

From
Martin Gainty
Date:
you can use setup a foreign key constraint in your create table so that column is only populated when
there is a value which syncs to the referenced value
http://www.postgresql.org/docs/7.4/interactive/sql-createtable.html

Martin
______________________________________________
Disclaimer and confidentiality note
Everything in this e-mail and any attachments relates to the official business of Sender. This transmission is of a confidential nature and Sender does not endorse distribution to any party other than intended recipient. Sender does not necessarily endorse content contained within this transmission.


> To: pgsql-general@postgresql.org
> From: matt@tplus1.com
> Subject: [GENERAL] Foreign Key normalization question
> Date: Tue, 2 Sep 2008 19:14:17 +0000
>
> I'm building an app that has a customers table, a locations table, a
> products table, and a product_locations table.
>
> They make a diamond shape.
>
> The locations table and the products table each have a customer_id
> column that links back to the customers table.
>
> Then the product_locations table table has just two columns: a
> location_id column and a product_id column, each linking back to the
> appropriate table.
>
> I want to write a constraint or a trigger or something else that makes
> sure that before a (location_id, product_id) tuple is inserted into the
> product_locations table, the system verifies that the product links to
> the same customer as the location.
>
> How do I do this?
>
> Thanks in advance.
>
> Matt
>
>
>
> --
> Sent via pgsql-general mailing list (pgsql-general@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-general


Get thousands of games on your PC, your mobile phone, and the web with Windows®. Game with Windows

Re: Foreign Key normalization question

From
"Scott Marlowe"
Date:
On Tue, Sep 2, 2008 at 1:14 PM, Matthew Wilson <matt@tplus1.com> wrote:
> I'm building an app that has a customers table, a locations table, a
> products table, and a product_locations table.
>
> They make a diamond shape.
>
> The locations table and the products table each have a customer_id
> column that links back to the customers table.
>
> Then the product_locations table table has just two columns: a
> location_id column and a product_id column, each linking back to the
> appropriate table.
>
> I want to write a constraint or a trigger or something else that makes
> sure that before a (location_id, product_id) tuple is inserted into the
> product_locations table, the system verifies that the product links to
> the same customer as the location.

If the two subordinate tables ALWAYS have to point to the same place,
why two tables?  Can't a customer have > 1 location?  I'm pretty sure
IBM has more than one corporate office you could ship things to.

Re: Foreign Key normalization question

From
Matthew Wilson
Date:
On Tue 02 Sep 2008 04:06:20 PM EDT, Martin Gainty wrote:
> you can use setup a foreign key constraint in your create table so that col=
> umn is only populated when
> there is a value which syncs to the referenced value
> http://www.postgresql.org/docs/7.4/interactive/sql-createtable.html

I don't think that will work.

When somebody inserts (99, 98) into product_location, I want to make
sure that product ID 99 has the same customer ID as location ID 98.

Matt

Re: Foreign Key normalization question

From
Matthew Wilson
Date:
On Tue 02 Sep 2008 04:19:41 PM EDT, Scott Marlowe wrote:
> If the two subordinate tables ALWAYS have to point to the same place,
> why two tables?  Can't a customer have > 1 location?  I'm pretty sure
> IBM has more than one corporate office you could ship things to.

Yeah, so the idea is one customer might have many locations and many
products.  And at each location, some subset of all their products is
available.

And I need to track many customers.  So, one customer sells fortified
wine (a product) at one location and fancy champagne at another
location.

Meanwhile, a different customer sells lottery tickets at a different
location (location number three) and sells handguns at a fourth
location.

So, I'd have tuples in product_location that look like this:

(ID of location #1 belonging to customer #1, ID for fortified wine),
(ID of location #2 belonging to customer #1, ID for fancy champagne),
(ID of location #3 belonging to customer #2, ID for lottery tickets),
(ID of location #3 belonging to customer #2, ID for handguns),

I want to guarantee that products and locations don't get mixed up
regarding customers.  In other words, since, customer #1 only sells wine
and champagne, I want to prevent somebody from putting into
product_location a tuple like this:

(ID of location #1, ID for handguns).

Here's all my tables:

create table customer (
    id serial primary key,
    name text
);

create table product (
    id serial primary key,
    name text,
    customer_id int references customer (id)
);

create table location (
    id serial primary key,
    name text,
    customer_id int references customer (id)
);

create table product_location (
    product_id int references product (id),
    location_id int references location (id),
);

I want to make sure that when somebody inserts a (product_id,
location_id) tuple into product_location, the product_id refers to a
product that has a customer_id that matches customer_id referred to by
the location_id's location.

Matt


Re: Foreign Key normalization question

From
"Scott Marlowe"
Date:
On Tue, Sep 2, 2008 at 2:35 PM, Matthew Wilson <matt@tplus1.com> wrote:
> On Tue 02 Sep 2008 04:19:41 PM EDT, Scott Marlowe wrote:
>> If the two subordinate tables ALWAYS have to point to the same place,
>> why two tables?  Can't a customer have > 1 location?  I'm pretty sure
>> IBM has more than one corporate office you could ship things to.
>
> Yeah, so the idea is one customer might have many locations and many
> products.  And at each location, some subset of all their products is
> available.

You could have the product_locations have a custid1 and custid2 fields
that reference the two parent tables, and then a check constraing on
product_locations that custid1=custid2

Re: Foreign Key normalization question

From
Matthew Wilson
Date:
On Tue 02 Sep 2008 04:40:55 PM EDT, Scott Marlowe wrote:
> On Tue, Sep 2, 2008 at 2:35 PM, Matthew Wilson <matt@tplus1.com> wrote:
>> On Tue 02 Sep 2008 04:19:41 PM EDT, Scott Marlowe wrote:
>>> If the two subordinate tables ALWAYS have to point to the same place,
>>> why two tables?  Can't a customer have > 1 location?  I'm pretty sure
>>> IBM has more than one corporate office you could ship things to.
>>
>> Yeah, so the idea is one customer might have many locations and many
>> products.  And at each location, some subset of all their products is
>> available.
>
> You could have the product_locations have a custid1 and custid2 fields
> that reference the two parent tables, and then a check constraing on
> product_locations that custid1=custid2

You inspired me to change my tables to this:

create table location (
    id serial unique,
    name text,
    customer_id int references customer,
    primary key (id, customer_id)
);

create table product (
    id serial unique,
    name text,
    customer_id int references customer,
    primary key (id, customer_id)
);

create table product_location (
    product_id int references product (id),
    product_customer_id int references customer (id),
    location_id int references location (id),
    location_customer_id int references customer (id) check product_customer_id = location_customer_id,
    foreign key (product_id, product_customer_id) references product (id, customer_id),
    foreign key (location_id, location_customer_id) references location (id, customer_id),
);

This seems to work based on my informal testing, but it seems really
byzantine.  I wish I didn't have to explicitly put the customer IDs in
the table.

Is there a better way?

Re: Foreign Key normalization question

From
"Roberts, Jon"
Date:
Sounds like you really want this:

create table customer (
    id serial primary key,
    name text
);

create table location (
    id serial primary key,
    name text,
    customer_id int references customer (id) );

create table product (
    id serial primary key,
    name text,
    location_id int references location (id) );


Jon

> -----Original Message-----
> From: pgsql-general-owner@postgresql.org [mailto:pgsql-general-
> owner@postgresql.org] On Behalf Of Matthew Wilson
> Sent: Tuesday, September 02, 2008 3:35 PM
> To: pgsql-general@postgresql.org
> Subject: Re: [GENERAL] Foreign Key normalization question
>
> On Tue 02 Sep 2008 04:19:41 PM EDT, Scott Marlowe wrote:
> > If the two subordinate tables ALWAYS have to point to the same
place,
> > why two tables?  Can't a customer have > 1 location?  I'm pretty
sure
> > IBM has more than one corporate office you could ship things to.
>
> Yeah, so the idea is one customer might have many locations and many
> products.  And at each location, some subset of all their products is
> available.
>
> And I need to track many customers.  So, one customer sells fortified
> wine (a product) at one location and fancy champagne at another
> location.
>
> Meanwhile, a different customer sells lottery tickets at a different
> location (location number three) and sells handguns at a fourth
> location.
>
> So, I'd have tuples in product_location that look like this:
>
> (ID of location #1 belonging to customer #1, ID for fortified wine),
> (ID of location #2 belonging to customer #1, ID for fancy champagne),
> (ID of location #3 belonging to customer #2, ID for lottery tickets),
> (ID of location #3 belonging to customer #2, ID for handguns),
>
> I want to guarantee that products and locations don't get mixed up
> regarding customers.  In other words, since, customer #1 only sells
wine
> and champagne, I want to prevent somebody from putting into
> product_location a tuple like this:
>
> (ID of location #1, ID for handguns).
>
> Here's all my tables:
>
> create table customer (
>     id serial primary key,
>     name text
> );
>
> create table product (
>     id serial primary key,
>     name text,
>     customer_id int references customer (id)
> );
>
> create table location (
>     id serial primary key,
>     name text,
>     customer_id int references customer (id)
> );
>
> create table product_location (
>     product_id int references product (id),
>     location_id int references location (id),
> );
>
> I want to make sure that when somebody inserts a (product_id,
> location_id) tuple into product_location, the product_id refers to a
> product that has a customer_id that matches customer_id referred to by
> the location_id's location.
>
> Matt
>
>
>
> --
> Sent via pgsql-general mailing list (pgsql-general@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-general

Re: Foreign Key normalization question

From
"D. Dante Lorenso"
Date:
Matthew Wilson wrote:
> On Tue 02 Sep 2008 04:40:55 PM EDT, Scott Marlowe wrote:
>> On Tue, Sep 2, 2008 at 2:35 PM, Matthew Wilson <matt@tplus1.com> wrote:
>>> On Tue 02 Sep 2008 04:19:41 PM EDT, Scott Marlowe wrote:
>>>> If the two subordinate tables ALWAYS have to point to the same place,
>>>> why two tables?  Can't a customer have > 1 location?  I'm pretty sure
>>>> IBM has more than one corporate office you could ship things to.
>>> Yeah, so the idea is one customer might have many locations and many
>>> products.  And at each location, some subset of all their products is
>>> available.
>> You could have the product_locations have a custid1 and custid2 fields
>> that reference the two parent tables, and then a check constraing on
>> product_locations that custid1=custid2
>
> You inspired me to change my tables to this:
>
> create table location (
>     id serial unique,
>     name text,
>     customer_id int references customer,
>     primary key (id, customer_id)
> );
>
> create table product (
>     id serial unique,
>     name text,
>     customer_id int references customer,
>     primary key (id, customer_id)
> );
>
> create table product_location (
>     product_id int references product (id),
>     product_customer_id int references customer (id),
>     location_id int references location (id),
>     location_customer_id int references customer (id) check product_customer_id = location_customer_id,
>     foreign key (product_id, product_customer_id) references product (id, customer_id),
>     foreign key (location_id, location_customer_id) references location (id, customer_id),
> );
>
> This seems to work based on my informal testing, but it seems really
> byzantine.  I wish I didn't have to explicitly put the customer IDs in
> the table.
>
> Is there a better way?

You could add a trigger to your product_location table that just
double-checked the customers matched or prevents the insert/update.  A
PL/PGSQL function like this might help:

---------- 8< -------------------- 8< ----------

DECLARE
   is_ok BOOLEAN;
BEGIN
   SELECT p.customer_id = l.customer_id
   INTO is_ok
   FROM product p, location l
   WHERE p.product_id = NEW.product_id
   AND l.location_id = NEW.location_id;

   -- didnt find the product and location ... weird
   IF NOT FOUND THEN
       RETURN NULL;
   END;

   -- product customer matches the location customer
   IF is_ok = TRUE THEN
       RETURN NEW;
   END;

   -- product and location customers did NOT match, reject changes
   RETURN NULL;
END;
---------- 8< -------------------- 8< ----------

Disclaimer: I have no idea if that code works.  I just whipped it up now
without testing it.  That might do your checks without having to add
columns to tables you don't want to add.

Good luck.

-- Dante

Re: Foreign Key normalization question

From
Matthew Wilson
Date:
On Tue 02 Sep 2008 05:35:25 PM EDT, D. Dante Lorenso wrote:
> You could add a trigger to your product_location table that just
> double-checked the customers matched or prevents the insert/update.  A
> PL/PGSQL function like this might help:
>
> ---------- 8< -------------------- 8< ----------
>
> DECLARE
>    is_ok BOOLEAN;
> BEGIN
>    SELECT p.customer_id = l.customer_id
>    INTO is_ok
>    FROM product p, location l
>    WHERE p.product_id = NEW.product_id
>    AND l.location_id = NEW.location_id;
>
>    -- didnt find the product and location ... weird
>    IF NOT FOUND THEN
>        RETURN NULL;
>    END;
>
>    -- product customer matches the location customer
>    IF is_ok = TRUE THEN
>        RETURN NEW;
>    END;
>
>    -- product and location customers did NOT match, reject changes
>    RETURN NULL;
> END;
> ---------- 8< -------------------- 8< ----------
>
> Disclaimer: I have no idea if that code works.  I just whipped it up now
> without testing it.  That might do your checks without having to add
> columns to tables you don't want to add.

Thanks!  This is what I was looking for.  Although I got a few syntax
errors in postgreSQL 8.3 until I changed a few END; statements to END
IF;

Also, I had to put:

    create or replace function check_customer ()
    returns trigger $$

at the top of this, and

$$ language 'plpgsql';

at the bottom.  I'm a novice at writing triggers, and this is really
useful.

Thanks again.

Matt