【问题标题】:Enforce composite unique constraint that depends on parent column value强制执行取决于父列值的复合唯一约束
【发布时间】:2020-12-23 17:18:36
【问题描述】:

使用提供的架构,我想以某种方式强制每次显示都有唯一的 reserved_seat:seat_id。换句话说,如果该节目中已经预订了特定座位,您将无法预订该座位。

一种选择是将showing_id也添加到reserving_seat(这是多余的),然后对(showing_id,seat_id)进行唯一约束。

这可以在 sql 中完成还是属于应用程序代码?

DDL:

CREATE TABLE showing
(
    id              INT  NOT NULL  AUTO_INCREMENT,
    name            VARCHAR(45) NOT NULL,
    PRIMARY KEY (id)
)

CREATE TABLE reservation
(
    id              INT  NOT NULL  AUTO_INCREMENT,
    showing_id      INT  NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (showing_id) REFERENCES showing(id)
)

CREATE TABLE reservation_seat
(
    id              INT  NOT NULL  AUTO_INCREMENT,
    reservation_id  INT  NOT NULL,
    seat_id         INT  NOT NULL,
    confirmed       TINYINT,
    PRIMARY KEY (id),
    FOREIGN KEY (reservation_id) REFERENCES reservation(id),
    FOREIGN KEY (seat_id) REFERENCES seat(id)
)

CREATE TABLE seat
(
    id              INT  NOT NULL  AUTO_INCREMENT,
    row             VARCHAR(45) NOT NULL,
    column          VARCHAR(45) NOT NULL,
    PRIMARY KEY (id)
)

【问题讨论】:

  • 人们希望将表格定义视为文本,而不是图像。尤其是黑色背景上带有微弱灰色线条的图像。
  • @GordonLinoff 如果它可以帮助你回答我的问题,我很乐意改变它。
  • 什么是“预订”?那一晚有 4 个相邻的座位?
  • 这是一个非常有趣的问题。我建议将其移动或重新发布到dba.stackexchange.com,您可能会得到更多的答案和关注。我还建议遵循@GordonLinoff 的建议并将表定义发布为 SQL 命令文本而不是图表图像。 (如果您确实重新发布,请务必关闭/删除此)。
  • 我会继续为您制作 SQL 命令文本...

标签: mysql sql constraints database-normalization unique-constraint


【解决方案1】:

我相信这是使用代理键(auto_increment id's)而不是自然键导致您误入歧途的罕见情况之一。考虑一下如果您使用自然键,您的表定义会是什么样子:

CREATE TABLE showing
(
    name            VARCHAR(45) NOT NULL,   -- globally unique
    PRIMARY KEY (name)
)

CREATE TABLE reservation
(
    showing_name    VARCHAR(45) NOT NULL,
    name            VARCHAR(45) NOT NULL,   -- only unique within showing_name
    PRIMARY KEY (name, showing_name),
    FOREIGN KEY (showing_name) REFERENCES showing(name)
)

CREATE TABLE reservation_seat
(
    showing_name    VARCHAR(45) NOT NULL,
    reservation_name VARCHAR(45) NOT NULL,
    seat_row        VARCHAR(45) NOT NULL,
    seat_column     VARCHAR(45) NOT NULL,
    confirmed       TINYINT,
    PRIMARY KEY (showing_name, reservation_name, seat_row, seat_column),
    FOREIGN KEY (showing_name, reservation_name) REFERENCES reservation(showing_name, name),
    FOREIGN KEY (seat_row, seat_column) REFERENCES seat(row, column)
)

现在您可以根据显示限制将您预留的座位添加为 reservation_seat 上的备用键:

CREATE TABLE reservation_seat
(
    showing_name    VARCHAR(45) NOT NULL,
    reservation_name VARCHAR(45) NOT NULL,
    seat_row        VARCHAR(45) NOT NULL,
    seat_column     VARCHAR(45) NOT NULL,
    confirmed       TINYINT,
    PRIMARY KEY (showing_name, reservation_name, seat_row, seat_column),
    FOREIGN KEY (showing_name, reservation_name) REFERENCES reservation(showing_name, name),
    FOREIGN KEY (seat_row, seat_column) REFERENCES seat(row, column),
    CONSTRAINT UC_seat_showing_reserved UNIQUE(showing_name, seat_row, seat_column)
)

但是,这清楚地表明主键是多余的,因为它只是我们添加的约束的较弱版本,所以我们应该用我们的新约束替换它。

CREATE TABLE reservation_seat
(
    showing_name    VARCHAR(45) NOT NULL,
    reservation_name VARCHAR(45) NOT NULL,
    seat_row        VARCHAR(45) NOT NULL,
    seat_column     VARCHAR(45) NOT NULL,
    confirmed       TINYINT,
    PRIMARY KEY (showing_name, seat_row, seat_column),
    FOREIGN KEY (showing_name, reservation_name) REFERENCES reservation(showing_name, name),
    FOREIGN KEY (seat_row, seat_column) REFERENCES seat(row, column)
)

现在我们可能会担心我们的 reservation_seat 可能会引用与 reservation_seat 本身不同的showing_id 的预订,但这对于自然键来说不是问题,因为第一个外键引用可以防止这种情况发生。

现在我们需要做的就是将其转换回代理键:

CREATE TABLE reservation_seat
(
    id              INT  NOT NULL  AUTO_INCREMENT,
    showing_id      INT  NOT NULL,
    reservation_id  INT  NOT NULL,
    seat_id         INT  NOT NULL,
    confirmed       TINYINT,
    PRIMARY KEY (id),
    FOREIGN KEY (showing_id, reservation_id) REFERENCES reservation(showing_id, id),
    FOREIGN KEY (seat_id) REFERENCES seat(id),
    CONSTRAINT UC_seat_showing_reserved UNIQUE(showing_id, seat_id)
)

因为我们将 reservation_seat(id) 设为主键,所以我们必须将命名的 PK 定义改回唯一约束。与您最初的 reservation_seat 定义相比,我们最终添加了showing_id,但是通过修改后的更强的第一个外键定义,我们现在确保reservation_seat 在一次放映中是唯一的,并且reservation_seat 的showing_id 不能与其父预订不同。

(注意:您可能必须在上面的 SQL 代码中引用“行”和“列”列名)

附加说明: DBMS 对此有所不同(在这种情况下我不确定 MySql),但许多人会要求外键关系在目标上具有相应的主键或唯一约束(参考)表。这意味着您必须使用以下新约束来更改 reservation 表:

CONSTRAINT UC_showing_reserved UNIQUE(showing_id, id)

以匹配我上面建议的 reservation_seat 上的新 FK 定义:

FOREIGN KEY (showing_id, reservation_id) REFERENCES reservation(showing_id, id),

从技术上讲,这将是一个冗余约束,因为它是保留表上主键的较弱版本,但在这种情况下,SQL 可能仍需要它来实现 FK。

【讨论】:

  • (仅供参考,这就是为什么我们喜欢文本而不是图像中的数据库定义)
【解决方案2】:

指定“座位”是否需要 90 个字符?我熟悉的座位就像“103-45”或“J17”。甚至是“Sec 4 Row 43 Seat 105”。您没有提到它,但是 row/column 不足以回答“这两个座位相邻吗?”的问题

我解决这个问题的第一个方法是摆脱桌子seat,而不是能够枚举场地中的所有座位。

然后我会质疑表reservation_seat,它闻起来像多对多映射(加上一个标志)。多:多意味着非唯一性。所以,必须付出一些。

原始的、未标准化的数据似乎是

showing:  showing_id (PK), date, time, location
reservation:  showing_id, seat, confirmed

拥有这个(在reservation)可能会回答你的问题:

PRIMARY KEY(showing_id, seat)

它将两个表联系在一起,提供“自然”PK,并且仍然允许confirmed 标志。

我不知道你“确认”的逻辑。我假设您在等待确认时无法重新分配座位?

回到我的开始评论。 seat VARCHAR(15) 可能是合适的。而且,如果你需要它,另一张桌子可以有

CREATE TABLE venue (
    venue_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
    name VARCHAR (144) NOT NULL,
    location ...
    capacity SMALLINT UNSIGNED NOT NULL,
    ...
    PRIMARY KEY(venue_id)
) ENGINE=InnoDB

CREATE TABLE seat (
    venue_id SMALLINT UNSIGNED NOT NULL,
    seat_num VARCHAR(30) NOT NULL,
    is_handicap ...,
    strip_num SMALLINT UNSIGNED NOT NULL,  -- see below
    PRIMARY KEY(venue_id, seat_num)
) ENGINE=InnoDB

这无法解决您有时想要挡住阳台的场地,从而使某些座位无效。拥有不同的venueid 并且大部分信息相同可能是要采取的方向。

确保在适当的情况下使用事务(BEGIN..COMMITFOR UPDATE)。

CREATE TABLE showing (
    showing_id MEDIUM UNSIGNED NOT NULL AUTO_INCREMENT,
    venue_id SMALLINT UNSIGNED NOT NULL,
    date ...
    notes ...
    PRIMARY KEY(showing_id)
) ENGINE=InnoDB

为了处理座位相邻问题,我建议手动为每一段相邻座位分配一个“条形”编号,停在过道、柱子等处。唉,那就是对于座位“1”在中间的中间部分来说是不够的,偶数朝一侧,奇数朝另一方向。所以 K-8 和 K-9 相距很远,但 K-8 和 K-10 是相邻的,尽管分得很远。

至于confirmed,它“属于”reservation。但是对于其他操作来说,将它放在seat 中可能会更方便。我们可能需要制定 SQL 语句来做出决定。此外,SQL 语句对于决定要拥有什么辅助 INDEXes 是必需的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-11
    相关资源
    最近更新 更多