强制执行依赖于父列值的复合唯一约束

Enforce composite unique constraint that depends on parent column value

使用提供的模式,我想以某种方式强制每次显示有唯一的 reserved_seat:seat_id。换句话说,如果特定的座位已经在那个节目中被预订了,你就不能预订它。

一个选项是也将 showing_id 添加到 reservation_seat(这是多余的),然后对 (showing_id, seat_id).[=12 进行唯一约束=]

这可以在 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)
)

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

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

然后我会质疑 table reservation_seat,它闻起来像 many-to-many 映射(加上一个标志)。 Many:many 表示非唯一性。所以,必须有所付出。

原始的、未规范化的数据似乎是

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

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

PRIMARY KEY(showing_id, seat)

它将两个 table 联系在一起,提供 'natural' PK,并且仍然允许使用 confirmed 标志。

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

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

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 是必要的。

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

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 代码中引用 'row' 和 'column' 列名称)

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

CONSTRAINT UC_showing_reserved UNIQUE(showing_id, id)

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

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

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