【问题标题】:Postgres Composite Primary Key Dependency IssuePostgres 复合主键依赖问题
【发布时间】:2014-10-28 20:25:40
【问题描述】:

我正在马里兰州的约翰霍普金斯大学参加数据库课程并且有一个问题。我已经给我的教授发了电子邮件,他知道我在这里问这个问题,他对此很满意。所以我正在 Postgres 中开发一个 COOKBOOK 数据库,我在 Postgres 中遇到了一个有趣的问题,我似乎无法构建 PRICE 表。我有一本不错的 Cookbook ERD,但显然要等到我的声誉至少达到 10 时才能发布。我会尽力描述 ERD。有三个与 PRICE 相关的保理表。这些是成分、替代品和价格。

这里是 ERD 的链接(请注意 1:M 表示成分到替代): [ERD]

我可以有一个可能有许多替代品的成分,并且替代品和价格之间可能是一对一的(如果价格已知,则每个替代品一个价格)。如果 PRICE 是已知的,那么 PRICE 就能够定义一个具有复合主键的元组:(price_id, ingredients_id (fk), substitution_id (fk))

我面临的挑战是 Postgres SQL 不允许我建立这种关系,我不确定为什么。我已经将 SUBSTITUTION 中的键设置为具有 UNIQUE 约束,因此这不应该是问题。我唯一能想到的是 SUBSTITUTION 中的成分 ID 是 INGREDIENT 的外键,因此可能不会在 SUBSTITUTION 中物理建立,但我得到的错误并不表明这一点。这就是我在终端中得到的(首先描述 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"

【问题讨论】:

  • 不知道是不是真的明白了,一个外键在自己的表中必须是唯一的,如果要引用元组:substitition_id和component_id,可以加一列类似@987654323 @ 作为序列到您的substitution 表中,并将其设为主键,此键可以作为price 表中的外键引用。
  • @Houari 添加一个键和 FK 并不能阻止从 PRICE(substitution_id,component_id)到 SUBSTITUTION 的约束。您的所有建议只是存在一个 FK,但没有原始所需约束点的效果。

标签: sql postgresql foreign-keys primary-key composite-primary-key


【解决方案1】:

我省略了一些列,因此您可以专注于键。我几乎没有看一眼你的 ERD。 (我讨厌拥有一千个太阳的炽热热情的 ERD。)

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)
);

【讨论】:

  • 我收回我原来的评论。是的,这行得通。我会发布更新。您的 SUBSTITUTE 和 INGERDIENTS 的 SQL 有点不对劲,但是 PRICE 的创建表是正确的。此外,当您命名表时,最好将它们命名为单数而不是复数(例如 PRICE 而不是 PRICES)。
【解决方案2】:

问题是因为price 表中定义的FOREIGN KEY substitution(ingredient_id) 不是唯一的。

substitution 表中,您定义了以下索引:

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

这意味着该表的唯一性当前需要(substitution_id, ingredient_id)元组。顺便说一句,这两个索引实际上是彼此重复的,因为 PRIMARY KEY 约束通过定义保证了唯一性。

所以,您有多种选择,但我发现最简单的做法是通常使用为每个表定义的单个唯一 ID——我喜欢使用 id serial,这将创建一个隐式序列,然后将其定义为PRIMARY KEY

然后您可以只使用该键来定义FOREIGN KEY 关系。虽然使用多键元组这样做,但它肯定会使事情复杂化,而且我发现使用单个 ID 更容易。如果您需要用于SELECT 性能等的多键元组,您始终可以在多键元组上创建额外的唯一索引。

同样的事情也适用于ingredient_id 上的 FK 约束——它不是唯一的。我上面提到的相同类型的补救措施也适用于该列。

【讨论】:

  • 我很想实现那个单一的PK。那会很好,但我被限制(没有双关语)应用多个键以符合我的 ERD 设计。他不希望我们偷工减料,但我同意您的方法是我将在现实世界中实施的方法(是的,我做了一些数据库工作)。我尝试了以下方法以确保您所建议的成分 ID 是唯一的:更改表成分添加约束唯一成分 ID 唯一(成分 ID);希望解决问题,但仍然遇到相同的“非唯一”错误。我做错了什么?
  • 我不会说单一 PK 方法是“偷工减料”——我认为它实际上更相反,因为它本质上是一个额外的而不是仅仅使用多键元组。分配是否明确禁止单个 PK?你真的可以在ingredient 上添加UNIQUE 约束,然后错误出现在substitution 中的FK 上吗?
  • 是的,我不能那样做。需要使用复合键遵循严格的 ERD 设计。设法解决问题。看看我的回答。我明天会解决这个问题,以确保 1NF、2NF 和 3NF。
  • @JasonRice & khampson 添加一个键和 FK 并不能阻止从 PRICE (substitution_id, ingredients_id) 到 SUBSTITUTION 的约束。您的所有建议都是存在一个 FK,但没有原始所需约束点的效果。
【解决方案3】:

** 更新:2014-11-02 决定只删除 PRICE 表。我的教授说,将两个单独的价格属性吸收到 INGREDIENT 和 SUBSTITUTION 实体类型中会很好。他说我像往常一样努力思考。 **

问题解决了!感谢大家的所有反馈。 这个网站真的很棒。这是最终的解决方案布局:

按顺序构建成分、替代品和价格...

   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)
   );

如果有人感兴趣,我会建立一个 github 并链接到我所有的 SQL 脚本。我正在制作一个带有 ERD 和 12 个表的 COOKBOOK 数据库。应该是一个有趣的项目。再次感谢您的所有帮助。

【讨论】:

  • 还是错了。 PRICE 表中的 {ingredient_id,substitution_id} 应该是 INTEGER NOT NULL 而不是 serial 它们只是指 INGREDIENT 和 SUBSTITUTION 表的 PK。
  • @wildplasser 我决定不走这条路。我想我最终会在以后的某个时候删除这篇文章。
猜你喜欢
  • 2021-10-03
  • 1970-01-01
  • 2011-08-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多