转载自mohang
最近在做dmp,负责设计一套标签管理系统。在对现有标签进行整理的过程中,整理出了这套东西。
对于标签(tag),很难列出一个公认的定义,指明这个概念的种差与属概念。所以为了把握这个概念,就需要采取定义另一种办法:分类与枚举。
我们要解决的第一个问题是,有哪些类型的标签,如何对标签进行分类。首先不妨对“如何分类”本身进行分类,我们可以从“形式”与“内容”上分辨考察标签的分类。
标签的形式是标签分类最主要的依据。
首先,我们可以列出一些常见或者不常见的的“标签”样例:
通过观察我们可以发现一些东西
通常意义上的标签是单值标签,或称原子标签。其取值是一个独立的值。如<code>女</code>,<code>23</code>,<code>90.6</code>。
一部分标签是多值标签,多个原子标签整个作为一个整体形成一个标签,例如微博上人们用来描述自己的关键词列表:例如:<code>['90后','处女座','么么哒']</code>。
还有一部分标签,为每个原子标签额外添加了一个权值参数。这也是很常见的需求,例如用来表示用户对逻辑上相关联的一组取值的频次频率值,预测概率等。
最后,为多值标签的每个原子标签添加关联权值就得到了权值标签,而单个的原子标签带有权值也是常见的事情,例如给出一个预测年龄及其置信度。如果这种单kv结构也用权值标签来表示,就会显得累赘与奇怪。因此适合单独作为一类。
从标签的组织形式上看,标签可以分为四类:<code>单值标签</code>,<code>单权标签</code>,<code>多值标签</code>,<code>多权标签</code>。
于是,我们就获得了两个基本正交的维度:<code>是否为多值标签</code>, <code>是否带有权值</code>。
这四种标签结构类型,除特殊的<code>单权标签</code>后,恰好与json的三种primitive type:<code>atomic, array, object</code>相对应。
我们知道,计算机的物理实现本质上只提供了<code>整形</code>与<code>浮点</code>两种原子数据类型。将指针,单字符,布尔,浮点数都归入数值类型,将极其常用的字符数组看做字符串类型,那么逻辑上其实我们只有两种原子数据类型:即<code>数值类型(numeric)</code>与<code>字符串(string)</code>。
我们当然希望所有原子标签的类型只有<code>数值</code>与<code>字符串</code>两种简单的分类。但出于一些现实的约束(比如就是有离散标签与连续值标签的区分,比如odps就区分bigint和double),我们还是会将<code>数值</code>细分为<code>整形</code>与<code>浮点</code>,所以,用于原子标签的类型就变为三种:<code>整型、浮点,字符串</code>。
对于权值标签,除了原子标签的类型,其权值也应当有一个合适的类型。强制其类型为<code>数值</code>是一个合理且合适的约束,可以通过强制其物理类型为double轻易实现。
标签的<code>原子类型维度</code>和标签的<code>结构类型维度</code>并不完全正交,这是出于一些技术上的约束。例如python中的dict,<code>数值类型</code>是可以作为key的,然而json规范中,只有<code>字符串</code>可以作为object的key。<code>整形</code>可以通过序列化安全地转换为字符串间接地作为key。但<code>浮点</code>的不精密则会带来诸多麻烦,所以<code>多值标签</code>的原子类型维度不可以取值<code>浮点</code>。
从标签的原子类型上看:标签可以分为<code>整形</code>,<code>浮点</code>,<code>字符串</code>。
在2.1.2中,我们对标签的原子类型进行了分类,但出于生产实践的考虑,我们必须考虑另一种非常常见的情况,即<code>枚举标签</code>。枚举标签通常用一个整形表示标签取值,同时提供一个从整型值到字符串的字典,用于解释这个整型值。
例如:
再比如:
所以我们也可以发现,其实布尔类型就是一种特殊的枚举标签,有0:'false',1:'true'两个选项(先不考虑nullable)。完全可以自然地纳入枚举标签的体系中。
所以,对于整形原子类型的解释方法,又可以成为一个标签分类的维度。即<code>是否为枚举标签</code>,由于这个维度和2.1.3中原子标签类型的维度高度相关(当原子标签类型为整形时本维度才有效),所以这两个维度应当合二为一。
faq:
枚举与整形的区别在哪里,即什么时候用整形什么时候用枚举?
很简单,取值可以穷尽且数目合理的时候用枚举。例如,城市代码就是一个很合适的枚举标签。但一个人的头发数目虽然肯定是一个整数,但就不适合作为枚举标签了。
字符标签与枚举标签的区别?
枚举标签与其他标签的区别在哪里?
枚举标签的特点就是需要维护一张标签字典表,用于维护从枚举entry的id到item的映射关系。
枚举标签可以具有层次关系。例如"城市枚举标签"就可以有上层标签:"省份枚举标签"。
多个枚举标签的字典可以放在同一张表中。
为什么不采用字符串作为枚举的id?
即 : 原子标签的取值类型与解释方式。
该维度的取值有4种:<code>枚举</code>,<code>整形</code>,<code>浮点</code>,<code>字符串</code>
由上述可知,从标签的形式上,我们获得了两个大的,基本正交的分类维度:
组织形式:{ <code>单值标签</code>,<code>单权标签</code>,<code>多值标签</code>,<code>多权标签</code> }
原子类型:{ <code>枚举标签</code> , <code>整形标签</code>, <code>文本标签</code>, <code>浮点标签</code> , }
除了<code>浮点多权标签</code>不是合理的组合之外,其他共计<code>4 x 4 -1 = 15</code>种组合。
即,标签从形式上可以分为15个类型。恰好可以用4个bit表示。
因为最常见的标签都是单值标签,所以将标签结构类型的位域放在标签原子类型的位域之前是合理的设计。
结构
标记
说明
单值标签
0x00
取值为单一原子类型相应值
单权标签
0x01
取值为单一原子类型及其权值,可以以分隔符值,数组或字典等形式实现。
多值标签
0x10
取值为同种原子类型组成的列表
权值标签
取值为同种原子类型组成的字典,key只能为string或string(bigint)
枚举标签
实际为bigint类型,默认类型,需要对照类型字典解读
整形标签
整形数值原子标签
文本标签
字符串原子标签
浮点标签
0x11
浮点数数值原子标签
类型id
英文代号
名称
结构id
结构名
原子id
原子名称
存储
atom-enum
单值枚举
单值
枚举
int
1
atom-int
单值整形
整形
2
atom-text
单值文本
文本
text
3
atom-float
单值浮点
浮点
float
4
paire-enum
单权枚举
单权
json
5
paire-int
单权整形
6
paire-text
单权文本
7
paire-float
单权浮点
8
list-enum
多值枚举
多值
9
list-int
多值整形
10
list-text
多值文本
11
list-float
多值浮点
12
dict-enum
多权枚举
多权
13
dict-int
多权整形
14
dict-text
多权文本
这里需要提一下的是,标签形式分类与其存储类型的映射关系:
存储上,单值标签采用<code>bigint</code>,<code>double</code>,<code>string</code>存储。单权标签采用长度固定为2的数组<code>[value,weight]</code>存储,多值标签采用数组存储<code>[value1,value2,...]</code>,多权标签采用对象<code>{v1:w1}</code>存储,且当原子类型为整形与枚举时,其中的<code>value</code>应当存储其字符串序列化形式以符合json对key类型的要求。
从结果上看,所有单值标签都直接以其对应类型进行序列化存储。其余所有标签都采用json序列化的方式存储。
下面给出每种标签的样例:
id
title
storage
sample
性别标签:1 {"0":"男", "1" :"女"}
年龄:23
喜爱小说:"百年孤独"
体重:60.13
预测性别:[1, 0.99]
预测年龄:[23, 0.99]
电视剧-喜爱度:["星际迷航", 9.8]
预测体重:[60.13, 0.78]
闹钟设置:[1, 2, 3, 4, 5]
三围:[100, 100, 100]
喜爱电视剧:["星际迷航", "绝命毒师", "是的,大臣!"]
月度消费记录:[6379.13, 6378.24, 6356.12]
闹钟设置概率分布:{"1":0.98, "2" :0.75, "3" :0.75, "4" :0.5, "5" :0.3}
幸运数字 - 喜爱度:{"7":0.32, "5" :0.63}
网站浏览偏好标签:{"问答类":0.55, "交友类" :0.75}
标签按内容性质分类的方式,相比形式分类显得十分多样。可以纯粹的从标签的取值特性上分类(nullable,权值是否归一化,etc...),也可以从标签的来源场景(移动端,pc端),标签的所有权(私有,内部,群组,公司),标签的规模,标签的依赖,标签的row id类型(oneid, acookie, mobile, umid, etc...),或者前端展示时采用的层级类目等等很多维度上进行分类。
形式分类会决定标签的展现形式,但内容分类并没有这种影响。所以按内容分类的结果更适合作为描述字段而非放入类型字段。换句话说,与其把内容分类称为分类,倒不如称之为枚举属性更为合适。
但是对于内容分类,我们仍然需要进一步的考察。按标签内容分类可以进一步细分为: 按标签固有属性分类,和按人为用途分类。属于标签固有属性的,适合放入标签元数据表中,作为一个字段。而属于人为用途划分的,因为需求可能会频繁地发生变化,所以需要提供一种在不改变数据库schema的前提下支持动态增添分类体系的机制来实现这一需求。本文建议采用类似"wordpress"的taxonomy概念,为人为划分构建一个动态分类体系。
为了提供适应这种变化需求的灵活性,可以考虑构建一张分类体系表(tag_taxonomy)、一张分类项表(tag_term)、一张分类表(tag_classification)。动态的实现分类体系的增添。如果需要实现带有层次结构的分类体系,只需要在分类项中,为每个分类条目维护一个父条目的指针即可。
举个例子,如果我们需要动态添加一个"公私"分类。首先,需要在分类体系表里注册这种分类体系:"标签公私分类体系"。然后在分类项表中,添加"公有","私有",两个分类体系的具体取值条目。最后,在标签分类表中,将标签与分类具体取值相关联。
对于标签的内容分类:
标签的固有性质适合作为标签表的字段出现
标签的人为分类适合使用动态分类体系通过外键引入。
schema的设计如下:

欢迎加入“数加·maxcompute购买咨询”钉钉群(群号: 11782920)进行咨询,群二维码如下: