A有很多B,只有一个'有效'B

时间:2013-10-03 13:13:51

标签: database database-design

说,User有很多Devices。也就是Device.user_id references User.id

我们需要为用户 - 设备关系添加“活动”状态。这意味着User只能有一个“有效”Device

- 附录

似乎我必须澄清事情。

  • User可能没有Device,一个Device或几个Device;
  • Device可以拥有User但不得拥有;
  • '所有权'是引用Device.user_id references User.id;
  • User.idDevice.id是唯一的PK,不得更改(因此,没有复合PK);
  • User可以不超过一个'有效'Device,但它可以是NULL,但如果不是NULL,它必须引用Device所拥有的User之一。

- 结束附录

我可以想象两种方法,都很简单:

  1. active_device_id字段添加到User,即User.active_device_id references Device.id;
  2. Boolean实体添加Device - 类型标记。
  3. 最常见的查询是:

    • 为给定的Device;
    • 选择有效的User
    • 检查拥有Device;
    • 的当前User是否有效

    很少使用,但非常重要的是:

    • 为拥有Device(并使其他User的设备处于非活动状态)激活某个User;
    • 更改给定Device的所有权。

    第一种方法有两点需要注意:

    • user1将有效设备设置为device1,但device1拥有user2;
    • user1user2都有相同的有效device(虽然可以通过字段User.active_device_id上的唯一约束轻松修复。

    2d方法可以为一个Device带来几个有效User个。

    这两种方法的其他缺点是什么?

    ......我应该选择什么?为什么? :)

4 个答案:

答案 0 :(得分:1)

我肯定会选择第一种方法 - active_device_id。

它可以保证每个用户只有一个活动设备,正如您所说,允许所有用户可选择保证活动设备ID的唯一性。

使用此方法,所有查询都非常简单。

答案 1 :(得分:1)

你没有提到你的DBMS,但是大多数DBMS都有可能创建部分索引,这对他们来说似乎是一个完美的用例。

我对设备表的外观有点困惑,但假设结构:

create table device
(
   device_id           integer not null,
   user_id             integer not null,
   is_active_for_user  boolean, 
   primary key (device_id, user_id)
);

您可以使用以下索引确保每个用户只有一个活动设备(Postgres语法,其他DBMS有其他语法来定义部分索引):

create unique index idx_unique_active_device 
    on device (user_id)
    where is_active_for_user;

答案 2 :(得分:0)

如果您尝试为每个用户强制实施一个唯一设备,则您需要的只是deviceId表中引用的user列的唯一约束。没有标志。

device
------
id

user
------
id
deviceId (fk, unique constraint)

如果您遵循David's方法,则无法保证每个用户只能使用一个活动设备。您必须具有UNIQUE约束才能保证每个用户使用一台设备:

这将允许重复的设备:

CREATE TABLE device (
  `id` int,
  PRIMARY KEY(`id`)
) ENGINE=INNODB;

INSERT INTO device (`id`)
VALUES (1), (2);

CREATE TABLE user (
  `id` int, 
  `active_device_id` int,
  INDEX (`active_device_id`),
    FOREIGN KEY (`active_device_id`)
    REFERENCES `device`(`id`)
) ENGINE=INNODB;

INSERT INTO user (`id`, `active_device_id`)
VALUES (1, 1), (2, 1);

添加UNIQUE保证每个用户都有唯一的设备。

CREATE TABLE device (
  `id` int,
  PRIMARY KEY(`id`)
) ENGINE=INNODB;

INSERT INTO device (`id`)
VALUES (1), (2);

CREATE TABLE user (
  `id` int, 
  `active_device_id` int,
  INDEX (`active_device_id`),
  UNIQUE (`active_device_id`),
    FOREIGN KEY (`active_device_id`)
    REFERENCES `device`(`id`)
) ENGINE=INNODB;

INSERT INTO user (`id`, `active_device_id`)
VALUES (1, 1), (2, 1);
-- Duplicate entry '1' for key 'active_device_id_2': 

答案 3 :(得分:0)

您可以使用以下型号......

enter image description here

......确保:

  • 每个用户有多个设备,但最多每个用户一个有效设备,只是因为ActiveDeviceNo与用户位于同一行。
  • 在支持延迟外键的DBMS上,此模型还可以为每个用户强制执行完全一个活动设备(通过使ActiveDeviceNo NOT NULL)。
  • 保证为用户有效的设备属于相同用户。这是通过使用从用户到设备携带UserId识别关系来实现的,然后将其带回用户并且"合并"使用原始UserId

此模型非常clustering - 非常友好。如果将PK用作集群密钥,则获取给定用户的所有设备将导致最小的I / O,因为同一用户的设备在物理上靠近在一起存储在数据库中。它自然也代表了设备的每用户顺序(如果这非常重要)。

这很简单,节省空间,但却在关键设计中强制进行某些权衡:

  • 当设备移动到另一个用户时,需要修改密钥,这可能需要级联到引用它的其他表。此外,关键是" fatter",使引用表中的外键更胖。如果避免这些问题很重要,您可以随时向设备添加surrogate keyDeviceId),然后从其他表中引用 it
  • 虽然您可以对DeviceNo使用自动增量,但这不是完全最优的,并且可以生成大于必要数量的数字(如果DBMS支持数字的可变长度表示,则会产生浪费空间)。 OTOH,在并发环境中找到DeviceNo的最佳值可能有些麻烦。

除此之外,您可以使用过滤/部分唯一索引,其中"是活动的" flag(正如其他人所建议的那样),或者如果你的DBMS不支持,你可以这样做:

CREATE TABLE Device (
    DeviceId INT PRIMARY KEY,
    UserId INT REFERENCES User (UserId),
    ActiveUserId INT UNIQUE,
    CHECK (ActiveUserId IS NULL OR ActiveUserId = UserId)
);

这是有效的,因为UNIQUE约束会忽略NULL值(假设您的DBMS正确处理了NULL)。