Task03:复杂一点的查询(二)

本文深入探讨了SQL中的各类函数应用,包括算术、字符串、日期及类型转换等,并介绍了谓词函数与case表达式的使用技巧。通过丰富的示例演示了如何进行数据处理和条件筛选。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

教程地址

https://github.com/datawhalechina/wonderful-sql
https://gitee.com/datawhalechina/wonderful-sql

3. 各种各样的函数

mysql> show tables;
+-------------------+
| Tables_in_shop    |
+-------------------+
| ViewPractice5_1   |
| ViewPractice5_2   |
| chars             |
| empskills         |
| inventoryproduct  |
| product           |
| product2          |
| productcopy       |
| productins        |
| productsum        |
| producttype       |
| samplelike        |
| samplemath        |
| samplestr         |
| shop_product      |
| shopproduct       |
| skills            |
| user              |
| view_product      |
| view_shop_product |
+-------------------+
20 rows in set (0.01 sec)

mysql> select * from samplemath;
+----------+------+------+
| m        | n    | p    |
+----------+------+------+
|  500.000 |    0 | NULL |
| -180.000 |    0 | NULL |
|     NULL | NULL | NULL |
|     NULL |    7 |    3 |
|     NULL |    5 |    2 |
|     NULL |    4 | NULL |
|    8.000 | NULL |    3 |
|    2.270 |    1 | NULL |
|    5.555 |    2 | NULL |
|     NULL |    1 | NULL |
|    8.760 | NULL | NULL |
+----------+------+------+
11 rows in set (0.00 sec)

先看一下表,已经有了,那就不用再建了

3.1 算术函数

3.1.1 绝对值

先来求个绝对值

mysql> select m, abs(m) as abs_col from samplemath;
+----------+---------+
| m        | abs_col |
+----------+---------+
|  500.000 | 500.000 |
| -180.000 | 180.000 |
|     NULL |    NULL |
|     NULL |    NULL |
|     NULL |    NULL |
|     NULL |    NULL |
|    8.000 |   8.000 |
|    2.270 |   2.270 |
|    5.555 |   5.555 |
|     NULL |    NULL |
|    8.760 |   8.760 |
+----------+---------+
11 rows in set (0.02 sec)

null 的绝对值还是 null

3.1.2 求余

再来做一下求余

mysql> select n, p, mod(n,p) as mod_col from samplemath;
+------+------+---------+
| n    | p    | mod_col |
+------+------+---------+
|    0 | NULL |    NULL |
|    0 | NULL |    NULL |
| NULL | NULL |    NULL |
|    7 |    3 |       1 |
|    5 |    2 |       1 |
|    4 | NULL |    NULL |
| NULL |    3 |    NULL |
|    1 | NULL |    NULL |
|    2 | NULL |    NULL |
|    1 | NULL |    NULL |
| NULL | NULL |    NULL |
+------+------+---------+
11 rows in set (0.00 sec)


小数不能进行求余数,该操作只对整数有效

需要我们注意的是,SQL Server 是使用 % 来进行求余计算的,不支持 mod 函数

3.1.3 四舍五入

接下来是四舍五入的操作,我们保留一位小数来看看

mysql> select m, round(m,1) as round_col from samplemath;
+----------+-----------+
| m        | round_col |
+----------+-----------+
|  500.000 |     500.0 |
| -180.000 |    -180.0 |
|     NULL |      NULL |
|     NULL |      NULL |
|     NULL |      NULL |
|     NULL |      NULL |
|    8.000 |       8.0 |
|    2.270 |       2.3 |
|    5.555 |       5.6 |
|     NULL |      NULL |
|    8.760 |       8.8 |
+----------+-----------+
11 rows in set (0.00 sec)

需要注意的是,我们最好直接使用数字来指定保留小数的位数

如果使用变量的话可能会有错误

3.2 字符串函数

之前已经建好表了

