将除一列以外的所有列标记为主键是否合理?


9

我有一张代表电影的桌子。字段是:
id (PK), title, genre, runtime, released_in, tags, origin, downloads

我的数据库不能被重复的行污染,所以我想强制唯一性。问题在于,除了tags和之外,不同的电影可能具有相同的标题,甚至相同的字段downloads。如何实施唯一性?

我想到了两种方法:

  • 使除downloads主键之外的所有字段。我将其downloads排除在外,因为它是JSON,它可能会影响性能。
  • 仅保留id为主键,但为所有其他列添加唯一约束(再次除外downloads)。

我读过这个非常相似的问题,但是我不太明白该怎么办。当前,该表与任何其他表均不相关,但将来可能与此相关。

目前,我的记录略少于20,000,但是我希望这个数字会增加。我不知道这是否与问题有关。

编辑:我修改了架构,这是我将如何创建表:

CREATE TABLE movies (
    id          serial PRIMARY KEY,
    title       text NOT NULL,
    runtime     smallint NOT NULL CHECK (runtime >= 0),
    released_in smallint NOT NULL CHECK (released_in > 0),
    genres      text[] NOT NULL default ARRAY[]::text[],
    tags        text[] NOT NULL default ARRAY[]::text[],
    origin      text[] NOT NULL default ARRAY[]::text[],
    downloads   json NOT NULL,
    inserted_at timestamp NOT NULL default current_timestamp,
    CONSTRAINT must_be_unique UNIQUE(title,runtime,released_in,genres,tags,origin)
);

我也添加了timestamp专栏,但这不是问题,因为我不会碰它。因此,它将始终是自动且独特的。


关于SO的密切相关的问题(带有答案):我是否需要我的表的主键具有一个UNIQUE(复合4列),其中一个可以为NULL?。如果任何列都可以为NULL,请紧急考虑:dba.stackexchange.com/q/9759/3684
Erwin Brandstetter,2015年

Answers:


4

您的表定义现在看起来很合理。对于所有列NOT NULLUNIQUE约束都将按预期工作-除了错别字和拼写上的细微差别,我担心这可能很常见。考虑@a_horse的评论

具有功能唯一索引的替代项

另一个选项是功能唯一索引(类似于@Dave comment)。但是我会使用一种uuid数据类型来优化索引大小和性能。

从数组到文本的转换不是IMMUTABLE(由于其通用实现):

因此,您需要一个辅助函数来声明它是不可变的:

CREATE OR REPLACE FUNCTION f_movie_uuid(_title text
                                      , _runtime int2
                                      , _released_in int2
                                      , _genres text[]
                                      , _tags text[]
                                      , _origin text[])
  RETURNS uuid LANGUAGE sql IMMUTABLE AS  -- faking IMMUTABLE
'SELECT md5(_title || _runtime::text || _released_in::text
         || _genres::text || _tags::text || _origin::text)::uuid';

将其用于索引定义:

CREATE UNIQUE INDEX movies_uni_idx
ON movies (f_movie_uuid(title,runtime,released_in,genres,tags,origin));

SQL提琴。

更多细节:

您可以将生成的UUID用作PK,但我仍将使用serial其4字节的列,这对于FK引用和其他用途而言既简单又便宜。对于需要独立生成PK值的分布式系统,UUID将是一个不错的选择。或者对于非常大的桌子,但是在我们的太阳系中没有足够的电影。

利弊

一个独特的约束与对所涉及的列的唯一索引实现的。首先将相关列放在约束定义中,您将有一个有用的索引(作为附带利益)用于其他目的。

还有其他特定的好处,下面是列表:

所述官能唯一索引是(可能许多)的尺寸更小,它可以使基本上更快。如果您的栏不太大,则差异不会太大。计算的开销也很小。

