我正在为锦标赛跟踪软件创建数据库,其中一部分涉及将用户的 ID 存储为稍后参考的值。为此,我使用了 2 个表; player(主键 player_ID),存储事件中玩家的信息,例如他们的 user_ID(不相关表的一部分)和他们参加的比赛的结果,以及 event,它存储所有事件详细信息,以及所有 16 个事件参与者的 player_ID。这些列名为 player1 -> player16
我的问题是试图找到一种方法来单独填充这些列。创建事件时,所有 16 列均以 Null 值开头,并将更改为用户注册事件时在 player 表中创建的 player_ID。
我的第一个想法是为每个列创建单独的查询,以顺序检查它们是否包含非空值,然后更新第一列以匹配此条件并突破检查,但是拥有 32 个似乎效率非常低查询涵盖每个选项。有没有办法将其压缩到更合理的大小,如果是的话,我会怎么做?
建议您考虑第三个表,这是一个将玩家映射到事件的地图(关联表、参考表、映射表和其他术语)。
这样的表支持多对多关系。也就是说,一个赛事可以有很多玩家,而一个玩家可以参加很多赛事。
这样的表格由 2 个主要列组成,一列用于引用事件,另一列用于引用玩家。主键通常应由这两列组成(因此玩家不能处于同一事件中)。
活动最初没有玩家,可以添加玩家。唯一的问题是一场赛事的玩家人数限制为 16 人,这意味着检查赛事中的玩家数量(演示 SELECT QUERY 包括获取计数)。
这是一个演示:-
/* just in case cleanup the environment */
DROP TABLE IF EXISTS event_participant;
DROP TABLE IF EXISTS player;
DROP TABLE IF EXISTS event;
CREATE TABLE IF NOT EXISTS player (player_id INTEGER PRIMARY KEY, player_name TEXT);
CREATE TABLE IF NOT EXISTS event (event_id INTEGER PRIMARY KEY, event_name TEXT /* etc */);
/* THE MAPPING TABLE */
CREATE TABLE IF NOT EXISTS event_participant (
event_id_map INTEGER REFERENCES event(event_id) ON DELETE CASCADE ON UPDATE CASCADE,
player_id_map INTEGER REFERENCES player(player_id) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY (event_id_map,player_id_map));
/* Note FOREIGN KEY (aka REFERENCES) is optional but suggested as it enforces referential integrity */
/* ON DELETE and ON UPDATE are optional but help to maintain referential integrity again suggested */
/* SUGGESTED INDEX ON the 2nd column, will liekly improve efficiency */
CREATE INDEX ix_event_participant_playermap ON event_participant (player_id_map);
/* ADD SOME DATA */
INSERT OR IGNORE INTO player (player_name) VALUES('Mary'),('Jane'),('Tom'),('Fred'),('Anne'),('Bert'),('Alan'),('Beth');
INSERT OR IGNORE INTO event (event_name) VALUES('E1'),('E2'),('E3'),('E4');
/* NOTHING SELECTED as there are currently no events with participants */
SELECT
event_name,
group_concat(player_name,'::') AS players,
count(*) AS num_of_players
FROM event
JOIN event_participant ON event.event_id = event_participant.event_id_map
JOIN player ON player_id_map = player_id
GROUP BY event_id
ORDER BY num_of_players
/* Add some participants to some events */
;
INSERT OR IGNORE INTO event_participant VALUES
(1,2),(1,4),(1,8) /* Jane, Fred and Beth in event 1*/,
(2,1),(2,3),(2,5),(2,7) /* Mary, Tom, Anne and Alan in event 2 */,
(3,1),(3,4),(3,8),(3,6),(3,2),(3,7),(3,5),(3,3) /* all in event 3 note any order */,
/*!!!!!!!!!! OOOPS ON PURPOSE !!!!!!!!!!*/ (1,2),(1,4),(1,8) /* Jane, Fred and Beth in event 1??????????*/
/* ooops because they already exist as participants in the event */
;
/* Now there are participants then the exact same query as above will yield results */
SELECT
event_name,
group_concat(player_name,'::') AS players,
count(*) AS num_of_players
FROM event
JOIN event_participant ON event.event_id = event_participant.event_id_map
JOIN player ON player_id_map = player_id
GROUP BY event_id
ORDER BY num_of_players
;
/* Cleanup demo environment */
DROP TABLE IF EXISTS event_participant;
DROP TABLE IF EXISTS player;
DROP TABLE IF EXISTS event;
运行时消息日志显示:-
/* just in case cleanup the environment */
DROP TABLE IF EXISTS event_participant
> OK
> Time: 0.323s
DROP TABLE IF EXISTS player
> OK
> Time: 0.041s
DROP TABLE IF EXISTS event
> OK
> Time: 0.024s
CREATE TABLE IF NOT EXISTS player (player_id INTEGER PRIMARY KEY, player_name TEXT)
> OK
> Time: 0.024s
CREATE TABLE IF NOT EXISTS event (event_id INTEGER PRIMARY KEY, event_name TEXT /* etc */)
> OK
> Time: 0.024s
/* THE MAPPING TABLE */
CREATE TABLE IF NOT EXISTS event_participant (
event_id_map INTEGER REFERENCES event(event_id) ON DELETE CASCADE ON UPDATE CASCADE,
player_id_map INTEGER REFERENCES player(player_id) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY (event_id_map,player_id_map))
> OK
> Time: 0.024s
/* Note FOREIGN KEY (aka REFERENCES) is optional but suggested as it enforces referential integrity */
/* ON DELETE and ON UPDATE are optional but help to maintain referential integrity again suggested */
/* SUGGESTED INDEX ON the 2nd column, will liekly improve efficiency */
CREATE INDEX ix_event_participant_playermap ON event_participant (player_id_map)
> OK
> Time: 0.024s
/* ADD SOME DATA */
INSERT OR IGNORE INTO player (player_name) VALUES('Mary'),('Jane'),('Tom'),('Fred'),('Anne'),('Bert'),('Alan'),('Beth')
> Affected rows: 8
> Time: 0.024s
INSERT OR IGNORE INTO event (event_name) VALUES('E1'),('E2'),('E3'),('E4')
> Affected rows: 4
> Time: 0.024s
/* NOTHING SELECTED as there are currently no events with participants */
SELECT
event_name,
group_concat(player_name,'::') AS players,
count(*) AS num_of_players
FROM event
JOIN event_participant ON event.event_id = event_participant.event_id_map
JOIN player ON player_id_map = player_id
GROUP BY event_id
ORDER BY num_of_players
/* Add some participants to some events */
> OK
> Time: 0s
INSERT OR IGNORE INTO event_participant VALUES
(1,2),(1,4),(1,8) /* Jane, Fred and Beth in event 1*/,
(2,1),(2,3),(2,5),(2,7) /* Mary, Tom, Anne and Alan in event 2 */,
(3,1),(3,4),(3,8),(3,6),(3,2),(3,7),(3,5),(3,3) /* all in event 3 note any order */,
/*!!!!!!!!!! OOOPS ON PURPOSE !!!!!!!!!!*/ (1,2),(1,4),(1,8) /* Jane, Fred and Beth in event 1??????????*/
/* ooops because they already exist as participants in the event */
> Affected rows: 15
> Time: 0.024s
/* Now there are participants then the exact same query as above will yield results */
SELECT
event_name,
group_concat(player_name,'::') AS players,
count(*) AS num_of_players
FROM event
JOIN event_participant ON event.event_id = event_participant.event_id_map
JOIN player ON player_id_map = player_id
GROUP BY event_id
ORDER BY num_of_players
> OK
> Time: 0s
/* Cleanup demo environment */
DROP TABLE IF EXISTS event_participant
> OK
> Time: 0.023s
DROP TABLE IF EXISTS player
> OK
> Time: 0.024s
DROP TABLE IF EXISTS event
> OK
> Time: 0.052s
第一个查询的结果(即什么都没有):-
第二次查询的结果(即当事件有参与者时):-
首先有 3 行,因为 event_id 上有
GROUP BY
子句。即具有相同 event_id 值的所有行都分组在一起作为单个输出行。
可以看出,行按事件中的参与者数量进行
ORDER
排列,数字较小的先输出(因为默认顺序是 ASC
结束)。
玩家数量正是如此,并利用 SQLite 聚合
count
函数。请参阅https://www.sqlite.org/lang_aggfunc.html#count
players 列再次使用 SQLite 聚合函数,这次是使用 group_concat
函数,它将传递给它的表达式中的值与组中每行的所有其他值连接起来。请参阅https://www.sqlite.org/lang_aggfunc.html#group_concat
JOIN
中的
SELECT
演示了指定列的两种形式。一种形式是用句点将表名与列名分隔开,从而完全限定列名。第二种形式只是列名。后一种形式更短、更容易但是 容易产生歧义。
id
,那么使用列名 id 将产生歧义并导致错误。