跳转到内容

表键、枚举与数据关联

在配表系统中,主键、唯一键、枚举和入口都是访问表中数据行的不同方式,而外键则用于建立表之间的关联关系。

核心概念

  • 主键、唯一键、枚举、入口:访问表中数据行的不同方式
  • 外键:建立表之间的关联关系,形成数据网络

每个表(table)必须定义主键,逻辑代码通过主键来访问表中的具体数据行。

使用单个字段作为主键,这是最常见的主键形式。

table task[taskid] {
taskid:int;
text:text;
}

特点

  • 绝大多数表都使用简单主键
  • 支持的类型:intlongstr
  • 简单高效,易于理解和使用

使用自定义结构体作为主键。

struct LevelRank {
level:int;
rank:int;
}
table jewelryrandom[lvlRank] {
lvlRank:LevelRank;
attack:int;
}

特点

  • lvlRank 结构体作为主键
  • 重要限制:在生成 Lua 代码时,复合结构主键是基于引用(内存地址)而非内容比较的,因此如果需要生成 Lua 代码,不建议使用这种形式

使用多个字段组合作为主键。

table jewelryrandom[level,rank] {
level:int;
rank:int;
attack:int;
}

特点

  • levelrank 两个字段共同构成主键
  • Lua 实现细节:当两个字段都是数字类型时,底层会使用 k + j * 100000000 作为键值
  • 约束条件:第一个字段值必须小于 1 亿,第二个字段值必须小于 1 万

当逻辑代码需要通过多种不同方式访问同一行数据时,可以选择一种方式作为主键,其他方式作为唯一键。

唯一键定义在字段定义之前,使用 [] 包围。

table task[taskid] {
[nexttask];
taskid:int; // 任务id
text:text;
nexttask:int;
rewardItems:list<RewardItem> (block=1); // 物品奖励
}

说明

  • 在表定义中,以 [ 开头的行表示定义唯一键
  • 上面的例子中,nexttask 被定义为唯一键
  • 唯一键的使用场景相对较少,主要用于需要多种访问方式的特殊情况

注意:本节介绍的是通过 Table 的 enum 属性定义的枚举。 如果需要在 schema 文件中直接定义编译时常量枚举(只有 name 和 comment), 请参考 结构定义 - 枚举

为了避免在代码中出现大量的”魔数”(magic numbers),配表系统提供了枚举和入口机制, 直接在配置表中定义程序使用的英文名称,这些名称会被生成到代码中。

枚举用于定义一组固定的、不重复的英文名称。

table itemtype[id] (enum='type'){
id:int;
type:str;
}

特点

  • 使用 enum='type' 声明枚举
  • type 列中的所有值必须是唯一的英文名称
  • 不能为空值
  • 由程序代码填写和维护

入口用于标识表中的特定行,通常用于配置系统的入口点。

table scene[id] (entry='entry') {
id:int;
entry:str;
info:text;
}

特点

  • 使用 entry='entry' 声明入口
  • entry 列中大部分行为空值
  • 只有少数几行包含英文名称
  • 由程序代码填写和维护

外键用于建立表之间的关联关系,支持多种引用方式。

直接在字段定义时声明外键。

struct RewardItem {
chance:int; // 掉落概率
itemids:list<int> ->item.item; // 掉落物品
range:Range; // 数量范围
}

说明->item.item 表示 list<int> 中的每个整数都引用 item 表的主键

struct AA {
taskid:int ->task[nexttask];
}

说明->task[nexttask] 表示引用 task 表的 nexttask 唯一键

索引到任意字段(生成列表引用)
Section titled “索引到任意字段(生成列表引用)”
table drop[dropid] {
dropid:int =>dropItem[dropid];
name:text;
}

说明

  • =>dropItem[dropid] 使用 => 表示指向任意字段
  • 生成的程序代码会返回 listRefDropId,这是一个列表

在字段定义完成后,单独声明外键关系。

table monster[id] {
id:int;
lootId:int; // loot
lootItemId:int; // item
->Loot:[lootId,lootItemId] ->lootitem;
->AllLoot:[lootId] ->loot[id];
}

语法解析

  • ->Loot:[lootId,lootItemId] ->lootitem
    • ->:外键定义标识
    • Loot:外键名称
    • [lootId,lootItemId]:本地键由两个字段组成
    • ->lootitem:引用目标表
table drop[dropid] {
dropid:int;
name:text;
->AllDrops:[dropid] =>dropItem[id];
}

说明

  • 会生成 listRefAllDrops
  • 与字段级声明的主要区别:外键名称后缀 AllDrops 可以自定义

外键不仅支持单个字段,还支持容器类型中的每个元素。

列表中每个元素都可以作为外键引用。

示例:

struct RewardItem {
// 掉落物品列表,每个物品ID都指向item表的item字段
itemids:list<int> ->item.item (fix=2);
}

说明:

  • list<int> 中的每个整数都作为外键引用 item 表的 item 字段
  • 系统会验证列表中的每个引用是否有效

map 中每个 value 都可以作为外键引用(不支持 key 的外键)。

示例:

struct MonsterDrops {
// 怪物掉落表,key是掉落位置ID,value是物品ID
drops:map<int,int> =>item.item;
}

说明:

  • map<int,int> 中每个 value 都作为外键引用 item 表的 item 字段
  • key(第一个 int)不支持外键

系统会自动验证容器中的每个外键引用:

  • list<T>: 验证列表中每个 T 类型的引用
  • map<K,V>: 验证每个 value 的引用(key 不支持外键)
符号指向目标生成类型描述
->唯一键(主键或唯一键)refXxx生成单个引用
=>任意字段listRefXxx生成列表引用

可空外键允许外键引用不存在的情况,为数据关联提供更大的灵活性。

table task[taskid] (entry='entry') {
taskid:int ->task.taskextraexp (nullable); // 任务id
entry:str;
text:text;
nexttask:int ->task (nullable);
}
  1. 代码生成

    • 在配置了外键引用的字段上添加 (nullable) 修饰符
    • 生成的代码会使用 nullableRefXXX 类型
  2. Excel 约束规则

    • 当 Excel 单元格为空时,允许外键不存在
    • 当 Excel 单元格有内容时,必须能找到对应的外键
  3. 例外情况

    • 主键/唯一键字段:如果字段是当前表主键或唯一键的一部分(如 task.taskid),不受上述约束限制
    • 数值类型为 0:如果字段类型是数值(intlongfloat)且内容为 0(如 task.nexttask),也不受约束限制

  1. 主键选择

    • 优先使用简单主键(intlongstr
    • 避免在需要生成 Lua 代码的项目中使用复合结构主键
    • 多字段主键在 Lua 中有数值范围限制,注意设计时考虑这些约束
  2. 外键使用

    • 明确区分 ->(单个引用)和 =>(列表引用)的使用场景
    • 合理使用 nullable 修饰符提高数据灵活性
  3. 枚举与入口

    • 使用它们消除代码中的”魔数”