CategoryResourceRepost/极客时间专栏/MySQL 必知必会/实践篇/10 | 如何进行数学计算、字符串处理和条件判断?.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

366 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<audio id="audio" title="10 | 如何进行数学计算、字符串处理和条件判断?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a3/af/a3b33304a92fc8e444335a190fdc7aaf.mp3"></audio>
你好,我是朱晓峰。
MySQL提供了很多功能强大而且使用起来非常方便的函数包括数学函数、字符串处理函数和条件判断函数等。
在很多场景中 ,我们都会用到这些函数,比如说,在超市项目的实际开发过程中,会有这样的需求:
- 会员积分的规则是一元积一分不满一元不积分这就要用到向下取整的数学函数FLOOR()
- 在打印小票的时候收银纸的宽度是固定的怎么才能让打印的结果清晰而整齐呢这个时候就要用到CONCAT()等字符串处理函数;
- 不同数据的处理方式不同怎么选择正确的处理方式呢这就会用到IF(表达式V1V2)这样的条件判断函数;
- ……
这些函数对我们管理数据库、提高数据处理的效率有很大的帮助。接下来,我就带你在解决实际问题的过程中,帮你掌握使用这些函数的方法。
## 数学函数
我们先来学习下数学函数它主要用来处理数值数据常用的主要有3类分别是取整函数ROUND()、CEIL()、FLOOR()绝对值函数ABS()和求余函数MOD()。
知道了这些函数,我们来看看超市经营者的具体需求。他们提出,为了提升销量,要进行会员营销,主要是给会员积分,并以积分数量为基础,给会员一定的优惠。
积分的规则也很简单,就是消费一元积一分,不满一元不积分,那我们就需要对销售金额的数值进行取整。
这里主要用到四个表,分别是销售单明细表、销售单头表、商品信息表和会员信息表。为了方便你理解,我对表结构和数据进行了简化。
销售单明细表:
<img src="https://static001.geekbang.org/resource/image/54/93/543b4ce8c0c8b1f3bb7028c911213f93.jpeg" alt="">
销售单头表:
<img src="https://static001.geekbang.org/resource/image/a4/33/a4df12d3469aaf2f770fbfa8fb842c33.jpeg" alt="">
商品信息表:
<img src="https://static001.geekbang.org/resource/image/26/03/262121b96d4ce48e310cdff37d536203.jpeg" alt="">
会员信息表:
<img src="https://static001.geekbang.org/resource/image/10/8e/105d12853e4b09aefd96f2423613648e.jpeg" alt="">
这个场景下可以用到MySQL数学函数中的取整函数主要有3种。
- 向上取整CEIL(X)和CEILING(X)返回大于等于X的最小INT型整数。
- 向下取整FLOOR(X)返回小于等于X的最大INT型整数。
- 舍入函数ROUND(X,D)X表示要处理的数D表示保留的小数位数处理的方式是四舍五入。ROUND(X)表示保留0位小数。
现在积分的规则是一元积一分不满一元不积分显然是向下取整那就可以用FLOOR函数。
首先,我们要通过关联查询,获得会员消费的相关信息:
```
mysql&gt; SELECT
-&gt; c.membername AS '会员', -- 从会员表获取会员名称
-&gt; b.transactionno AS '单号',-- 从销售单头表获取单号
-&gt; b.transdate AS '交易时间', -- 从销售单头表获取交易时间
-&gt; d.goodsname AS '商品名称', -- 从商品信息表获取商品名称
-&gt; a.salesvalue AS '交易金额'
-&gt; FROM
-&gt; demo.transactiondetails a
-&gt; JOIN
-&gt; demo.transactionhead b ON (a.transactionid = b.transactionid)
-&gt; JOIN
-&gt; demo.membermaster c ON (b.memberid = c.memberid)
-&gt; JOIN
-&gt; demo.goodsmaster d ON (a.itemnumber = d.itemnumber);
+------+------------------+---------------------+----------+----------+
| 会员 | 单号 | 交易时间 | 商品名称 | 交易金额 |
+------+------------------+---------------------+----------+----------+
| 张三 | 0120201201000001 | 2020-12-01 14:25:56 | 书 | 176.22 |
| 张三 | 0120201201000001 | 2020-12-01 14:25:56 | 笔 | 24.75 |
| 李四 | 0120201202000001 | 2020-12-02 10:50:50 | 书 | 234.96 |
| 李四 | 0120201202000001 | 2020-12-02 10:50:50 | 笔 | 26.40 |
+------+------------------+---------------------+----------+----------+
4 rows in set (0.01 sec)
```
接着我们用FLOORa.salesvalue对销售金额向下取整获取会员积分值代码如下
```
mysql&gt; SELECT
-&gt; c.membername AS '会员',
-&gt; b.transactionno AS '单号',
-&gt; b.transdate AS '交易时间',
-&gt; d.goodsname AS '商品名称',
-&gt; a.salesvalue AS '交易金额',
-&gt; FLOOR(a.salesvalue) AS '积分' -- 使用FLOOR函数向下取整
-&gt; FROM
-&gt; demo.transactiondetails a
-&gt; JOIN
-&gt; demo.transactionhead b ON (a.transactionid = b.transactionid)
-&gt; JOIN
-&gt; demo.membermaster c ON (b.memberid = c.memberid)
-&gt; JOIN
-&gt; demo.goodsmaster d ON (a.itemnumber = d.itemnumber);
+------+------------------+---------------------+----------+----------+------+
| 会员 | 单号 | 交易时间 | 商品名称 | 交易金额 | 积分 |
+------+------------------+---------------------+----------+----------+------+
| 张三 | 0120201201000001 | 2020-12-01 14:25:56 | 书 | 176.22 | 176 |
| 张三 | 0120201201000001 | 2020-12-01 14:25:56 | 笔 | 24.75 | 24 |
| 李四 | 0120201202000001 | 2020-12-02 10:50:50 | 书 | 234.96 | 234 |
| 李四 | 0120201202000001 | 2020-12-02 10:50:50 | 笔 | 26.40 | 26 |
+------+------------------+---------------------+----------+----------+------+
4 rows in set (0.01 sec)
```
你看通过FLOOR(),我们轻松地获得了超市经营者需要的积分数据。
类似的如果用户的积分规则改为“不满一元积一分”其实就是对金额数值向上取整这个时候我们就可以用CEIL()函数。操作方法和前面是一样的,我就不具体解释了。
最后我再来讲一讲舍入函数ROUND的使用方法。
超市经营者提出,收银的时候,应收金额可以被设定四舍五入到哪一位。比如,可以设定四舍五入到元、到角,或者到分。
按照指定的位数对小数进行四舍五入计算这样的场景就要用到ROUNDX,D了。它的作用是通过四舍五入对数值X保留D位小数。
根据超市经营者的要求我们把函数ROUND(X,D)中的保留小数的位数D设置成0、1和2。
如果要精确到分我们可以设置保留2位小数
```
mysql&gt; SELECT ROUND(salesvalue,2) -- D设置成2表示保留2位小数也就是精确到分
-&gt; FROM demo.transactiondetails
-&gt; WHERE transactionid=1 AND itemnumber=1;
+---------------------+
| ROUND(salesvalue,2) |
+---------------------+
| 176.22 |
+---------------------+
1rows in set (0.00 sec)
```
如果要精确到角可以设置保留1位小数
```
mysql&gt; SELECT ROUND(salesvalue,1) -- D设置成1表示保留1位小数也就是精确到角
-&gt; FROM demo.transactiondetails
-&gt; WHERE transactionid=1 AND itemnumber=1;
+---------------------+
| ROUND(salesvalue,1) |
+---------------------+
| 176.2 |
+---------------------+
1 rows in set (0.00 sec
```
如果要精确到元可以设置保留0位小数
```
mysql&gt; SELECT ROUND(salesvalue,0)-- D设置成0表示保留0位小数也就是精确到元
-&gt; FROM demo.transactiondetails
-&gt; WHERE transactionid=1 AND itemnumber=1;
+---------------------+
| ROUND(salesvalue,0) |
+---------------------+
| 176 |
+---------------------+
1 rows in set (0.00 se
```
除了刚刚我们所学习的函数MySQL还支持绝对值函数ABS和求余函数MODABSX表示获取X的绝对值MODXY表示获取X被Y除后的余数。
这些函数使用起来都比较简单,你重点掌握它们的含义就可以了,下面我再带你学习下字符串函数。
## 字符串函数
除了数学计算,我们还经常会遇到需要对字符串进行处理的场景,比如我们想要在金额前面加一个“¥”的符号,就会用到字符串拼接函数;再比如,我们需要把一组数字以字符串的形式在网上传输,就要用到类型转换函数。
常用的字符串函数有4个。
- CONCATs1,s2,...表示把字符串s1、s2……拼接起来组成一个字符串。
- CAST表达式 AS CHAR表示将表达式的值转换成字符串。
- CHAR_LENGTH字符串表示获取字符串的长度。
- SPACEn表示获取一个由n个空格组成的字符串。
接下来我还是借助超市项目中的实际应用场景,来说明一下怎么使用这些字符串函数。
顾客交了钱完成交易之后系统必须要打出一张小票。打印小票时对格式有很多要求。比如说一张小票纸57毫米宽大概可以打32个字符也就是16个汉字。用户要求一条流水打2行第一行是商品信息第二行要包括数量、价格、折扣和金额4种信息。那么怎么才能清晰地在小票上打印出这些信息并且打印得整齐漂亮呢这就涉及对字符串的处理了。
首先,我们来看一下如何打印第一行的商品信息。商品信息包括:商品名称和商品规格,而且商品规格要包含在括号里面。这样就必须把商品名称和商品规格拼接起来,变成一个字符串。
这时我们就可以用合并字符串函数CONCAT如下所示
```
mysql&gt; SELECT
-&gt; CONCAT(goodsname, '(', specification, ')') AS 商品信息 -- 这里把商品名称、括号和规格拼接起来
-&gt; FROM
-&gt; demo.goodsmaster
-&gt; WHERE itemnumber = 1;
+----------+
| 商品信息 |
+----------+
| 书(16开) |
+----------+
1 row in set (0.00 sec)
```
这样我们就得到了商品编号是1的商品它的商品信息是“书16开”。
第二步我们来看一下如何打印第二行。第二行包括数量、价格、折扣和金额一共是4种信息。
因为一行最多是32个字符我们给数量分配7个字符价格分配7个字符折扣分配6个字符金额分配9个字符加上中间3个空格正好是32个字符。
为啥这么分配呢?我简单解释下。
- 数量7个字符就是小数点前面给3位小数点后面给3位外加小数点1位最大999.999,基本满足零售的需求了。
- 同样道理价格给7位意思是小数点前面4位小数点后面2位外加小数点这样最大可以表示9999.99。
- 折扣6位小数点后面2位小数点前面2位加上小数点和“%”,这样是够用的。
- 金额9位最大可以显示到999999.99,也够用了。
分配好了各部分信息的字符串大小,我再讲一下格式处理,因为数据的取值每次都会不同,如果直接打印,会参差不齐。这里我以数量为例,来具体说明一下。因为数量比较有代表性,而且比较简单,不像金额或者折扣率那样,有时还要根据用户的需求,加上“¥”或者“%”。
第一步把数量转换成字符串。这里我们需要用到把数值转换成字符串的CAST函数如下所示
```
mysql&gt; SELECT
-&gt; CAST(quantity AS CHAR) -- 把decimal类型转换成字符串
-&gt; FROM
-&gt; demo.transactiondetails
-&gt; WHERE
-&gt; transactionid = 1 AND itemnumber =1;
+---------------------+
| CAST(price AS CHAR) |
+---------------------+
| 2.000 |
+---------------------+
1 rows in set (0.00 sec)
```
第二步计算字符串的长度这里我们要用到CHAR_LENGTH函数。
需要注意的是虽然每个汉字打印的时候占2个字符长度但是这个函数获取的是汉字的个数。因此如果字符串中有汉字函数获取的字符串长度跟实际打印的长度是不一样的需要用空格来补齐。
我们可以通过下面的查询,获取数量字段转换成字符串后的字符串长度:
```
mysql&gt; SELECT
-&gt; CHAR_LENGTH(CAST(quantity AS CHAR)) AS 长度
-&gt; FROM
-&gt; demo.transactiondetails
-&gt; WHERE
-&gt; transactionid = 1 AND itemnumber =1;
+---------------------+
| 长度 |
+---------------------+
| 5 |
+---------------------+
1 rows in set (0.00 sec)
```
第三步用空格补齐7位长度。这时我们要用到SPACE函数。
因为我们采用左对齐的方式打印(左对齐表示字符串从左边开始,右边空余的位置用空格补齐),所以就需要先拼接字符串,再在字符串的后面补齐空格:
```
mysql&gt; SELECT
-&gt; CONCAT(CAST(quantity AS CHAR),
-&gt; SPACE(7 - CHAR_LENGTH(CAST(quantity AS CHAR)))) AS 数量
-&gt; FROM
-&gt; demo.transactiondetails
-&gt; WHERE
-&gt; transactionid = 1 AND itemnumber = 1;
+----------+
| 数量 |
+----------+
| 2.000 |
+----------+
1 row in set (0.00 sec)
```
除此以外MySQL还支持SUBSTR、MID、TRIM、LTRIM、RTRIM。我画了一张图来展示它们的含义你可以了解一下。
<img src="https://static001.geekbang.org/resource/image/86/d9/86f0f4ebe3055db5c112784d86aa07d9.jpg" alt="">
一般来说关于字符串函数你掌握这些就足够了。不过MySQL支持的字符串函数还有很多如果你在实际工作中遇到了更复杂的情况可以参考MySQL官方的[文档](https://dev.mysql.com/doc/refman/8.0/en/string-functions.html)。
## 条件判断函数
我们刚才在对商品信息字符串进行拼接的时候会有一种例外的情况那就是当规格为空的时候商品信息会变成“NULL”。这个结果显然不是我们想要的因为名称变成NULL顾客会觉得奇怪也不知道买了什么商品。我们希望如果规格是空值就不用加规格了。怎么实现呢这就要用到条件判断函数了。
条件判断函数的主要作用,就是根据特定的条件返回不同的值,常用的有两种。
- IFNULLV1V2表示如果V1的值不为空值则返回V1否则返回V2。
- IF表达式V1V2如果表达式为真TRUE则返回V1否则返回V2。
我们希望规格是空的商品,拼接商品信息字符串的时候,规格不要是空。这个问题,可以通过 IFNULL(specification, '')函数来解决。具体点说就是对字段“specification”是否为空进行判断如果为空就返回空字符串否则就返回商品规格specification的值。代码如下所示
```
mysql&gt; SELECT
-&gt; goodsname,
-&gt; specification,
-&gt; CONCAT(goodsname,'(', IFNULL(specification, ''),')') AS 拼接 -- 用条件判断函数,如果规格是空,则括号中是空字符串
-&gt; FROM
-&gt; demo.goodsmaster;
+-----------+---------------+----------+
| goodsname | specification | 拼接 |
+-----------+---------------+----------+
| 书 | 16开 | 书(16开) |
| 笔 | NULL | 笔() |
+-----------+---------------+----------+
2 rows in set (0.00 sec)
```
结果是,如果规格为空,商品信息就变成了“商品信息()”,好像还不错。但是也存在一点问题:商品名称后面的那个空括号“()”会让客人觉得奇怪,能不能去掉呢?
如果用IFNULLV1V2函数就不容易做到但是没关系我们可以尝试用另一个条件判断函数IF表达式V1V2来解决。这里表达式是ISNULL(specification),这个函数用来判断字段"specificaiton"是否为空V1是返回商品名称V2是返回商品名称拼接规格。代码如下所示
```
mysql&gt; SELECT
-&gt; goodsname,
-&gt; specification,
-&gt; -- 这里做判断,如果是空值,返回商品名称,否则就拼接规格
-&gt; IF(ISNULL(specification),
-&gt; goodsname,
-&gt; CONCAT(goodsname, '(', specification, ')')) AS 拼接
-&gt; FROM
-&gt; demo.goodsmaster;
+-----------+---------------+----------+
| goodsname | specification | 拼接 |
+-----------+---------------+----------+
| 书 | 16开 | 书(16开) |
| 笔 | NULL | 笔 |
+-----------+---------------+----------+
2 rows in set (0.02 sec)
```
这个结果就是,如果规格为空,商品信息就是商品名称;如果规格不为空,商品信息是商品名称拼接商品规格,这就达到了我们的目的。
## 总结
今天,我们学习了用于提升数据处理效率的数学函数、字符串函数和条件判断函数。
<img src="https://static001.geekbang.org/resource/image/06/f7/06f0cb9251af48e626b81016630f9ff7.jpg" alt="">
这些函数看起来很容易掌握但是有很多坑。比如说ROUNDX是对X小数部分四舍五入那么在“五入”的时候返回的值是不是一定比X大呢其实不一定因为当X为负数时五入的值会更小。你可以看看下面的代码
```
mysql&gt; SELECT ROUND(-1.5);
+-------------+
| ROUND(-1.5) |
+-------------+
| -2 |
+-------------+
1 row in set (0.00 sec)
```
所以,我建议你在学习的时候,**多考虑边界条件的场景,实际测试一下**。就像这个问题对于ROUND(X,0)并没有指定X是正数那如果是负数会怎样呢你去测试一下就明白了。
## 思考题
这节课我介绍了如何用FLOOR函数来计算会员积分那么如果不用FLOOR有没有其他办法来实现会员积分的计算呢
欢迎在留言区写下你的思考和答案,我们一起交流讨论。如果你觉得今天的内容对你有所帮助,也欢迎你分享给你的朋友或同事,我们下节课见。