MySQL的复合数据类型:ENUM和SET

极客匠人说
• 阅读 14108

MySQL的常用数据类型包括:Number/Date/String,而String类型中又包含了Char/Varchar/Binary/blob/text等长度不同的简单数据类型,有时我们需要对数据做更细致的管理,比如枚举和集合,就需要复合类型ENUMSET了。

ENUM 枚举类型

ENUM适合于只能在一组固定值中选一个的场景,比如性别只能为男或者女。
ENUM的优势在于:

  • 只能在固定值中选择,可以在数据库层面限制非法值。
  • 数据的存储用数字来存储,占用空间少。

但是它的使用有很多需要我们注意的地方,一不小心你就会得到错误的结果。

使用ENUM枚举类型

mysql> create table test (name varchar(40), sex enum('male', 'female') );
mysql> insert into test (name, sex) values('a', 'male'), ('b', 'female'), ('c', 'male');
mysql> select * from test;
+------+--------+
| name | sex    |
+------+--------+
| a    | male   |
| b    | female |
| c    | male   |
+------+--------+
3 rows in set (0.00 sec)

创建枚举类型时,我们使用关键字enum,同时跟着一组可枚举值列表,这些可枚举值必须使用字符串的格式,否则会报错。如果插入值的大小写不匹配,会自动转换成枚举值。

ENUM类型数据存储的实际值是索引值

我们所有枚举值都是按照枚举值列表中的索引值进行存储的,如上面的ENUM('male', 'female')sex字段所有值为:

字面值存储值
NULLNULL
''0
'male'1
'female'2

因此如果有1000条记录都存储为male,我们可能认为数据库存储了4000个字符,其实只存储了10001字符。而在查询的时候又会将这个编码过的数字转为实际的值。

我们可以用两个例子测试下:

mysql> select * from test where sex=1;
+------+------+
| name | sex  |
+------+------+
| a    | male |
| c    | male |
+------+------+
2 rows in set (0.00 sec)

mysql> select name, sex+0 from test;
+------+-------+
| name | sex+0 |
+------+-------+
| a    |     1 |
| b    |     2 |
| c    |     1 |
+------+-------+
3 rows in set (0.00 sec)

这种存储和查询的方式会导致一些处理数字的函数,也会使用存储的值来进行计算,如SUM()AVG()

mysql> select name, avg(sex) from test;
+------+--------------------+
| name | avg(sex)           |
+------+--------------------+
| a    | 1.3333333333333333 |
+------+--------------------+
1 row in set (0.00 sec)

读写时不要使用数字

由于上面介绍的用索引值存储的特性,我们不要用枚举类型来存储数字格式的列,否则会引起很大的混淆,如:

mysql> create table test2 (numbers enum('0', '1', '2'));
Query OK, 0 rows affected (0.04 sec)

# 此时2被当做索引值,因此是'1';'2'就是'2';'3'因为不是合法值,会用索引值尝试,因此是'2'
mysql> insert into test2 (numbers) values (2), ('2'), ('3');
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> select * from test2;
+---------+
| numbers |
+---------+
| 1       |
| 2       |
| 2       |
+---------+
3 rows in set (0.00 sec)

枚举类型的默认值

即便一列被设定为枚举类型,但依然有额外两种值为合法值:NULL''

当我们插入一个非法值时,在宽松模式下,会插入一个普通的空字符'',其值为0。而在严格模式下会报错。

当该字段设定为允许为空时,NULL字段可以被正常插入。当不允许为空时,如果你不填值,会使用默认值:枚举值的第一个,如上面的male

除了设置为严格模式,否则没有合适的办法让一列数据必须插入合法枚举值。使用默认值很多情况下不能满足需求。

枚举类型的排序

常规使用order by进行排序时,会按照字母的文本顺序。但枚举类型由于存储为索引值,因此会按照索引值进行排序:NULL < '' = 0 < 1 < 2

如果希望按照文本类型进行排序,可以使用:

order by cast(col as char)
或者
order by concat(col)

枚举值声明的限制

创建数据类型时,枚举值不允许为表达式,如:

mysql> create table test (name varchar(40), sex enum('male', concat('fem', 'ale') );
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'concat('fem', 'ale') )' at line 1

枚举值数量的限制

枚举值用1-2个字节来存储,因此上限值为2^16-1=65535

SET集合类型

SETENUM类型非常相似,它适合于只能在一组固定值中选零个或多个的场景,比如一个人喜欢的颜色可以为红、黄、蓝等颜色中的一个或多个,也可以都不喜欢。

SET的优势和ENUM也相似,在于:

  • 只能在固定值中选择,可以在数据库层面限制非法值。
  • 数据的存储用数字来存储,占用空间少。但在枚举值数量很多,而枚举值字符数少时这一可能不成立。

使用SET枚举类型

mysql> create table test2 (name varchar(40), color set('red', 'green', 'blue', 'yellow'));
Query OK, 0 rows affected (0.04 sec)

mysql> insert into test2(name,color) values ('a', 'red'), ('b', 'red,green'), ('c', 'green,blue,yellow');
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> select * from test2;
+------+-------------------+
| name | color             |
+------+-------------------+
| a    | red               |
| b    | red,green         |
| c    | green,blue,yellow |
+------+-------------------+
3 rows in set (0.00 sec)

创建时,我们使用关键字set,同时跟着一组可枚举值列表,这些可枚举值必须使用字符串的格式,否则会报错。

SET类型数据存储的实际值是索引值的和

我们所有枚举值都是按照列表中的索引值进行存储的,不同的是通过设置二进制数为1的位置,即2的幂次方。如上面的SET('red', 'blue', 'green', 'yellow')color字段所有值为:

枚举值二进制值十进制数字
red00011
green00102
blue01004
yellow10008

而当有多个值时,通过所有值的求和得到存储的值。因此存储的数据量变少了,当取出的时候编码过的数字又会被转义成实际的字符串。

我们可以用两个例子测试下:

mysql> select name,color+0 from test2;
+------+---------+
| name | color+0 |
+------+---------+
| a    |       1 |
| b    |       3 |
| c    |      14 |
+------+---------+
3 rows in set (0.00 sec)

mysql> select name,color from test2 where color=14;
+------+-------------------+
| name | color             |
+------+-------------------+
| c    | green,blue,yellow |
+------+-------------------+
1 row in set (0.00 sec)

这种存储和查询的方式会导致一些处理数字的函数,也会使用存储的值来进行计算,如SUM()AVG()

mysql> select avg(color) from test2;
+------------+
| avg(color) |
+------------+
|          6 |
+------------+
1 row in set (0.00 sec)

插入时的顺序和次数

当插入值时,set类型不关注你插入的顺序和一个枚举值的插入次数,它会自动去重并进行求和得到值,等到取出时,会按照声明的顺序返回:

mysql> insert into test2(name,color) values ('d', 'yellow,green,red,yellow');
Query OK, 1 row affected (0.00 sec)

mysql> select name,color from test2;
+------+-------------------+
| name | color             |
+------+-------------------+
| d    | red,green,yellow  |
+------+-------------------+
4 rows in set (0.00 sec)

查找集合值

由于set类型的特殊性,因此有专用的查找函数:

mysql> select * from test2 where find_in_set('red', color);
+------+------------------+
| name | color            |
+------+------------------+
| a    | red              |
| b    | red,green        |
| d    | red,green,yellow |
+------+------------------+
3 rows in set (0.00 sec)

# 这一种方法当出现lightred颜色的时候就无法正确工作了
mysql> select * from test2 where color like '%red%';
+------+------------------+
| name | color            |
+------+------------------+
| a    | red              |
| b    | red,green        |
| d    | red,green,yellow |
+------+------------------+
3 rows in set (0.00 sec)

集合值的计算方式是位运算

前面说是对枚举值去重并自动求和只是为了方便理解,实际上是进行位运算,得到最终的值,如0001 + 0100 = 0101

因此我们也可以用类似的方法来查找值:

mysql> select name,color from test2 where color & 10;
+------+-------------------+
| name | color             |
+------+-------------------+
| b    | red,green         |
| c    | green,blue,yellow |
| d    | red,green,yellow  |
+------+-------------------+
3 rows in set (0.00 sec)

mysql> select name,color from test2 where color & 12;
+------+-------------------+
| name | color             |
+------+-------------------+
| c    | green,blue,yellow |
| d    | red,green,yellow  |
+------+-------------------+
2 rows in set (0.00 sec)

上面的这个&符号什么含义我没查到,但我猜测这个&符号的含义就是位运算,当两个数在一个位置都为1时返回true,如果没有一个位置两者都为1则为false

具体的可以计算下:a,b,c,d分别为0001,0011,1110,1011,此时101010121100,当& 10时,也就是只需要第1位或第3位存在1时(为真)就算匹配成功,从第1位到第4位(高位到低位)分别是yellow,blue,green,red,所以这个语句类似于

select name,color from test2 where find_in_set('yellow', color) or find_in_set('green', color);

可以计算的到上面的结果,其他数字的结果也都符合。

枚举类型的排序

常规使用order by进行排序时,会按照字母的文本顺序。但枚举类型由于存储为索引值,因此会按照索引值进行排序:NULL < 0 < 1 < 2

如果希望按照文本类型进行排序,可以使用:

order by cast(col as char)
或者
order by concat(col)

枚举值数量的限制

枚举值用1-8个字节来存储,因此上限值为8*8=64个。

参考资料

  1. 11.4.4 The ENUM Type:https://dev.mysql.com/doc/ref...
  2. 11.8 Data Type Storage Requirements:https://dev.mysql.com/doc/ref...
  3. 11.4.5 The SET Type:https://dev.mysql.com/doc/ref...
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Easter79 Easter79
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这