【长文干货】MySQL必知必会——增删改查知识总结

1.准备工作

1.1使用wampserver安装本地服务器

1.2安装Navicat(交互式的mysql控制软件):
Navicat for mysql破解

1.3下载本文所需数据库文件
MySQL Crash Course

1.4登录你的mysql

1.5将下载的sql文件导入到本地mysql服务器
https://blog.csdn.net/zoroday/article/details/54809042

2.上手使用Mysql

2.1 查看你的数据库和表

查看所有可用的数据库,返回一个数据库列表

SHOW DATABASES;

选择数据库

USE crashcourse;

查看当前数据库中所有可用的表

SHOW TABLES;

查看某个表中的所有列(相当于SPSS中的变量视图)

SHOW COLUMNS FROM customers;

【长文干货】MySQL必知必会——增删改查知识总结_第1张图片

讲解:Field是列名,Type是数据类型(括号中的是数据长度),Null是是否允许Null,Key是键信息(如主键),Default是默认值,Extra是其他信息(如自动增量)

2.2 其他可能会用到的SHOW语句

SHOW STATUS        --显示服务器状态信息
SHOW GRANTS        --显示用户的安全权限
SHOW ERRORS        --显示服务器错误
SHOW WARNINGS      --显示服务器警告信息

2.3 SQL基础知识汇总

2.3.1 子句(clause) SQL语句由子句构成,有些子句是必须的有些是可选的,如SELECT语句的FROM子句是必须的,ORDER BY子句是可选的。
2.3.2 SQL中完全不区分大小写,对表名,列名也是。
2.3.3 操作符(operator),用来联结或改变WHERE子句中的子句的关键字。也叫逻辑操作符(logical operator)
2.3.4 通配符(wildcard):用来匹配值的一部分的特殊字符
2.3.5 谓词(predicate):从技术上说,LIKE其实是谓词而不是操作符。
2.3.6 字段(field):基本上与列(column)意思相同,经常互换使用,不过数据库列一般称为列,而术语字段常用在计算字段的连接上。
2.3.7 别名(alias):是一个字段或值的替代名
2.3.8 聚集函数(aggregate function):运行在行组上,计算和返回单个值的函数。
2.3.9 外键(foreign key):外键为某个表中的一列,它包含另一个表的主键值,定义了两个表间的关系。

3.检索数据

3.1 基本按列检索语句

SELECT prod_name FROM products;      --检索一列
SELECT prod_id, prod_name, prod_price FROM products;      --检索多列
SELECT * FROM products;      --检索所有列

3.2 检索唯一值(去除重复项)

SELECT DISTINCT vend_id FROM products;

注意: 如果DISTICT后面带有多列,如SELECT DISTINCT vend_id, prod_price,DISTINCT不是只应用于后面接的第一列,而是后面的所有列。如这个例子中,除非两行的vend_id和prod_price都相同才会去除重复项,vend_id相同而prod_price不同的两行都会被检索出来。

3.3 用LIMIT限制结果,仅返回第一行或前几行

SELECT prod_name FROM products LIMIT 5;           --指示MySQL返回不多于5行
SELECT prod_name FROM products LIMIT 3, 5;        --返回从行3开始的5行
SELECT prod_name FROM products LIMIT 5 OFFSET 3;        --返回从行3开始的5行,等价于上面

注意:
①第一行为行0而不是行1
②LIMIT中指定的行数为最大数,如果没有足够的行,Mysql就会返回它能返回的那么多行。

3.4 使用完全限定的表名

SELECT products.prod_name FROM crashcourse.products;

代码中限定了行所属的表名和表所属的数据库名,这个与3.1中的语句等价,后面会介绍限定表名的作用。

3.5 对检索数据排序

SELECT prod_name FROM products ORDER BY prod_name;
SELECT prod_name FROM products ORDER BY prod_id;       --使用非检索列用于排序是完全合法的

SELECT prod_id, prod_price, prod_name FROM products 
ORDER BY prod_price, prod_name;               --按多个列排序

SELECT prod_price, prod_name FROM products ORDER BY prod_price DESC;  --降序排列

SELECT prod_id, prod_price, prod_name FROM products 
ORDER BY prod_price DESC, prod_name;    --按多列排序时,DESC只作用于前面的列名

注意:如果想在多个列上使用降序排序,则需要为每个列指定DESC关键字
与DESC相反的是ASC关键字,代表升序,它是默认选项。
在字典(dictionary)排序顺序中,A和a是相同的顺序,是Mysql的默认行为,但是这个是可以被DBA设置的,如果需要改变这种排序,靠ORDER BY无法做到只能找DBA。

3.5.1 使用ORDER BY 和LIMIT结合找到一列中的最高值或最低值

SELECT prod_price FROM products ORDER BY prod_price DESC LIMIT 1;

LIMIT 必须位于ORDER BY之后

4. 过滤数据

4.1 基础WHERE子句

SELECT prod_name, prod_price FROM products WHERE prod_price < 10;
SELECT prod_name, prod_price FROM products WHERE prod_name = 'fuses';
SELECT * FROM products WHERE prod_price BETWEEN 5 AND 10;     --BETWEEN包括起始值结束值
SELECT cust_id FROM customers WHERE cust_email IS NULL;
SELECT cust_id FROM customers WHERE cust_email IS NOT NULL;

WHERE子句操作符包括=, <>, !=, BETWEEN,<, <=等;
WHERE在ORDER BY之前
将值与字符串比较时,字符串要加单引号
过滤数据时,即使在选择不具有特定值得行时(如!=),也不会返回具有NULL的值,因此在过滤数据时,一定要验证返回数据中确实给出了被过滤列具有NULL值的行。

4.2 通过操作符组合WHERE子句

操作符(operator),用来联结或改变WHERE子句中的子句的关键字。

4.2.1 AND和OR操作符

SELECT * FROM products WHERE vend_id = 1003 AND prod_price <= 10;
SELECT * FROM products WHERE vend_id = 1002 OR vend_id = 1003;

-- 当AND和OR同时存在时,AND优先级大于OR,但是推荐用()限定顺序:
SELECT * FROM products WHERE (vend_id = 1002 OR vend_id = 1003) AND prod_price >=10;

4.2.2 IN和NOT操作符

-- 选择vend_id为1002或1003的所有行:
SELECT * FROM products WHERE vend_id IN (1002, 1003) ORDER BY prod_name;
-- 选择vend_id不为1002或1003的所有行:
SELECT * FROM products WHERE vend_id NOT IN (1002, 1003) ORDER BY prod_name;

上面的代码中,IN的作用等价于OR,但是更推荐用IN,因为如果选项更多时IN更简洁清晰,而且执行更快,同时IN还可以包含其他SELECT子句(后面介绍)
NOT只有一个功能:否定它后面所跟的任何条件
Mysql中支持NOT对IN, BETWEEN和EXISTS子句取反,而多数其他DBMS支持对各种条件取反。

4.3 构造通配符搜索模式进行过滤

通配符(wildcard):用来匹配值的一部分的特殊字符
搜索模式(search pattern):由字面值、通配符或两者组合成的搜索条件
在搜索子句中使用通配符必须使用LIKE操作符,LIKE指示MySQL后跟的搜索模式利用通配符而不是直接相等匹配进行比较
谓词(predicate):从技术上说,LIKE其实是谓词而不是操作符。

  • %通配符表示任何字符出现任意次数,包括0次
  • _通配符表示任何的单个字符,不能多也不能少
SELECT * FROM products WHERE prod_name LIKE 'jet%';
SELECT * FROM products WHERE prod_name LIKE '%anvil%';
SELECT * FROM products WHERE prod_name LIKE 's%e';

SELECT * FROM products WHERE prod_name LIKE '_ ton anvil';

注意
①如果Mysql配置为区分大小写的话,结果将会不一样
②注意尾部空格,如果被搜索项后面带有一个或多个空格时,那么将匹配不到,解决方法是在搜索模式后也加一个%,更好的办法是通过函数去掉首尾空格
③%无法匹配NULL值
④通配符搜索比其他搜索处理时间更长,因此可以用其他代替尽量用其他代替,而且除非必要否则不要讲通配符置于搜索模式的开始处,因为这样是最慢的。

4.4 用正则表达式进行搜索

正则表达式(Regular Expression)是用来匹配文本的特殊的串(字符集合)

4.4.1 REGEXP与LIKE的对比
正则表达式中使用REGEXP关键字,它告诉MySQL后面所跟的东西为正则表达式。

REGEXP与LIKE的区别在于,LIKE会匹配整个列值,如果被匹配的文本在列值中出现,LIK将不会找到它,相应的行也不会返回(除非使用通配符),而REGEXP是在列值内匹配,如果被匹配的文本在列值中出现,REGEXP将会找到它,相应的行将会被返回。

SELECT prod_name FROM products WHERE prod_name LIKE '1000';
SELECT prod_name FROM products WHERE prod_name REGEXP '1000';;

如上方的代码,LIKE语句不会返回任何行,REGEXP则会返回’JetPack 1000’
不过REGEXP也可以使用^和$定位符用来匹配整个列值。

4.4.2 几个基本字符

. :匹配任意一个字符
| :正则表达式的OR操作符
[] :匹配几个字符之一,另一种形式的OR操作符
- :结合[]匹配范围

-- 返回JetPack 1000和JetPack 2000
SELECT prod_name FROM products WHERE prod_name REGEXP '.000';
SELECT prod_name FROM products WHERE prod_name REGEXP '1000|2000';

-- 匹配1 ton 或 2 ton 或 3ton
SELECT prod_name FROM products WHERE prod_name REGEXP '[123] ton'

-- 匹配1 ton 或 2 ton 或 3ton 或 4 ton 或 5 ton  等价于'[12345] ton'
SELECT prod_name FROM products WHERE prod_name REGEXP '[1-5] ton'

4.4.3 转义(escaping)字符 \\

对于特殊字符. [] | -,如果需要匹配这些字符,就需要通过\\进行转义,\\-表示查找-\\.表示查找.\\\转义自身,表示查找\
其他可以转义的元字符、
【长文干货】MySQL必知必会——增删改查知识总结_第2张图片
4.4.4 匹配字符类

字符类(character class):有时候需要检索出我们需要的数字、所有字母字符或所有数字字母字符等的匹配,我们可以使用预定义的字符集,称为字符类;如下:
【长文干货】MySQL必知必会——增删改查知识总结_第3张图片
4.4.5 匹配多个实例

有时候需要对匹配的数目进行更强的控制,比如:寻找所有的数,不管数中包含多少数字,或寻找一个单词并尾随一个s(如果存在)等情况,我们可以利用正则表达式中的重复元字符来完成;如下
【长文干货】MySQL必知必会——增删改查知识总结_第4张图片
4.4.6 定位符

有时候为了匹配特定位置的文本,需要使用定位符,常用定位符列表如下:
【长文干货】MySQL必知必会——增删改查知识总结_第5张图片
4.4.7 举例

mysql> SELECT prod_name FROM products WHERE prod_name REGEXP '\\([0-9] sticks?\\)';

+----------------+
| prod_name      |
+----------------+
| TNT (1 stick)  |
| TNT (5 sticks) |
+----------------+

\\对括号进行转义,[0-9]表示匹配任意数字,stick?匹配stick和sticks

mysql> SELECT prod_name FROM products WHERE prod_name REGEXP '[[:digit:]]{4}';
+--------------+
| prod_name    |
+--------------+
| JetPack 1000 |
| JetPack 2000 |
+--------------+

[:digit:]匹配任意数字,因而它为一个数字的集合。{4}则确切地要求它前面的字符(任意数字)出现4次,所以上述表达式的意思是匹配连在一起的任意4位数字。

mysql> SELECT prod_name FROM products WHERE prod_name REGEXP '^[0-9\\.]';
+--------------+
| prod_name    |
+--------------+
| .5 ton anvil |
| 1 ton anvil  |
| 2 ton anvil  |
+--------------+

上述表达式希望找出一个以一个数(包括以小数点开始的数)开始的产品。

注意: ^在集合中还可以用于否定该集合。

5. 计算字段和数据处理函数

5.1 计算字段

如果想在一个字段中既显示公司名,又显示公司地址,但这两个信息一般在一张表的两个列中
有时候我们需要直接从数据库中检索出转换、计算或格式化过的数据,而不是先检索出原始数据然后在客户端重新格式化,这就是计算字段发挥作用的所在了。计算字段是运行时在SELECT语句内创建的
字段(field):基本上与列(column)意思相同,经常互换使用,不过数据库列一般称为列,而术语字段常用在计算字段的连接上。

5.1.1 拼接字段/别名

拼接(concatenate):将值联结到一起构成单个值。
别名(alias):是一个字段或值的替代名,除了可以用在计算字段中外,常见的用途还包括在实际的表列名包含不符合规定的字符(如空格)时重新命名它,在原来的名字容易误解时扩充它,等。

mysql> SELECT Concat(vend_name, '(', vend_country, ')') AS vend_title
       FROM vendors ORDER BY vend_name;
+------------------------+
| vend_title             |
+------------------------+
| ACME(USA)              |
| Anvils R Us(USA)       |
| Furball Inc.(USA)      |
+------------------------+

Mysql中使用Concat函数拼接两个列(注意多数DBMS使用+或||拼接)

5.1.2 执行算术计算
计算字段的另一常见用途是对检索出的数据进行算术计算,例如我们需要根据数量和单价计算总价:

mysql> SELECT prod_id, quantity, item_price, quantity*item_price AS expanded_price
    -> FROM orderitems WHERE order_num = 20005;
+---------+----------+------------+----------------+
| prod_id | quantity | item_price | expanded_price |
+---------+----------+------------+----------------+
| ANV01   |       10 | 5.99       | 59.90          |
| ANV02   |        3 | 9.99       | 29.97          |
| TNT2    |        5 | 10         | 50.00          |
| FB      |        1 | 10         | 10.00          |
+---------+----------+------------+----------------+
4 rows in set

5.2 数据处理函数

虽然函数可移植性不强,但多数SQL支持以下类型的函数:
用于处理文本串的文本函数(如删除或填充值,转换大小写);
对数值数据进行算术操作的数值函数(如返回绝对值,平方根);
处理日期和时间值并提取特定成分的日期&时间函数(如返回日期之差,年,月);
返回DBMS正使用的特定信息的系统函数(如返回用户登录信息,检查版本)。

5.2.1 文本处理函数

mysql> SELECT vend_name, Upper(vend_name) AS vend_name_upcase
    -> FROM vendors ORDER BY vend_name;
+----------------+------------------+
| vend_name      | vend_name_upcase |
+----------------+------------------+
| ACME           | ACME             |
| Anvils R Us    | ANVILS R US      |
+----------------+------------------+

5.2.2 日期和时间处理函数
日期和时间采用了特定的数据类型和格式存储,以便能快速和有效的排序和过滤,并节省物理存储空间。
在分析数据时,通常我们都需要用日期对数据进行过滤,这里要注意的是,日期的标准格式永远都是yyyy-mm-dd。

mysql> SELECT * FROM orders WHERE order_date = '2005-09-01';
mysql> SELECT * FROM orders WHERE Date(order_date) = '2005-09-01';
+-----------+---------------------+---------+
| order_num | order_date          | cust_id |
+-----------+---------------------+---------+
|     20005 | 2005-09-01 00:00:00 |   10001 |
+-----------+---------------------+---------+

上述代码中,两条语句都会返回同样的结果,但是在通过日期进行过滤时,建议使用Date()函数,因为它们只有在时间值都为00:00:00的时候才等价。而当某个值为2005-09-01 11:30:05时,上面的语句就无法匹配出来。
Date()函数会提取指定列的日期部分,类似的是Time()函数提取指定列的时间部分。

-- 以下两行代码都可以提取时间为2005年9月的所有订单
SELECT * FROM orders WHERE Date(order_date) BETWEEN '2005-09-01' AND '2005-09-30';
SELECT * FROM orders WHERE Year(order_date)=2005 AND Month(order_date)=09;

5.2.3 数值处理函数
主要用于代数、三角或几何运算,如Abs(), Cos(), Exp()等。

关于更多常用函数可以查看这篇博客:https://blog.csdn.net/yyc794990923/article/details/77228304

5.3 聚集函数

很多时候,我们需要的是表中数据的汇总值而不是实际数据本身,那么返回实际数据的列表就是对时间和处理资源的浪费,因此我们可以使用SQL的聚集函数,MySQL中包含五种聚集函数:COUNT(), AVG(), SUM(), MAX(), MIN(),此外还有计算标准差的聚集函数等。

聚集函数(aggregate function):运行在行组上,计算和返回单个值的函数。

-- 计算products表中所有产品的平均价格
SELECT AVG(prod_price) AS avg_price FROM products;

--返回customers表中客户的总数
SELECT COUNT(*) AS num_cust FROM customers;

--返回具有邮件地址的客户总数
SELECT COUNT(cust_email) AS num_cust FROM customers;

-- 返回列中的最大值和最小值,注意可以通过逗号组合聚集函数
SELECT MAX(prod_price) AS max_price, MIN(prod_price) AS min_price FROM products;

-- 返回指定列的总和
SELECT SUM(quantity) AS items_ordered FROM orderitems WHERE order_num = 20005;

--用SUM来合计计算值
SELECT SUM(item_price*quantity) AS total_price FROM orderitems WHERE order_num=20005;

注意:
AVG只能作用于单个列,如要获取多个列的平均值,必须使用多个AVG()函数。
AVG()忽略值为NULL的行,COUNT(*)对表行进行计数,不管列中包含的是否NULL值,COUNT(column)则忽略值为NULL的行,MAX(),MIN(),SUM()也忽略值为NULL的行。
MAX和MIN除了可以对数值取极值,对文本也是可以的。
如上面SUM的例子所示,利用标准的算术操作符,所有聚集函数都可以对多个列的计算值进行聚集。

5.3.1 聚集不同值
以上5个聚集函数,都可以在计算中指定ALL参数(默认)或DISTINCT参数。以取均值为例,ALL参数代表默认是对所有的值进行平均,DISTINCT则表示对所有的唯一值取平均。

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

5.3.2 组合聚集函数

mysql> 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;
+-----------+-----------+-----------+-----------+
| num_items | price_min | price_max | price_avg |
+-----------+-----------+-----------+-----------+
|        14 | 2.5       | 55        | 16.133571 |
+-----------+-----------+-----------+-----------+

如代码所示,可以使用SELECT语句一次执行多个聚集计算并返回多个值。

5.4 分组数据

5.4.1 通过GROUP BY 创建分组

mysql> SELECT vend_id, COUNT(*) AS num_prods FROM products GROUP BY vend_id;
+---------+-----------+
| vend_id | num_prods |
+---------+-----------+
|    1001 |         3 |
|    1002 |         2 |
|    1003 |         7 |
|    1005 |         2 |
+---------+-----------+

从上述代码可以看出,GROUP BY子句指示MySQL按vend_id排序并分组数据,这导致对每个vend_id而不是整个表计算num_prods一次。
用Excel的数据透视表来理解就是,SELECT后跟的是行名和值,GROUP BY后指定行名。

以下是一些使用GROUP BY的重要规定:

  • GROUP BY子句可以包含任意数量的列,也就是可以嵌套。
  • 如果在GROUP BY中嵌套了分组,数据将在最后规定的分组上进行汇总。
  • GROUP BY子句中列出的每个列都必须是检索列或有效的表达式(但不能是聚集函数)。如果在SELECT中使用表达式,则必须在GROUP BY中指定相同的表达式。不能使用别名。
  • 除聚集计算语句外,SELECT语句中每个列都必须在GROUP BY子句中给出。
  • 如果分组列中具有NULL值,则NULL值将作为一个分组返回。
  • GROUP BY子句必须出现在WHERE子句之后,ORDER BY子句之前。
  • 使用WITH ROLLUP可以得到每个分组及每个分组汇总级别的值。

5.4.2 使用HAVING过滤分组
在我们得到分组数据后,有时候我们并不是每个分组的数据都想要,比如我们想要列出至少有两个订单的所有顾客。为了得到这种数据,必须基于完整的分组(HAVING)而不是个别的行(WHERE)进行过滤。

HAVING支持WHERE的所有的操作符

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

上述代码返回了至少有两个订单的所有顾客列表,这里是不能使用WHERE的。
那么HAVING 和 WHERE的区别怎么理解呢:WHERE 是在数据分组前进行过滤,HAVING是在数据分组后进行过滤。也就是说WHERE排除的行不包括在分组中,这可能会改变计算值,从而影响HAVING过滤的分组结果。

事实上,WHERE和HAVING是可以同时使用的,比如我们希望返回具有数量2个以上,价格10以上的产品的供应商:

SELECT vend_id, COUNT(*) AS orders 
FROM products WHERE prod_price >=10 
GROUP BY vend_id HAVING COUNT(*) >=2;

5.4.3 SELECT子句顺序

子句 说明
SELECT 要返回的列或表达式
FROM 从中检索数据的表
WHERE 行级过滤
GROUP BY 分组
HAVING 组级过滤
ORDER BY 要输出的排序的顺序
LIMIT 要检索的行数

注意:虽然GROUP BY 的输出确实是以分组顺序输出的,但情况并不总是这样,而且也不符合SQL规范,因此在提取数据时仍然应该提供明确的ORDER BY子句,即使其效果等同于GROUP BY也是如此。

6.多表查询

6.1 子查询

查询(query):任何SQL语句都是查询。但此术语一般指SELECT语句
子查询(subquery):嵌套在其他查询中的查询

6.1.1利用子查询进行过滤

假设我们有3个表:

  • orders表存储订单号,客户ID
  • orderitems存储订单号,物品信息
  • customers 存储客户ID,客户信息

又假设我们想要列出所有订购物品TNT2的客户信息,那么可能的做法是:

  • 在orderitems中检索所有TNT2的订单号
  • 根据步骤1的订单号在orders中检索客户ID
  • 根据步骤2的客户ID在customers中检索客户信息

上述3步可以写成3条SELECT语句,把上一步骤的结果作为下一步的WHERE子句的筛选条件。
自然也可以通过子查询把3步组合成一条语句。

-- 分成三步的版本
SELECT order_num FROM orderitems WHERE prod_id = 'TNT2';
SELECT cust_id FROM orders WHERE order_num IN (20005, 20007);
SELECT cust_name, cust_contact FROM customers WHERE cust_id IN (10001, 10004);
-- 通过子查询一步完成的版本
mysql> 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 = 'TNT2'));

虽然对于能嵌套的子查询数目没有限制,但由于性能限制,不要嵌套太多
子查询中的SELECT语句必须具有和上一级的WHERE子句相同数目的列,通常都是单个列
子查询一般与IN操作符结合使用,但也可以用于测试等于(=),不等于(<>)等

6.1.2 将子查询作为计算字段
使用子查询的另一方法是创建计算字段。
例如还是对于上面的orders,orderitems,customers三个表,
假设需要查询customers中每个客户的订单总数,我们需要:

  • 从customers中检索客户列表
  • 对于检索出的每个客户,统计其在orders表中的订单数目
-- 比如我们得到客户列表后,对其中的10001客户订单进行计数
SELECT COUNT(*) AS orders FROM orders WHERE cust_id = 10001;

为了对每个客户进行COUNT(*)计算,应该将COUNT(*)作为一个子查询,如下代码所示:

mysql> SELECT cust_name,
    -> 	      cust_state,
    -> 	      (SELECT COUNT(*)
    -> 	       FROM orders
    ->         WHERE orders.cust_id = customers.cust_id) AS orders
    -> FROM customers ORDER BY cust_name;
+----------------+------------+--------+
| cust_name      | cust_state | orders |
+----------------+------------+--------+
| Coyote Inc.    | MI         |      2 |
| E Fudd         | IL         |      1 |
| Mouse House    | OH         |      0 |
| Wascals        | IN         |      1 |
| Yosemite Place | AZ         |      1 |
+----------------+------------+--------+

上述子查询对customers.cust_id中每个值都执行了一次,一共执行了5次。这种类型的子查询也叫作相关子查询,即涉及外部查询的子查询。任何时候列名有多意性,就必须使用类似WHERE orders.cust_id = customers.cust_id的语法(表名和列名由一个句点分隔)。如果不指定表名的话,结果就会完全不对。

这里要注意的是,本小节中使用的子查询方法虽然有效,但并不是最有效的方法,后面的表联结效率更高。

6.2 表联结

SQL最强大的功能之一就是能在数据检索查询的执行中联结(join)表。如何在一张表中读取数据是相对简单的,但是在真正的应用中经常需要从多个数据表中读取数据.

在能有效使用联结之前,必须明白关系型数据库和关系表的基本原理。关系表把信息分解成多个表,一类数据一个表。各表通过某些常用的值互相关联。每一个表中都有一个主键,也可能有其他表的主键,其他表的主键称之为外键,通过外键可以把两个表相关联。这么做可以减少数据重复,避免大量修改,可扩展性更好。

联结分为几种:

  • INNER JOIN(内联结,或等值联结):获取两个表中字段关系匹配的记录
  • LEFT JOIN(左联结):获取左表所有记录,即使右表没有对应匹配的记录。
  • RIGHT JOIN(右联结):获取右表所有记录,即使左表没有对应匹配的记录。
  • CROSS JOIN(交叉联结):也叫笛卡尔积,检索出行的数目是第一个表中的行数乘以第二个表中的行数。

6.2.1 内联结

mysql> SELECT vend_name, prod_name, prod_price 
	-> FROM vendors, products
    -> WHERE vendors.vend_id = products.vend_id
    -> ORDER BY vend_name, prod_name;
mysql> SELECT vend_name, prod_name, prod_price
    -> FROM vendors INNER JOIN products
    -> ON vendors.vend_id = products.vend_id;

上面两条代码使用的都是内联结,一个用的是WHERE 一个用的是 INNER JOIN & ON,它们返回的结果是相同的,但是根据规范,应该使用INNER JOIN语法。

SQL对一条SELECT语句中可以联结的表的数目没有限制,因此我们可以使用多表联结更加高效的完成上一小节用子查询完成的任务:

mysql> SELECT cust_name, cust_contact
    -> FROM customers, orders, orderitems
    -> WHERE customers.cust_id = orders.cust_id
    -> 	 AND orders.order_num = orderitems.order_num
    -> 	 AND orderitems.prod_id = 'TNT2';

6.2.2 使用表别名
前面我们已经将别名用于列名和计算字段,其实SQL还允许给表名起别名,这样有两个作用:缩短SQL语句,允许单条SELECT语句中多次使用相同的表。

例如我们将上一条代码使用别名:

mysql> SELECT cust_name, cust_contact
    -> FROM customers AS c, orders AS o, orderitems AS oi
    -> WHERE c.cust_id = o.cust_id
    ->   AND o.order_num = oi.order_num
    ->   AND oi.prod_id = 'TNT2';

表别名不仅能用于WHERE子句,还可以用于SELECT的列表,ORDER BY子句以及语句的其他部分。但要注意的是,表别名智能在查询执行中使用,与列别名不一样,表别名不返回到客户端。

6.2.3 自联结
自联结即某个表自己与自己联结,因此刚刚提到的表别名可以允许单条SELECT语句中多次使用相同的表就派上用场了。
假设你发现某个ID为DTNTR的物品存在问题,想知道这个供应商的其他物品是否也有问题。
那么你就需要:

  • 先在products表中找到DTNTR的供应商ID
  • 继续在products表中找到这个供应商的其他产品

你可以通过子查询来做到,或分两次查询也可以:

mysql> SELECT prod_id, prod_name
    -> FROM products
    -> WHERE vend_id = (SELECT vend_id
    -> 			FROM products
    -> 			WHERE prod_id = 'DTNTR');

而使用表联结的效率会更高:

mysql> SELECT p1.prod_id, p1.prod_name
    -> FROM products AS p1, products AS p2
    -> WHERE p1.vend_id = p2.vend_id
    ->   AND p2.prod_id = 'DTNTR';

6.2.4 自然联结
自然联结排除多次出现,使每个列只返回一次。
迄今为止,我们建立的内部联结都是自然联结,很可能永远也用不到不是自然联结的内联结,所以不过多介绍。

6.2.5 外联结
有时候我们需要在返回的结果包含没有关联行的那些行,这种类型的联结叫做外联结,如:列出所有客户及其订单,包括没有订单的客户

外联结使用OUTER JOIN来指定联结的类型,并且必须要使用RIGHT 或 LEFT 来指定包括其所有行的表。

  • LEFT OUTER JOIN选择左边的表所有的行
  • RIGHT OUTER JOIN选择右边的表所有的行
  • 可以简写为LEFT JOIN 或 RIGHT JOIN,因为它们默认是外联结
  • 左右联结唯一的差别是所关联表的顺序不同,两者可以颠倒后完全互换
    【长文干货】MySQL必知必会——增删改查知识总结_第6张图片【长文干货】MySQL必知必会——增删改查知识总结_第7张图片【长文干货】MySQL必知必会——增删改查知识总结_第8张图片

继续上面的例子,列出所有客户及其订单,包括没有订单的客户:

mysql> SELECT customers.cust_id, orders.order_num
    -> FROM customers LEFT OUTER JOIN orders
    -> ON customers.cust_id = orders.cust_id;
+---------+-----------+
| cust_id | order_num |
+---------+-----------+
|   10001 |     20005 |
|   10001 |     20009 |
|   10002 | NULL      |
|   10003 |     20006 |
|   10004 |     20007 |
|   10005 |     20008 |
+---------+-----------+
6 rows in set

我们可以看到,所有的客户都列出来了,包括还没有订单的客户。

6.2.6 使用带聚集函数的联结
假设我们要检索所有客户及每个客户所下的订单数:

-- 使用内联结
mysql> SELECT customers.cust_name, customers.cust_id, COUNT(orders.order_num) AS num_order
    -> FROM customers INNER JOIN orders
    -> ON customers.cust_id = orders.cust_id
    -> GROUP BY customers.cust_id;

这里要使用GROUP BY,不然就会聚集为一个数值了。

-- 使用外联结
mysql> SELECT customers.cust_name, customers.cust_id, COUNT(orders.order_num) AS num_order
    -> FROM customers LEFT JOIN orders
    -> ON customers.cust_id = orders.cust_id
    -> GROUP BY customers.cust_id;
+----------------+---------+-----------+
| cust_name      | cust_id | num_order |
+----------------+---------+-----------+
| Coyote Inc.    |   10001 |         2 |
| Mouse House    |   10002 |         0 |
| Wascals        |   10003 |         1 |
| Yosemite Place |   10004 |         1 |
| E Fudd         |   10005 |         1 |
+----------------+---------+-----------+

7.组合查询及全文本搜索

7.1使用UNION操作符

组合查询是指从一个或多个表中执行多个查询(多条SELECT语句),并将结果作为单个查询结果集返回,这些组合查询被称为并(union)。

通常有两种情况需要用到组合查询:

  • 在单个查询中从不同的表返回类似结构的数据;
  • 对单个表执行多个查询,按单个查询返回数据

其实组合查询和多个WHERE条件(通过OR)完成的工作在多数情况下相同,但在不同的查询中性能不一样,因此可以辩证的选择。

组合查询中需要用到UNION关键字,它的使用很简单,只需要在每条SELECT语句中间放上关键字UNION就可以了。

假设我们需要返回一个列表,其中包括:价格小于5的物品,和供应商10001和10002生产的所有物品(不考虑价格)。

mysql> SELECT vend_id, prod_id, prod_price
    -> FROM products
    -> WHERE prod_price < 5
    -> UNION
    -> SELECT vend_id, prod_id, prod_price
    -> FROM products
    -> WHERE vend_id IN (1001, 1002);

使用WHERE子句也能做到:

mysql> SELECT vend_id, prod_id, prod_price
    -> FROM products
    -> WHERE prod_price < 5 OR vend_id IN (1001,1002);
+---------+---------+------------+
| vend_id | prod_id | prod_price |
+---------+---------+------------+
|    1001 | ANV01   | 5.99       |
|    1001 | ANV02   | 9.99       |
|    1001 | ANV03   | 14.99      |
|    1003 | FC      | 2.5        |
|    1002 | FU1     | 3.42       |
|    1002 | OL1     | 8.99       |
|    1003 | SLING   | 4.49       |
|    1003 | TNT1    | 2.5        |
+---------+---------+------------+

在这个简单的例子中,使用UNION可能比WHERE更复杂,但在更复杂的过滤条件或者从哪个多个表检索数据时,使用UNION可能会更简单。

使用UNION的规则:
①UNION必须由两条或以上的SELECT语句组成,语句间用关键字UNION分隔
②UNION中每个查询必须包含相同的列,表达式或聚集函数(不过顺序不一定要一致)
③列数据类型必须兼容:虽然不必完全相同,但必须是DBMS可以隐含地转换的类型

7.2 包含或取消重复的行

在上面的查询中,其实有一条数据是同时满足两个条件的,而UNION默认会把重复的行给取消掉,所以本来第一条SELECT语句返回4行,第二条返回5行,而最终只返回8行。

