一对多关系,使用复合键

时间:2018-07-21 21:10:54

标签: sql database sqlite database-design

在我的游戏中,原型是相关特征,攻击类型,损害类型和资源类型的集合。每个数据对于每个数据都是唯一的 原型。例如,法师原型可能如下所示:

原型:法师
攻击:目标区域效果
损坏:电击
资源:法力
trait_defense:意志力
trait_offense:情报

这是SQLite语法中的原型表:

create table archetype
(
archetype_id varchar(16) not null,
attack_id varchar(16) not null,
damage_id varchar(16) not null,
resource_type_id varchar(16) not null,
trait_defense_id varchar(16) not null,
trait_offense_id varchar(16) not null,
archetype_description varchar(128),
constraint pk_archetype primary key (archetype_id),
constraint uk_archetype unique (attack_id, damage_id,
                                resource_type_id,
                                trait_defense_id, 
                                trait_offense_id)
);

主键应该是完整的组合键,但是我不想通过 除非有必要,否则所有其他表的数据。例如,有 与每个原型相关的手工技能,无需了解任何知识 其他原型数据。

效果是可以应用于朋友或敌人的战斗结果。效果具有应用程序类型(即时,加班),类型(buff,debuff,伤害,治疗等)和描述该效果应用于哪个状态的详细信息。它还具有大多数原型数据,以使每个效果都独一无二。还包括用于进度和技能检查的相关特征。例如,效果可能如下:

应用:即时
类型:危害
详细信息:健康
原型:法师
Attack_id:目标区域效果
damage_id:电击
资源:法力
trait_id:智能

这是SQLite语法中的效果表:

create table effect
(
effect_apply_id varchar(16) not null,
effect_type_id varchar(16) not null,
effect_detail_id varchar(16) not null,
archetype_id varchar(16) not null,
attack_id varchar(16) not null,
damage_id varchar(16) not null,
resource_type_id varchar(16) not null,
trait_id varchar(16),
constraint pk_effect primary key(archetype_id, effect_type_id, 
                                 effect_detail_id, effect_apply_id, 
                                 attack_id, damage_id, resource_type_id),

constraint fk_effect_archetype_id foreign key(archetype_id, attack_id, 
                                              damage_id, resource_type_id) 
                        references archetype (archetype_id, attack_id,
                                              damage_id, resource_type_id)
);

异能是一个可以容纳多种效果的容器。没有限制 它可以保持的各种效果,例如同时具有法师和战士效应 相同的能力,甚至具有两个相同的效果,都可以。每种效果 能力中将具有原型数据和效果数据。

再次。

SQLite语法中的能力表:

create table ability
(
ability_id varchar(64),
ability_description varchar(128),
constraint pk_ability primary key (ability_id)
);

create table ability_effect
(
ability_effect_id integer primary key autoincrement,
ability_id varchar(64) not null,
archetype_id varchar(16) not null,
effect_type_id varchar(16) not null,
effect_detail_id varchar(16) not null,
effect_apply_id varchar(16) not null,
attack_id varchar(16) not null,
damage_id varchar(16) not null,
resource_type_id varchar(16) not null,
trait_id varchar(16),
constraint fk_ability_effect_ability_id foreign key (ability_id) 
                                 references ability (ability_id),
constraint fk_ability_effect_effect_id foreign key  (archetype_id, 
                                                     effect_type_id,
                                                     effect_detail_id, 
                                                     effect_apply_id) 
                                 references effect  (archetype_id, 
                                                     effect_type_id, 
                                                     effect_detail_id, 
                                                     effect_apply_id)
);

这基本上是一对多的关系,所以我需要一个技术 键,以便在capability_effect表中具有重复的效果。

问题:

1)有没有更好的方法来设计这些表,以避免重复 这三个表中的数据?

2)这些表格应该进一步细分吗?

3)执行多个表查找以收集所有数据是否更好?例如,仅绕过archetype_id并在必要时对数据进行查找(这很常见)。

更新:

我实际上有用于攻击,破坏等的父表。我删除了这些表 表和样本中的相关索引以使问题更清晰, 简洁,并专注于我的重复数据问题。

我试图避免每个表都具有ID和名称,因为这两个表都是候选键,因此都浪费了空间。我试图将SQLite数据库保持尽可能小。 (因此,许多“ varchar(16)” 声明,我现在知道SQLite会忽略它。)在SQLite中似乎同时具有这两个声明 值是不可避免的,除非在使用 表创建期间的WITHOUT ROWID选项。因此,我将数据库重写为 通过rowid实现使用ID和名称。

