简体   繁体   中英

Postgres Composite Primary Key Dependency Issue

I'm taking a database course at Johns Hopkins in Maryland and have a question. I already emailed my professor and he knows I'm asking this question here and he's cool with that. So I'm developing a COOKBOOK DB in Postgres and I have an interesting problem I've been facing in Postgres where I just can't seem to be able to build the PRICE table. I have a nice Cookbook ERD but apparently can't post until my reputation is at least 10. I'll do my best to describe the ERD. There are three factoring tables relating to PRICE. These are INGREDIENT, SUBSTITUTION, and PRICE.

Here's a link to the ERD (notice the 1:M for INGREDIENT to SUBSTITUTION): [ ERD ]

I can have one INGREDIENT with potentially many SUBSTITUTIONS and a possible one-to-one between SUBSTITUTION and PRICE (one PRICE per SUBSTITUTION if a PRICE is known). If the PRICE is known then the PRICE is able to define a tuple with a composite primary key: (price_id, ingredient_id (fk), substitution_id (fk))

The challenge I'm facing is that Postgres SQL is not allowing me to establish this relationship and I'm not exactly sure why. I've set the keys in SUBSTITUTION to have a UNIQUE constraint so that shouldn't be the problem. The only thing I can think of is that the ingredient_id in SUBSTITUTION is a foreign key to INGREDIENT and therefore may not be physically established in SUBSTITUTION but the error I'm getting doesn't suggest that. This is what I'm getting in the terminal ( first describing SUBSTITUTION ):

   cookbook=# \d+ SUBSTITUTION
                                                             Table "public.substitution"
   Column       |         Type          |                                Modifiers                                 | Storage  | Description 
   --------------------+-----------------------+--------------------------------------------------------------------------+----------+-------------
   substitution_id    | integer               | not null default nextval('subsitution_substitution_id_seq'::regclass)    | plain    | 
   ingredient_id      | integer               | not null default nextval('subsitution_ingredient_id_seq'::regclass)      | plain    | 
   name               | character varying(50) | not null                                                                 | extended | 
   measurement_ref_id | integer               | not null default nextval('subsitution_measurement_ref_id_seq'::regclass) | plain    | 
   metric_unit        | character varying(25) | not null                                                                 | extended | 
   Indexes:
   "subsitution_pkey" PRIMARY KEY, btree (substitution_id, ingredient_id)
   "uniqueattributes" UNIQUE, btree (substitution_id, ingredient_id)
   Foreign-key constraints:
   "subsitution_ingredient_id_fkey" FOREIGN KEY (ingredient_id) REFERENCES ingredient(ingredient_id)
   "subsitution_measurement_ref_id_fkey" FOREIGN KEY (measurement_ref_id) REFERENCES measurement_ref(measurement_ref_id)
   Has OIDs: no

   cookbook=# create table price(
   price_id serial not null,
   ingredient_id serial references substitution(ingredient_id),
   cookbook(# substitution_id serial references substitution(substitution_id),
   cookbook(# usdollars smallint not null,
   cookbook(# availability season,
   cookbook(# seasonal boolean,
   cookbook(# primary key (price_id, ingredient_id, substitution_id)
   cookbook(# );
   NOTICE:  CREATE TABLE will create implicit sequence "price_price_id_seq" for serial column "price.price_id"
   NOTICE:  CREATE TABLE will create implicit sequence "price_ingredient_id_seq" for serial column "price.ingredient_id"
   NOTICE:  CREATE TABLE will create implicit sequence "price_substitution_id_seq" for serial column "price.substitution_id"
   NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "price_pkey" for table "price"
   ERROR:  there is no unique constraint matching given keys for referenced table "substitution"

I've omitted some columns, so you can focus on the keys. I barely glanced at your ERD. (I hate ERDs with the burning passion of a thousand suns.)

create table ingredients (
  ingredient_id serial primary key,
  -- Don't allow duplicate names. 
  ingredient_name varchar(35) not null unique
);

create table substitutions (
  -- These are properly declared integer, not serial. 
  -- Also note two separate foreign key references.
  ingredient_id integer not null references ingredients (ingredient_id),
  substitute_id integer not null references ingredients (ingredient_id),
  primary key (ingredient_id, substitute_id)
);

create table prices (
  -- Price id number is unnecessary.
  ingredient_id integer not null,
  substitute_id integer not null,
  -- Money is usually declared numeric(n, m) or decimal(n, m).
  us_dollars numeric(10, 2) not null 
    -- Negative amounts don't make sense.
    check (us_dollars >= 0),
  -- Only one row per distinct substitution.
  primary key (ingredient_id, substitute_id),
  -- One single foreign key reference, but it references *two* columns.
  foreign key (ingredient_id, substitute_id) references substitutions (ingredient_id, substitute_id)
);

The issue is because the FOREIGN KEY defined in the price table, substitution(ingredient_id) , is not unique.

In the substitution table, you have the following indexes defined:

  "subsitution_pkey" PRIMARY KEY, btree (substitution_id, ingredient_id)
   "uniqueattributes" UNIQUE, btree (substitution_id, ingredient_id)

This means that for that table uniqueness currently requires a tuple of (substitution_id, ingredient_id) . As an aside, those two indexes are really duplicates of each other, since a PRIMARY KEY constraint guarantees uniqueness by defintion.

So, you have a variety of options, but I find the easiest thing to do is often use a single unique ID which is defined for every table -- I like to use id serial , which will create an implicit sequence, and then define that to be the PRIMARY KEY .

Then you can use just that key to define FOREIGN KEY relationships. While doing that with multi-key tuples, it certainly complicates things, and I find using a single ID easier. You can always create additional unique indexes on multi-key tuples if you need those for SELECT performance, etc.

The same thing applies to the FK constraint on ingredient_id -- it's not unique. The same type of remedy I mentioned above applies to that column also.

** UPDATE: 2014-11-02 decided to just drop the PRICE table. My professor said that it would be fine to absorbe two separate price attributes into the INGREDIENT and SUBSTITUTION entity types. He said I was thinking to hard as I often do. **

Problem Solved! Thanks everyone for all the feedback. This site is really good. Here's the final solution layout:

Building the INGREDIENT, SUBSTITUTION, and PRICE in that order...

   create table INGREDIENT(
      ingredient_id serial not null,
      description text,
      amount smallint,
      measurement_ref_id serial references measurement_ref(measurement_ref_id),
      nutritional_info_id serial references nutritional_info(nutritional_info_id),
      primary key (ingredient_id)
   );

   create table SUBSTITUTION(
      substitution_id serial not null unique,
      ingredient_id serial not null references ingredient(ingredient_id),
      name varchar(50) not null,
      measurement_ref_id serial references measurement_ref(measurement_ref_id),
      metric_unit varchar(25) not null,
      primary key (substitution_id, ingredient_id)
   );

   -- NOTE: I'll add the other variables to this later.
   CREATE TABLE PRICE(
      ingredient_id serial not null,
      substitution_id serial not null,
      -- link the two keys from substitution in as two unique foreign keys
      foreign key (ingredient_id, substitution_id) references substitution (ingredient_id, substitution_id),   
      -- combine foreign keys as one single composite primary key
      primary key (ingredient_id, substitution_id)
   );

If anyone is interested I'm putting up a github and linking in all my SQL scripts. I'm making a COOKBOOK database with an ERD and 12 tables. Should be a fun project. Thanks again for all your help.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM