【SQL】MySQL基础1:对表的DQL,DML,DDL

文章目录

1. 一些概念

数据库:保存有组织的数据的容器
表:一种结构化的文件,用于存储特定类型的数据。一个数据库里的表名称必须唯一。
模式(schema):关于数据库和表的布局及特性的信息。
列:表的资格字段,一张表由多列构成,每列由固定的数据类型。
行:也称记录,表是关于某个对象的多维度的多条信息,则行这个对象的这些纬度下的一条数据。
主键:特殊的一列或者几列,不同记录在主键上的取值是唯一的,从而保证主键可以唯一标示一条记录。主键的特点:任两行主键值不同;主键各列不许为空值;主键列中的值不许修改或者更新;主键值不可重用,如果某记录被删除,则它占用的主键值不能用于后续新建的记录上。
子句:SQL关键字加上数据构成,SELECT * FROM Products; 其中FROM关键字加上表名称就是FROM子句。

1.1 数据库模式

模式:描述了数据的逻辑结构和组织方式,包含数据项的定义,数据项之间的关系、数据约束条件和数据操作规则。模式有三个层次:概念模式、外模式、内模式
概念模式:整个数据库的数据结构和关系。一个数据库有一个概念模式。
外模式:用户视角下的数据视图。不同角色的用户将看到整个数据的部分。一个数据库可以有多个概念模式。
内模式:物理存储方式。一个数据库只有一个内模式。
三级模式的二级映射:外模式/模式;模式/内模式。
数据的逻辑独立性:应用程序只能看到外模式,如果修改模式,外模式不变,应用程序无感知。
数据的物理独立性:修改数据库的物理存储方式,数据库的模式不变,从而外模式不变,应用程序不会受到影响。

1.2 SQL语言类型

DDL(Data Define Language):创建(CREATE)、修改(ALTER)和删除(DROP)数诸如数据库、表、视图、索引、存储过程等数据库对象。
DQL(Date Query Language):用于查询(SELECT)数据。可以对数据进行筛选,分组,排序,限制结果集(选多少行,选哪些列)。
DML(Data Manage Language):插入(INSERT INTO)、修改(UPDATE)、删除(DELETE FROM)数据。
DCL(Date Control Language):主要用于权限控制(GRANT、REVOKE)
TCL(Transaction Control Language):用于使用事务。START TRANSACTION、ROLLBACK、COMMIT

2. 查询

-- 语法顺序
SELECT
FROM
WHERE
GROUP BY
HAVING
ORDER BY
LIMIT

-- 执行顺序:取表,筛选,分组筛选,选择列,指定的列排序,限制行
FROM
WHERE
GROUP BY
HAVING
SELECT
ORDER BY
LIMIT

2.1 WHERE子句:过滤条件

WHERE子句在SELECT语句中位于FROM语句之后。
过滤即可以放在数据库层面做,也可放在应用层去做。放在应用层做不妥,因为数据库和应用之间传递大量多于数据,浪费网络带宽。另外,优化的数据库过滤操作效率也更高。

2.1.1 WHERE子句的条件操作符

注意不同的DBMS支持不同的条件操作符
NULL值表示无值,不同于值为0,空字符串,空格的情形。在进行非匹配的时候,NULL值的情况不会被返回。

条件操作符说明
=等于
<>, !=不等于
<小于
>大于
!>, <=小于等于
!<, >=大于等于
BETWEEN AND闭区间
IS NULL为NULL

2.1.2 单过滤条件

-- BETWEEN AND
SELECT prod_name, prod_price
FROM Products
WHERE prod_price BETWEEN 5 AND 10;

-- 判断NULL值,注意判断NULL值要用专门的IS NULL子句
SELECT prod_name
FROM Products
WHERE prod_price IS NULL;

2.1.3 多过滤条件

使用逻辑操作符组合多个过滤条件。常用的操作符有:AND, OR,IN,NOT
OR操作符的的第一个条件得到满足就返回,不再判断二个条件。
AND和OR的顺序

-- 在WHERE子句中,先过滤AND后的条件prod_price >= 10,然后再过滤完的结果上执行OR操作符的过滤。AND操作有比OR高的优先级。
SELECT prod_name, prod_price
FROM Products
WHERE vend_id = 'DLL01' OR venc_id = 'BRS01'
    AND prod_price >= 10;

-- ()有比AND和OR都搞定优先级,给整个OR过滤条件加上括号,则过滤操作先执行括号里的OR语句。
SELECT prod_name, prod_price
FROM Products
WHERE (vend_id = 'DLL01' OR venc_id = 'BRS01')
    AND prod_price >= 10;

当过滤需要同时使用AND和OR时,应当总是使用圆括号。
IN:用于判断查询字段值是否在给定离散值范围内。用括号包含给定的离散值,每个值用逗号隔开。
IN比一组OR过滤更快。

SELECT prod_name, prod_price
FROM Products
WHERE vend_id in ('DLL01', 'BRS01')  -- 等价于 WHERE vend_id = 'DLL01' OR vend_id = 'BRS01'
ORDER BY prod_name;

2.1.4 通配符

通配符用于表示一些特殊字符,含有通配符的字符串可以作为用于搜索的搜索模式,搜索模式描述了一个特定的字符串集合,字段值如果匹配搜索模式的话将被选中。SQL中的统配符:%(表示0个或者多个任意字符),_ (表示任意单个字符),[] (落入一个字符集的单个字符,字符集内加上^前缀则表示该字符集合之外的字符集)。
MySQL中谓词LIKE可以使用的通配符:% _。REGEXP/RLIKE可以使用的通配符:. * [] + ^ $。

-- LIKE通配符%
SELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE 'Fish%';  -- 'Fish bean bag toy'

-- LIKE通配符_
ELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE '__ inch teddy bear';  -- '12  inch teddy bear'

-- REGEXP通配符 ^:查找以A开头的用户名
SELECT * FROM users WHERE username REGEXP '^A';
特点

通配符%不会匹配NULL值。
通配效率较低,如果其他操作符能达到同样的效果优先使用其他方案。
需要使用通配符时,不应把它放在最前面而优先处理。

2.2 GROUP BY子句:分组

2.2.1 特点

GROUP BY子句可以包含任意数量的列,聚集将在逐层分组后的最后一个列上进行。
GROUP BY子句不支持别名
GROUP BY子句不允许列带有长度可变的数据类型(文本类型,备注类型)
如果被分组的列有NULL值,则NULL值也被作为一组。
GROUP BY子句在WHER子句之后,ORDER BY子句之前。

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

-- 多层分组
SELECT vend_id, sub_vend_id, COUNT(*) AS num_prods
FROM Products
GROUP BY (vend_id, sub_vend_id);

-- 相对位置指示列
SELECT vend_id, sub_vend_id, COUNT(*) AS num_prods
FROM Products
GROUP BY (1, 2);

2.2.2 分组的过滤

WHERE在分组前进行过滤,HAVING在分组后过滤。注意HAVING主要用于过滤分组,每个分组都有一个聚集值,因此HAVING过滤的条件针对分组的聚集值。

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

2.3 DISTINCT: 查询某列不同的值

-- 单列去重
SELECT DISTINCT vend_id 
FROM Prodcuts;

-- 多列去重(笛卡尔积后这些列的不同值)。
SELECT DISTINCT vend_id, prod_price
FROM Prodcuts;

2.4 LIMIT OFFSET:限制查询结果数量

2.4.2 取前n行

-- MySQL MariaDB, PostgreSQL, SQLite
SELECT prod_name
FROM Prodcuts
LIMIT 5;

2.4.2 取从m行起的n行数据

-- 返回从第2行起的5行数据,此处行号从0起算。
SELECT prod_name
FROM Prodcuts
LIMIT 5 OFFSET 2;

-- 简化版本(MySQL MariaDB,SQLite),注意2是起始行号,5是行数
SELECT prod_name
FROM Prodcuts
LIMIT 2,5;

2.5 ORDER BY子句:排序

ORDER BY子句应是SELECT语句的最后一个子句,否则报错。默认升序排序。

2.5.1 单列排序

SELECT prod_name
FROM Products
ORDER BY prod_name;

2.5.2 多列排序

-- 先按照prod_price排序,再按照prod_name排序
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY prod_price, prod_name;

-- 通过列的相对位置进行多行排序,即prod_price排序,再按照prod_name排序
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY 2, 3;

2.5.3 指定排序方向

-- 单列排序,指定排序方向
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY prod_price DESC;

/* 
多列情况下,指定排序方向
排序方向关键字DESC仅仅作用于prod_price,而prod_name仍使用默认的升序。
如果需要指定更多列采取DESC,则应每列都加上DESC关键字。
 */
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY prod_price DESC, prod_name;

2.6 计算字段

存储在数据库中的数据格式不是应用所需要的,需要直接从数据库中检索出转换,计算或者格式化后的数据,而非检索出数据让应用程序去转换,计算或者格式化。同样的数据的转换、计算或者格式化放在SQL语句中完成要比在应用中完成高效得多。

2.6.1 格式调整

-- SQL Server
SELECT vend_name + '(' + vend_counutry + ')'
FROM Vendors
ORDER BY vend_name;

-- MySQL: 使用函数,两个被合并的字段之间用空格填充,从而保持足以容纳两个字段值的固定列宽。
SELECT CONCAT(vend_name, '(', vend_counutry, ')')
FROM Vendors
ORDER BY vend_name;