mysql> select * from samplestr;
+-----------+--------+--------+
| str1      | str2   | str3   |
+-----------+--------+--------+
| opx       | rt     | NULL   |
| abc       | def    | NULL   |
| 山田      | 太郎   | 是我   |
| aaa       | NULL   | NULL   |
| NULL      | xyz    | NULL   |
| @!#$%     | NULL   | NULL   |
| ABC       | NULL   | NULL   |
| aBC       | NULL   | NULL   |
| abc太郎   | abc    | ABC    |
| abcdefabc | abc    | ABC    |
| micmic    | i      | I      |
+-----------+--------+--------+
11 rows in set (0.00 sec)

3.2.1 拼接

先来看看 concat

mysql> select str1, str2, concat(str1,'-',str2) from samplestr;
+-----------+--------+-----------------------+
| str1      | str2   | concat(str1,'-',str2) |
+-----------+--------+-----------------------+
| opx       | rt     | opx-rt                |
| abc       | def    | abc-def               |
| 山田      | 太郎   | 山田-太郎             |
| aaa       | NULL   | NULL                  |
| NULL      | xyz    | NULL                  |
| @!#$%     | NULL   | NULL                  |
| ABC       | NULL   | NULL                  |
| aBC       | NULL   | NULL                  |
| abc太郎   | abc    | abc太郎-abc           |
| abcdefabc | abc    | abcdefabc-abc         |
| micmic    | i      | micmic-i              |
+-----------+--------+-----------------------+
11 rows in set (0.00 sec)

啊,对 null 无效啊,其他的都还好说,貌似最多只能连三列

3.2.2 长度

mysql> select str1, length(str1) as len_str1 from samplestr;
+-----------+----------+
| str1      | len_str1 |
+-----------+----------+
| opx       |        3 |
| abc       |        3 |
| 山田      |        6 |
| aaa       |        3 |
| NULL      |     NULL |
| @!#$%     |        5 |
| ABC       |        3 |
| aBC       |        3 |
| abc太郎   |        9 |
| abcdefabc |        9 |
| micmic    |        6 |
+-----------+----------+
11 rows in set (0.00 sec)

普普通通的一个测量长度,没什么好讲的

3.2.3 大小写转换

mysql> select str1, lower(str1) as lower_str1, upper(str1) as upper_str1 from samplestr;
+-----------+------------+------------+
| str1      | lower_str1 | upper_str1 |
+-----------+------------+------------+
| opx       | opx        | OPX        |
| abc       | abc        | ABC        |
| 山田      | 山田       | 山田       |
| aaa       | aaa        | AAA        |
| NULL      | NULL       | NULL       |
| @!#$%     | @!#$%      | @!#$%      |
| ABC       | abc        | ABC        |
| aBC       | abc        | ABC        |
| abc太郎   | abc太郎    | ABC太郎    |
| abcdefabc | abcdefabc  | ABCDEFABC  |
| micmic    | micmic     | MICMIC     |
+-----------+------------+------------+
11 rows in set (0.00 sec)

只对字母有效啊,而且有效范围是全部字母,包含被混杂在中文中的字母

3.2.4 替换

mysql> select str1, replace(str1,'a','Z') as replace_str1 from samplestr;
+-----------+--------------+
| str1      | replace_str1 |
+-----------+--------------+
| opx       | opx          |
| abc       | Zbc          |
| 山田      | 山田         |
| aaa       | ZZZ          |
| NULL      | NULL         |
| @!#$%     | @!#$%        |
| ABC       | ABC          |
| aBC       | ZBC          |
| abc太郎   | Zbc太郎      |
| abcdefabc | ZbcdefZbc    |
| micmic    | micmic       |
+-----------+--------------+
11 rows in set (0.00 sec)

我们把小写的 a 全部替换成大写的 Z,范围覆盖全部字母

3.2.5 截取

