起因
在某个功能的开发中,遇到一个问题,数据库中的某一行数据,可能被两个不同模块使用,每个模块取这行数据的时候,其类型必须是符合的。也就是说这行数据可能具有两种不同的类型。
比如:车辆数据, 类型包括 乘用车、客车、卡车等等, 另外我们由于业务需要在类型中增加了 新能源、燃油 两种类型。某辆车同时是 乘用车 和 新能源, 而且我们需要用一个类型字段保存这两种类型,怎么做?
原设计使用字符串表示
首先想到的是,用定长字符串的每一位置指定对应类型,该位置0表示不是,1 表示是。解析字符串做匹配。
比如定长为5的字符串,其字符串的索引与值的含义如下:
idx | values | desc |
---|---|---|
0 | 0/1 | 是否是乘用车 |
1 | 0/1 | 是否是客车 |
2 | 0/1 | 是否是卡车 |
3 | 0/1 | 是否是新能源车 |
4 | 0/1 | 是否是燃油车 |
如果type值是字符串"01010",字符串idx的1和3位的字符为1,表示该行数据同时是客车和新能源车。判断时,解析字符串,取出指定idx的字符值,判断其为"1"则表示符合该类型。
使用整数的二进制表示类型
写着写着,就觉得每次要解析字符串的指定字符,然后判断是0或者1,这样写很麻烦,执行效率也不高。
那么既然想到了使用字符0和1表示非和是,那么还有种数据的表达方式是这样的,二进制整数!我用二进制整数表示不就得了!
于是,我改进了类型的存储设计,使用二进制位移运算设计了车辆类型枚举如下:
export enum EVehicleType{
CAR = 1 << 0, // 二进制 1 十进制 1
BUS = 1 << 1, // 二进制 10 十进制 2
TRUCK = 1 << 2, // 二进制 100 十进制 4
NEWENERGY = 1 << 3, // 二进制 1000 十进制 8
FUEL = 1 << 4 // 二进制 10000 十进制 16
}
车辆表vehicle结构如下:
id | plate | type(bigint) |
---|
这样type字段只需要存一个整数,然后根据这个整数的二进制位运算进行判断。下面看这种存储设计如何使用。
1.创建属性
设置属性的时候如何设置呢?
如果我们创建一个车辆车牌'京A.SB250',同时是乘用车 和 新能源车,更新该字段值如下:
vehicle.plate = '京A.SB250';
vehicle.type = EVehicleType.CAR | EVehicleType.NEWENERGY;
// 保存到数据库
使用位的或运算,即(二进制) 1 | 1000 = 1001, 十进制 9。保存入库后如下:
id | plate | type(bigint) |
---|---|---|
1 | 京A.SB250 | 9 |
这样, type = 9 表示该车辆同时为 CAR 和 NEWENERGY。
2.属性条件判断
下面我们来说下,取数的时候如何进行判断呢?
比如,我们要查询type为CAR的数据,sql如下(EVehicleType.CAR的值为1):
SELECT * from vehicle where (type & 1) = 1;
还是以上面创建的"京A.SB250"进行解释,type值为9,二进制为1001,使用位的与运算, 1001 & 1 = 1, 这样就可以判断出type是1的数据。
这样有个问题,如果type字段加了索引,经过type & 1计算再进行判断索引会失效。但实际使用时我们会使用其他条件将筛选范围缩小至一个很小的范围内,再进行type的值判断时,type字段上的索引的影响会很小。
3.typeOrm中查询条件的写法
上面搞清楚了数据库中如何操作,代码中(node + typeorm)中如何使用呢?
如果使用queryBuild或者queryRunner就不说了,直接拼上面的sql条件就可以了,这里说下使用entity的Repository的查询条件如何写:
- 直接使用findcondition查询类型为CAR的数据
let vehicleList = await this.repository.find({type: Raw(alias => `(${EVehicleType.CAR} & ${alias}) = ${EVehicleType.CAR}`)});
- 另一种方式,使用findManyOptions方式查询类型为NEWENERGY的数据
let vehicleList = await this.repository.find({
where: {
type: Raw(alias => `(${EVehicleType.NEWENERGY} & ${alias}) = ${EVehicleType.NEWENERGY}`)
}
});