感谢您的投入!

2 个答案:

答案 0 :(得分:0)

我可能会避免使用那些复合主键。 并使用带有自动增量的更常用的整数。

然后在需要的地方添加唯一或非唯一的复合索引。

尽管即时通讯在某些情况下使用短CHAR或VARCHAR作为主键并不总是一个坏主意。通常在易于理解的缩写时可以使用。

一个例子。假设您有一个国家/地区的参考表。在2个字符的CountryCode上具有主键。然后,使用该CountryCode上的外键查询表时,对于人类而言,比起整数,更容易理解“ US”。即使不加入国家/地区,您也可能会知道所引用的国家/地区。

这是您的表,布局略有不同。

create table archetype
(
archetype_id integer primary key autoincrement,
attack_id varchar(16) not null,
damage_id varchar(16) not null,
resource_type_id varchar(16) not null,
trait_defense_id varchar(16) not null,
trait_offense_id varchar(16) not null,
archetype_description varchar(128),
constraint uk_archetype unique (attack_id, damage_id,
                                resource_type_id,
                                trait_defense_id, 
                                trait_offense_id)
);

create table effect
(
effect_id integer primary key autoincrement,
archetype_id integer not null, -- FK archetype
effect_apply_id varchar(16) not null,
effect_type_id varchar(16) not null,
effect_detail_id varchar(16) not null,
attack_id varchar(16) not null,
damage_id varchar(16) not null,
resource_type_id varchar(16) not null,
trait_id varchar(16),
constraint pk_effect unique(archetype_id, effect_type_id, 
                            effect_detail_id, effect_apply_id, 
                            attack_id, damage_id, resource_type_id),

constraint fk_effect_archetype_id foreign key(archetype_id) 
                        references archetype (archetype_id)
);

create table ability
(
ability_id integer primary key autoincrement,
ability_description varchar(128)
);

create table ability_effect
(
ability_effect_id integer primary key autoincrement,
ability_id integer not null, -- FK ability
effect_id integer not null, -- FK effect
attack_id varchar(16) not null,
damage_id varchar(16) not null,
resource_type_id varchar(16) not null,
trait_id varchar(16),
constraint fk_ability_effect_ability_id foreign key (ability_id) 
                                 references ability (ability_id),
constraint fk_ability_effect_effect_id foreign key  (effect_id) 
                                 references effect  (effect_id)
);

答案 1 :(得分:0)

  

1)有没有更好的方法来设计这些表,以避免   这三个表中的数据重复吗?

还有

  

2)这些表格应该进一步细分吗?

它看起来会如此。

看来法师和战士都是独特的原型。 (例如,基于,法师原型可能如下:)。

因此,为什么不将archtype_id用作主键,然后从表中引用攻击类型,破坏等。即拥有攻击表和伤害表。

例如,您可以使用类似的东西(为演示而简化):-

DROP TABLE IF EXISTS archtype;
DROP TABLE IF EXISTS attack;
DROP TABLE IF EXISTS damage;
CREATE TABLE IF NOT EXISTS attack (attack_id INTEGER PRIMARY KEY, attack_name TEXT, a_more_columns TEXT);
INSERT INTO attack (attack_name, a_more_columns) VALUES 
    ('Targetted Affect','ta blah'), -- id 1
    ('AOE','aoe blah'), -- id 2
    ('Bounce Effect','bounce blah') -- id 3
    ;

CREATE TABLE IF NOT EXISTS damage (damage_id INTEGER PRIMARY KEY, damage_name TEXT, d_more_columns TEXT);
INSERT INTO damage (damage_name,d_more_columns) VALUES
  ('Shock','shock blah'), -- id 1
    ('Freeze','freeze blah'), -- id 2
    ('Fire','fire blah'), -- id 3
    ('Hit','hit blah')
    ;