mysql> select str1, substring(str1,0,1), substring(str1,1,1) from samplestr;
+-----------+---------------------+---------------------+
| str1      | substring(str1,0,1) | substring(str1,1,1) |
+-----------+---------------------+---------------------+
| opx       |                     | o                   |
| abc       |                     | a                   |
| 山田      |                     ||
| aaa       |                     | a                   |
| NULL      | NULL                | NULL                |
| @!#$%     |                     | @                   |
| ABC       |                     | A                   |
| aBC       |                     | a                   |
| abc太郎   |                     | a                   |
| abcdefabc |                     | a                   |
| micmic    |                     | m                   |
+-----------+---------------------+---------------------+
11 rows in set (0.00 sec)

我们可以看到这个字符串的下标是从 1 开始的

第一个参数是字符串列,第二个参数是起点,第三个参数是长度

3.2.6 索引截取

mysql> SELECT SUBSTRING_INDEX('www.mysql.com', '.', 2);
+------------------------------------------+
| SUBSTRING_INDEX('www.mysql.com', '.', 2) |
+------------------------------------------+
| www.mysql                                |
+------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT SUBSTRING_INDEX(SUBSTRING_INDEX('www.mysql.com', '.', 2), '.', -1);
+--------------------------------------------------------------------+
| SUBSTRING_INDEX(SUBSTRING_INDEX('www.mysql.com', '.', 2), '.', -1) |
+--------------------------------------------------------------------+
| mysql                                                              |
+--------------------------------------------------------------------+
1 row in set (0.00 sec)

这个函数也挺神奇的,大概意思应该是先按照分隔符分割

然后分割结果的下标是从 1 开始的,然后根据长度获取分割结果

3.2.7 重复

mysql> select str1, repeat(str1,3) as repeat_str1 from samplestr;
+-----------+-----------------------------+
| str1      | repeat_str1                 |
+-----------+-----------------------------+
| opx       | opxopxopx                   |
| abc       | abcabcabc                   |
| 山田      | 山田山田山田                |
| aaa       | aaaaaaaaa                   |
| NULL      | NULL                        |
| @!#$%     | @!#$%@!#$%@!#$%             |
| ABC       | ABCABCABC                   |
| aBC       | aBCaBCaBC                   |
| abc太郎   | abc太郎abc太郎abc太郎       |
| abcdefabc | abcdefabcabcdefabcabcdefabc |
| micmic    | micmicmicmicmicmic          |
+-----------+-----------------------------+
11 rows in set (0.00 sec)

简单传个参数,规定好重复次数就可以了

3.3 日期函数

mysql> SELECT CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP;
+--------------+--------------+---------------------+
| CURRENT_DATE | CURRENT_TIME | CURRENT_TIMESTAMP   |
+--------------+--------------+---------------------+
| 2022-05-25   | 16:42:35     | 2022-05-25 16:42:35 |
+--------------+--------------+---------------------+
1 row in set (0.00 sec)

mysql> SELECT CURRENT_TIMESTAMP as now,
    -> EXTRACT(YEAR   FROM CURRENT_TIMESTAMP) AS year,
    -> EXTRACT(MONTH  FROM CURRENT_TIMESTAMP) AS month,
    -> EXTRACT(DAY    FROM CURRENT_TIMESTAMP) AS day,
    -> EXTRACT(HOUR   FROM CURRENT_TIMESTAMP) AS hour,
    -> EXTRACT(MINUTE FROM CURRENT_TIMESTAMP) AS MINute,
    -> EXTRACT(SECOND FROM CURRENT_TIMESTAMP) AS second;
+---------------------+------+-------+------+------+--------+--------+
| now                 | year | month | day  | hour | MINute | second |
+---------------------+------+-------+------+------+--------+--------+
| 2022-05-25 16:42:48 | 2022 |     5 |   25 |   16 |     42 |     48 |
+---------------------+------+-------+------+------+--------+--------+
1 row in set (0.01 sec)

mysql> 

current 前缀的相关函数可以获得当前的日期、时间以及时间戳

extract 函数是可以从时间戳当中分离出年份、月份、分钟、秒

3.4 转换函数

先来看一看类型转换

mysql> SELECT CAST('0001' AS SIGNED INTEGER) AS int_col,
    ->  CAST('2009-12-14' AS DATE) AS date_col;
