SQLite表结构设计:过继与兼祧宗法关系的数据模型实现
宗法关系在代码层面如何表达
族谱数字化中最棘手的问题,不是UI,不是渲染,而是如何在关系型数据库里忠实地表达中国传统宗法制度中那些“非标准”的关系。过继、兼祧、招婿入赘,这些在家族中真实存在了几百年的关系形态,在传统家谱软件里往往只能塞进备注字段,成为二等公民。
本质上,这是一个数据建模问题:如何用表结构承载宗法关系的复杂性,同时保持查询的简洁和一致。本文以SQLite为存储引擎,拆解我们在知烛宗族管理系统中实际落地的方案。
一、设计起点:一个人为什么需要两套父母
传统家谱软件的通用做法是给Person表加father_id和mother_id,指向各自的父母。这在核心家庭场景下完全够用,但面对“过继”时,问题出现了:
一个孩子同时有亲生父母和嗣父母,father_id该填哪一个?填亲生父亲,嗣父这一支的世系就断了;填嗣父,血脉联系就丢了。无论怎么选,都在丢失信息。
兼祧更麻烦。一个人同时在两房担任祧子,两房各为他娶妻,各房妻子所生的孩子归属各自的房系。father_id指向一个人,但“这个人的哪个妻子生的哪个孩子”需要额外维度来区分。传统模型完全应付不了。
解决思路是明确的:亲子关系不能只有一个字段,要区分“生物关系”和“宗法关系”。具体到表结构,我们这样定义:
sql
CREATE TABLE person ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, gender TEXT CHECK(gender IN ('M','F')), generation INTEGER NOT NULL, birth_year INTEGER, death_year INTEGER, -- 生物父母:记录血脉来源 bio_father_id INTEGER REFERENCES person(id), bio_mother_id INTEGER REFERENCES person(id), -- 宗法父母:决定世系归属 legal_father_id INTEGER REFERENCES person(id), legal_mother_id INTEGER REFERENCES person(id), -- 兼祧标记 multi_lineage INTEGER DEFAULT 0 CHECK(multi_lineage IN (0,1)), -- 其他字段省略 created_at TEXT DEFAULT (datetime('now')) ); CREATE INDEX idx_legal_father ON person(legal_father_id); CREATE INDEX idx_bio_father ON person(bio_father_id);
核心设计是两套字段的分离:
bio_father_id/bio_mother_id:保留血缘记录,永远不变,用于血脉溯源。legal_father_id/legal_mother_id:决定此人在宗法世系中的位置。世系图渲染、房系归属、字辈推算,全部走legal字段。
二、过继的双字段设计落地
当一个孩子张德厚过继给二叔时,数据写入如下:
sql
-- 张德厚的记录 UPDATE person SET bio_father_id = (SELECT id FROM person WHERE name='张父'), -- 亲生父亲 bio_mother_id = (SELECT id FROM person WHERE name='张母'), -- 亲生母亲 legal_father_id = (SELECT id FROM person WHERE name='张二叔'), -- 嗣父 legal_mother_id = (SELECT id FROM person WHERE name='张二婶') -- 嗣母 WHERE name = '张德厚';
世系图渲染时,系统只查询legal_father_id和legal_mother_id来构建树。张德厚会出现在张二叔的子嗣列表里,而不会再出现在亲生父亲的子嗣列表中。亲生父亲这一支的世系展示时,张德厚自动移除,因为他的legal_father已不再是亲生父亲。
但这不代表血脉信息的丢失。系统仍保留bio_father_id,当用户需要查看“血缘后代”时,可以切换到血脉追溯模式,此时查询走bio字段。两种视角共存,互不干扰。
这种设计让过继在数据层面不再是一种“备注里写明的特殊情况”,而是表结构的自然表达。
三、兼祧的约束机制
兼祧是宗法关系中最复杂的场景。一个人同时在两房继承香火,两房后代必须严格分离。用legal父母字段虽然可以标记祧子本人归属于谁,但无法约束“两房各娶妻生子”这个现实。
我们在Person表中增加了multi_lineage标记,一旦某个人被设为兼祧,系统强制要求满足以下约束:
祧子本人的
legal_father_id指向兼祧的两房中主祭的那一方(通常是大房),但multi_lineage=1标记意味着此人存在多房归属,世系图会在两房同时展示他。祧子的配偶通过独立的婚姻关系表(
marriage表)记录,每条婚姻关联一个lineage_branch字段指明所属房系。最关键的约束:兼祧人员必须拥有至少两名子嗣,且子嗣的
legal_mother_id分别指向不同房系的配偶,确保每房都有独立的继承人。如果录入的子嗣数量不足两名或子嗣的房系归属未覆盖所有兼祧房系,数据校验直接报错,不予通过。
婚姻和子嗣的约束通过下面的表结构支撑:
sql
CREATE TABLE marriage ( id INTEGER PRIMARY KEY, person_id INTEGER NOT NULL REFERENCES person(id), spouse_id INTEGER NOT NULL REFERENCES person(id), lineage_branch TEXT, -- 房系标识,如 '大房'、'伯父房' UNIQUE(person_id, spouse_id, lineage_branch) ); CREATE TABLE child ( id INTEGER PRIMARY KEY, child_id INTEGER NOT NULL REFERENCES person(id), parent_id INTEGER NOT NULL REFERENCES person(id), legal_mother_id INTEGER REFERENCES person(id), -- 指定生母 lineage_branch TEXT, -- 继承自母亲的房系 FOREIGN KEY (child_id, legal_mother_id) REFERENCES person(id, id) );
当录入兼祧子嗣时,系统会进行实时校验:
查询该兼祧人员的所有
marriage记录,获取其所有配偶及其房系。确认子嗣的
legal_mother_id所对应的lineage_branch覆盖了所有必须继承的房系。若子嗣数量不足或房系有缺失,前端直接阻断保存,并给出明确提示:“兼祧人员必须为每房录入至少一名子嗣”。
这个设计将宗法规则从“人为记忆”变成了“数据库约束”,杜绝了数据录入时的逻辑错误。
四、纯本地架构与数据导出
族谱数据是家族的私产,不应受制于任何平台。知烛采用纯本地架构,所有数据存储在单个SQLite文件中,文件位于用户指定的本地目录。不需要注册账号,不需要联网,不需要授权任何第三方服务。SQLite本身就是零配置、自包含的嵌入式数据库,适合这种长期归档的场景。
换电脑或备份时,直接拷贝那个.db文件即可,里面包含了所有人员、关系、字辈、媒体资源路径。没有数据库连接串,没有云同步,数据主权完全在用户手里。
导出方面,系统内置了三种格式的一键导出:
SQLite原格式:直接复制数据库文件,作为完整备份。
JSON:将全部Person、Relation、字辈表等序列化为结构化JSON,便于程序化处理或与其他系统对接。
GEDCOM 5.5.1:族谱数据交换的国际标准格式。导出时,legal关系映射到GEDCOM的
FAM/CHIL结构,bio关系及兼祧特殊信息存储在NOTE字段的自定义标签中,最大限度保留数据完整性。
所有导出操作均在本地完成,无需联系任何人,无需等待后台处理。点按钮 → 选路径 → 保存,数秒内完成。
五、总结
传统族谱软件面对过继、兼祧时的无力,本质上是数据模型对宗法制度的简化所致。我们的方案没有发明新算法,只是把“生物父母”和“宗法父母”拆开,把“多房继承”的约束写进表结构和校验逻辑。当数据结构忠实地反映了现实世界的复杂性时,上层功能就变得顺理成章——世系图自然正确,房系归属自然清晰,数据校验自然严密。
SQLite作为存储引擎,在单表百万级数据下表现稳定,配合合理的索引和递归CTE,族谱查询完全可以在普通PC上流畅运行。而纯本地的架构选择,确保了用户对自己数据的绝对控制。技术方案的选择,最终回归到对修谱人真正需求的尊重:把数据管好,让关系理清,让数据永远属于自己。