连接所有列可以引入误报('foo ' || 'bar' = 'foob ' || 'ar',但似乎对这种情况的可能性不大。错别字那么更可能是您可以放心地在这里忽略它。

唯一性和数组

必须对数组进行一致的排序,以使其在依赖于=运算符的任何唯一排列中有意义,因为'{1,2}' <> '{2,1}'。我建议使用的查找表genretagorigin带有serialPK和唯一条目,以允许模糊搜索数组元素。然后:

无论哪种方式,直接使用数组或使用规范化模式和实例化视图,使用正确的索引和运算符都可以非常有效地进行搜索:

在旁边

如果您使用的是Postgres 9.4或更高版本的考虑jsonb,而不是json


6

想象你和一群朋友出去玩,而谈话变成了电影。有人问:“您如何看待'三剑客'?” 您回答:“哪个?”

您需要什么其他信息才能绝对确定您都在考虑同一部电影?导演叫什么名字 生产工作室?它发布的年份?明星的名字之一?两个或多个的某种组合?

我的问题的答案与您的问题相同。

但是,我认为该类型不会是一个不错的选择。原因之一,体裁过于主观的标准。是“三剑客”的动作吗?戏剧?冒险?喜剧?动作冒险?爱情喜剧?我经常看到同一部电影的流派不同。即使允许多种类型,您的用户也可以选择一个与他们正在寻找的实际电影未列出的完全不同的类型。

即使是运行时间也可能有所不同,尤其是在影院和VCR / DVD / b-ray版本之间。

因此,您需要硬而客观的属性,这些属性不会从一种媒体版本更改为另一种媒体版本。不幸的是,这可以排除电影的名称,因为已知电影已被重命名,尤其是在续集发行之后。

那发布日期呢?1993年的戏剧发布?1999年的VCR版本?DVD发行于2004年吗?你明白了。

想一想,艾伦·史密斯执导的所有电影中有哪些?事实发生之后,真正的导演有没有最终挺身而出地将自己的名字放在该项目上?我不知道。

嗯,我还想停下来,但仍有一些标准。

其他一些要点:

  • 是的,保留代理键并在自然键字段上创建唯一索引(如果最终可以确定这些索引)。代理键最适合于外键引用。您不想在每个包含对电影的引用的表中重复所有自然键字段。
  • 删除数组字段(类型,标签,原点)。继续并正确规范化那些属性。我从未见过一个数组字段,它没有比它值得的麻烦大得多,尤其是如果您希望它们可搜索(“ ... where genre ='horror'...”)。请注意,除非您正确维护查找表,否则不会自动消除大小写差异和拼写问题(“科幻小说”与“科幻小说”)。但是比起大表每一行的每个数组单元,检查小表的一个字段中的这种差异要容易得多。

4

当您想要/需要强制执行唯一性时,ID列根本没有优势。属性的任何组合的唯一性永远不会通过添加无意义的ID来实现。它的“优势”仅在您需要新表且需要该表的外键的时候显示。在这种情况下,并且如果您已包含ID,则可以在新表中将其用作FK。(但是不要以为这将是免费的午餐。这种方法的缺点是,您可能会发现自己编写更多的联接只是出于获取信息的目的,而这些信息完全可能是您创建的新表的一部分。 )


1
如果业务规则说属性FOO和BAR中的值的组合必须唯一,那么添加ID并不能实现这一点。添加ID只是为了避免避免在引用表中包括FOO和BAR。这又需要更多的联接,因为FOO和BAR属性(带有BUSINESS标识符)不在原本的位置(至少从业务的角度来看,很可能在预期的位置)。
Erwin Smout 2015年

1
并不是唯一的“行”,而是企业所说的标识符必须是唯一的。如果这是属性FOO和BAR的组合,则它是属性FOO和BAR的组合。
Erwin Smout 2015年

2
是否具有ID不能解决任何强制表中“业务”列唯一性的问题。必须通过声明适当的键来实现唯一性(您这样做-您使用语法词“ CONSTRAINT”而不是“ KEY”这一事实并不意味着它不是键)。
Erwin Smout,2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.