将所有列标记为主键是否合理?

rub*_*bik 9 postgresql database-design primary-key unique-constraint

我有一张代表电影的表。这些字段是:
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)
);
Run Code Online (Sandbox Code Playgroud)

我还添加了该timestamp列,但这不是问题,因为我不会碰它。所以它永远是自动的和独特的。

Tom*_*att 6

想象一下,你和一群朋友出去玩,话题转向电影。有人问:“你觉得《三剑客》怎么样?” 你回答:“哪一个?”

你需要什么额外的信息来绝对确定你们正在考虑同一部电影?导演的名字?制作工作室?是哪年发布的?明星的名字之一?两个或多个的某种组合?

我的问题的答案和你的答案是一样的。

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

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

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

发布日期呢?1993年上映?1999 年的 VCR 版本?2004 年的 DVD 发行版?你明白了。

想想看,艾伦·史密西 (Alan Smithee) 导演的那些电影怎么样?事后,真正的导演有没有最终站出来把他的名字放在这个项目上?我不知道。

嗯,我最好在还有一些标准时停下来。

补充几点:

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


Erw*_*ter 4

您的表定义现在看起来很合理。对于所有列,NOT NULL约束UNIQUE都将按预期工作 - 除了打字错误和拼写上的细微差异,这恐怕是相当常见的。考虑@a_horse 的评论

具有功能唯一索引的替代方案

另一个选项是功能唯一索引(类似于@Dave 评论的内容)。但我会使用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';
Run Code Online (Sandbox Code Playgroud)

将其用于索引定义:

CREATE UNIQUE INDEX movies_uni_idx
ON movies (f_movie_uuid(title,runtime,released_in,genres,tags,origin));
Run Code Online (Sandbox Code Playgroud)

SQL 小提琴。

更多细节:

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

优点和缺点

唯一约束是通过相关列上的唯一索引来实现的。首先将相关列放入约束定义中,这样您就有了一个用于其他目的的有用索引作为附带好处。

还有其他具体好处,这里列出:

功能唯一索引的大小(可能要小得多),这可以使其速度更快。如果你的列不是太大,差异不会太大。计算也有少量的间接成本。

连接所有列可能会引入误报 ( 'foo ' || 'bar' = 'foob ' || 'ar',但这对于本例来说似乎不太可能。拼写错误的可能性更大,您可以在这里安全地忽略它。

唯一性和数组

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

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

在旁边

如果您使用 Postgres 9.4 或更高版本,请考虑jsonb代替json.