如果想返回所有匹配的行而不取消重复的行的话,可以使用UNION ALL而不是UNION,而这个功能也正是WHERE无法做到的。

mysql> SELECT vend_id, prod_id, prod_price
    -> FROM products
    -> WHERE prod_price < 5
    -> UNION ALL
    -> SELECT vend_id, prod_id, prod_price
    -> FROM products
    -> WHERE vend_id IN (1001, 1002);

7.3 对组合查询进行排序

如果想要对输出结果进行排序,在UNION组合只能使用一条ORDER BY子句,它必须出现在最后一条SELECT语句之后。对于结果集,不存在用一种方式排序一部分,而又用另一种方式排序另一部分的情况,因此不允许使用多条ORDER BY子句。

mysql> SELECT vend_id, prod_id, prod_price
    -> FROM products
    -> WHERE prod_price < 5
    -> UNION
    -> SELECT vend_id, prod_id, prod_price
    -> FROM products
    -> WHERE vend_id IN (1001, 1002)
    -> ORDER BY vend_id, prod_price;

注意:为了表述简单,上文组合的都是相同的表的查询,其实通常用于不同的表的组合查询更多。

7.4 全文本搜索

为什么要使用全文本搜索?
其实有类似于全文本搜索的功能:
1、LIKE关键字:利用通配符操作匹配文本,使用LIKE,能够查找包含特殊值或部分值的行(不管这些值在什么位置)。
2、正则表达式:基于文本搜索的正则表达式可以编写查找所需行的更复杂的匹配模式。
尽管以上两种搜索匹配机制很有用,但是存在这么几个重要的限制:

  • 性能–通配符和正则表达式匹配通常要求MYSQL尝试匹配表所有行(这些搜索极少使用变索引)。因此随着表数据的增加,这些搜索可能非常耗时;
  • 明确控制–使用通配符和正则表达式很难明确地控制匹配什么和不匹配什么。例如指定一个词必须匹配,另一个词必须不匹配。
  • 智能化的结果–通配符和正则表达式的搜索功能尽管强大,但是他们并不能提供一种智能化的选择结果的方法。如:一个特殊值的搜索会返回所有匹配的行,而不区分单个匹配的行与多个匹配的行(按照可能是更好匹配来排列搜索结果);一个特殊值的搜索将不会找出不包含该词组但包含其他相关词的行。

所有的这些限制,用全文本搜索都能得到很好的解决,而且在使用全文本搜索功能时,MYSQL不需要分别查看每个行,不需要分别分析和处理每个词、MYSQL创建指定列中各词的一个索引,搜索可以针对这些词进行,因此效率更高。

我们在使用MySQL时,一般会用到两种引擎--MyISAM和InnoDB,MyISAM支持全文本搜索,但不支持事务处理;而InnoDB支持事务处理,但不支持全文本搜索,因此当你要在某个表(一般是包含长文本字段的表)中使用全文本搜索功能时,选择引擎的时候就要选MyISAM了。

8.对数据库进行增、删、改

8.1 插入数据

毫无疑问,SELECT是最常用的语句,此外还有3个经常使用的SQL语句需要学习,第一个就是INSERT,插入可以用以下几种方式进行:

  • 插入完整的行
  • 插入行的一部分
  • 插入多行
  • 插入某些查询结果

8.1.1 插入一行或一部分

mysql> INSERT INTO customers(cust_name,cust_address,cust_city,cust_state,cust_zip,cust_country,cust_contact,cust_email)
    -> VALUES('Pep','100 Steet','LA','CA','90046','USA',NULL,NULL);

需要注意的是:

  • 可以不给出列名,但是这样做值就必须严格按照顺序给出,容易出错,因此不推荐
  • 给出列名后,列不一定需要按照表的顺序排列,只要和值的顺序对应即可
  • 上代码中,有两行给的是NULL值,那么可以在插入时省略这两列
  • 可以省略的前提是必须满足:该列定义允许NULL值 或 设置了默认值,如果不满足这两个条件之一,那么就会报错。
  • INSERT,UPDATA,DELETE操作可能会很耗时,而且可能会降低等待中的SELECT语句的性能。一般来说数据检索是最重要的,因此可以使用INSERT LOW_PRIORITY INTO语句,插入LOW_PRIORITY关键字后,INSERT语句的优先级会降低。

8.1.2 插入多行
插入多行有两种方法,比较笨的方法是输入 多条INSERT 语句。
而如果插入的行都是属于相同的列的话,那么就可以合并为一条INSERT语句:
合并之后不仅方便,性能也会提高

mysql> INSERT INTO customers(cust_name,cust_address,cust_city,cust_state,cust_zip,cust_country)
    -> VALUES('Pep','100 Steet','LA','CA','90046','USA'),
    ->       ('Mar','42 Street', 'NY', 'NY','11212','USA');

8.1.3 插入检索的数据
INSERT除了可以用来向表插入一个指定列值的行,还可以利用它将一条SELECT语句的结果插入表中,这就是所谓的INSERT SELECT。

假设你想从custnew表中合并客户到你的customers表内,可以通过以下代码完成:

INSERT INTO customers(cust_id,cust_contact,cust_email,cust_name,cust_address,cust_city,cust_state,cust_zip.cust_country)
SELECT cust_id,cust_contact,cust_email,cust_name,cust_address,cust_city,cust_state,cust_zip.cust_country
FROM custnew;

