- 苏格团队
- 作者:YaoLang
角色:产品汪小T,程序员小C
小T:小C,有活干了。我们想做个在线题库系统,老师可以搜索题目来备课。
小C看着简易的需求稿,心想,我一分钟几百万上下,竟然找我做这么简单的需求。建个题目表不就完事了。
小C:题目数据从哪里来,包含什么属性?
小T:我们第一期题目数据是从A公司那里买过来的,题目包含正文,选项,答案,题型,难度。
小C:嗯,也就是我要建一张question表,包括这五个属性。那题型和难度有哪些呢?
小T:题型有五种,单选,多选,判断,填空,解答。难度有3种,简单,一般,困难。用户可以根据题型或者难度来筛选。 小C拿着笔画了一下:OK,表设计出来了
t_question表
字段 | 属性 | 描述 |
---|---|---|
uid | char(32) | 唯一标示 |
body | TEXT | 正文 |
options | JSON | 题目选项 |
answer | TEXT | 答案 |
type | int(11) | 题目类型,1单选、2多选、3判断、4填空、5解答 |
difficult | int(11) | 题目难度,1简单、2一般、3困难 |
小C:搜索根据body, options模糊匹配,然后筛选让前端传入type = 1或者difficult = 3 进行题型和难度的筛选
小T:哇,果然靠谱,那我们上线吧。
小T:小C,我们的题库系统发到市场上有很多用户反馈说题目量不太够,最近我们找到了B公司合作,希望能把B公司的题库也整合到我们的系统,数据的结构和A公司的很相似,你看下要弄多久。
小C心想,敢情这产品汪不生产题目,只是题目的搬运工啊。
小C:导一下数据就完事了。把接口文档发我对接一下就好了。
小T:好,待会文档发你。
拿到B公司的题目接口,题目整体结构不变,可是题型和难度的分类都比A公司多一点。题型有单选题,多选题,分析题,一般分解题,APP分解题。题型有简单,一般,困难,极难。
小C心想:我去,我要以哪个公司的题目分类作为标准。于是找到了小T
小C:数据如果做整合的话,可不可以将B公司的分析题,一般分解题,APP分解题变成我们的解答题,极难不要,都变成困难。因为现在没有定义一个标准,我不太好整合数据。
小T:那好吧,先按你说的去做。
自那以后,小T又找了两家公司合作,让小C整合数据。并且小T认为其中一家公司的方法(配方法,消元法,排除法)和能力(推理能力,分析能力,计算能力)数据也是很重要的维度,希望能做补充。
小C崩溃了,我一分钟几百万上下,竟然找我来导数据。每次还要去看数据的分类值应该怎么做整合。还经常要加字段。现在因为要接入那两家公司的题库数据,要将表修改成
t_question表
字段 | 属性 | 描述 |
---|---|---|
uid | char(32) | 唯一标示 |
body | TEXT | 正文 |
options | JSON | 题目选项 |
answer | TEXT | 答案 |
type | int(11) | 题目类型,1单选、2多选、3判断、4填空、5解答 |
difficult | int(11) | 题目难度,1简单、2一般、3困难 |
ability | int(11) | 能力属性,1推理能力,2分析能力 。。。。 |
method | int(11) | 方法属性,1配方法,2消元法,3排除法。。。 |
- 现在题库有300W数据,未来还会不断地增加,如果频繁改表的话,线上会直接锁表
- 如果每个分类我还要去看哪些值应该映射为我们定义的哪些值,后面肯定会吃不消的,因为我们没有一套统一的标准。。。
小C意识到自己跳到了一个大坑中,原来这东西并没有一开始想的这么简单。
经过仔细的思考,小C得出结论:
- 行业内根本就没有一套标准,必须针对变化点做扩展
- 不同的公司题目的维度数据不一样(如某公司多了能力与方法两个维度)
- 不同的公司同一维度的数据值不一样(如B公司的题型和A公司不一样)
专门针对标签建立表
- 一个标签分类下有多个标签值
- 一道习题有多个标签属性
t_tag表
字段 | 属性 | 描述 |
---|---|---|
uid | char(32) | 唯一标示 |
tag_name | varchar(64) | 标签分类 |
tag_key | varchar(64) | 标签key |
t_tag_value表
字段 | 属性 | 描述 |
---|---|---|
uid | char(32) | 唯一标示 |
tag_id | char(32) | 标签分类id |
value_name | varchar(64) | 标签值名 |
value_code | varchar(64) | 标签值编码 |
t_question_tag表
字段 | 属性 | 描述 |
---|---|---|
id | char(32) | 唯一标示 |
tag_value_id | char(32) | 标签值id |
question_id | char(32) | 题目id |
当一次查询时,先将查询到的题目id到t_question_tag表中查出tag_value_id集合。 标签进行GROUP BY tag_id聚合后得到以下json
[{分类名:难度, 分类key: difficult,值列表:[{ 值名:简单, 值编码: 1},{ 值名:一般, 值编码: 2},{ 值名:困难, 值编码: 3}]}]复制代码
通过返回标签聚合,可以在前端展示
难度:简单,一般,困难题型:选择题,判断题,作文,完形填空。。。方法:配方法,消元法。。。。复制代码
筛选时,传入标签key (difficult),标签value (1)得到tag_value_id 然后可以筛选出跟标签绑定的题目。
拓展总结:
- 当某张数据表未来可能数据量会很庞大的,不能因为需求变更频繁地增加表的字段,考虑增加中间表的方式来进行拓展
- 一般实体数据信息不确定的时候,也可以考虑使用NOSQL检索,如设计成以下的文档,就可以利用NOSQL的数组查询功能检索标签对应的实体。
{ "uid":"", "description":"", "tag_ids":["标签1","标签2","标签3"]}复制代码
- 该设计也能应用于电商中,如商品的分类筛选
颜色:黄色,蓝色,绿色尺码:M,L,XL,XXL风格:休闲,商务复制代码
- 标签只适合用于有限个数的分类,如文件大小,价格这些不固定的属性是不能做成标签的。
- 标签字段是不会有排序需求的,如按照某个分类进行order by。因为标签定位是有限分类,排序没有任何的意义,标签只能用来做筛选。如果一定要排序,建议另外计算标签和其他属性计算出一个分数字段。
问题点:
- 为什么标签值要分成标签uid和标签code呢
标签code属于多变的,可以自定义,如让简单定义为1,困难定义为2。如果直接让题目绑定1,很可能和其他的分类冲突。如题型的1为单选题。
- 为什么要前端传入key和value不直接传标签uid
会有一些场景需要业务自定义标签,如省市区,用户使用时更想用101100这种全国通用的地区编码来做标签筛选。
做到这里,小C稍微松了一口气,之后你来一个公司的数据,如果有新的分类,就可以加标签类别再加标签值。如果同一分类下来新的值,先看一下分类的中文名是不是对应的上,对应不上新建标签,然后让产品去做标签的整合或者就当成两个不同的标签来算。再也不怕整合数据了。