“范老湿从不会SQL”

于是又到了梧桐絮飘满校园的四月,又到了梧桐色相簿的季节,我们迎来了实习生招聘。

“范老湿从不写代码”,“范老湿从不会SQL”,说起来都是泪。去年就因为SQL挂了面了一下午的X团,然而今年仍然不会。近来又因为C能力低下挂掉了N多OJ,满屏AC的日子一去不复返。所幸现在拯救也许还不晚,那让我们来愉快的治疗它吧~

参考书籍是Ben Forta的《SQL必知必会》

一、SQL相关概念

  • 数据库(database):保存有组织的数据的容器(通常是一个文件或一组文件);
  • 表(table):某种特定类型数据的结构化清单;
    每一个数据库中的表都有一个唯一的表名
  • 模式:关于数据库和表的布局及特性的信息;
  • 列(column):表中的一个字段;
    所有表都是由一个或多个列组成的
    每一个表列都有一个数据类型
  • 行(row):表中的一个纪录;
    有时称纪录(record),行才是正确的术语
  • 主键(primary key):一(组)列,其值存在且唯一,故能标识表中的每一行;
    主键列的值一般不允许修改与更新,且不能重用(即被删除行的值删除后不能用于以后的值)
范老湿总结:每一个database里面可能有很多table,既然是table那一定有column和row,起唯一标识作用的column被称为primary key。Primary key中的值一定符合以下性质:存在性、唯一性、不可复用性。

以及:

  • 关键字:即SQL中的保留字,不能用做表或列的名字。
    SQL支持以下三种注释:

#, --, /* */

二、SELECT

2.1 SELECT的基本使用

SELECT, like its name, 就是从表中选择数据嘛。那怎样才是SELECT的正确姿势呢?

SELECT prod_name FROM Products;

相信机智的你一眼就能看出,这是从Products这个表中选择了prod_name这个column。

从上面那条SQL语句中,可以(或者不可以)看出以下几点:

  • SQL语句中,结束要加分号(半角的不用说了吧);
  • SQL语句不区分大小写(看当地规范了,这个例子就是关键字大写,表名首字母大写,其它小写);
  • SQL语句中,不区分空格与回车(所以上面那四个词语写每行一个也没关系,你开心就好);
  • SQL语句的输出,并不一定遵循某些顺序。

那这样呢:

SELECT prod_id, prod_name, prod_price FROM Products;

当然就是从Products这个表中同时选择了prod_id, prod_name, prod_price这三列。
在语句中,列名之间以逗号分隔。

这样呢:

SELECT * FROM Products;

这样就是从Products里面选择了所有的column。

有时候,我们需要去重:

SELECT DISTINCT vend_id FROM Products;

这样,输出的vend_id就实现了去重
需要注意的是,DISTINCT作用于所有的列,而并非跟在后面的那一列,比如:

SELECT DISTINCT vend_id, prod_price FROM Products;

我们也可以对输出的内容作出限制,比如我们想取prod_name中的TOP 5:

SELECT prod_name FROM Products LIMIT 5;

(不同SQL实现的本条语句,语法可能不同,本文以MySQL为准,下同)
如果想看后面的呢?

SELECT prod_name FROM Products LIMIT 5 OFFSET 5;

这样就返回了Products中prod_name这列从第5行起的5行数据。

2.2 排序:ORDER BY

可是,就像我们之前说的,SQL的输出默认是没有顺序的。如果你问我啊,我可以说一句“无可奉告”。但是看你们这么热情啊,一句话不说也不好。所以说啊,我们来学习一个,如何才能让SQL的输出有顺序:

SELECT prod_name FROM Products ORDER BY prod_name;

这样的话,就是按照prod_name的字母顺序升序排列了。
需要注意的是,ORDER BY子句应被保证为SELECT语句中的最后一条子句。

不过这样只能按一个列排序啊Orz如果我们想按多个列排序呢?

SELECT prod_id, prod_price, prod_name FROM Products ORDER BY prod_price, prod_name;

可是这样写语句是不是有点太长了呢?其实这样也可以的:

SELECT prod_id, prod_price, prod_name FROM Products ORDER BY 2, 3;

就可以达到和上面语句一样的效果,其实就是从那三个查看的列里面选择了第二个和第三个吧。

