SQL必知必会笔记
SQL必知必会全书一共22章,涵盖了数据库增删改查的操作,但是可能是定义为入门书籍,所以书籍中的内容还是比较简单的,其中就用了14章来着重的介绍了查的部分,增删改都是指用一个或半个章节来介绍,不过对于刚刚开始学习数据库的新手来说,了解认识数据库也足够了,后续可以通过其他的书籍来了解数据库的其他高级特性。
要了解数据库的操作,实践也是很重要的,书中附录A给出了样例脚本,大家可以到 http://www.forta.com/books/0672336073/TeachYourselfSQL_MySQL.zip
地址下载MySQL的样例,我学习使用的也是MySQL数据库。然后按照里面的readme进行操作就行了。
ps: 因为只是简单记录一下数据库的操作,所以会带有一些例子,但是不会具体给出例子执行后的答案,建议大家构建上面的数据库然后具体执行一下看看检索的结果,会更加的清晰。
1、SELECT操作
1.1 基本句式
SELECT操作的基本句式都是SELECT FROM,即从哪个数据库表中查找什么数据,例如
1 |
SELECT * FROM Customers; |
就表明从Customers表中查找所有的数据。当然不推荐一直使用SELECT *,因为查找所有的数据会导致数据库性能下降,耗费资源。如果要检索一个列,就在SELECT关键字后面加上需要检索的列就行了,如果要检索多个列,在SELECT后加上这几个列,列名与列名之间用逗号分隔开,例如:
1 |
SELECT prod_id, prod_name, prod_price FROM Products; |
这样就能检索出prod_id、prod_name和prod_price三个列了。
另外SQL语句是不区分大小写的,在上面的语句中,你使用全部大写、全部小写或者大小写混合,结果都是一样的。
1.2 DISTINCT关键字
DISTINCT关键字如它的意思一样,是用来合并查询过程中出现的相同列的。举例说,就是如果你只检索一列,然后这一列中有六条数据,分别是A,B,B,C,C,C,六条数据。如果你希望SELECT查询帮你将两条相同的B和三条相同的C合并为一条,可以通过DISTINCT关键字实现。DISTINCT关键字放在需要查询的列名前,例如:
1 |
SELECT DISTINCT vend_id FROM Products; |
从Products表中查询vend_id列,DISTINCT表明合并相同的列。
1.3 LIMIT && OFFSET关键字
LIMIT关键字
LIMIT关键表示限制SELECT返回的行数,例如LIMIT 5表示SELECT结果最多只能返回5行。
OFFSET关键字表示SELECT返回的数据从第几行开始输出显示,例如OFFSET 4表示和没有用OFFSET关键字限定的SELECT查询比,带OFFSET的查询会从第4行开始显示,前面的3行的数据就跳过了。
1 |
SELECT num FROM Alpha LIMIT 5; |
ps: Alpha表示我自己添加用于记录的,里面只有一列num,内容就是大写的因为字母,A,B,C,D,E……
上面的例子中,如果没有LIMIT 5的话就会列出num列中的所有数据,但是加上LIMIT 5后就只会列出5行。
OFFSET关键字
1 |
SELECT num FROM Alpha LIMIT 2 OFFSET 3; |
上面的例子中,如果没有LIMIT和OFFSET限制时,输出是A,B,C,D,E,F,G…..的话,那么LIMIT 2表明只输出两行,OFFSET 3跳过前面三行,所以这里输出的是D,E
这里要注意的是,OFFSET是不能单独使用的,需要和LIMIT一同使用。
1.4 数据排序
ORDER BY 关键字
SELECT检索出来的数据一般都是乱序的,没有特定的规律。但是我们平时检索的时候一般会有这样的需求,我们希望某列的数据按升序排序好分析趋势。排序用到的关键字是ORDER BY后面跟需要排序的列名。例如
1 |
SELECT num FROM Alpha ORDER BY num; |
上面的SELECT语句检索出来后就会默认按照升序进行排列,如果想按照降序排列,那么可以在最后添加DESC关键字:
DESC 关键字
1 |
SELECT num FROM Alpha ORDER BY num DESC; |
这样,上面的语句就会按照降序排列了,J、I、H…..
当然也可以按照多个列进行排序,只需要在ORDER BY后用逗号分隔多个列就好了。数据库会首先按照ORDER BY后第一顺位的列进行排序,如果该列存在相同的内容,在按照第二顺位的列进行排序,一次类推。例如:
多列排序
1 |
SELECT prod_id, prod_price, prod_name FROM Products ORDER BY prod_price, prod_name; |
上面的例子将首先按照prod_price进行排序,如果prod_price内容相同,再按照prod_name排序。
按列文字排序
还有一种方式可以获得和上面一个例子一样的输出:
1 |
SELECT prod_id, prod_price, prod_name FROM Products ORDER BY 2,3; |
可以看到,ORDER BY后面通过数字2,3来分别表示prod_price和prod_name。所以这条语句的输出是和前一条语句的输出是一致的。
1.5 过滤数据
WHERE关键字
数据的过滤需要用到WHERE关键字,WHERE关键字后面跟列名及其条件,例如:
1 |
SELECT num FROM Alpha WHERE num = 'A'; |
那么这条语句通过WHERE设定了num=’A’,那么就只会输出num=’A’的那一行,如果有多行就输出多行。当然除了‘=’操作外,还包含其他的操作,如下表格:
操作符 | 说明 | 操作符 | 说明 | |
---|---|---|---|---|
= | 等于 | > | 大于 | |
<> | 不等于 | >= | 大于等于 | |
!= | 不等于 | !> | 不大于 | |
< | 小于 | BETWEEN | 指定的两个值之间 | |
<= | 小于等于 | IS NULL | 为NULL值 | |
!< | 不小于 |
上面的操作符和‘=’操作时类似的,大家多试试就会了,例如:
1 |
SELECT num FROM Alpha WHERE num > 'D'; |
上面的语句就输出E,F,G…..
组合WHERE子句
如果一个WHERE限定得出的数据还是过多的话,就可以通过组合WHERE子句来再一步限定输出,要使用WHERE子句的同时需要了解两个关键字AND和OR,在讲WHERE子句的过程中会同步讲解这两个关键字。
如果想同时让检索的数据符合两条WHERE要求的话,就需要使用AND关键字对这两个WHERE子句进行组合了:
1 |
SELECT prod_id, prod_price, prod_name FROM Products WHERE vend_id = 'DLL01' AND prod_price <= 4; |
所以上面的语句检索出来的结果就是既符合vend_id='DLL01'
又符合prod_price<=4
这两个条件的。而OR关键字就刚好相反:
1 |
SELECT prod_id, prod_price, prod_name FROM Products WHERE vend_id = 'DLL01' OR prod_price <= 4; |
上面的语句检索出来的结果是只要符合vend_id='DLL01'
或者prod_price<=4
其中之一个条件即可。
再多的WHERE子句也按照上面的内容组合即可。
AND和OR的求值顺序
只要记住这句话即可:SQL在处理OR操作符之前会优先处理AND操作符。
所以:
1 |
SELECT prod_name, prod_price FROM Products WHERE vend_id='DLL01' OR vend_id = 'BRS01' AND prod_price >= 10; |
就等同于:
1 |
SELECT prod_name, prod_price FROM Products WHERE vend_id='DLL01' OR (vend_id = 'BRS01' AND prod_price >= 10); |
所以这里推荐的是,如果既包含AND操作符和OR操作符,比较好的处理方式是按照逻辑添加圆括号()进行处理。这样阅读起来也比较流畅。
IN 操作符
除了使用WHERE组合子句,还可以通过IN操作符来限定检索条件:
1 |
SELECT prod_name, prod_price FROM Products WHERE vend_id IN ( 'DLL01', 'BRS01'); |
上面的语句表示检索的数据只要符合vend_id='DLL01'
或者 vend_id='BRS01'
其中之一个条件即可。是不是有点熟悉,其实这条语句是和下面一条语句起到相同的作用的:
1 |
SELECT prod_name, prod_price FROM Products WHERE vend_id = 'DLL01' OR vend_id = 'BRS01'; |
NOT 操作符
NOT操作符基本和不等于是类似的,只是NOT操作符是跟在WHERE后面的。
1 |
SELECT prod_name, prod_price FROM Products WHERE NOT vend_id = 'DLL01'; |
上面的语句表示检索所有vend_id列不等于DLL01的行
1.6 通配符
LIKE操作
百分号(%)通配符
%表示任何字符出现任意次数。例如:
1 |
SELECT prod_id, prod_name FROM Products WHERE prod_name LIKE 'Fish%'; |
就表示检索prod_name列以Fish开头的任意字符串的行。
1 |
SELECT prod_id, prod_name FROM Products WHERE prod_name LIKE 'F%y'; |
就表示检索prod_name列以F开头,y结尾的字符串的行。
下划线(_)通配符
下划线的用途和百分号是一样的,唯一的差别是%可以匹配任意多个字符,但是下划线值匹配一个字符,例如:
1 |
SELECT prod_id, prod_name FROM Products WHERE prod_name LIKE 'Fish_'; |
就表示检索prod_name列以Fish开头,后面跟一个任意字符的行。
方括号([])通配符
方括号([])表示用来指定一个字符串,它必须匹配指定位置(通配符的位置)的一个字符。
1 |
SELECT cuts_contact FROM Customers WHERE cust_contact LIKE '[JM]%'; |
就表示找出J或M开头的cust_contact行。
1.7 拼接字段
字段:基本上与列的意思相同,经常互换使用,不过数据库列一般称为列,而术语字段通常与计算字段一起是使用。
拼接:将值联结到一起(将一个值附加到另一个值)构造单个值。
MySQL数据库拼接字段需要通过Concat方法来进行,例如:
1 |
SELECT Concat(vend_name, ' (' , vend_country, ')') FROM Vendors ORDER BY vend_name; |
上面的语句,通过Concat方法将1)存储在vend_name列中的名字;2)包含一个空格和一个左圆括号的字符串;3)存储在vend_country列中的国家;4)包含一个右圆括号的字符串拼接在一起。
AS关键字
AS关键字用于给计算字段构建别名,例如上面一条检索语句检索出来的列的名字是Concat(vend_name, ' (' , vend_country, ')')
,非常不便于阅读,但是我们可以通过AS关键字来简化这个名字。AS的用法跟在字段的后面,AS后面跟的就是重新赋予的别名,例如:
1 |
SELECT Concat(vend_name, ' (' , vend_country, ')') AS vend_title FROM Vendors ORDER BY vend_name; |
这样就可以将检索出来的列的名字修改为vend_title了,而不是原来的Concat(vend_name, ' (' , vend_country, ')')
。
计算字段
有时候我们在一个表里面存储了一张订单的货物数量和货物单价,但是没有存储该订单的总价,这时候我们可以通过计算字段,让数据库帮我们计算总价并返回给我们。
1 |
SELECT prod_id, quantity, item_price, quantity*item_price AS total_price FROM OrderItems WHERE order_num = 20008; |
上面的语句中quantity*item_price AS total_price
表明,计算OrderItems表中20008号订单的总价,将计算后的总价显示为total_price列中。
1.8 常用函数
常用文本处理函数
函数 | 说明 |
---|---|
LEFT()(或使用子字符串函数) | 返回字符串左边的字符 |
LENGTH() (也使用DATALENGTH()或LEN() | 返回字符串的长度 |
LOWER() | 将字符串转换为小写 |
LTRIM() | 去掉字符串左边的空格 |
RIGHT() | 返回字符串右边的字符 |
RTRIM() | 去掉字符串右边的空格 |
SOUNDEX() | 返回字符串的SOUNEX值 |
UPPER() | 将字符串转换成大写 |
数值处理函数
函数 | 说明 |
---|---|
ABS() | 返回一个数的绝对值 |
COS() | 返回一个角度的余弦 |
EXP() | 返回一个数的指数值 |
PI() | 返回圆周率 |
SIN() | 返回一个角度的正弦 |
SQRT() | 返回一个数的平方根 |
TAN() | 返回一个角度的正切 |
1.9 汇总数据
函数 | 说明 |
---|---|
AVG() | 返回某列的平均值 |
COUNT() | 返回某列的行数 |
MAX() | 返回某列的最大值 |
MIN() | 返回某列的最小值 |
SUM() | 返回某列值之和 |
COUNT()函数的两种使用方式:
- 使用COUNT(*)对表中行的数目进行计算,不管表列中包含的是空值(NULL)还是非空值
- 使用COUNT(column)对特定列中具有值的行进行计算,忽略NULL值
1.10 分组数据
通过COUNT(*)可以检索出某一列包含的数量,但是却不能将相同的行合并在一起。所以,COUNT(*)统计出来的行数就包括了相同的行数了。大家是否觉得可以通过DISTINCT来合并相同的数据行,但是很可惜啊,DISTINCT是不能用于COUNT(*),但是我们可以通过GROUP BY进行合并
1 |
SELECT vend_id, COUNT(*) AS num_prods FROM Products GROUP BY vend_id; |
这样就能通过GROUP BY来将vend_id列的数据进行合并了。当然我们也可以根据条件对分组进行过滤,不过这里的过滤用的关键字就不再是WHERE了,而是HAVING,例如:
1 |
SELECT vend_id, COUNT(*) AS num_prods FROM Products GROUP BY vend_id HAVING COUNT(*) > 2; |
那么上面的语句会在前一条语句检索结果的基础上再次进行过滤,这里只会显示数量大于2的分组。
1.11 子查询
一些数据经常是分开存放在不同的表的。例如:
- 订单的编号、客户的ID和订单日期在表Orders中存储为一行
- 各个订单包含的物品存储在OrderItems表中
- 而顾客的详细信息存储在Customers表中
那么我们想知道有哪些顾客订购了’RGAN01’这个上商品,按照上面的表结构呢,我们
- 首先需要查看表OrderItems中,包含这个物品的订单编号是多少;
- 然后根据这个订单编号查询Orders表,获得对应客户的ID;
- 最后再根据这个客户ID来查询Customers表,来获取具体的客户信息
根据上面的过程,第一步使用如下查询语句:
1 |
SELECT order_num FROM OrderItems WHERE prod_id = 'RGAN01'; |
这样就能得到订单编号的ID了,之后我们将订单编号ID(例如是,20007和20008)记录下来,执行下面的查询语句:
1 |
SELECT cust_id FROM Orders WHERE order_num IN (20007, 20008); |
上面一步,我们已经通过IN关键字来简化了操作的,IN关键字的作用忘记了可以往上看。这一步就能得到cust_id(例如是,100000007和100000008)了,最后还得通过cust_id来获取用户的详细信息啊:
1 |
SELECT cust_name, cust_contact FROM Customers WHERE cust_id IN ('1000000007', '1000000008'); |
经过了上面三步,终于获取到了用户名字cust_name和联系方式了cust_contact,真是艰难。首先要经过三个步骤不说,还要记录中间产生的记录,真是不方便。
鉴于上面的过程,所以我们需要使用子查询来简化上面的操作,其实子查询也没有什么特别的地方,查询的需求和上面的类似,我们直接看查询语句:
1 2 3 4 |
SELECT cust_name, cust_contact FROM Customers WHERE cust_id IN ( SELECT cust_id FROM Orders WHERE order_num IN ( SELECT order_num FROM OrderItems WHERE prod_id = 'RGAN01') ); |
因为SQL会将中间的换行和空格去掉,所以上面的语句只是为了好看而做的分行处理。这样一看,其实就是讲三个查询语句组合成一个而已,没什么特别的地方。
用作计算字段的子查询
如果要统计Customers表汇总每个顾客的订单数,需要现获取Customers表中所有的顾客信息,然后再到Orders表中统计订单数。这也是两个查询的过程,而不同的是这里需要统计的是总数。不只是单纯的查询,但是还是可以通过计算字段的子查询来达到。
1 2 3 4 |
SELECT cust_name, cust_state, ( SELECT COUNT(*) FROM Orders WHERE Orders.cust_id = Customers.cust_id) AS orders FROM Customers ORDER BY cust_name; |
上面的SELECT COUNT(*) FROM Orders WHERE Orders.cust_id = Customers.cust_id
计算字段的子查询作为列存在。
1.12 联结
如果数据存储在多个表中,想要用一条SELECT语句将检索出数据的话,就需要用到联结了。简单说,联结是一种机制,用来在一条SELECT语句中关联表,一次称为联结。
创建联结很简单,指定需要联结的所有表以及关联它们的方式即可。例如:
1 |
SELECT vend_name, prod_name, prod_price FROM Vendors, Products WHERE Vendors.vend_id = Products.vend_id; |
这个SELECT语句和之前所有的语句一样都指定了要检索的列。最大的差别是这些列是来自两个表的,分别是Vendors和Products。它们就是这条SELECT语句联结的两个表的名字。这两个表使用WHERE子句正确的联结,如果没有WHERE子句来进行限定的话,那么得到的行数量解释Vendors表中的行数乘以Products表中的行数。
内联结
上面介绍的联结方式称为等值联结,它是基于两个表之间的相等测试的,这种联结也称为内联结。这种联结还有不同的语法可以使用:
1 |
SELECT vend_name, prod_name, prod_price FROM Venders INNER JOIN Products ON Vendors.vend_id = Products.vend_id; |
上面的语句中,两个表的关系是通过INNER JOIN指定的部分FROM子句,在使用这种语法时,联结条件用特定的ON子句而不是WHERE子句来指出。
另外,联结的表不宜过多,否则数据库的性能会下降的很快。
1.13 组合
组合就是通过UNION来组合多条SQL查询。利用UNION可以给出多条SELECT语句,将他们的结果组合成一个结果集。
大家可以试一试在一个查询里面执行两个SELECT语句的话,最后只会显示最后一条SELECT查询的结果,其他的结果都被最后的查询给覆盖了。所以如果需要通过UNION来组合查询。
用法如下:
1 2 3 |
SELECT cust_name, cust_contact, cust_email FROM Customers WHERE cust_state IN ('IL', 'IN', 'MI') UNION SELECT cust_name, cust_contact, cust_email FROM Customers WHERE cust_name = 'Fun4All'; |
UNION指示数据库执行这两条SELECT语句,并把输出组合成一个查询结果集。
另外UNION从查询结果集中自动删除重复的行的;换句话说,它的行为和一条SELECT语句中使用多个WHERE子句条件一样。如果想让他返回所有的匹配的行,可以通过UNION ALL关键字:
1 2 3 |
SELECT cust_name, cust_contact, cust_email FROM Customers WHERE cust_state IN ('IL', 'IN', 'MI') UNION ALL SELECT cust_name, cust_contact, cust_email FROM Customers WHERE cust_name = 'Fun4All'; |
那么符合上面两个SELECT的所有语句都会被列出,包括检索重复的部分。
2、INSERT操作
插入数据由两种方式:
1 2 3 4 5 6 7 8 9 10 |
INSERT INTO Customers VALUES ( '1000000006', 'Toy Land', '123 Any Street', 'New York', 'NY', '11111', 'USA', NULL, NULL); |
或者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
INSERT INTO Customers ( cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email) VALUES ( '1000000006', 'Toy Land', '123 Any Street', 'New York', 'NY', '11111', 'USA', NULL, NULL); |
上面两个语句的差别可以看出,第一种方法依赖创建表时SQL语句字段的次序,要是后面有在数据库表中添加字段或者修改字段排序的话,那么第一种INSERT INTO方法就很有可能会报错了,因为和它匹配的数据库表的字段次序已经发生该表了。
而第二种方法在执行INSERT INTO时已经提供了字段名,就算表的结构改变,这条INSERT语句还是可以继续使用的。
其次,使用第二种方法,我们还可以只进行插入部分列的操作。
从另一个表复制到另一个表
在MySQL中通过下面的语句可以进行表的复制操作:
1 2 |
CREATE TABLE CustCopy AS SELECT * FROM Customers; |
3、UPDATE操作
有两种使用UPDATE的方式:
- 更新表中的特定行
- 更行表中的所有行
基本的UPDATE语句由三部分组成:
- 要更新的表
- 列名和它们的新值
- 确定要更新的哪些行的过滤条件
例如:
1 2 3 4 |
UPDATE Customers SET cust_contact = 'Sam Roberts', WHERE cust_id = '1000000006'; |
4、删除数据
相比之前的UPDATE操作,DELETE就更加简单了:
1 2 |
DELETE FROM Customers WHERE cust_id = '100000006'; |
5、 创建表
利用CREATE TABLE创建表,必须给出以下信息:
1 2 3 4 5 6 7 8 |
CREATE TABLE Products ( prod_id CHAR(10) NOT NULL, vend_id CHAR(10) NOT NULL, prod_name CHAR(254) NOT NULL, prod_price DECIMAL(8, 2) NOT NULL, prod_desc TEXT NULL ); |
6、更新表
通过ALTER TABLE进行表的更新,添加列可以:
1 |
ALTER TABLE Vendors ADD vend_phone CHAR(20); |
或者,删除列:
1 |
ALTER TABLE Vendors DROP COLUMN vend_phone; |
7、删除表
1 |
DROP TABLE CustCopy; |