【问题标题】:Do I need a primary key for my table, which has a UNIQUE (composite 4-columns), one of which can be NULL?我的表是否需要一个主键,它有一个 UNIQUE(复合 4 列),其中一个可以为 NULL?
【发布时间】:2012-05-18 00:02:59
【问题描述】:

我有下表(PostgreSQL 8.3),其中存储了一些产品的价格。价格与另一个数据库同步,基本上下面的大部分字段(除了一个)不是由我们的客户更新的 - 而是每隔一段时间删除和刷新一次以与另一个股票数据库同步:

CREATE TABLE product_pricebands (
    template_sku varchar(20) NOT NULL,
    colourid integer REFERENCES colour (colourid) ON DELETE CASCADE,        
    currencyid integer NOT NULL REFERENCES currency (currencyid) ON DELETE CASCADE,
    siteid integer NOT NULL REFERENCES site (siteid) ON DELETE CASCADE,

    master_price numeric(10,2),

    my_custom_field boolean, 

    UNIQUE (template_sku, siteid, currencyid, colourid)
);

在同步时,我基本上删除了上面的大部分数据,除了数据 WHERE my_custom_field 为 TRUE(如果为 TRUE,则表示客户端通过其 CMS 更新了此字段,因此不应删除此记录)。然后我在表中插入 100 到 1000 行,并在插入失败的地方更新(即 (template_sku、siteid、currencyid、 colourid) 的组合已经存在的地方)。

我的问题是 - 这里应该应用什么最佳实践来创建主键?甚至需要主键吗?我想让主键 = (template_sku, siteid, currencyid, colourid) - 但是 colourid 字段可以为 NULL,并且在复合主键中使用它是不可能的。

从我在其他论坛帖子上看到的内容来看,我认为我已经正确地完成了上述操作,只需要澄清一下:

1) 我是否应该使用“串行”主键以防万一我需要一个?目前我没有,而且我认为我永远不会,因为表中的重要数据是价格和我的自定义字段,仅由 (template_sku, siteid, currencyid, colourid) 组合标识。

2) 由于 (template_sku, siteid, currencyid, colourid) 是我将用来查询产品价格的组合,我是否应该在我的列中添加任何进一步的索引,例如“template_sku”,它是一个 varchar?还是 UNIQUE 约束已经是我的 SELECT 的一个很好的索引?

【问题讨论】:

  • colourid 可以为空。这使它成为主键的讨厌成员。
  • 好的,这是简单的答案。如果您有一个表,其中列 a、b、c、d 和 d 可以为空,您可以使用具有相同 a、b 和 c 字段的行吗?如果是这样,那么您所拥有的一切都很好。如果您需要将它们区分开来,那么您需要 a、b、c 上的唯一索引或 a、b、c 上的部分索引,其中 d 为空。

标签: postgresql database-design null indexing primary-key


【解决方案1】:

我是否应该使用“串行”主键以防万一我需要一个?

如果需要,您可以在以后轻松添加序列列:

ALTER TABLE product_pricebands ADD COLUMN id serial;

该列将自动填充唯一值。您甚至可以在同一条语句中将其设为主键(如果尚未定义主键):

ALTER TABLE product_pricebands ADD COLUMN id serial PRIMARY KEY;

如果您从其他表中引用该表,我建议您使用这样的代理主键,因为通过四列链接起来相当笨拙。在带有 JOIN 的 SELECT 中也比较慢。

无论哪种方式,您都应该定义一个主键。包含可为空列的 UNIQUE 索引不是完全替换。它允许包含 NULL 值的组合重复,因为两个 NULL 值永远不会被视为相同。这可能会导致麻烦。


作为

colorid 字段可以为 NULL

您可能想要创建两个唯一索引。组合(template_sku, siteid, currencyid, colourid) 不能是PRIMARY KEY,因为colourid 可以为空,但您可以像已有的一样创建UNIQUE 约束(自动实现索引):

ALTER TABLE product_pricebands ADD CONSTRAINT product_pricebands_uni_idx
UNIQUE (template_sku, siteid, currencyid, colourid)

该索引完美地涵盖了您在 2) 中提到的查询。
如果您想避免与(colourid IS NULL) 发生“重复”,请另外创建一个部分唯一索引:

CREATE UNIQUE INDEX product_pricebands_uni_null_idx
ON product_pricebands (template_sku, siteid, currencyid)
WHERE colourid IS NULL;

覆盖所有基地。我在related answer on dba.SE 中写了更多关于该技术的内容。


上面的简单替代方法是使colourid NOT NULL 并创建一个主键而不是上面的product_pricebands_uni_idx


还有,像你一样

基本上删除大部分数据

对于您的重新填充操作,在重新填充操作期间删除不需要的索引会更快,然后再重新创建这些索引。从头开始构建索引比增量添加所有行要快一个数量级。

您怎么知道使用(需要)哪些索引?

  • 使用EXPLAIN ANALYZE 测试您的查询。
  • 或使用built-in statisticspgAdmin 在单独的选项卡中显示所选对象的统计信息。

将带有my_custom_field = TRUE 的几行选择到临时表中,TRUNCATE 基表并重新插入幸存者可能会更快。取决于您是否定义了外键。看起来像这样:

CREATE TEMP TABLE pr_tmp AS
SELECT * FROM product_pricebands WHERE my_custom_field;

TRUNCATE product_pricebands;
INSERT INTO product_pricebands SELECT * FROM pr_tmp;

这样可以避免大量吸尘。

【讨论】:

  • 嗨,非常感谢这个,从多个角度来看都非常有用!以下是我迄今为止尝试过的 cmets: - 我添加了一个串行列,只是为了便于编辑/删除。 - 通过代理主键,您是指序列键吗?我确实需要将此价格表加入到网站上产品列表和详细信息页面上的其他产品相关表中,因此我需要通过 (template_sku, siteid, currencyid, colourid) 进行查询。
  • - Colourid 对于 100 种产品中只有两种产品实际上可以为空。这两种产品与系统中的颜色无关。不过,我认为我需要这个字段为空,因为只为这两个产品添加一个特殊的表是没有意义的。另外,正如你所说,我添加了一个部分索引,“CREATE UNIQUE INDEX product_pricebands_uni_null_idx”。我现在的问题是你所说的“删除数据”。我在 INSERT 上尝试了 EXPLAIN ANALYZE - 它没有提及任何唯一索引,而仅提及 FK:例如约束product_pricebands_colourid_fkey的触发器:时间=0.238调用=1
  • 约束product_pricebands_currencyid_fkey的触发器:约束product_pricebands_siteid_fkey的触发器:--因此,我应该继续删除DELETE之前的索引,然后重新插入INDEXES吗?我是在插入/更新所有数据之前还是之后重新插入索引?我很困惑索引是否真的重新索引自己,或者我是否应该在每个同步批量插入/更新的开头和结尾(分别)删除并添加它们?
  • @rishijd:是的,序列列是surrogate primary key。索引始终自动保持最新。 如果您在批量操作中插入大部分或全部数据,删除和重新创建会提高性能。您可以对外键约束执行相同的操作,除非您需要在插入期间验证数据完整性。 bulk-INSERT 完成后重新创建索引和 fk 约束。
  • 只想对您的所有帮助表示非常感谢 :) 仅供参考,我保持索引完好无损,但您的回复非常有见地。
猜你喜欢
  • 1970-01-01
  • 2022-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-19
  • 2010-09-21
  • 2016-08-22
  • 1970-01-01
相关资源
最近更新 更多