强制执行取决于父列值的复合唯一约束

问题描述

使用提供的模式,我想以某种方式强制每个显示都有唯一的reserved_seat:seat_id。换句话说,如果那个座位已经在显示中被预留,您将无法预留。

一种选择是,还向showing_seat中添加showing_id(这是多余的),然后对(showing_id,seat_id)进行唯一约束。

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

enter image description here

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

解决方法

我相信这是使用代理键(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,column),CONSTRAINT UC_seat_showing_reserved UNIQUE(showing_name,seat_column)
)

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

CREATE TABLE reservation_seat
(
    showing_name    VARCHAR(45) NOT NULL,column)
)

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

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

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,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,

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

,

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

我第一个解决此问题的方法是摆脱桌子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

为处理座位邻接,我建议手动为每段相邻座位分配一个“条”号,该数字在过道,柱子等处停止。A对于中间位置为“ 1”的中间部分,这是不够的,偶数是一种方式,奇数是另一种方式。因此,尽管分选相距很远,但K-8和K-9相距很远,但K-8和K-10相近。

对于confirmed,它“属于” reservation。但是其他操作将其包含在seat中可能会更方便。我们可能需要计算出SQL语句才能做出决定。另外,SQL语句对于确定要拥有哪个辅助INDEXes是必需的。