“范老湿从不会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;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容