CREATE TABLE IF NOT EXISTS archtype (id INTEGER PRIMARY KEY, archtype_name TEXT, attack_id_ref INTEGER, damage_id_ref INTEGER, at_more_columns TEXT);
INSERT INTO archtype (archtype_name,attack_id_ref,damage_id_ref,at_more_columns) VALUES
    ('Mage',1,1,'Mage blah'),
    ('Warrior',3,4,'Warrior Blah'),
    ('Dragon',2,3,'Dragon blah'),
    ('Iceman',2,2,'Iceman blah')
    ;
    SELECT archtype_name, damage_name, attack_name FROM archtype JOIN damage ON damage_id_ref = damage_id JOIN attack ON attack_id_ref = attack_id;
  • 请注意, rowid 的别名已被用作id的名称,而不是名称,因为它们通常是最有效的。
    • rowid表的数据存储为B-Tree结构,其中使用rowid值作为键,每个表行包含一个条目。这意味着按rowid检索或排序记录很快。搜索具有特定rowid的记录,或搜索具有指定范围内的rowid的所有记录的速度大约是通过指定任何其他PRIMARY KEY或索引值进行的类似搜索的两倍。 {{3 }}
    • 通过指定?? INTEGER PRIMARY KEY列为所有行生成 rowid (除非指定了WITHOUT ROWID)?是 rowid 的别名。
    • 提防使用AUTOINCREMENT,这与其他RDMS会使用AUTOINCREMENT为行自动生成唯一ID有所不同。默认情况下,SQLite创建一个唯一的ID( rowid )。 AUTOINCREMENT关键字添加了一个约束,以确保生成的ID大于现有的ID。为此,需要附加表 sqlite_sequence ,该表必须维护和查询,因此具有开销。 AUTOINCREMENT关键字强加了额外的CPU,内存,磁盘空间和磁盘I / O开销,如果没有严格要求,则应避免使用。通常不需要。 SQL As Understood By SQLite - CREATE TABLE- ROWIDs and the INTEGER PRIMARY KEY

最后的查询将导致:-

SQLite Autoincrement


现在说您希望类型对每种类型具有多种攻击和破坏,然后可以通过引入引用/映射/链接表(使用相同的名称,只是使用不同的名称)来使用多对多关系轻松地对上述内容进行修改。这样的表将具有两列(有时其他列用于特定于不同引用/地图/链接的数据),一列用于父(原型)引用/地图/链接,另一列用于子对象(攻击/损坏)引用/映射/链接。

例如可以添加以下内容:-

DROP TABLE IF EXISTS archtype_attack_reference;
CREATE TABLE IF NOT EXISTS archtype_attack_reference 
    (aar_archtype_id INTEGER NOT NULL, aar_attack_id INTEGER NOT NULL, 
    PRIMARY KEY(aar_archtype_id,aar_attack_id)) 
    WITHOUT ROWID; 

DROP TABLE IF EXISTS archtype_damage_reference;
CREATE TABLE IF NOT EXISTS archtype_damage_reference 
    (adr_archtype_id INTEGER NOT NULL, adr_damage_id INTEGER NOT NULL, 
    PRIMARY KEY(adr_archtype_id,adr_damage_id)) 
    WITHOUT ROWID
    ;

INSERT INTO archtype_attack_reference VALUES
    (1,1), -- Mage has attack Targetted
    (1,3), -- Mage has attack Bounce
    (3,2), -- Dragon has attack AOE
    (2,1), -- Warrior has attack targetted
    (2,2), -- Warrior has attack AOE
    (4,2), -- Iceman has attack AOE
    (4,3) -- Icemane has attack Bounce
;

INSERT INTO archtype_damage_reference VALUES
  (1,1),(1,3), -- Mage can damage with Shock and Freeze
    (2,4), -- Warrior can damage with Hit
    (3,3),(3,4), -- Dragon can damage with Fire and Hit
  (4,2),(4,4) -- Iceman can damage with Freeze and Hit
;

SELECT archtype_name, attack_name,damage_name FROM archtype 
    JOIN archtype_attack_reference ON archtype_id = aar_archtype_id
    JOIN archtype_damage_reference ON archtype_id = adr_archtype_id
    JOIN attack ON aar_attack_id = attack_id
    JOIN damage ON adr_damage_id = damage_id
;

查询结果为:-

enter image description here

稍作更改,上述查询甚至可以用于执行随机攻击,例如:-

SELECT archtype_name, attack_name,damage_name FROM archtype 
    JOIN archtype_attack_reference ON archtype_id = aar_archtype_id
    JOIN archtype_damage_reference ON archtype_id = adr_archtype_id
    JOIN attack ON aar_attack_id = attack_id
    JOIN damage ON adr_damage_id = damage_id
    ORDER BY random() LIMIT 1 -- ADDED THIS LINE
;

您可以获得:-

enter image description here

下次您可能会得到:-

enter image description here

  

3)执行多个表查找以收集所有   数据?例如,仅绕过archetype_id并执行   必要时查找数据(通常是这样)。

这很难说。您最初可能会认为一次收集所有数据并将其作为对象保存在内存中。但是,有时底层数据由于已被缓存而很可能已经在内存中。也许最好利用它们各自的一部分。因此,我相信答案是,您将需要测试各种情况。