/*
Rtrim()去除字段值右边的空字符,LTrim()去除右边的空字符,Trim()去除两边的空字符
*/

-- MySQL:使用RTIM()去掉字段之间的空格
SELECT CONCAT(RTrim(vend_name), '(', RTrim(vend_counutry), ')') AS vend_tittle  -- AS关键字,用于设置列别名/导出列
FROM Vendors
ORDER BY vend_name;

2.6.2 算数计算

SELECT prod_if,
       quantity,
       item_price,
       quantity*item_price AS expanded_price
FROM OrderItems
WHERE order_num = 2008;

2.7 函数

类型:文本处理,算术操作,时间日期处理,格式化,返回特殊信息。

2.7.1 常用文本处理函数

常用的文本处理函数
函数说明
LEFT()返回给定字符串左右的字符
LENGTH()返回字符串长度
LOWER()将字符串转换为小写
UPPER()将字符串转换为大写
Trim()去除字符串两边的空格
RTrim()去除字符串右边的空格
LTrim()去除字符串左边的空格
SUBSTR()/SUBSTRING()提取字符串子串
SOUNDEX()返回字符串的SOUNDEX值
case1:按分隔符截取

SUBSTRING_INDEX分割后的第一项或者最后一个项可以直接截取

+----+-----------+----------------------+-----------------------+
| id | device_id | profile              | blog_url              |
+----+-----------+----------------------+-----------------------+
|  1 |      2138 | 180cm,75kg,27,male   | http:/url/bisdgboy777 |
|  1 |      3214 | 165cm,45kg,26,female | http:/url/dkittycc    |
|  1 |      2131 | 168cm,45kg,22,female | http:/url/sysdney     |
+----+-----------+----------------------+-----------------------+

-- 提取性别
SELECT SUBSTRING_INDEX(profile,",",-1) number FROM user_submit;
+--------+
| number |
+--------+
| male   |
| female |
| female |
+--------+

-- 提取身高
SELECT SUBSTRING_INDEX(profile,",", 1) number FROM user_submit;
+--------+
| number |
+--------+
| 180cm  |
| 165cm  |
| 168cm  |
+--------+

若要提取分割后位于中间的seg,则需要嵌套SUBSTRING_INDEX,提取任意中间的seg都只需要两层SUBSTRING_INDEX。

-- 一层SUBSTR_INDEX无法单独提取体重
SELECT SUBSTRING_INDEX(profile,",", 2) number FROM user_submit;
+------------+
| number     |
+------------+
| 180cm,75kg |
| 165cm,45kg |
| 168cm,45kg |
+------------+

-- 嵌套SUBSTR_INDEX单独提取体重
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(profile, ",", 2),",", -1) number FROM user_submit;
+--------+
| number |
+--------+
| 75kg   |
| 45kg   |
| 45kg   |
+--------+

-- 提取年龄
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(profile, ",", -2),",", 1) number FROM user_submit;
+--------+
| number |
+--------+
| 27     |
| 26     |
| 22     |
+--------+

2.7.2 日期和时间处理函数

MySQL常用日期函数
  • YEAR(),MONTH(), DAY():分别提取date类型字段中的年,月,日。
  • TO_DATE():将字符串转换为date类型值。
  • DATE_FORMAT():别名strftime,按照指定格式从date类型字段中提取字符串。格式如下表。
说明符描述
%Y四位数的年份(例如:2024)
%y两位数的年份(例如:24)
%m两位数的月份(01 - 12)
%c一位或两位数的月份(1 - 12)
%d两位数的日(01 - 31)
%e一位或两位数的日(1 - 31)
%H24 小时制的小时数(00 - 23)
%h 或 %I12 小时制的小时数(01 - 12)
%i分钟数(00 - 59)
%s秒数(00 - 59)
%pAM 或 PM
%W星期的完整名称(例如:Sunday)
%a星期的缩写名称(例如:Sun)
%M月份的完整名称(例如:January)
%b月份的缩写名称(例如:Jan)
case1:根据年份进行等值筛选
-- 提取年份再筛选:DATE_FORMAT()
SELECT order_num
FROM Orders
WHERE DATE_FORMAT(order_date, '%Y') = 2020;

-- 提取年份再筛选:YEAR()
SELECT order_num
FROM Orders
WHERE YEAR(order_date) = 2020;

-- 日期范围查询

case2:根据年份进行范围筛选

直接使用字符串类型的上下限,这会导致 字符串类型 -> date类型 的隐式类型转换

-- 使用between and
select order_num
FROM Orders;
WHERE order_date BETWEEN '2020-01-01' AND '2020-12-31';

-- 使用关系运算符
select order_num
FROM Orders;
WHERE order_date >= '2020-01-01' AND order_date <= '2020-12-31';

显式类型转换方式,上下限转换为date类型再比较