+---------+------------+
| int_col | date_col   |
+---------+------------+
|       1 | 2009-12-14 |
+---------+------------+
1 row in set (0.00 sec)

这是直接用 cast 函数,标明类型就好

然后还有一个,转换 null 值的,我们之前也提到过,coalesce

mysql> select str3, coalesce(str3,'Zeta') from samplestr;
+--------+-----------------------+
| str3   | coalesce(str3,'Zeta') |
+--------+-----------------------+
| NULL   | Zeta                  |
| NULL   | Zeta                  |
| 是我   | 是我                  |
| NULL   | Zeta                  |
| NULL   | Zeta                  |
| NULL   | Zeta                  |
| NULL   | Zeta                  |
| NULL   | Zeta                  |
| ABC    | ABC                   |
| ABC    | ABC                   |
| I      | I                     |
+--------+-----------------------+
11 rows in set (0.00 sec)

4. 谓词

先来看看表,我们之前建好的

mysql> select * from samplelike;
+--------+
| strcol |
+--------+
| abcdd  |
| abcddd |
| abddc  |
| abdddc |
| ddabc  |
| dddabc |
+--------+
6 rows in set (0.00 sec)

mysql> 

谓词函数的返回值一般都是布尔型,例如 true、false、unknown

4.1 like

这个函数主要用于字符串的查询匹配

我们在这里使用 %,它代表零个或者多个任意字符

mysql> SELECT *
    -> FROM samplelike
    -> WHERE strcol LIKE 'd%';
+--------+
| strcol |
+--------+
| ddabc  |
| dddabc |
+--------+
2 rows in set (0.00 sec)

例如这里选取的是以字符 d 开头的行

我们还可以使用下划线 _ 来代表任意一个字符

mysql> SELECT *
    -> FROM samplelike
    -> WHERE strcol LIKE 'abdd_';
+--------+
| strcol |
+--------+
| abddc  |
+--------+
1 row in set (0.01 sec)

mysql> SELECT *
    -> FROM samplelike
    -> WHERE strcol LIKE 'abdd%';
+--------+
| strcol |
+--------+
| abddc  |
| abdddc |
+--------+
2 rows in set (0.01 sec)

4.2 between

那这个就是很显然的范围查询,包含临界值

mysql> -- 选取销售单价为100~ 1000元的商品
mysql> SELECT product_name, sale_price
    -> FROM product
    -> WHERE sale_price BETWEEN 100 AND 1000;
+--------------+------------+
| product_name | sale_price |
+--------------+------------+
| T恤          |       1000 |
| 打孔器       |        500 |
| 叉子         |        500 |
| 擦菜板       |        880 |
+--------------+------------+
4 rows in set (0.00 sec)

4.3 null 判断

那这个就是 is null 和 is not null

然后 isnull() 好像还是个函数来着

可以直接在查询语句当中包裹列的那种

我记得 null 值是可以和其他值比较的,但是默认是最小的

然后不能用 == 来判断 null 值,所以要用这个 is null

mysql> SELECT product_name, purchase_price
    -> FROM product
    -> WHERE purchase_price IS NULL;
+--------------+----------------+
| product_name | purchase_price |
+--------------+----------------+
| 叉子         |           NULL |
| 圆珠笔       |           NULL |
+--------------+----------------+
2 rows in set (0.00 sec)

4.4 in

如果我们要查询在指定范围的数据,可能第一反应是between,或者用大于、小于去调整

但是这个都是连续的范围,如果是离散的,那可能是多个的 == 用 or 连接

好在我们这里还有 in,我感觉非常像 python,判定值在不在列表中

mysql> SELECT product_name, purchase_price
    -> FROM product
    -> WHERE purchase_price IN (320, 500, 5000);
+--------------+----------------+
| product_name | purchase_price |
+--------------+----------------+
| T恤          |            500 |
| 打孔器       |            320 |
| 高压锅       |           5000 |
+--------------+----------------+
3 rows in set (0.00 sec)

