如何在几个链接行中只有一行的列为"true",其余列为"false"



我环顾四周,发现了与我相似但不完全相同的问题,所以很明显,他们得到的答案并不适合我:(

为了描绘整个画面,我有一个数据库,其中包括一个"用户"表,每个用户在另一个表"电话号码"中都有一个或多个电话号码。一个用户有一个主电话号码,所以我试图通过在电话号码表中添加一个布尔列"main_number"来解决这个问题,但我没能找到一种方法来确保在用户的所有行中,只有一行的为"true">

[编辑]:更准确地说,"电话号码"表记录可以按"user_Id"排序,并且在每个记录组(user_Id组)中,只有一个main_number为"true"。因此,"true"处的"main_number"数量将与用户数量一样多

有办法做到这一点吗?

对于我发现的类似问题,一般的答案是使用另一个指向主数字行的表。但是,这样做显然无法通过一个查询检索用户的所有行(包括第三个表中的列),除非是我的请求出错。

有人能给我指正确的方向吗?

Thx

您有两个选项可供选择。

Option1:用户表中的"Main_phone"列

只需将此列添加到表中并编码,假设它是主电话,"phone_Number"表中的数据是其他可用电话。

优点:易于实施。曼丹没那么难。放弃电话号码表中的"main_number"列

缺点:当检索所有电话时,你需要一个联接,这将扼杀所有索引使用和查询性能。如果你的桌子这么大,那就有问题了

选项2:非电话号码表上的触发器

根据建议添加"main_number"列。在插入之前、更新之前和删除之前编写一些触发器,以控制您的限制。

插入前:

应该控制你的限制

更新前:

应该控制你的限制。应该控制如果删除该用户的"主电话"行会发生什么(如果必要)。

删除前:

应该控制如果删除该用户的"主电话"行会发生什么(如果必要)。

优点:易于检索,易于维护(一旦开发)

缺点:难以编码。

这不是一个答案,而是一个不适合注释部分的注释。

尽管MySQL不支持部分索引,但我想展示如何在PostgreSQL(Oracle、SQL Server、SQLite)中实现这一点,让您知道这是可能的。

例如:

create table users (
id int not null,
phone varchar(10) not null,
main_number boolean not null
);
create unique index ix1 on users (id, main_number) where main_number;
insert into users (id, phone, main_number) values (1, '123', true);
insert into users (id, phone, main_number) values (1, '456', false);
insert into users (id, phone, main_number) values (1, '789', false); -- succeeds
insert into users (id, phone, main_number) values (1, '468', true); -- fails

如您所见,第三次插入成功,因为每个id允许多个false值。然而,第四次插入失败,因为每个id只允许一个true值。

这很棘手。原因如下:

您希望每个用户只有一个主电话号码。因此,如果用户只有一个电话号码,这就是主号码。如果一个用户有四个号码,那么其中一个必须是主号码,其他号码必须是辅助号码。

。。。至少在COMMIT到表的时候

假设一个用户有两个条目。123456是主号码654321是次号码。现在用户希望654321成为他们的主要号码。

这必须起作用:

start transaction;
update user_phone set main = true where number = '654321';
-- Just for this microsecond there are two main numbers for this user.
update user_phone set main = false where number = '123456';
commit;
-- The user has one main number again.

这个:

start transaction;
update user_phone set main = false where number = '123456';
-- Just for this microsecond there are only secondary numbers for this user.
update user_phone set main = true where number = '654321';
commit;
-- The user has one main number again.

但不是这个:

start transaction;
update user_phone set main = true where number = '654321';
commit;
-- There are two main numbers now for the user.

或者这个:

start transaction;
update user_phone set main = false where number = '123456';
commit;
-- There are only secondary numbers now for the user.

在某些DBMS中,您可以使用延迟约束来解决此问题,即仅适用于COMMIT的约束。在用户表中,你会在电话表中添加一个主电话ID,然后插入一个用户,然后插入他们的手机,然后在一次交易中用他们的主手机更新用户。在COMMIT上,所有数据都是一致的。否则,违反的外键约束将被激发。MySQL没有延迟约束。

以下是我解决这个问题的方法:对电话号码进行排名。这可以是1、2、3。。。或10、20、30。。。这其实并不重要;你会认为最低级别的号码是主电话。

create table user_phone
(
user_id int         not null,
phone   varchar(20) not null,
prio    int         not null,
unique (user_id, phone),
unique (user_id, prio)
);

相关查询:

select 
user_id, phone,
case when row_number() over (partition by user_id order by prio) = 1
then 'main'
else 'secondary'
end as type
from user_phone
order by user_id, type;

如果你想让另一部手机成为主手机,那就改变排名吧。例如

update user_phone set prio = prio + 1 where user_id = 1;
-- Still the same order, still the same main number.
update user_phone set prio = 1 where user_id = 1 and phone = '54321';
-- Phone '54321' is the new main number for user 1.

分析函数ROW_NUMBER需要MySQL版本8。它在早期版本中不可用。

演示:https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=c8a483f769db850f90cc4a6059e59832

您可以使用唯一的约束和生成的列来实现这一点。

alter table t add is_main_number boolean
generated always as (case when main_number then 1 end);

然后你可以在一个唯一的索引中使用这个:

create unique index unq_t_user_number_main on t(user_id, is_main_number)

MySQL允许在唯一索引中重复NULL值,所以这符合您的要求。

这是一个数据库<>不停摆弄

最新更新