-- 使用between and
select order_num
FROM Orders;
WHERE order_date BETWEEN to_date('2020-01-01') AND order_date <= to_date('2020-12-31');

-- 直接使用关系运算符
select order_num
FROM Orders;
WHERE order_date >= to_date('2020-01-01') AND order_date <= to_date('2020-12-31');

2.7.3 数值处理函数

常用的数值处理函数:ABS(),COS(),EXP(),PI(),SIN(),SQRT(),TAN()。

2.7.4 聚集函数

聚集函数的作用是将查询的字段的多个值作为输入,计算得到一个值。
常见的聚集函数:AVG(),COUNT(),MAX(),MIN(),SUM()。

2.7.5 条件函数

二分支判断

-- 统计女生人数
+-----+------+------+--------+-------+
| uid | name | age  | gender | class |
+-----+------+------+--------+-------+
|   1 | u1   |    1 | male   |     1 |
|   3 | u3   |    3 | male   |     2 |
|   7 | u7   |    7 | female |     1 |
|   9 | u9   |    9 | female |     3 |
|  13 | u13  |   13 | female |     3 |
+-----+------+------+--------+-------+
select sum(if(gender='female', 1,0)) as female_total_cnt
from users;
+------------------+
| female_total_cnt |
+------------------+
|                3 |
+------------------+

-- 统计每个班女生人数
select class, sum(if(gender='female', 1,0)) as femle_class_cnt
from users
group by class;
+-------+-----------------+
| class | femle_class_cnt |
+-------+-----------------+
|     1 |               1 |
|     2 |               0 |
|     3 |               2 |
+-------+-----------------+

-- 统计每个班男生女生各多少人,每个班有两行,一行记录男生数量,一行记录女生数量
SELECT 
    class,
    SUM(IF(gender = 'female', 1, 0))  AS female_cnt,
    SUM(IF(gender = 'male', 1, 0)) AS male_cnt
FROM 
    users
GROUP BY 
    class;
+-------+------------+----------+
| class | female_cnt | male_cnt |
+-------+------------+----------+
|     1 |          1 |        1 |
|     2 |          0 |        1 |
|     3 |          2 |        0 |
+-------+------------+----------+

多分支判断

-- 分年龄段筛选:小于3岁,3岁到10岁,10岁以上的人数
select (case 
		when age < 3 then '<3 years old' 
		when age >= 3 and age <= 10 then '3-10 years old' 
		else '>10 years old' 
	end) as age_cut, 
	count(*) as cnt 
from users 
group by age_cut;
+----------------+-----+
| age_cut        | cnt |
+----------------+-----+
| <3 years old   |   1 |
| 3-10 years old |   3 |
| >10 years old  |   1 |
+----------------+-----+

2.7.6 特点

avg(),min(),max(),sum()忽略列值为NULL的行。
count(*)计数包含NULL值的情况,count(column)计数不包含NULL值的情况。

-- 去重后再聚集, DISTINCT不能用于count(*),只能用于count(DISTINCT column)
SELECT AVG(DISTINCT prod_price) AS avg_price
FROM Products
WHERE cend_id = 'DLL01';

-- 使用多个聚合函数
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 子查询

作为子查询的SELECT语句只能是单列。子查询效率不高。

2.8.1 位于WEERE子句的子查询

/* 例子,查出某个订购商品的用户信息
1. 根据产品ID查询订单产品表,得到订单编号
2. 根据订单编号查询订单表,得到顾客id
*/

-- 1. 根据产品ID查询订单产品表,得到订单编号
SELECT order_num
FROM OrderItems
WHERE prod_id = 'RGAN01';  -- 20007, 20008

-- 2. 根据订单编号查询订单表,得到顾客id
SELECT cust_id
FROM Orders
WHERE order_num IN (20007, 20008);

-- 使用子查询
SELECT cust_id
FROM Orders
WHERE order_num IN (SELECT order_num
                    FROM OrderItems
                    WHERE prod_id = 'RGAN01');

-- 使用多层嵌套的子查询:
SELECT cust_name, cust_contact
FROM Customs
WHERE cust_id IN (SELECT cust_id
                  FROM Orders
                  WHERE order_num IN (SELECT order_num
                                      FROM OrderItems
                                      WHERE prod_id = 'RGAN01');)

2.8.2 作为计算字段的子查询

SELECT cust_name,
       cust_state,
       (SELECT COUNT(*)
        FROM Orders
        WHERE Orders.cust_id = Customers.cust_id) AS orders
FROM Customers
ORDER BY cust_name;
-- 注意子查询中使用了完全限定列名

3. 连接

3.1 关系表的概念

