【问题标题】:SQL unique constraint of a max of two entries per user每个用户最多两个条目的 SQL 唯一约束
【发布时间】:2020-09-08 18:01:55
【问题描述】:

是否可以在 SQL 中生成唯一约束,允许单个用户 (user_id) 最多启用两个条目 (enabled)?一个例子如下

user_id  | enabled
------------------
123      | true
123      | true
123      | false
456      | true

以上内容是有效的,但添加另一个 user_id = 123 和 enabled = true 会失败,因为会有三个条目。此外,添加 user_id = 123 和 enabled = false 将是有效的,因为该表仍将满足规则。

【问题讨论】:

  • 不,唯一约束不能允许重复。你可能需要一个触发器。
  • 明白。谢谢。
  • 在考虑发布之前,请阅读您的教科书和/或手册并在谷歌上搜索任何错误消息或您的问题/问题/目标的许多清晰、简洁和准确的措辞,无论是否包含您的特定字符串/名称和站点:stackoverflow.com & 标签;阅读许多答案。如果您发布问题,请使用一个短语作为标题。反映你的研究。请参阅How to Ask 和投票箭头鼠标悬停文本。

标签: sql postgresql database-design database-trigger unique-constraint


【解决方案1】:

您不能允许两个“启用”值。但这里有一个接近你想要的解决方案,而不使用触发器。这个想法是将值编码为数字并在其中两个值上强制唯一性:

create table t (
    user_id int,
    enabled_code int,
    is_enabled boolean as (enabled_code <> 0),
    check (enabled_code in (0, 1, 2))
);

create unique index unq_t_enabled_code_1
    on t(user_id, enabled_code)
    where enabled_code = 1;

create unique index unq_t_enabled_code_2
    on t(user_id, enabled_code)
    where enabled_code = 2;

插入新值有点棘手,因为您需要检查值是在插槽“1”还是“2”中。但是,您可以使用is_enabled 作为布尔值进行查询。

【讨论】:

    【解决方案2】:

    您可以通过将另一个 boolean 列添加到 UNIQUE 或 PRIMARY KEY 约束(或 UNIQUE 索引)来使其工作:

    CREATE TABLE tbl (
       user_id int
     , enabled bool
     , enabled_first bool DEFAULT true
     , PRIMARY KEY (user_id, enabled, enabled_first)
    );
    

    enabled_first 用true 标记每个实例的第一个。我将其设为DEFAULT true 以允许根据user_id 对第一个enabled 进行简单插入 - 没有提及添加的enabled_first。插入第二个实例需要显式 enabled_first = false。

    NULL 值 被我使用的 PK 约束自动排除。请注意,简单的UNIQUE 约束仍然允许NULL 值,以解决您想要的约束。您必须另外定义所有三列 NOT NULL。见:

    db小提琴here

    当然,现在true/false这两个值在内部是不同的,需要调整写操作。这可能会也可能不会被接受。甚至可能是可取的。

    欢迎副作用:由于每个索引元组的最小有效负载(实际数据大小)为 8 个字节,并且布尔值占用 1 个字节而不需要对齐填充,因此索引仍然与 (user_id, enabled) 的最小大小相同。 表类似:添加的boolean 不会增加物理存储。 (可能不适用于具有更多列的表格。)请参阅:

    【讨论】:

      【解决方案3】:

      已经解释过,约束或唯一索引不能强制执行您想要的逻辑。

      另一种方法是使用物化视图。逻辑是使用窗口函数在视图中创建一个附加列,该列每两行重置一次,具有相同的(user_id, enabled)。然后,您可以在该列上放置一个唯一的部分索引。最后,您可以创建一个触发器,在每次插入或更新记录时刷新视图,从而有效地强制执行唯一约束。

      -- table set-up
      create table mytable(user_id int, enabled boolean);
      
      -- materialized view set-up
      create materialized view myview as 
      select 
          user_id, 
          enabled, 
          (row_number() over(partition by user_id, enabled) - 1) % 2 rn 
      from mytable;
      
      -- unique partial index that enforces integrity
      create unique index on myview(user_id, rn) where(enabled);
      
      -- trigger code
      create or replace function refresh_myview()
      returns trigger language plpgsql
      as $$
      begin
          refresh materialized view myview;
          return null;
      end$$;
      
      create trigger refresh_myview
      after insert or update
      on mytable for each row
      execute procedure refresh_myview();
      

      有了这个设置,让我们插入初始内容:

      insert into mytable values
          (123, true),
          (123, true),
          (234, false),
          (234, true);
      

      这可行,视图的内容现在是:

      用户 ID |启用 | rn ------: | :-------- | -: 123 |吨 | 0 123 |吨 | 1 234 | f | 0 234 |吨 | 0

      现在,如果我们尝试插入违反约束的行,则会引发错误,insert 会被拒绝。

      insert into mytable values(123, true);
      -- ERROR:  could not create unique index "myview_user_id_rn_idx"
      -- DETAIL:  Key (user_id, rn)=(123, 0) is duplicated.
      -- CONTEXT:  SQL statement "refresh materialized view myview"
      -- PL/pgSQL function refresh_myview() line 3 at SQL statement
      

      Demo on DB Fiddle

      【讨论】:

        猜你喜欢
        • 2023-03-23
        • 1970-01-01
        • 2012-05-07
        • 1970-01-01
        • 2021-07-22
        • 2021-12-13
        • 1970-01-01
        • 1970-01-01
        • 2011-03-25
        相关资源
        最近更新 更多