mysql创建表时的空值和非空值设置有啥讲究

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mysql创建表时的空值和非空值设置有啥讲究相关的知识,希望对你有一定的参考价值。

空值是一种特殊的状态, 表示某一个字段"没有被处理过"
几乎在所有的数据库中甚至是编程语言中, 空与非空都有其存在的意义, 举个例子, 一个人员表中, 有一个年龄字段, 这个年龄字段如果为非 空, 则在不知道人员年龄的情况下, 数据无法入库, 否则会显示个奇怪的年龄(比如0岁, 或者-1岁等)
而有些情况泽不可以为空, 最常见的就是"主键", 比如身份证号之类的
参考技术A 你这样设置之后,是不能插入的空(NULL),而是能插入空串(\\'\\')。 如果不让插入空串,应该使用check,例如: CREATE TABLE table2 ( title varchar(50) NOT NUL 参考技术B NULL 对外部程序来说,具体为不知道、不确切的、无法表述的值。所以在很多家公司的开发规范里都明确规定了,必须为 NOT NULL。
其实用到 NULL 的场景都可以转换为有意义的字符或者数值,一是有利用数据的易读性以及后期的易维护性;二是降低 SQL 语句的编写难度。
关于 NULL 的特性如下:
1. 参与 NULL 字段拼接的结果都为 NULL,预期的可能会有差异
预想把字段 r1 做个拼接,再插入到新的表 t3 里,结果 t3 表的记录全为 NULL,跟预期不符。
mysql> show create table t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`r1` varchar(10) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
mysql> show create table t2\G
*************************** 1. row ***************************
Table: t2
Create Table: CREATE TABLE `t2` (
`r1` varchar(10) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
mysql> create table t3 like t1
Query OK, 0 rows affected (0.04 sec)
mysql> insert into t3 select concat(r1,'database') from t1 limit 2;
Query OK, 2 rows affected (0.02 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> select * from t3;
+------+
| r1 |
+------+
| NULL |
| NULL |
+------+
2 rows in set (0.00 sec)
那正确的方法如下,对 NULL 用函数 IFNULL 特殊处理。
mysql> insert into t3 select concat(ifnull(r1,''),'database') from t1 limit 2;
Query OK, 2 rows affected (0.01 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> select * from t3;
+----------+
| r1 |
+----------+
| database |
| database |
+----------+
2 rows in set (0.00 sec)
2. 对于包含 NULL 列的求 COUNT 值也不准确
t1 和 t2 的记录数是一样的,但是字段 r1 包含了 NULL,这导致结果忽略了这些值。
mysql> select count(r1) as rc from t1;
+-------+
| rc |
+-------+
| 16384 |
+-------+
1 row in set (0.01 sec)
mysql> select count(r1) as rc from t2;
+-------+
| rc |
+-------+
| 32768 |
+-------+
1 row in set (0.03 sec)
这时候我们可能想到了,正确的方法是用 NULL 相关函数处理,
mysql> select count(ifnull(r1,'')) as rc from t1;
+-------+
| rc |
+-------+
| 32768 |
+-------+
1 row in set (0.03 sec)
或者是直接用 COUNT(*) 包含了所有可能的值
mysql> select count(*) as rc from t1;
+-------+
| rc |
+-------+
| 32768 |
+-------+
1 row in set (0.02 sec)
当然了不仅仅是 COUNT,除了 NULL 相关的函数,大部分对 NULL 处理都不友好。
所以其实从上面两点来看,NULL 的处理都得特殊处理,额外增加了编写 SQL 的难度。
3. 包含 NULL 的索引列
对包含 NULL 列建立索引,比不包含的 NULL 的字段,要多占用一个 BIT 位来存储。
示例
key_len 分别为 43 和 42,t1 比 t2 多了一个字节,那这里可能有人要问了,不是说占了一个 BIT 位吗?那为什么多了一个字节?可以关注我上一篇文章(第02期:MySQL 数据类型的艺术 - 大对象字段)关于 BIT 的详细描述。
mysql> pager grep -i 'key_len'
PAGER set to 'grep -i 'key_len''
mysql> explain select * from t1 where r1 = ''\G

key_len: 43
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from t2 where r1 = ''\G

key_len: 42
1 row in set, 1 warning (0.00 sec)
4. 各存储引擎相关的对 NULL 的处理
在 MySQL 8.0 发布后,仅有 InnoDB、MyISAM 和 Memory 支持对包含 NULL 列的索引,其他引擎不支持。比如 NDB。

MySQL 按空值和非空值分组

【中文标题】MySQL 按空值和非空值分组【英文标题】:MySQL group by null and not null values 【发布时间】:2021-09-03 12:09:43 【问题描述】:

我有一张这样的桌子:

id | cluster_id | user_id | name      | ...
1  | 1          | 1       | test name
2  | 1          | 3       | other
3  | null       | 1       | one more
4  | 2          | 1       | foo
5  | null       | 1       | bar
6  | 1          | 1       | baz

我想创建一个按cluster_id 列分组的查询,但只按具有非空值的列分组,这样我就得到了这样的结果:

id | cluster_id | user_id | ...
1  | 1          | 1       | test name
3  | null       | 1       | one more
4  | 2          | 1       | foo
5  | null       | 1       | bar

我想要一个具有不同 cluster_id 的列表,但仅限于 cluster_id 不为空的地方。我还想过滤任意列,例如user_id

在上述结果中,我还查询了user_id,其中user_id 为1。

如何创建这样的查询?

提前致谢!

【问题讨论】:

这看起来一样,你可以创建一个minimal reproducible example 以便我们了解你想要达到的目标 @nbk 我已经更新了问题。我希望现在应该更清楚了:) 嗯,the columns that have a not null value 并且您的示例目标表中有一个空值 - 不太确定您要在那里实现什么。 @LukeBriggs 已修复 【参考方案1】:

查询很简单。

GROUP BY 也适用于 NULL 值

我做了两个查询,第一个包含 user_id 最后一个不包含

你必须用id作为主键来测试这个,看看排除NULL是否会带来一些性能

CREATE TABLE tab1 (
  `id` INTEGER,
  `cluster_id` int,
  `user_id` INTEGER,
  `name` VARCHAR(20)
);
INSERT INTO tab1
  (`id`, `cluster_id`, `user_id`, `name`)
VALUES
  ('1', '1', '1', 'test name'),
  ('2', '1', '3', 'other'),
  ('3', null, '1', 'one more'),
  ('4', '2', '1', 'foo'),
  ('5', null, '1', 'bar'),
  ('6', '1', '1', 'baz');
SELECT * FROM  tab1 WHERE `id` IN (SELECT MIN(`id`) FROM tab1 GROUP BY `cluster_id`,`user_id`)
UNION 
SELECT * FROM tab1 WHERE `cluster_id` IS NULL
编号 | cluster_id |用户 ID |姓名 -: | ---------: | ------: | :-------- 1 | 1 | 1 |测试名称 2 | 1 | 3 |其他 3 | | 1 |多一个 4 | 2 | 1 |富 5 | | 1 |酒吧
SELECT * FROM  tab1 WHERE `id` IN (SELECT MIN(`id`) FROM tab1 WHERE `cluster_id` IS NOT NULL GROUP BY `cluster_id`,`user_id`)
UNION 
SELECT * FROM tab1 WHERE `cluster_id` IS NULL
编号 | cluster_id |用户 ID |姓名 -: | ---------: | ------: | :-------- 1 | 1 | 1 |测试名称 2 | 1 | 3 |其他 4 | 2 | 1 |富 3 | | 1 |多一个 5 | | 1 |酒吧
SELECT * FROM  tab1 WHERE `id` IN (SELECT MIN(`id`) FROM tab1 GROUP BY `cluster_id`)
UNION 
SELECT * FROM tab1 WHERE `cluster_id` IS NULL
编号 | cluster_id |用户 ID |姓名 -: | ---------: | ------: | :-------- 1 | 1 | 1 |测试名称 3 | | 1 |多一个 4 | 2 | 1 |富 5 | | 1 |酒吧
SELECT * FROM  tab1 WHERE `id` IN (SELECT MIN(`id`) FROM tab1 WHERE `cluster_id` IS NOT NULL GROUP BY `cluster_id`)
UNION 
SELECT * FROM tab1 WHERE `cluster_id` IS NULL
编号 | cluster_id |用户 ID |姓名 -: | ---------: | ------: | :-------- 1 | 1 | 1 |测试名称 4 | 2 | 1 |富 3 | | 1 |多一个 5 | | 1 |酒吧

db小提琴here

【讨论】:

cluster_idnull 时,不应将其分组,仅对具有非空值的记录进行分组。在这种情况下,这将导致 2 个空记录。 我错过了那部分。 mi改了我的答案,你必须测试哪个更快 感谢您的回答。我遇到的问题是,每当我想过滤掉特定用户的结果时,查询总是会返回带有 cluster_id 的记录结果。如果我在 user_id=3 上查询,查询总是返回记录,即使不存在具有该 ID 的记录。 就是基本的sql add AND user_id = 3 after cluster_id IS NULLAND cluster_id IS NOT NULL 你要明白union是结合了两个独立的查询结构相同的,所以你过滤了之前的UNION或者之后

以上是关于mysql创建表时的空值和非空值设置有啥讲究的主要内容,如果未能解决你的问题,请参考以下文章

从 Pyspark 中的数据框中计算空值和非空值

Python 空值和非空值

数据库数据插入,空值和非空判断,自动排序,约束主键,唯一约束,外健约束

SQL将多行中的字段的空值填充为先前的非空值

MySQL的空值和NULL区别

数据库怎么用非空值填充为空值?