同一供应商生产多种产品,产品需要记录的信息包括:产品描述、价格、生产该产品的供应商等。而供应商需要记录的数据包含:供应商名称、地址、联系方式等。用一张表将产品信息和供应商信息一起存储,一行代表一个产品,则产品有关供应商信息的字段会大量重复(坏处:浪费存储空间,查询耗时高,维护供应商信息很麻烦,输入产品信息可能出错导致信息不一致)。
正确的做法是,将产品数据和供应商信息分开置表存放。Vendors只存放供应商信息(主键为供应商ID),Products表只存放产品数据,Products表存有供应商ID字段。像这样Products表和Vendors表用共同的供应商ID字段关联起来,它们就是关系表。关系表具有良好的可扩展性。

3.2 连接表进行查询

数据被放置在不同的表中,需要在一条SELECT语句中连接这些表进行检索。

3.2.1 内连接

case1: 内连接就是等值链接
-- 等值连接/内连接: 使用WHERE
SELECT vend_name, prod_name, prod_price
FROM Vendors, Products
WHERE Vendors.vend_id = Products.vend_id;

-- 等值连接/内连接: 使用INNER JOIN
SELECT vend_name, prod_name, prod_price
FROM Vendors
INNER JOIN Products ON Vendors.vend_id = Products.vend_id;
case2:理解等值连接

如果A中有1行id=1的记录,B中有3行id=1的记录,则A inner join B on A.id=B.id的结果中有3行关于id=1的记录。