上面的都是升序,那如果我们想降序排列呢?

SELECT prod_id, prod_price, prod_name FROM Products ORDER BY prod_price DESC;

要降序排列的列要后面加上DESC,反过来说,就是DESC只作用于它前面的那一列:

SELECT prod_id, prod_price, prod_name FROM Products ORDER BY prod_price DESC, prod_name;

这样,prod_price列就是降序的,prod_name列则呈升序排列。

2.3 数据过滤:WHERE

但是这样的话,似乎还是不够方便啊。如果我们想查看某一个列的值符合特定规律的数据呢?这样我们就用到WHERE

SELECT prod_name, prod_price FROM Products WHERE prod_price = 3.49;

这样就输出了所有 prod_price值为3.49的prod_name和prod_price

WHERE字句都资瓷哪些操作符呢?

=, <>, !=, <, <=, !<, >, >=, !>, BETWEEN, IS NULL

SQL有个特点,就是非常直观,这些符号的含义也就不用做过多解释了。

SQL还资瓷很多花式的过滤方法!是的!就算过滤条件更复杂一些也没关系!比如像这样:

SELECT prod_id, prod_price, prod_name FROM Products WHERE vend_id = 'DLL01' AND prod_price <= 4;

既然资瓷了AND,你问我资不资瓷OR?当然资瓷!我就这么明确告诉你:

SELECT prod_id, prod_price, prod_name FROM Products WHERE vend_id = 'DLL01' AND vend_id = 'BRS01';

既然说到ANDOR,那么不可避免的就是出现逻辑运算顺序的问题了。SQL优先处理AND运算符。在需要指定顺序时,也可以使用括号:

SELECT prod_name, prod_price FROM Products WHERE (vend_id = 'DLL01' OR vend_id = 'BRS01') AND prod_price >= 10;

以及ORDED BY子句,一般是放在WHERE后面。

其实OR操作也可以这样实现

SELECT prod_name, prod_price FROM Products WHERE vend_id IN ('DLL01', 'BRS01') ORDER BY prod_name;

而且,IN的操作一般比OR更快哦~而且IN还可以用来实现更多更灵活的操作!

那么既然有了ANDOR,自然而然就有了NOT

SELECT prod_name FROM Products WHERE NOT vend_id = 'DLL01' ORDER BY prod_name;

2.4 更灵活的过滤:LIKE

这样我们就学会了过滤,但是似乎好像资瓷的过滤方式还太少啊Orz连字符串匹配都不资瓷(就你事多(误

好好好,这就说怎样才是资瓷更高级过滤条件的正确姿势:

SELECT prod_id, prod_name FROM Products WHERE prod_name LIKE 'Fish%';

SELECT prod_id, prod_name FROM Products WHERE prod_name LIKE '%bean bag%';

就是过滤出prod_name里面以Fish开头或者含有bean bag的数据。

也可以这样:

SELECT prod_id, prod_name FROM Products WHERE prod_name LIKE 'F%y';

过滤出prod_name中以F开头以y结尾的数据。

这样存在一个问题:很多DBMS会在字符串后自动添加空格,以达到和字符串类型定义时相等的长度。

以及LIKE '%'不会匹配NULL。

当然,都说了是“更灵活的”过滤姿势,怎么能只有%一种?

SELECT prod_id, prod_name FROM Products WHERE prod_name LIKE ‘__ inch teddy bear';

_操作符相较于%更加严格?感觉这么说也不好,算是用法不同吧。%是匹配字符串,而一个_则是匹配单个字符,不许多也不许少。

有时候我们还会遇到要匹配的字符有多种可能性的情况,除了用OR,还有什么更优雅的方案么?

SELECT cust_contact FROM Customers WHERE cust_contact LIKE ‘[JM]%' ORDER BY cust_contact;

这样就匹配了cust_contact里面以J或M开头的数据。同样的,[]也只是对应单个字符。

如果我们想否定,可以用NOT,当然也可以用更优雅的^

SELECT cust_contact FROM Customers WHERE cust_contact LIKE ‘[^JM]%' ORDER BY cust_contact;

主要的通配符就是%, _[]。不幸的是通配符一般速度会比较慢,建议不要的过度使用,使用的时候也尽量放在搜索模式的后面,以减少检索时间。

2.5 计算字段与格式化输出

看来仅仅简单的把数据输出还不行啊,有时候我们还需要将数据按照一定格式输出,或者做一些数值计算。说白了就是字符串操作和数值计算嘛。比如这样:

SELECT Concat(vend_name, ' (', vend_country, ')') FROM vendors ORDER BY vend_name;

即在vend_country字段两侧加了括号。Concat则是起连接作用的函数。

我们前面说过,有的DBMS(比如MySQL)会在字符串末尾补全空格。这时就需要用RTrim函数解决:

SELECT Concat(RTrim(vend_name), ' (', RTrim(vend_country), ')') FROM vendors ORDER BY vend_name;

除了去除右边空格的RTrim以外,还有去掉左边空格的LTrim和左右空格的Trim

有时候我们要把新生成的部分起一个名字,也就是别名:

SELECT Concat(RTrim(vend_name), ' (', RTrim(vend_country), ')') AS vend_title FROM vendors ORDER BY vend_name;

字符串的计算就是上面那样,那么如何进行数学计算呢?

SELECT prod_id, quantity, item_price, quantity*item_price AS expanded_price FROM orderitems WHERE order_num = 20005;

这样就计算了数量和单价的乘积,并将其别名设为expanded_price。

2.6 函数运算

是的,既然资瓷一般的字符串运算,那么更应该资瓷一下更复杂的函数运算,上一节我们提到了Trim等函数这一届里面我们简要的把函数分为文本处理函数、日子时间处理函数和数值处理函数。

首先说文本处理函数

函数 说明
Left() 返回串左边的字符
Length() 返回串的长度
Locate() 找出串的一个子串
Lower() 将串转化为小写
LTrim() 去掉串左边的空格
Right() 返回串右边的字符
RTrim() 去掉串右边的空格
Soundex() 返回串的SOUNDEX值
SubString() 返回子串的字符
Upper() 将串转化为大写

其中SOUNDEX的含义需要解释一下,简单来说就是读音相似的,比如:

SELECT cust_name, cust_contact FROM Customers WHERE SOUNDEX(cust_contact) = SOUNDEX('Michael Green')

cust_contact里面如果有'Michelle Green'这样读音相似的,就会返回。

接下来是日期时间处理函数:

函数 说明
AddDate() 增加一个日期
AddTime() 增加一个事件
CurDate() 返回当前日期
CurTime() 返回当前时间
Date() 返回一个日期时间的日期部分
DateDiff() 计算两个日期之差
Date_Add() 高度灵活的日期运算函数
Date_Format() 返回一个格式化的日期或时间串
Day() 返回一个日期的天数部分
DayOfWeek() 返回一个日期是星期几
Hour() 返回一个时间的小时部分
Minute() 返回一个时间的分钟部分
Month() 返回一个日期的月份部分
Now() 返回当前的日期和时间
Second() 返回一个时间的秒部分
Time() 返回一个日期时间的时间部分
Year() 返回一个日期的年份部分

例子:

SELECT cust_id, order_num FROM orders WHERE Date(order_date) BETWEEN '2005-09-01' AND '2005-09-30';

即匹配的日期在2005年9月1日到2005年9月30日。需要注意的是,order_date是一个datetime类型,对它进行date部分的匹配时要用Date函数截取其date部分。

最后是数值处理函数:

函数 说明
Abs() 返回一个数的绝对值
Cos() 返回一个角度的余弦
Exp() 返回一个数的指数值
Mod() 返回除操作的余数
Pi() 返回圆周率
Rand() 返回一个随机数
Sin() 返回一个角度的正弦
Sqrt() 返回一个数的平方根
Tan() 返回一个角度的正切

这个不用多说了吧

2.7 聚集函数

上面说的是对于一些数值的计算。有时候我们做数据分析时需要关注整个Dataset中某个特征的宏观特点。(说人话:比如平均值、最大值最小值等)

函数 说明
Avg() 返回某列的平均值
Count() 返回某列的行数
Max() 返回某列的最大值
Min() 返回某列的最小值
Sum() 返回某列值之和

下面分别举例说明:

SELECT AVG(prod_price) AS avg_price FROM products WHERE vend_id = 1003;

即得出vend_id为1003的产品的平均价格。

需要注意的是,Avg函数将自动忽略值为NULL的行。

Count函数也是类似

SELECT COUNT(cust_email) AS num_cust FROM Customers;

也将返回cust_email不为NULL的行数。Min,Max,Sum同理。

我们前面提到过用DISTINCT去重,这里也是一样:

SELECT AVG(DISTINCT prod_price) AS avg_price FROM products WHERE vend_id = 1003;

得到的是每个商品的单价去重之后的平均价格。

聚集函数也可以组合使用:

SELECT COUNT(*) AS num_items, MIN(prod_price) AS price_min, MAX(prod_price) AS price_max, AVG(prod_price) AS price_avg FROM products;

2.8 分组

在前面的例子中,我们知道,如果想得到某一个vend_id的num_prods数据,我们可以这样:

SELECT COUNT(*) AS num_prods FROM products WHERE vend_id = 1003;

如果我们想同时查看所有vend_id的num_prods,就需要分组处理:

SELECT vend_id, COUNT(*) AS num_prods FROM products GROUP BY vend_id;

输出为两列,一列是排序后的vend_id,一个是每个vend_id对应的num_prods数。

GROUP BY子句中需要注意的是:

  • GROUP BY子句可以包含任意数目的列,因而可以对分组进行嵌套。这种情况下数据将在最后指定的分组上进行汇总;
  • GROUP BY子句中的每一列都必须是检索列或有效的表达式;
  • 大多数SQL实现不允许GROUP BY列带有长度可变的数据类型;
  • 除聚集计算语句外,SELECT语句中的每一列都必须在GROUP BY子句中给出;
  • 如果分组列中包含具有NULL值的行,则NULL将作为一个单独的分组;
  • GROUP BY分组必须出现在WHERE子句之后,ORDER BY子句之前。

除了用GROUP BY分组以外,SQL还支持过滤分组。这时候我们要用到HAVING子句:

SELECT cust_id, COUNT(*) AS orders FROM Orders GROUP BY cust_id HAVING COUNT(*) >= 2;

这样就过滤出了订单数>=2的分组。

  • HAVINGWHERE的区别:可以简要的理解为HAVING在数据分组前进行过滤,WHERE在数据分组后进行过滤。

有些时候,HAVINGWHERE也可以同时使用:

SELECT vend_id, COUNT(*) AS num_prods FROM Products WHERE prod_price >= 4 GROUP BY vend_id HAVING COUNT(*) >= 2

WHERE子句先是过滤出了prod_price至少为4的行,HAVING子句又过滤出计数为2或2以上的分组。

既然分组支持过滤,那么一定支持排序:

SELECT order_num, COUNT(*) AS items FROM OrderItems GROUP BY order_num HAVING COUNT(*) >= 3;

如上过滤出了订单数>=3的order_num。

SELECT order_num, COUNT(*) AS items FROM OrderItems GROUP BY order_num HAVING COUNT(*) >= 3 ORDER BY items, order_num;

加上一个ORDER BY子句,即按items、order_num从小到大排序。

到现在,我们总结一下SELECT子句使用的顺序:

子句 说明 是否必须使用
SELECT 要返回的列或表达式
FROM 从中检索数据的表 仅在表中选择数据时使用
WHERE 行级过滤
GROUP BY 分组说明 仅在按组计算聚集时使用
HAVING 组级过滤
ORDER BY 输出排序顺序
2.9 子查询

SQL还允许子查询,即查询中嵌套的查询。

例如如下两个SQL查询:

SELECT order_num FROM OrderItems WHERE prod_id = 'RGAN01';
SELECT cust_id FROM Orders WHERE order_num IN (20007, 20008);

有两个表,OrderItems和Orders。OrderItems中保存的是order_num和prod_id信息,而Orders中保存的是cust_id和order_num的信息。如果我们想根据prod_id查找cust_id的相关信息,除了使用两次查找,还可以使用如下方式:

SELECT cust_id FROM Orders WHERE order IN (SELECT order_num FROM OrderItems WHERE prod_id = 'RGAN01');

除此之外,还可以作为计算字段使用子查询。

SELECT cust_name, cust_state, (SELECT COUNT(*) FROM Orders WHERE Orders.cust_id = Customers.cust_id) AS orders FROM Customers ORDER BY cust_name;

你可能感兴趣的:(“范老湿从不会SQL”)