每个用户最多两个条目的sql唯一约束

dgenwo3n  于 2021-08-13  发布在  Java
关注(0)|答案(3)|浏览(407)

有没有可能在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 将是有效的,因为表仍然满足规则。

guykilcj

guykilcj1#

不能允许两个值为“enabled”。但这里有一个解决方案,接近你想要的不使用触发器。其思想是将值编码为数字,并对其中两个值实施唯一性:

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 作为用于查询的布尔值。

g52tjvyc

g52tjvyc2#

你可以通过添加另一个 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` 允许第一次简单插入 `enabled` 每 `user_id` -没有提到增加的 `enabled_first` . 明确的 `enabled_first = false` 需要插入第二个示例。 `NULL` 值被我使用的pk约束自动排除。请注意,一个简单的 `UNIQUE` 约束仍然允许 `NULL` 值,围绕所需的约束工作。您必须定义所有三列 `NOT NULL` 另外。请参见:
在唯一列中允许null
db<>在这里摆弄
当然,现在两个 `true` /  `false` 值在内部是不同的,您需要调整写入操作。这可能是可以接受的,也可能是不可以接受的。甚至可能是可取的。
欢迎的副作用:由于最小有效负载(实际数据大小)是每个索引元组8个字节,而boolean占用1个字节而不需要对齐填充,因此索引的最小大小仍然与刚才的相同 `(user_id, enabled)` .
与表相似:添加了 `boolean` 不会增加物理存储(可能不适用于包含更多列的表。)请参阅:
postgresql中空间的计算与节省
复合索引也适用于第一个字段的查询吗?
brtdzjyr

brtdzjyr3#

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

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

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

user_id | enabled | rn
------: | :------ | -:
    123 | t       |  0
    123 | t       |  1
    234 | f       |  0
    234 | t       |  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

db小提琴演示

相关问题