CREATE TABLE `TmpTbl1` (
  `id` int NOT NULL DEFAULT '1',
  `name` char(10) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE `TmpTbl2` (
  `id` int NOT NULL DEFAULT '1',
  `order_id` char(10) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE `TmpTbl3` (
  `id` int NOT NULL DEFAULT '1',
  `name` char(10) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

insert into TmpTbl1 values(1, ’aa’), (2, ‘bb’);
insert into TmpTbl2 values(1,001), (1,002), (2,003), (3,004);
insert into TmpTbl3 values(1, ‘aa’), (2, ‘bb’), (2, ‘bb’);

select t1.id, t1.name, t2.order_id
from TmpTbl1 as t1
inner join TmpTbl2 as t2 on t1.id = t2.id;
/*
+----+------+----------+
| id | name | order_id |
+----+------+----------+
|  1 | aa   | 001      |
|  1 | aa   | 002      |
|  2 | bb   | 003      |
+----+------+----------+
*/


select t1.id, t1.name
from TmpTbl1 as t1
inner join TmpTbl3 as t3 on t1.id = t3.id;
/*
+----+------+
| id | name |
+----+------+
|  1 | aa   |
|  2 | bb   |
|  2 | bb   |
+----+------+
*/

3.2.2 叉连接

-- 笛卡尔积,叉连接
SELECT vend_anme, prod_name, prod_price
FROM Vendors, Products

3.2.3 组合与嵌套中的等值条件

-- 嵌套子查询使用连接
SELECT cust_name, cust_contact
FROM Customs
WHERE cust_id IN (SELECT cust_id
                  FROM Orders
                  WHERE order_num IN (SELECT order_num
                                      FROM OrderItems
                                      WHERE prod_id = 'RGAN01');)

SELECT cust_name, cust_contact
FROM Customs AS C, Orders AS O, OrderItems AS OI
WHERE C.cust_id = O.cust_id
  AND OI.order_num = O.order_num
  AND prod_id = 'RGAN01';

3.3 其他连接类型

3.3.1 自连接

-- cust_name为客户所在公司名,cust_contact为客户名。找出和Jim Jones在同一公司的顾客

-- 方法一:使用子查询
SELECT cust_id, cust_name, cust_contact
FROM Customres
WHERE cust_name = (SELECT cust_name
                   FROM Customers
                   WHERE cust_contact = 'Jim Jones'); 

-- 方法二:使用自连接
SELECT c1.cust_id, c1.cust_name, c1.cust_contact
FROM Customers AS c1, Custoners AS c2
WHERE c1.cust_name = c2.cust_name
  AND c2.cust_contact = 'Jim Jones';

3.3.2 自然连接

具备共同的列的表才能被连接起来,DBMS在处理连接时,位于不同表中的共同列可能重复出现。自然连接指那些去除了重复公共列的连接(内连接不会去除各个表中的连接字段)。
自然连接不显示指定连接条件(不显示指定用于连接的公共字段),而DBMS默认找到公共字段并进行匹配。

-- case1: 提取所有列的内连接,结果列中会出现两个vend_id,这不是自然连接
SELECT *
FROM Vendors, Products
WHERE Vendors.vend_id = Products.vend_id;

-- case2: 提取的行中没有重复的列,这是自然连接
SELECT vend_name, prod_name, prod_price
FROM Vendors, Products
WHERE Vendors.vend_id = Products.vend_id;

-- 等同于
SELECT *
FROM Products
NATURAL JOIN Vendors;

-- case3: 通过给第一个表提取所有列,其他表挑选指定列来实现自然连接。
SELECT C.*, O.order_num, O.order_date,
        OI.prod_id, OI.quantity, OI.item_price
FROM Customers AS C, Order AS O, OrderItems AS OI
WHERE C.cust_id = O.cust_id
  AND OI.order_num = O.order_num
  AND prod_id = 'RGAN01';

3.3.3 外连接

有时候需要查看以一个表的关联列的所有取值为参照来进行连接。当连接条件不满足时的记录也出现在结果中,这被称为外连接。外连接分为左外连接,右外连接,全外连接。左外连接以左表的关联列取值为参照,右外连接以右表的关联列取值为参照,全外连接则合并了左外连接和右外连接。
可以通过转换左右表位置来得到左右外连接的效果。SQLite没有右外连接,可以通过交换左右表的位置来实现右外连接。·
MySQL,MariaDB,SQLite不支持 FULL OUTER JOIN,可以通过UNION来合并左右外连接得到全外连接的效果。

左外连接

inner join和left join的区别:假设A表中有一行id=1的记录,B表中2行id=1的记录和1行id=2的记录,则A inner join B on A.id=B.id结果中包含2行id=1的记录(从A中取出id=1,扫描B全表,发现2个匹配行);A left join A.id = B.id 结果中有2行id=1的记录,原理跟inner join相同,但是 B left join A on B.id = A.id 结果就和inner join不同了,结果中有2行id=1的记录和1行id=2的记录,2行id=1的记录仍然适用inner join的等值匹配法则,但是对1行id=2的记录,从B表id列中取得的id=2在A表中找不到匹配项,这一行就成了id=2,其余字段为null的一行。

TmpTbl2
+----+----------+
| id | order_id |
+----+----------+
|  1 | 001      |
|  1 | 002      |
|  2 | 003      |
|  3 | 004      |
+----+----------+

TmpTbl1
+----+------+
| id | name |
+----+------+
|  1 | aa   |
|  2 | bb   |
+----+------+


select t1.id, t2.id, name, order_id from TmpTbl2 t2 left join TmpTbl1 t1 on t1.id = t2.id;
+------+----+------+----------+
| id   | id | name | order_id |
+------+----+------+----------+
|    1 |  1 | aa   | 001      |
|    1 |  1 | aa   | 002      |
|    2 |  2 | bb   | 003      |
| NULL |  3 | NULL | 004      |
+------+----+------+----------+


select t1.id, t2.id, name, order_id from TmpTbl1 t1 left join TmpTbl2 t2 on t1.id = t2.id;     
+----+------+------+----------+
| id | id   | name | order_id |
+----+------+------+----------+
|  1 |    1 | aa   | 002      |
|  1 |    1 | aa   | 001      |
|  2 |    2 | bb   | 003      |
+----+------+------+----------+
右外连接
-- 右表式Customers,右外连接将以Orders的cust_id列的所有取值为参照进行连接
SELECT Customers.cust_id, Orders.order_num
FROM Customers
RIGHT OUTER JOIN Orders ON Customers.cust_id = Orders.cust_id;

-- Orders表中有两条cust_id为1000000001的记录,则将两次用1000000001匹配左表中的cust_id=1000000001的行
+------------+-----------+
| cust_id    | order_num |
+------------+-----------+
| 1000000001 |     20005 |
| 1000000001 |     20009 |
| 1000000003 |     20006 |
| 1000000004 |     20007 |
| 1000000005 |     20008 |
+------------+-----------+

3.3.4 对连接表使用聚合

SELECT Customers.cust_id, Orders.order_num
FROM Customers
INNER JOIN Orders ON Customers.cust_id = Orders.cust_id;

-- 关于cust_id的内连接中cust_id=1000000001有两个匹配行
+------------+-----------+
| cust_id    | order_num |
+------------+-----------+
| 1000000001 |     20005 |
| 1000000001 |     20009 |
| 1000000003 |     20006 |
| 1000000004 |     20007 |
| 1000000005 |     20008 |
+------------+-----------+

SELECT Customers.cust_id, COUNT(Orders.order_num) AS num_ord
FROM Customers
INNER JOIN Orders ON Customers.cust_id = Orders.cust_id
GROUP BY Customers.cust_id;

+------------+---------+
| cust_id    | num_ord |
+------------+---------+
| 1000000001 |       2 |
| 1000000003 |       1 |
| 1000000004 |       1 |
| 1000000005 |       1 |
+------------+---------+

-- 对左外连接表使用聚合的例子
SELECT Customers.cust_id, COUNT(Orders.order_num) AS num_ord
FROM Customers
LEFT OUTER JOIN Orders ON Customers.cust_id = Orders.cust_id
GROUP BY Customers.cust_id;

-- 左表中cust_id=1000000002在右表中没有匹配的行,因此计数为0
+------------+---------+
| cust_id    | num_ord |
+------------+---------+
| 1000000001 |       2 |
| 1000000002 |       0 |
| 1000000003 |       1 |
| 1000000004 |       1 |
| 1000000005 |       1 |
+------------+---------+

4. 组合查询

UNION操作符用于将多条SELECT语句执行结果合并。UNION要求各个结果集有相同的列,不要求各个SELECT语句中列的顺序相同。

4.1 case1:基本用法

SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL', 'IN', 'MI');
+---------------+--------------+-----------------------+
| cust_name     | cust_contact | cust_email            |
+---------------+--------------+-----------------------+
| Village Toys  | John Smith   | sales@villagetoys.com |
| Fun4All       | Jim Jones    | jjones@fun4all.com    |
| The Toy Store | Kim Howard   | NULL                  |
+---------------+--------------+-----------------------+

SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All';
+-----------+--------------------+-----------------------+
| cust_name | cust_contact       | cust_email            |
+-----------+--------------------+-----------------------+
| Fun4All   | Jim Jones          | jjones@fun4all.com    |
| Fun4All   | Denise L. Stephens | dstephens@fun4all.com |
+-----------+--------------------+-----------------------+

-- 合并查询结果,去除了重复的行,而不是简单的追加
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL', 'IN', 'MI')
UNION  -- 使用UNION ALL将不去重
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All'
ORDER BY cust_name, cust_contact;  -- 对合并后的结果集进行排序
+---------------+--------------------+-----------------------+
| cust_name     | cust_contact       | cust_email            |
+---------------+--------------------+-----------------------+
| Fun4All       | Denise L. Stephens | dstephens@fun4all.com |
| Fun4All       | Jim Jones          | jjones@fun4all.com    |
| The Toy Store | Kim Howard         | NULL                  |
| Village Toys  | John Smith         | sales@villagetoys.com |
+---------------+--------------------+-----------------------+

4.2 case2:为何要使用UNION

需要考察多个筛选条件并逻辑下的结果,可以直接使用where子句,用or来连接多个筛选条件,但是得到的结果可能不是按照每个筛选条件排列在一起的。使结果更加清晰的方法是考虑每个查询只使用一个筛选条件,然后使用union将结果拼接在一起,这样每个筛选条件下的结果排列在一起,展示更为清晰。

+-----+------+------+--------+-------+
| uid | name | age  | gender | class |
+-----+------+------+--------+-------+
|   1 | u1   |    1 | male   |     1 |
|   3 | u3   |    3 | male   |     2 |
|   7 | u7   |    7 | female |     1 |
|   9 | u9   |    9 | female |     3 |
|  13 | u13  |   13 | female |     3 |
+-----+------+------+--------+-------+
-- 使用or连接对个删选条件
select * from users where gender='female' or class = 1+-----+------+------+--------+-------+
| uid | name | age  | gender | class |
+-----+------+------+--------+-------+
|   1 | u1   |    1 | male   |     1 |
|   7 | u7   |    7 | female |     1 |
|   9 | u9   |    9 | female |     3 |
|  11 | u11  |   11 | male   |     1 |
|  13 | u13  |   13 | female |     3 |
+-----+------+------+--------+-------+

-- 使用union逐个拼接筛选结果
select * 
from users
where gender = 'female'
union (
	select * 
	from users
	where class = 1
);
+-----+------+------+--------+-------+
| uid | name | age  | gender | class |
+-----+------+------+--------+-------+
|   7 | u7   |    7 | female |     1 |
|   9 | u9   |    9 | female |     3 |
|  13 | u13  |   13 | female |     3 |
|   1 | u1   |    1 | male   |     1 |
|  11 | u11  |   11 | male   |     1 |
+-----+------+------+--------+-------+

5. 数据插入

当列允许NULL值或者给出默认值时,插入操作可以省略这些列。

5.1 直接插入数据

-- 给出列名进行插入,推荐(表结构变化依然可用)
INSERT INTO Customers(csut_id,
                      cust_name,
                      cust_address,
                      cust_city,
                      cust_state,
                      cust_zip,
                      cust_country,
                      cust_contact,
                      cust_email)
VALUES(1000000006
       'Tony Land'
       '123 Any Street',
       'New York',
       'NY',
       '11111',
       'USA',
       NULL,
       NULL);

-- 根据表定义中列的次序来插入,不推荐(表结构变化后可能不可用)
INSERT INTO Customers
VALUES(1000000006
       'Tony Land'
       '123 Any Street',
       'New York',
       'NY',
       '11111',
       'USA',
       NULL,
       NULL);

5.2 插入检索的数据

-- 将表CustNew中的数据插入到Customers中
INSERT INTO Customers(csut_id,
                      cust_contact,
                      cust_email,
                      cust_name,
                      cust_address,
                      cust_city,
                      cust_state,
                      cust_zip,
                      cust_country)

SELECT csut_id,
       cust_contact,
       cust_email,
       cust_name,
       cust_address,
       cust_city,
       cust_state,
       cust_zip,
       cust_country
FROM CustNew;

-- 复制表
CREATE TABLE CustCopy AS SELECT * FROM Customers;

6. 更新和删除数据

6.1 更新操作

-- 更新一列的值
UPDATE Customers
SET cust_email = 'kim@thetoystore.com'
WHERE cust_id = 1000000005;

-- 更行多列的值
UPDATE Customers
SET cust_contact = 'Sam Roberts',
    cust_email = 'sam@toyland.com'
WHERE cust_id = 1000000006;

6.2 删除数据

// 删除表的全部数据,而不删除表结构
delete from tableName;		// 自增计数不清空,记录数据变动到事务日志
truncate table tableName;	// 自增计数清空,不会记录数据变动到事务日志,删除更快

// 筛选后删除
delete from tableName where id = 1;

6.3 外键对于删除和插入的约束

Products(prod_id, vend_id, prod_name, prod_price, prod_desc),其中Products表的主键为prod_id,vend_id为Vendors表的主键,因而vend_id在Products作为外键存在,Products和Vendors存在外键约束。在外键vend_id的约束下,Products无法新增vend_id值不在Vendors.vend_id列中的新纪录,Vendors也无法删除vend_id值在Products表中出现过的那些记录。

7. 表的DML

7.1 创建表

一个列可以指定为使用NULL值(默认)或者不适用NULL值,主键列必须指定为不使用NULL值。

-- 指定默认值
CREATE TABLE OrderItems
(
  order_num  int          NOT NULL ,
  order_item int          NOT NULL ,
  prod_id    char(10)     NOT NULL ,
  quantity   int          NOT NULL default 1,
  item_price decimal(8,2) NOT NULL 
);

-- 默认值常用语指定日期或者时间戳
CREATE TABLE events (
    event_id INT AUTO_INCREMENT PRIMARY KEY,
    event_date DATE NOT NULL DEFAULT (CURRENT_DATE()),  -- 要将函数CURRENT_DATE()用括号包裹起来。
    event_time TIME,
    event_datetime DATETIME,
    event_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- MYSQL中常用的时间类型有 DATE('2025-01-01')、TIME('00:00:00')、DATETIME('2025-01-01 00:00:00')、TIMESTAMP

7.1 更改表结构

-- 增加列
ALTER TABLE Vendors
ADD COLUMN vend_phone CHAR(20);

-- 修改列
ALTER TABLE Vendors
MODIFY COLUMN vend_phone vendPhone VARCHAR(11);

-- 删除列
ALTER TABLE Vendors
DROP COLUMN vend_phone;

7.3 删除表

如果表是某个关系到的组成部分,则无法删除,除非先删除关系。

DROP TABLE CustCopy;

7.4 重命名表

RENAME TABLE CustCopy TO CustBak;
ALTER TABLE CustCopy RENAME [TO] CustBak;

8. 补充

8.1 tips

  • SQL不区分大小写,一般SQL关键字用大写,表名列名小写;SQL忽略空格,但是一般分行写SQL以增强可读性。
  • 相同数据出现多次是不好的,数据库设计需要避免这种情况(将冗余字段分出去单独成表,然后与原表的建立关系)。
  • 许多DBMS处理连接比处理子查询要快很多 尽量使用default value而不是NULL值 在使用ALTER
    TABLE最好先备份,数据库表的更改无法撤销。
  • 在 MySQL 里,IF语句属于流程控制语句,这类语句只能在存储过程、函数、触发器等特定的程序结构中使用,不能直接在普通的 SQL 脚本中使用。
  • 局部变量是使用declare显式声明在存储过程、函数、触发器等数据库对象中的变量,作用域仅在这些数据库对象内部。
  • 每个客户端连接到MySQL服务器都会有一个独立的会话,会话变量作用域在会话中,前缀为@,不需要显式声明。
  • 主键不要修改,主键值不要重用。

8.2 数据库安全需要关注的地方

  • 数据库管理能力(创建、删除、更新表)
  • 用户账户管理能力(DBMS用户)
  • 访问特定数据库或者表
  • 访问类型(增删改查,删改属于敏感操作,需要保护)
  • 通过视图或者存储过程访问(存储过程或者视图涉及重要表和敏感信息时,需要保护)

9. 参考

贝内特(Bennett, F. X.). (2020). SQL 必知必会(第 5 版) (钟鸣,刘晓霞,译). 北京:人民邮电出版社.

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值