这个例子就是,查询价格为320、500或者5000的记录

而且,我们还可以使用 not 来取反

此外,in 还可以用在子查询当中

假设我们需要取出大阪在售商品的销售单价

mysql> SELECT product_name, sale_price
    -> FROM product
    -> WHERE product_id IN (
    ->   SELECT product_id
    ->   FROM shopproduct
    ->   WHERE shop_id = '000C'
    ->   )
    -> ;
+--------------+------------+
| product_name | sale_price |
+--------------+------------+
| 运动T恤      |       4000 |
| 菜刀         |       3000 |
| 叉子         |        500 |
| 擦菜板       |        880 |
+--------------+------------+
4 rows in set (0.00 sec)

这个看起来就更像是联表查询了

我们得先去 shopproduct 表,根据 shop_id = ‘000C’,取出大阪所售商品的 id

然后再去 product 表当中,把这些 id 的记录挑选出来,再展示指定的列

直觉上来看好像关联子查询的样子,但是仔细想想并不是

先说这里吧,这里的感觉是类似于标量子查询,只不过返回的是一个列表的感觉

但是关联子查询当中,那个返回结果是带有一定的关系在里面的

之前关联子查询部分举的例子是,查询售价比本类均价高的商品

那简单来说,子查询拿到了每个类别的均价,如果只是这个的话,好像没法比较

因为我们每读到一个商品,希望的比较的都是该商品与其所属类别均价的大小

所以外查询的 where 语句,要找到那个类别对应的均价去比较,也就是对应关系

那么在关联子查询的返回结果当中,我们是包含了这个关系的

我们在子查询当中嵌入了 p1.product_type = p2.product_type 的语句

也就是返回的结果当中是包含了这么一个对应关系,供外查询的 where 调用的

4.5 exist

这个东西的作用就是 “判断是否存在满足某种条件的记录”

要求和上面一样,都是选出来大阪售卖货物的价格

mysql> SELECT product_name, sale_price
    ->   FROM product AS p
    ->  WHERE EXISTS (SELECT *
    ->                  FROM shopproduct AS sp
    ->                 WHERE sp.shop_id = '000C'
    ->                   AND sp.product_id = p.product_id);
+--------------+------------+
| product_name | sale_price |
+--------------+------------+
| 运动T恤      |       4000 |
| 菜刀         |       3000 |
| 叉子         |        500 |
| 擦菜板       |        880 |
+--------------+------------+
4 rows in set (0.00 sec)

哦吼吼,说什么来什么,那这不就是关联子查询嘛

我们可以看到在子查询当中,从 shopproduct 表选出大阪商店

然后 and 指定 sp.product_id = p.product_id,选出对应的 id 映射关系

然后把这个关系传给外查询,外查询用 exist 来处理映射关系

而且,exist 只关心记录是否存在,所以可能不太注意选出的列

于是,我们在子查询 select 的列可以不用那么严谨

但是,一般来说,我们还是选 * 比较稳妥

5. case表达式

就是类似于 switch 一样的条件分支

首先来看看最常规的,根据某一字段的不同值,对不同记录进行不同处理

mysql> SELECT  product_name,
    ->         CASE WHEN product_type = '衣服' THEN CONCAT('A : ',product_type)
    ->              WHEN product_type = '办公用品'  THEN CONCAT('B : ',product_type)
    ->              WHEN product_type = '厨房用具'  THEN CONCAT('C : ',product_type)
    ->              ELSE NULL
    ->         END AS abc_product_type
    ->   FROM  product;
+--------------+--------------------+
| product_name | abc_product_type   |
+--------------+--------------------+
| T恤          | A : 衣服          |
| 打孔器       | B : 办公用品      |
| 运动T恤      | A : 衣服          |
| 菜刀         | C : 厨房用具      |
| 高压锅       | C : 厨房用具      |
| 叉子         | C : 厨房用具      |
| 擦菜板       | C : 厨房用具      |
| 圆珠笔       | B : 办公用品      |
+--------------+--------------------+
8 rows in set (0.00 sec)