需要注意的是:

  • custnew中的cust_id不能与customers表中的cust_id有重复值,因为如果主键值重复,INSERT操作将会失败
  • 上述语句会插入多少行有赖于custnew中youduoshaohang有多少行,如果这个表为空,那么就不会有任何行被插入
  • 其实如果需要的话,可以省略导入cust_id列以避免出错
  • 在上面语句中,为简单起见在INSERT和SELECT中使用了相同的列名,事实上SQL根本不关心列名是否一致,它是按照顺序进行导入的
  • SELECT语句可包含WHEREA子句以过滤插入的数据

8.2 更新数据

为了更新(修改)表中的数据,可使用UPDATE语句,可以采用两种方式使用:

  • 更新表中特定行,带WHERE
  • 更新表中所有行,不带WHERE

假设需要更新某个客户的邮件地址:

mysql> UPDATE customers
    -> SET cust_email = '[email protected]'
    -> WHERE cust_id = 10005;
Query OK, 1 row affected
Rows matched: 1  Changed: 1  Warnings: 0

或者需要更新客户的名字和邮件:

mysql> UPDATE customers
    -> SET cust_name = 'Food',
    ->     cust_email = '[email protected]'
    -> WHERE cust_id = 10005;

要注意的是:

  • 一定要用WHERE过滤条件,不然所有行被都改了
  • 如果想要删除某条数据,SET其为NULL值即可
  • 在UPDATE中可以使用子查询,用检索出的数据来更新
  • 如果在更新多行的过程中某一个出现错误,整个UPDATE都会被取消,如果希望即使发生错误也继续更新的话,可以使用IGNORE关键字:UPDATE IGNORE customers

8.3 删除数据

为了从一个表中删除数据,可使用DELETE语句,可以采用两种方式使用:

  • 从表中删除特定行,指定WHERE
  • 从表中删除所有行,不指定WHERE

假设需要从表中删除一行:

mysql> DELETE FROM customers
    -> WHERE cust_id = 10006;
Query OK, 1 row affected

要注意的是:

  • DELETE语句不需要列名或通配符。它删除整行而不是删除列,为了删除指定的列,请使用UPDATE语句。
  • DELETE从表中删除行,但是不删除表本身
  • 如果想要删除所有行,建议不使用DELETE,而是TRUNCATE TABLE语句。
  • 对UPDATE或DELETE使用WHERE子句前应当先用SELECT语句进行测试,以防编写的WHERE子句不正确。

8.4 创建和操纵表

8.4.1创建表
一般来说,有两种创建表的方法:使用CREATE TABLE语句,或直接使用交互式的工具进行创建和管理表,如Navicat,会非常直观和方便。

下面我们用一个样例代码来介绍一下需要注意的地方:

CREATE TABLE customers
(
	cust_id		int			NOT NULL  AUTO INCREMENT,
	cust_name   char(50)    NOT NULL,
	cust_city	char(50)	NULL, DEFAULT ‘NY’
	cust_email  char(255)	NULL,
	PRIMARY KEY(cust_id)
) ENGINE=InnoDB;

从示例中可以看出,表名紧随CREATE TABLE之后,列的定义在括号中,以逗号分隔,包括列名,数据类型,是否允许NULL,需要了解的是:

  • 包含关键字NOT NULL后,就会阻止插入的数据省略了此列,那么就会阻止并报错
  • NULL是没有值,而不是空值,因此在NOT NULL中输入‘’是允许的(引号间为空)
  • 主键必须唯一,且只能使用NOT NULL的列
  • AUTO INCREMENT告诉MySQL,本列每增加一行时自动增量。每个表只允许一个AUTO INCREMENT,而且它必须被索引(如成为主键)。
  • 在INSERT中可以使用一个唯一的值代替AUTO INCREMENT默认将赋予的值,然后后面增加的列就会在这个值的基础上递增。 此外,可以使用SELECT last_insert_id()获取当前AUTO INCREMENT的值。
  • 用DEFAULT指定默认值,而不是NULL,特别是用于计算或数据分组的列
  • MySQL有多种引擎,如InnoDB(事务处理,不支持全文本搜索),MEMORY(适合临时表),MyISAM(全文本搜索,不支持事务处理),创建表时可以对引擎进行选择也可以使用默认的。但要注意,如果数据库中不同的表使用不同的引擎,外键是不能跨引擎的,即使用某个引擎的表不能引用具有不同引擎的表的外键。

本文中的例子都是使用单个列作为主键的,其实是可以使用多列作为主键,如果使用多个列,那么这些列的组合必须唯一,创建多个列作为主键的示例:

CREATE TABLE orderitems
(
	order_num		int		NOT NULL,
	order_item      int     NOT NULL,
	prod_id			char(10) NULL
	PRIMARY KEY(order_num, order_item)
) ENGINE=InnoDB;

8.4.2更新表
更新表包括增加列或删除列,使用ALTER TABLE 语句,但是理想情况下,当表中存储数据后,该表就不应该再被更新,而应该在表的设计过程中考虑清楚。

给表增加一个列:

mysql> ALTER TABLE vendors
    -> ADD vend_phone CHAR(20);

删除一个列:

mysql> ALTER TABLE vendors
    -> DROP COLUMN vend_phone;

ALTER COLUMN还有一个常见用途是定义外键

ALTER TABLE orders
ADD CONSTRAINT fk_orders_customers FOREIGN KEY (cust_id)
REFERENCES customers(cust_id);

8.4.3 删除表,重命名表

删除整个表,要注意操作没有确认也不能撤销:

DROP TABLE customers2;

重命名表:

RENAME TABLE customers2 TO customers3;

重命名多个表:

RENAME TABLE customers2 TO customers3,
			 vendors2 TO vendors3,
			 products2 TO products3;

你可能感兴趣的:(SQL)