CategoryResourceRepost/极客时间专栏/SQL必知必会/第一章:SQL语法基础篇/07丨什么是SQL函数?为什么使用SQL函数可能会带来问题?.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

236 lines
14 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="07丨什么是SQL函数为什么使用SQL函数可能会带来问题" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ec/7c/ec8520300d297469edd1231abef7167c.mp3"></audio>
函数在计算机语言的使用中贯穿始终在SQL中我们也可以使用函数对检索出来的数据进行函数操作比如求某列数据的平均值或者求字符串的长度等。从函数定义的角度出发我们可以将函数分成内置函数和自定义函数。在SQL语言中同样也包括了内置函数和自定义函数。内置函数是系统内置的通用函数而自定义函数是我们根据自己的需要编写的下面讲解的是SQL的内置函数。
你需要从以下几个方面掌握SQL函数
1. 什么是SQL函数
1. 内置的SQL函数都包括哪些
1. 如何使用SQL函数对一个数据表进行操作比如针对一个王者荣耀的英雄数据库我们可以使用这些函数完成哪些操作
1. 什么情况下使用SQL函数为什么使用SQL函数有时候会带来问题
## 什么是SQL函数
当我们学习编程语言的时候,也会遇到函数。函数的作用是什么呢?它可以把我们经常使用的代码封装起来,需要的时候直接调用即可。这样既提高了代码效率,又提高了可维护性。
SQL中的函数一般是在数据上执行的可以很方便地转换和处理数据。一般来说当我们从数据表中检索出数据之后就可以进一步对这些数据进行操作得到更有意义的结果比如返回指定条件的函数或者求某个字段的平均值等。
## 常用的SQL函数有哪些
SQL提供了一些常用的内置函数当然你也可以自己定义SQL函数。SQL的内置函数对于不同的数据库软件来说具有一定的通用性我们可以把内置函数分成四类
1. 算术函数
1. 字符串函数
1. 日期函数
1. 转换函数
这4类函数分别代表了算术处理、字符串处理、日期处理、数据类型转换它们是SQL函数常用的划分形式你可以思考下为什么是这4个维度
函数是对提取出来的数据进行操作,那么数据表中字段类型的定义有哪几种呢?
我们经常会保存一些数值,不论是整数类型,还是浮点类型,实际上对应的就是数值类型。同样我们也会保存一些文本内容,可能是人名,也可能是某个说明,对应的就是字符串类型。此外我们还需要保存时间,也就是日期类型。那么针对数值、字符串和日期类型的数据,我们可以对它们分别进行算术函数、字符串函数以及日期函数的操作。如果想要完成不同类型数据之间的转换,就可以使用转换函数。
### 算术函数
算术函数,顾名思义就是对数值类型的字段进行算术运算。常用的算术函数及含义如下表所示:
<img src="https://static001.geekbang.org/resource/image/19/e1/193b171970c90394576d3812a46dd8e1.png" alt="">
这里我举一些简单的例子,你来体会下:
`SELECT ABS(-2)`运行结果为2。
`SELECT MOD(101,3)`运行结果2。
`SELECT ROUND(37.25,1)`运行结果37.3。
### 字符串函数
常用的字符串函数操作包括了字符串拼接,大小写转换,求长度以及字符串替换和截取等。具体的函数名称及含义如下表所示:
<img src="https://static001.geekbang.org/resource/image/c1/4d/c161033ebeeaa8eb2436742f0f818a4d.png" alt=""><br>
这里同样有一些简单的例子,你可以自己运行下:
`SELECT CONCAT('abc', 123)`运行结果为abc123。
`SELECT LENGTH('你好')`运行结果为6。
`SELECT CHAR_LENGTH('你好')`运行结果为2。
`SELECT LOWER('ABC')`运行结果为abc。
`SELECT UPPER('abc')`运行结果ABC。
`SELECT REPLACE('fabcd', 'abc', 123)`运行结果为f123d。
`SELECT SUBSTRING('fabcd', 1,3)`运行结果为fab。
### 日期函数
日期函数是对数据表中的日期进行处理,常用的函数包括:
<img src="https://static001.geekbang.org/resource/image/3d/45/3dec8d799b1363d38df34ed3fdd29045.png" alt="">
下面是一些简单的例子,你可自己运行下:
`SELECT CURRENT_DATE()`运行结果为2019-04-03。
`SELECT CURRENT_TIME()`运行结果为21:26:34。
`SELECT CURRENT_TIMESTAMP()`运行结果为2019-04-03 21:26:34。
`SELECT EXTRACT(YEAR FROM '2019-04-03')`运行结果为2019。
`SELECT DATE('2019-04-01 12:00:05')`运行结果为2019-04-01。
这里需要注意的是DATE日期格式必须是yyyy-mm-dd的形式。如果要进行日期比较就要使用DATE函数不要直接使用日期与字符串进行比较我会在后面的例子中讲具体的原因。
### 转换函数
转换函数可以转换数据之间的类型,常用的函数如下表所示:
<img src="https://static001.geekbang.org/resource/image/5d/59/5d977d747ed1fddca3acaab33d29f459.png" alt=""><br>
这两个函数不像其他函数,看一眼函数名就知道代表什么、如何使用。下面举了这两个函数的例子,你需要自己运行下:
`SELECT CAST(123.123 AS INT)`,运行结果会报错。
`SELECT CAST(123.123 AS DECIMAL(8,2))`运行结果为123.12。
`SELECT COALESCE(null,1,2)`运行结果为1。
CAST函数在转换数据类型的时候不会四舍五入如果原数值有小数那么转换为整数类型的时候就会报错。不过你可以指定转化的小数类型在MySQL和SQL Server中你可以用`DECIMAL(a,b)`来指定其中a代表整数部分和小数部分加起来最大的位数b代表小数位数比如`DECIMAL(8,2)`代表的是精度为8位整数加小数位数最多为8位小数位数为2位的数据类型。所以`SELECT CAST(123.123 AS DECIMAL(8,2))`的转换结果为123.12。
## 用SQL函数对王者荣耀英雄数据做处理
我创建了一个王者荣耀英雄数据库一共有69个英雄23个属性值。SQL文件见Github地址[https://github.com/cystanford/sql_heros_data](https://github.com/cystanford/sql_heros_data)。
<img src="https://static001.geekbang.org/resource/image/7b/24/7b14aeedd80fd7e8fb8074f9884d6b24.png" alt=""><br>
我们现在把这个文件导入到MySQL中你可以使用Navicat可视化数据库管理工具将.sql文件导入到数据库中。数据表为heros然后使用今天学习的SQL函数对这个英雄数据表进行处理。
首先显示英雄以及他的物攻成长,对应字段为`attack_growth`。我们让这个字段精确到小数点后一位需要使用的是算术函数里的ROUND函数。
```
SQLSELECT name, ROUND(attack_growth,1) FROM heros
```
代码中,`ROUND(attack_growth,1)`中的`attack_growth`代表想要处理的数据“1”代表四舍五入的位数也就是我们这里需要精确到的位数。
运行结果为:
<img src="https://static001.geekbang.org/resource/image/fb/ed/fb55a715543e1ed3245ae37210ad75ed.png" alt=""><br>
假设我们想显示英雄最大生命值的最大值就需要用到MAX函数。在数据中“最大生命值”对应的列数为`hp_max`,在代码中的格式为`MAX(hp_max)`。
```
SQLSELECT MAX(hp_max) FROM heros
```
运行结果为9328。
假如我们想要知道最大生命值最大的是哪个英雄,以及对应的数值,就需要分成两个步骤来处理:首先找到英雄的最大生命值的最大值,即`SELECT MAX(hp_max) FROM heros`,然后再筛选最大生命值等于这个最大值的英雄,如下所示。
```
SQLSELECT name, hp_max FROM heros WHERE hp_max = (SELECT MAX(hp_max) FROM heros)
```
运行结果:
<img src="https://static001.geekbang.org/resource/image/93/20/9371fdcee4d1f7bdfdd71bc0a58aac20.png" alt="">
假如我们想显示英雄的名字,以及他们的名字字数,需要用到`CHAR_LENGTH`函数。
```
SQLSELECT CHAR_LENGTH(name), name FROM heros
```
运行结果为:
<img src="https://static001.geekbang.org/resource/image/41/8c/415aa09e2fdc121861e3c96bd8a2af8c.png" alt="">
假如想要提取英雄上线日期对应字段birthdate的年份只显示有上线日期的英雄即可有些英雄没有上线日期的数据不需要显示这里我们需要使用EXTRACT函数提取某一个时间元素。所以我们需要筛选上线日期不为空的英雄即`WHERE birthdate is not null`,然后再显示他们的名字和上线日期的年份,即:
```
SQL SELECT name, EXTRACT(YEAR FROM birthdate) AS birthdate FROM heros WHERE birthdate is NOT NULL
```
或者使用如下形式:
```
SQL: SELECT name, YEAR(birthdate) AS birthdate FROM heros WHERE birthdate is NOT NULL
```
运行结果为:
<img src="https://static001.geekbang.org/resource/image/26/16/26cacf4d619d9f177a1f5b22059f9916.png" alt="">
假设我们需要找出在2016年10月1日之后上线的所有英雄。这里我们可以采用DATE函数来判断birthdate的日期是否大于2016-10-01即`WHERE DATE(birthdate)&gt;'2016-10-01'`,然后再显示符合要求的全部字段信息,即:
```
SQL SELECT * FROM heros WHERE DATE(birthdate)&gt;'2016-10-01'
```
需要注意的是下面这种写法是不安全的:
```
SELECT * FROM heros WHERE birthdate&gt;'2016-10-01'
```
因为很多时候你无法确认birthdate的数据类型是字符串还是datetime类型如果你想对日期部分进行比较那么使用`DATE(birthdate)`来进行比较是更安全的。
运行结果为:
<img src="https://static001.geekbang.org/resource/image/e5/22/e5696b5ff0aae0fd910463b1f8e6ed22.png" alt="">
假设我们需要知道在2016年10月1日之后上线英雄的平均最大生命值、平均最大法力和最高物攻最大值。同样我们需要先筛选日期条件即`WHERE DATE(birthdate)&gt;'2016-10-01'`,然后再选择`AVG(hp_max), AVG(mp_max), MAX(attack_max)`字段进行显示。
```
SQL SELECT AVG(hp_max), AVG(mp_max), MAX(attack_max) FROM heros WHERE DATE(birthdate)&gt;'2016-10-01'
```
运行结果为:
<img src="https://static001.geekbang.org/resource/image/8f/6b/8f559dc1be7d62e4c58402ebe2e7856b.png" alt="">
## 为什么使用SQL函数会带来问题
尽管SQL函数使用起来会很方便但我们使用的时候还是要谨慎因为你使用的函数很可能在运行环境中无法工作这是为什么呢
如果你学习过编程语言就会知道语言是有不同版本的比如Python会有2.7版本和3.x版本不过它们之间的函数差异不大也就在10%左右。但我们在使用SQL语言的时候不是直接和这门语言打交道而是通过它使用不同的数据库软件即DBMS。DBMS之间的差异性很大远大于同一个语言不同版本之间的差异。实际上只有很少的函数是被DBMS同时支持的。比如大多数DBMS使用||)或者(+来做拼接符而在MySQL中的字符串拼接函数为`Concat()`。大部分DBMS会有自己特定的函数这就意味着采用SQL函数的代码可移植性是很差的因此在使用函数的时候需要特别注意。
## 关于大小写的规范
细心的人可能会发现我在写SELECT语句的时候用的是大写而你在网上很多地方包括你自己写的时候可能用的是小写。实际上在SQL中关键字和函数名是不用区分字母大小写的比如SELECT、WHERE、ORDER、GROUP BY等关键字以及ABS、MOD、ROUND、MAX等函数名。
不过在SQL中你还是要确定大小写的规范因为在Linux和Windows环境下你可能会遇到不同的大小写问题。
比如MySQL在Linux的环境下数据库名、表名、变量名是严格区分大小写的而字段名是忽略大小写的。
而MySQL在Windows的环境下全部不区分大小写。
这就意味着如果你的变量名命名规范没有统一,就可能产生错误。这里有一个有关命名规范的建议:
1. 关键字和函数名称全部大写;
1. 数据库名、表名、字段名称全部小写;
1. SQL语句必须以分号结尾。
虽然关键字和函数名称在SQL中不区分大小写也就是如果小写的话同样可以执行但是数据库名、表名和字段名在Linux MySQL环境下是区分大小写的因此建议你统一这些字段的命名规则比如全部采用小写的方式。同时将关键词和函数名称全部大写以便于区分数据库名、表名、字段名。
## 总结
函数对于一门语言的重要性毋庸置疑我们在写Python代码的时候会自己编写函数也会使用Python内置的函数。在SQL中使用函数的时候需要格外留意。不过如果工程量不大使用的是同一个DBMS的话还是可以使用函数简化操作的这样也能提高代码效率。只是在系统集成或者在多个DBMS同时存在的情况下使用函数的时候就需要慎重一些。
比如`CONCAT()`是字符串拼接函数在MySQL和Oracle中都有这个函数但是在这两个DBMS中作用却不一样`CONCAT`函数在MySQL中可以连接多个字符串而在Oracle中`CONCAT`函数只能连接两个字符串,如果要连接多个字符串就需要用(||)连字符来解决。
<img src="https://static001.geekbang.org/resource/image/8c/c9/8c5e316b466e8fa65789a9c6a220ebc9.jpg" alt=""><br>
讲完了SQL函数的使用我们来做一道练习题。还是根据王者荣耀英雄数据表请你使用SQL函数作如下的练习计算英雄的最大生命平均值显示出所有在2017年之前上线的英雄如果英雄没有统计上线日期则不显示。
欢迎你在评论区与我分享你的答案,也欢迎点击”请朋友读“,把这篇文章分享给你的朋友或者同事。