如上,我们根据商品种类的不同,对商品类别进行了不同的处理

然后,还可以实现类似行转列的效果,或者说是在列上展示聚合结果

mysql> -- 对按照商品种类计算出的销售单价合计值进行行列转换
mysql> SELECT SUM(CASE WHEN product_type = '衣服' THEN sale_price ELSE 0 END) AS sum_price_clothes,
    ->        SUM(CASE WHEN product_type = '厨房用具' THEN sale_price ELSE 0 END) AS sum_price_kitchen,
    ->        SUM(CASE WHEN product_type = '办公用品' THEN sale_price ELSE 0 END) AS sum_price_office
    ->   FROM product;
+-------------------+-------------------+------------------+
| sum_price_clothes | sum_price_kitchen | sum_price_office |
+-------------------+-------------------+------------------+
|              5000 |             11180 |             5500 |
+-------------------+-------------------+------------------+
1 row in set (0.01 sec)

第一列只选衣服,第二列选厨具,在不同的列展示对不同类别的记录做处理

我感觉这个很像是子查询,每个 sum 里面用子查询过滤一个类别出来

我们也可以对上述操作进行进一步延展,就是相对泛化的行转列操作

B.练习题

B.1

运算中含有 NULL 时,运算结果是否必然会变为NULL ?

字符串、数值类含有 null 的运算都是 null

B.2

对本章中使用的 product(商品)表执行如下 2 条 SELECT 语句,能够得到什么样的结果呢?

SELECT product_name, purchase_price
FROM product
WHERE purchase_price NOT IN (500, 2800, 5000);

这应该是选售价非500、2800、5000的物品名称和售价

SELECT product_name, purchase_price
FROM product
WHERE purchase_price NOT IN (500, 2800, 5000, NULL);

这个在上一步的基础上还加了个 null

但是好像不行吧,null 只能用 is null 这种的

这里的感觉是类似于 == 这种的

mysql> SELECT product_name, purchase_price
    ->   FROM product
    ->  WHERE purchase_price NOT IN (500, 2800, 5000);
+--------------+----------------+
| product_name | purchase_price |
+--------------+----------------+
| 打孔器       |            320 |
| 擦菜板       |            790 |
+--------------+----------------+
2 rows in set (0.00 sec)

mysql> SELECT product_name, purchase_price
    ->   FROM product
    ->  WHERE purchase_price NOT IN (500, 2800, 5000, NULL);
Empty set (0.00 sec)

果然

B.3

按照销售单价( sale_price )对练习 3.6 中的 product(商品)表中的商品进行如下分类。

低档商品:销售单价在1000日元以下(T恤衫、办公用品、叉子、擦菜板、 圆珠笔)
中档商品:销售单价在1001日元以上3000日元以下(菜刀)
高档商品:销售单价在3001日元以上(运动T恤、高压锅)
请编写出统计上述商品种类中所包含的商品数量的 SELECT 语句,结果如下所示。

执行结果
low_price | mid_price | high_price
----------±----------±-----------
5 | 1 | 2

mysql> SELECT SUM(CASE WHEN purchase_price < 1000 THEN 1 ELSE 0 END) AS low_price,
    ->        SUM(CASE WHEN purchase_price < 1000 OR ISNULL(purchase_price) THEN 1 ELSE 0 END) AS low_price2,
    ->        SUM(CASE WHEN purchase_price > 1000 AND purchase_price < 3000 THEN 1 ELSE 0 END) AS mid_price,
    ->        SUM(CASE WHEN purchase_price > 3000 THEN 1 ELSE 0 END) AS high_price
    ->   FROM product;
+-----------+------------+-----------+------------+
| low_price | low_price2 | mid_price | high_price |
+-----------+------------+-----------+------------+
|         3 |          5 |         2 |          1 |
+-----------+------------+-----------+------------+
1 row in set (0.00 sec)

mysql> 

可能是我的数据库改了?反正看结果应该是这个意思,但是和题中对不上

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值