我已经考虑过这个问题了,我还没有想出更好的东西。那么让我来描述我的问题,我目前的解决方案,以及我想要改进的地方。我也有一些顾虑,比如我的设计是否实际正常化。
我正在创建一个数据库,我希望为比赛存储VS比赛信息。为简单起见,我们假装他们是国际象棋比赛。 1V1。我目前的设计如下:
CREATE TABLE matches(
match_id bigserial PRIMARY KEY,
tournament_id int NOT NULL,
step int NOT NULL,
winner match_winner,
(etc. etc.)
UNIQUE(match_id, tournament_id, step), -- Actual primary key
FOREIGN KEY (tournament_id) references tournaments(tournament_id)
ON DELETE RESTRICT
ON UPDATE CASCADE
);
CREATE TABLE match_players(
match_id bigint NOT NULL,
tournament_id int NOT NULL,
step int NOT NULL,
player_id int NOT NULL,
first boolean NOT NULL,
PRIMARY KEY (match_id, tournament_id, step, player_id),
UNIQUE (tournament_id, step, player_id),
foreign key (match_id, tournament_id, step) -- keep em together
references matches(match_id, tournament_id, step)
ON DELETE RESTRICT
ON UPDATE CASCADE,
foreign key (player_id) references accounts(player_id)
ON DELETE RESTRICT
ON UPDATE CASCADE
);
-- Partial index, ensure no more than one "first" player exists per match
CREATE UNIQUE INDEX idx_match_players_primary
ON match_players
USING btree
(match_id, tournament_id, step)
WHERE first=true;
-- Also ensure that no more than one "not-first" player exists per match
CREATE UNIQUE INDEX idx_match_players_not_primary
ON match_players
USING btree
(match_id, tournament_id, step)
WHERE first=false;
为了得到实际的vs匹配,我可以简单地将match_players加入到自身中(在mp1.match_id = mp2.match_id,mp1.first = true和mp2.first = false,其中mp1和mp2是匹配的两个实例)。部分唯一索引确保最多可以添加两个玩家。
数据库已经这样规范化,因为玩家是无序的。如同,A vs B与B vs A相同。我已将“first”布尔值添加到匹配中,以便可以始终显示A vs B. (我想我可以简化它,以便mp1.player_id< mp2.player_id ......但“第一个”布尔值似乎有效。)
tournament_id和step在第二个表中重复,因为在该表的Unique索引上需要它们...以确保玩家每个步骤只有一个匹配。
这是我的主要问题:
以下是我的一个问题:
感谢任何可以帮助我的人。这是迄今为止我能做的最好的事情。我想如果我解决孤立的行问题,那么这个设计将是完美的。我想我可以设置一个cron工作来清除孤立的行,但我想知道在解决这个问题之前是否存在更清洁的设计。
我认为检查约束中的子查询可以解决问题,但是,我不认为PostgreSQL实际上支持该功能。
答案 0 :(得分:2)
这就是我所说的“前瞻性问题”,即您可能遇到有关依赖尚未插入的行的数据约束的问题。整个事务具有各行不需要的要求。大多数数据库提供的解决此问题的工具很少。幸运的是,PostgreSQL为您提供了一些选择。
使用TOAST
的非规范化“输入缓冲区”第一种方法是在匹配类型为match_player[]
的匹配项中添加一列。然后你会在比赛中存储一系列球员。这将使用触发器实现到match_player表。尽管在发展和预见角落案件方面存在严重的处罚。我确实认为这是一个可行的选择,但它不是一个理想的选择。这通过压平表来避免前瞻性约束。但是它只能存储第0步记录。一旦人们移动......必须通过仅插入match_players来完成。
约束触发器
第二种方法是创建一个触发器函数,该函数在每个语句中运行一次,作为在事务结束时执行的INITIALLY DEFERRED
DEFERRABLE
CONSTRAINT TRIGGER
。这将从表中提取系统列以查找插入的行,然后检查以确保匹配发生在另一个表中。这可能是解决前瞻性约束问题的最佳通用方法。