建表语句:https://blog.csdn.net/weixin_43372836/article/details/107815785
SELECT语句是SQL的查询。迄今为⽌我们所看到的所有SELECT语句都是简单查询,即从单个数据 库表中检索数据的单条语句。
SQL还允许创建⼦查询(subquery),即嵌套在其他查询中的查询。
订单存储在两个表中。对于包含订单号、客户ID、订单⽇期的每个订单,orders表存储⼀⾏。 各订单的 物品存储在相关的orderitems表中。orders表不存储客户信息。它只存储客户的ID。
实际的客户信息存储在customers表中。
现在,假如需要列出订购物品TNT2的所有客户,应该怎样检索?
--(1) 检索包含物品TNT2的所有订单的编号。
select order_num from orderitems where prod_id = 'TNT2';
+-----------+
| order_num |
+-----------+
| 20005 |
| 20007 |
+-----------+
--(2) 检索具有前⼀步骤列出的订单编号的所有客户的ID
select cust_id from orders where order_num IN (20005,20007);
+---------+
| cust_id |
+---------+
| 10001 |
| 10004 |
+---------+
--(3) 检索前⼀步骤返回的所有客户ID的客户信息。
select cust_name,cust_contact from customers where cust_id in (10001,10004);
+----------------+--------------+
| cust_name | cust_contact |
+----------------+--------------+
| Coyote Inc. | Y Lee |
| Yosemite Place | Y Sam |
+----------------+--------------+
可以把其中的WHERE⼦句转换为⼦查询⽽不是硬编码这些SQL返回的数据:
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'));
+----------------+--------------+
| cust_name | cust_contact |
+----------------+--------------+
| Coyote Inc. | Y Lee |
| Yosemite Place | Y Sam |
+----------------+--------------+
--为了执⾏上述SELECT语句,MySQL实际上必须执⾏3条SELECT语句。
--最⾥边的⼦查询返回订单号列表,此列表⽤于其外⾯的⼦查询的WHERE⼦句。
--外⾯的⼦查询返回客户ID列表,此客户ID列表⽤于最外层查询的WHERE⼦句。
--最外层查询确实返回所需的数据。
这⾥给出的代码有效并获得所需的结果。
但是,使⽤⼦查询并不总是执⾏这种类型的数据检索的最有效的⽅法。
使⽤⼦查询的另⼀⽅法是创建计算字段。
-- 假如需要显示customers表中每个客户的订单总数。订单与相应的客户ID存储在orders表中。
-- (1) 从customers表中检索客户列表。
select cust_id,cust_name from customers ;
+---------+----------------+
| cust_id | cust_name |
+---------+----------------+
| 10001 | Coyote Inc. |
| 10002 | Mouse House |
| 10003 | Wascals |
| 10004 | Yosemite Place |
| 10005 | E Fudd |
+---------+----------------+
-- (2) 对于检索出的每个客户,统计其在orders表中的订单数⽬。
select count(*) as orders from orders where cust_id = 10001;
+--------+
| orders |
+--------+
| 2 |
+--------+
为了对每个客户执⾏COUNT()计算,应该将COUNT()作为⼀个⼦查询。
select cust_id,cust_name,
(select count(*)
from orders
where orders.cust_id = customers.cust_id
) as orders
from customers
order by cust_name;
+---------+----------------+--------+
| cust_id | cust_name | orders |
+---------+----------------+--------+
| 10001 | Coyote Inc. | 2 |
| 10005 | E Fudd | 1 |
| 10002 | Mouse House | 0 |
| 10003 | Wascals | 6 |
| 10004 | Yosemite Place | 1 |
+---------+----------------+--------+
orders是⼀个计算字段,它是由圆括号中的⼦查询建⽴的。该⼦查询对检索出的每个客户执⾏⼀ 次。在此例⼦中,该⼦查询执⾏了5次,因为检索出了5个客户。
注意:
⼦查询中的WHERE⼦句与前⾯使⽤的WHERE⼦句稍有不同,因为它使⽤了完全限定列名 这种类型的⼦查询称为相关⼦查询。任何时候只要列名可能有多义性,就必须使⽤这种语法 (表名和列名由⼀个句点分隔)。因为有两个cust_id列,⼀个在customers中,另⼀个在 orders中,需要⽐较这两个列以正确地把订单与它们相应的顾客匹配。如果不完全限定列名, MySQL将假定你是对orders表中的cust_id进⾏⾃身⽐较。
SQL最强⼤的功能之⼀就是能在数据检索查询的执⾏中联结(join)表。 在能够有效地使⽤联结前,必须了解关系表以及关系数据库设计的⼀些基础知识。
--假如有⼀个包含产品⽬录的数据库表,其中每种类别的物品占⼀⾏。
--对于每种物品要存储的信息包括产品描述和价格,以及⽣产该产品的供应商信息。
产品表:
产品,描述,价格,供应商名称,供应商地址,供应商联系⽅式
A6 ... ... 奥迪 ... ....
520li .. .... 宝⻢ ... ...
...
--现在,假如有由同⼀供应商⽣产的多种物品,那么在何处存储供应
--商信息(如,供应商名、地址、联系⽅法等)呢?
产品,描述,价格,供应商名称,供应商地址,供应商联系⽅式
A6 ... ... 奥迪 ... ....
520li .. .... 宝⻢ ... ...
A8 .. ... 奥迪 ... ...
相同数据出现多次决不是⼀件好事,此因素是关系数据库设计的基础。
关系表的设计就是要保证把信息分解成多个表,⼀类数据⼀个表。
各表通过某些常⽤的值(即关系设计中的关系(relational))互相关联。
在这个例⼦中,可建⽴两个表,⼀个存储供应商信息,另⼀个存储产品信息。
-- vendors表包含所有供应商信息
|vend_id | vend_name | vend_address| vend_city ....
-- products表只存储产品信息,它除了存储供应商ID(vendors表的主键)外不存储其他供应商信息。
prod_id | vend_id | prod_name | prod_price | prod_desc
vendors表的主键⼜叫作products的外键,它将vendors表与products表关联,利⽤供应商ID能
从vendors表中找出相应供应商的详细信息。 这样做的好处如下:
- 供应商信息不重复,从⽽不浪费时间和空间;
- 如果供应商信息变动,可以只更新vendors表中的单个记录,相关表中的数据不⽤改动;
- 由于数据⽆重复,显然数据是⼀致的,这使得处理数据更简单
关系数据可以有效地存储和⽅便地处理。因此,关系数据库的可伸缩性远⽐⾮关系数据库要好。
一对一:就是在一个表中的数据,对应着另外一张表中的一个数据,只能有一个
外键:就是在一个表中的字段,这个字段中存储的数据是另一张表中的主键。就是在一个表中的字段,代表着这个数据属于谁。
了解:外键实现的方式,有两种:物理外键、逻辑外键
- 物理外键:
- 就是在创建表时,就指定这个表中的字段是一个外键,并且强关联某个表中的某个字段。
- 需要在定义表时,使用sql语句来实现
- 逻辑外键:
- 就是在表中创建一个普通的字段,没有强关联关系,需要通过程序逻辑来实现
员工表:id,姓名,年龄,籍贯,联系方式,学历,工龄...
由上面的一个表,拆分成两个表
员工表:id,姓名,年龄,联系方式,工龄
详情表:性别,籍贯,学历...yid
上面的表关系就是一对一的关系,通过详情表中的yid这个字段来标记员工表中的逐渐。
一个员工表有着一个对应的详情信息,存储在详情表中,
一对多:
- 就是在一个表中的数据,对应着另外一张表中的多条数据
- 或者一张表中的多条数据,对应着另外一张表中的一条数据
多对多:
- 就是在一个表中的数据,对应着另外一张表中的多条数据
- 并且另外一张表中的一条数据对应着这张表中的多条数据
如果数据存储在多个表中,怎样⽤单条SELECT语句检索出数据?
答案是使⽤联结。简单地说,联结是⼀种机制,⽤来在⼀条SELECT语句中关联表,因此称之为联结。
使⽤特殊的语法,可以联结多个表返回⼀组输出,联结在运⾏时关联表中正确的⾏。
例如:我们需要查询出所有的商品及对应的供应商信息怎么办?where方法
-- 联结的创建⾮常简单,规定要联结的所有表以及它们如何关联即可。
select vend_name,prod_name,prod_price
from vendors,products
where vendors.vend_id = products.vend_id
order by vend_name,prod_name;
+-------------+----------------+------------+
| vend_name | prod_name | prod_price |
+-------------+----------------+------------+
| ACME | Bird seed | 10.00 |
| ACME | Carrots | 2.50 |
| ACME | Detonator | 13.00 |
| ACME | Safe | 50.00 |
| ACME | Sling | 4.49 |
| ACME | TNT (1 stick) | 2.50 |
| ACME | TNT (5 sticks) | 10.00 |
| Anvils R Us | .5 ton anvil | 5.99 |
| Anvils R Us | 1 ton anvil | 9.99 |
| Anvils R Us | 2 ton anvil | 14.99 |
| Jet Set | JetPack 1000 | 35.00 |
| Jet Set | JetPack 2000 | 55.00 |
| LT Supplies | Fuses | 3.42 |
| LT Supplies | Oil can | 8.99 |
+-------------+----------------+------------+
14 rows in set (0.00 sec)
--这两个表⽤WHERE⼦句正确联结,WHERE⼦句指示MySQL匹配vendors表中的vend_id和products表
中的vend_id。
--可以看到要匹配的两个列以 vendors.vend_id 和 products. vend_id指定。这⾥需要这种完全限
定列名,因为如果只给出vend_id,则MySQL不知道指的是哪⼀个(它们有两个,每个表中⼀个)。
--在引⽤的列可能出现⼆义性时,必须使⽤完全限定列名(⽤⼀个点分隔的表名和列名)。
在联结两个表时,你实际上做的是将第⼀个表中的每⼀⾏与第⼆个表中的每⼀⾏配对。
WHERE⼦句作为过滤条件,它只包含那些匹配给定条件(这⾥是联结条件)的⾏。
你能想象上⾯的sql如果没有where条件时会怎样吗?
select vend_name,prod_name,prod_price from vendors,products
如果没有where条件,第⼀个表中的每个⾏将与第⼆个表中的每个⾏配对,⽽不管它们逻辑上是 否可以配在⼀起 由没有联结条件的表关系返回的结果为笛卡⼉积。检索出的⾏的数⽬将是第⼀个表中的⾏数乘以 第⼆个表中的⾏数。
不要忘了WHERE⼦句
应该保证所有联结都有WHERE⼦句,否则MySQL将返回⽐想要的数据多得多的数据。 同理,应该保证WHERE⼦句的正确性。不正确的过滤条件将导致MySQL返回不正确的数据
其实,对于这种联结可以使⽤稍微不同的语法来明确指定联结的类型 join...on...方法
select vend_name,prod_name,prod_price from vendors inner join products on
vendors.vend_id = products.vend_id;
--可以不写inner
select vend_name,prod_name,prod_price from vendors join products on
vendors.vend_id = products.vend_id;
两个表之间的关系是FROM⼦句的组成部分,以INNER JOIN指定。
在使⽤这种语法时,联结条件⽤特定的ON⼦句⽽不是WHERE⼦句给出。
传递给ON的实际条件与传递给WHERE的相同。
SQL规范⾸选INNER JOIN语法。
SQL对⼀条SELECT语句中可以联结的表的数⽬没有限制。
创建联结的基本规则也相同。⾸先列出所有表,然后定义表之间的关系。
where方法
select prod_name,vend_name,prod_price,quantity
from orderitems,products,vendors
where products.vend_id = vendors.vend_id
and orderitems.prod_id = products.prod_id
and order_num = 20005;
改写 join 的语法
select prod_name,vend_name,prod_price,quantity
from orderitems
join products on orderitems.prod_id = products.prod_id
join vendors on products.vend_id = vendors.vend_id
where order_num = 20005;
MySQL在运⾏时关联指定的每个表以处理联结。 这种处理可能是⾮常耗费资源的,因此应该仔 细,不要联结不必要的表。联结的表越多,性能下降越厉害。
别名除了⽤于列名和计算字段外,SQL还允许给表名起别名。
这样做有两个主要理由:
- 缩短SQL语句;
- 允许在单条SELECT语句中多次使⽤相同的表
应该注意,表别名只在查询执⾏中使⽤。与列别名不⼀样,表别名不返回到客户机
自联结:当前这个表与自己这个表 做联结(join)
假如你发现某物品(其ID为DTNTR)存在问题,因此想知道⽣产该物品的供应商⽣产的其他物品 是否也存在这些问题。此查询要求⾸先找到⽣产ID为DTNTR的物品的供应商,然后找出这个供应 商⽣产的其他物品。
⼦查询(嵌套查询)
-- 使⽤⼦查询(嵌套查询)
select prod_id,prod_name
from products
where vend_id = (select vend_id from products where prod_id = 'DTNTR');
+---------+----------------+
| prod_id | prod_name |
+---------+----------------+
| DTNTR | Detonator |
| FB | Bird seed |
| FC | Carrots |
| SAFE | Safe |
| SLING | Sling |
| TNT1 | TNT (1 stick) |
| TNT2 | TNT (5 sticks) |
+---------+----------------+
自联结
-- 使⽤联结的相同查询:
select p1.prod_id,p1.prod_name
from products as p1
join products as p2
on p1.vend_id = p2.vend_id and p2.prod_id = 'DTNTR';
--改成where语句
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';
+---------+----------------+
| prod_id | prod_name |
+---------+----------------+
| DTNTR | Detonator |
| FB | Bird seed |
| FC | Carrots |
| SAFE | Safe |
| SLING | Sling |
| TNT1 | TNT (1 stick) |
| TNT2 | TNT (5 sticks) |
+---------+----------------+
-- 此查询中需要的两个表实际上是相同的表,因此products表在FROM⼦句中出现了两次。虽然这是完全
合法的,但对products的引⽤具有⼆义性,因为MySQL不知道你引⽤的是products表中的哪个实例。
-- 为解决此问题,使⽤了表别名。products的第⼀次出现为别名p1,第⼆次出现为别名p2。现在可以将
这些别名⽤作表名。
--例如,SELECT语句使⽤p1前缀明确地给出所需列的全名。如果不这样,MySQL将返回错误,因为分别存
在两个名为prod_id、prod_name的列。MySQL不知道想要的是哪⼀个列(即使它们事实上是同⼀个
列)。WHERE(通过匹配p1中的vend_id和p2中的vend_id)⾸先联结两个表,然后按第⼆个表中的
prod_id过滤数据,返回所需的数据
⽤⾃联结⽽不⽤⼦查询 ⾃联结通常作为外部语句⽤来替代从相同表中检索数据时使⽤的⼦查询语 句。 虽然最终的结果是相同的,但有时候处理联结远⽐处理⼦查询快得多。⼦查询(嵌套查询)是目前可明确知道的sql运行效率最低的一种方式,尽可能不使用嵌套语句
许多联结将⼀个表中的⾏与另⼀个表中的⾏相关联。但有时候会需要包含没有关联⾏的那些⾏。
例如,可能需要使⽤联结来完成以下⼯作:
在上述例⼦中,联结包含了那些在相关表中没有关联⾏的⾏。这种类型的联结称为外部联结。
-- 内部联结。它检索所有客户及其订单:只能对两个表中相关联的数据进行查询
select customers.cust_id,orders.order_num
from customers inner join orders
on customers.cust_id = orders.cust_id;
+---------+-----------+
| cust_id | order_num |
+---------+-----------+
| 10001 | 20005 |
| 10001 | 20009 |
| 10003 | 20006 |
| 10004 | 20007 |
| 10005 | 20008 |
+---------+-----------+
5 rows in set (0.00 sec)
什么是外部联结?
left join: 是以 left join 左侧表为基准,去关联右侧的表进行联结,如果有未关联的数据 ,那么结果为空
right join: 是以 right join 右侧表为基准,去关联左侧的表进行联结,如果有未关联的数据 ,那么结果为空
--外部联结语法类似。检索所有客户,包括那些没有订单的客户
select customers.cust_id,orders.order_num
from customers left 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 (0.00 sec)
聚集函数也可以⽅便地与其他联结⼀起使⽤。
如果要检索所有客户及每个客户所下的订单数,下⾯使⽤了COUNT()函数的代码可完成此⼯作
包含那些没有任何下订单的客户。
--对每个客户下了多少订单进行计数,包括那些至今尚未下了订单的客户
select customers.cust_id,count(orders.order_num) as nums
from customers left join orders
on customers.cust_id = orders.cust_id
group by customers.cust_id;
+----------------+---------+---------+
| cust_name | cust_id | num_ord |
+----------------+---------+---------+
| Coyote Inc. | 10001 | 2 |
| Mouse House | 10002 | 0 |
| Wascals | 10003 | 1 |
| Yosemite Place | 10004 | 1 |
| E Fudd | 10005 | 1 |
+----------------+---------+---------+
保证使⽤正确的联结条件,否则将返回不正确的数据。
应该总是提供联结条件,否则会得出笛卡⼉积。
在⼀个联结中可以包含多个表,甚⾄对于每个联结可以采⽤不同的联结类型。虽然这样做是
合法的,⼀般也很有⽤,但应该在⼀起使用它们前,分别测试每个联结。这将使故障排除更
为简单。
内部联结:where,inner join(join)
自联结:是在一个sql中,用当前的表,联结自己这个表进行关联查询
外部联结:left join,right join
MySQL也允许执⾏多个查询(多条SELECT语句),并将结果作为单个查询结果集返回。
这些组合查询通常称为并(union)或复合查询(compound query)。
UNION规则
- UNION必须由两条或两条以上的SELECT语句组成,语句之间⽤关键字UNION分隔(因此,如果组合4条SELECT语句,将要使⽤3个UNION关键字)。
- UNION中的每个查询必须包含相同的列、表达式或聚集函数(不过各个列不需要以相同的次序列出)
- 列数据类型必须兼容:类型不必完全相同,但必须是DBMS可以隐含地转换的类型(例如,不同的数值类型或不同的⽇期类型)。
--假如需要价格⼩于等于5的所有物品的⼀个列表,⽽且还想包括供应商1001和1002⽣产的所有物品。
-- 先查询第⼀个结果
--假如需要价格⼩于等于5的所有物品的⼀个列表,⽽且还想包括供应商1001和1002⽣产的所有物品。
-- 先查询第⼀个结果
select vend_id,prod_id,prod_price from products where prod_price <= 5;
+---------+---------+------------+
| vend_id | prod_id | prod_price |
+---------+---------+------------+
| 1003 | FC | 2.50 |
| 1002 | FU1 | 3.42 |
| 1003 | SLING | 4.49 |
| 1003 | TNT1 | 2.50 |
+---------+---------+------------+
4 rows in set (0.00 sec)
-- 再查询第⼆个结果
-- 再查询第⼆个结果
select vend_id,prod_id,prod_price from products where vend_id in(1001,1002);
+---------+---------+------------+
| vend_id | prod_id | prod_price |
+---------+---------+------------+
| 1001 | ANV01 | 5.99 |
| 1001 | ANV02 | 9.99 |
| 1001 | ANV03 | 14.99 |
| 1002 | FU1 | 3.42 |
| 1002 | OL1 | 8.99 |
+---------+---------+------------+
5 rows in set (0.00 sec)
--使⽤union将两个sql⼀并执⾏
--使⽤union将两个sql⼀并执⾏
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);
+---------+---------+------------+
| vend_id | prod_id | prod_price |
+---------+---------+------------+
| 1003 | FC | 2.50 || 1002 | FU1 | 3.42 |
| 1003 | SLING | 4.49 |
| 1003 | TNT1 | 2.50 |
| 1001 | ANV01 | 5.99 |
| 1001 | ANV02 | 9.99 |
| 1001 | ANV03 | 14.99 |
| 1002 | OL1 | 8.99 |
+---------+---------+------------+
8 rows in set (0.09 sec)
-- 这条语句由前⾯的两条SELECT语句组成,语句中⽤UNION关键字分隔。
-- UNION指示MySQL执⾏两条SELECT语句,并把输出组合成单个查询结果集
-- 以下是同样结果,使⽤where的多条件来实现
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.50 |
| 1002 | FU1 | 3.42 |
| 1002 | OL1 | 8.99 |
| 1003 | SLING | 4.49 |
| 1003 | TNT1 | 2.50 |
+---------+---------+------------+
8 rows in set (0.00 sec)
- 现在思考⼀个问题,上⾯的语句分别返回了⼏条数据?
- 第⼀条sql返回4⾏,第⼆条sql返回5⾏,那么union返回了⼏⾏?
UNION从查询结果集中⾃动去除了重复的⾏(换句话说,它的⾏为与单条SELECT语句中使⽤多个WHERE⼦句条件⼀样)。
这是UNION的默认⾏为,但是如果需要,可以改变它。如果想返回所有匹配⾏,可使⽤UNION ALL⽽不是UNION
- 对组合查询结果排序
SELECT语句的输出⽤ORDER BY⼦句排序。在⽤UNION组合查询时,只能使⽤⼀条ORDER BY⼦句,它必须出现在最后⼀条SELECT语句之后。
对于结果集,不存在⽤⼀种⽅式排序⼀部分,⽽⼜⽤另⼀种⽅式排序另⼀部分的情况,因此不允 许使⽤多条ORDER BY⼦句。
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 prod_price;
--这条UNION在最后⼀条SELECT语句后使⽤了ORDER BY⼦句。
--虽然ORDER BY⼦句似乎只是最后⼀条SELECT语句的组成部分,但实际上MySQL将⽤它来排序所有 SELECT语句返回的所有结果。
建表代码:https://blog.csdn.net/weixin_43372836/article/details/107815785
--1.学⽣表
Student(SId,Sname,Sage,Ssex)
--SId 学⽣编号,Sname 学⽣姓名,Sage 出⽣年⽉,Ssex 学⽣性别
--2.课程表
Course(CId,Cname,TId)
--CId 课程编号,Cname 课程名称,TId 教师编号
--3.教师表
Teacher(TId,Tname)
--TId 教师编号,Tname 教师姓名
--4.成绩表
SC(SId,CId,score)
--SId 学⽣编号,CId 课程编号,score 分数
1. 查询" 01 "课程⽐" 02 "课程成绩⾼的学⽣的信息及课程分数
1.查询" 01 "课程比" 02 "课程成绩高的学生的信息及课程分数
-- 1. 先分别查询01和02课程的学员id和分数
select sid,score from sc where cid = '01';
select sid,score from sc where cid = '02';
-- 2. 对比两个结果,发现两个结果中有一些sid是不对应,
-- 因此可以对两个结果做join联结,条件是 sid要相等
-- 并且01的成绩要大于02的
select s1.sid,s1.score from
(select sid,score from sc where cid = '01') as s1
join
(select sid,score from sc where cid = '02') as s2
on s1.sid = s2.sid
where s1.score > s2.score;
+------+-------+
| sid | score |
+------+-------+
| 02 | 70.0 |
| 04 | 50.0 |
+------+-------+
-- 3. 通过以上的sql,得到了符合条件的学员id和分数,再联结学生表,获取学员信息
select stu.sid,stu.sname,s.score
from student as stu
join
(
select s1.sid,s1.score from
(select sid,score from sc where cid = '01') as s1
join
(select sid,score from sc where cid = '02') as s2
on s1.sid = s2.sid
where s1.score > s2.score
) as s
on stu.sid = s.sid;
2. 查询同时存在" 01 "课程和" 02 "课程的情况
2. 查询同时存在" 01 "课程和" 02 "课程的情况
select s1.* from
(select sid,score from sc where cid = '01') as s1
join
(select sid,score from sc where cid = '02') as s2
on s1.sid = s2.sid;
+------+-------+
| sid | score |
+------+-------+
| 01 | 80.0 |
| 02 | 70.0 |
| 03 | 80.0 |
| 04 | 50.0 |
| 05 | 76.0 |
+------+-------+
5 rows in set (0.00 sec)
3. 查询学了" 01 "课程但可能不学" 02 "课程的情况(不存在时显示为 null )
3.查询存在" 01 "课程但可能不存在" 02 "课程的情况(不存在时显示为 null )
select s1.*,s2.* from
(select sid,score from sc where cid = '01') as s1
left join
(select sid,score from sc where cid = '02') as s2
on s1.sid = s2.sid;
+------+-------+------+-------+
| sid | score | sid | score |
+------+-------+------+-------+
| 01 | 80.0 | 01 | 90.0 |
| 02 | 70.0 | 02 | 60.0 |
| 03 | 80.0 | 03 | 80.0 |
| 04 | 50.0 | 04 | 30.0 |
| 05 | 76.0 | 05 | 87.0 |
| 06 | 31.0 | NULL | NULL |
+------+-------+------+-------+
4. 查询不学" 01 "课程但学了" 02 "课程的情况
4.查询不存在" 01 "课程但存在" 02 "课程的情况
select * from sc
where sid not in (select sid from sc where cid = '01')
and cid = '02'
+------+------+-------+
| SId | CId | score |
+------+------+-------+
| 07 | 02 | 89.0 |
+------+------+-------+
5.查询平均成绩⼤于等于 60 分的同学的学⽣编号和学⽣姓名和平均成绩,round(数值,保留小数点位数)
5.查询平均成绩大于等于 60 分的同学的学生编号和学生姓名和平均成绩
select sc.sid,sname,round(avg(score),2) as avg_score
from sc,student
where sc.sid = student.sid
group by sc.sid,sname
having avg_score >= 60;
+------+--------+-----------+
| sid | sname | avg_score |
+------+--------+-----------+
| 01 | 赵雷 | 89.67 |
| 02 | 钱电 | 70.00 |
| 03 | 孙风 | 80.00 |
| 05 | 周梅 | 81.50 |
| 07 | 郑竹 | 93.50 |
+------+--------+-----------+
6.查询在 SC 表存在成绩的学⽣信息 distinct去重
6.查询在 SC 表存在成绩的学生信息
-- distinct 去重
select distinct stu.* from student as stu join sc on stu.sid = sc.sid;
+------+--------+---------------------+------+
| SId | Sname | Sage | Ssex |
+------+--------+---------------------+------+
| 01 | 赵雷 | 1990-01-01 00:00:00 | 男 |
| 02 | 钱电 | 1990-12-21 00:00:00 | 男 |
| 03 | 孙风 | 1990-12-20 00:00:00 | 男 |
| 04 | 李云 | 1990-12-06 00:00:00 | 男 |
| 05 | 周梅 | 1991-12-01 00:00:00 | 女 |
| 06 | 吴兰 | 1992-01-01 00:00:00 | 女 |
| 07 | 郑竹 | 1989-01-01 00:00:00 | 女 |
+------+--------+---------------------+------+
7.查询所有同学的学⽣编号、学⽣姓名、选课总数、所有课程的总成绩(没成绩的显示为 null )
7.查询所有同学的学生编号、学生姓名、选课总数、所有课程的总成绩(没成绩的显示为 null )
select stu.sid,stu.sname,count(sc.cid) as total,sum(sc.score) as sum_score
from student as stu
left join sc on stu.sid = sc.sid
group by stu.sid,stu.sname;
+------+--------+-------+-----------+
| sid | sname | total | sum_score |
+------+--------+-------+-----------+
| 01 | 赵雷 | 3 | 269.0 |
| 02 | 钱电 | 3 | 210.0 |
| 03 | 孙风 | 3 | 240.0 |
| 04 | 李云 | 3 | 100.0 |
| 05 | 周梅 | 2 | 163.0 |
| 06 | 吴兰 | 2 | 65.0 |
| 07 | 郑竹 | 2 | 187.0 |
| 09 | 张三 | 0 | NULL |
| 10 | 李四 | 0 | NULL |
| 11 | 李四 | 0 | NULL |
| 12 | 赵六 | 0 | NULL |
| 13 | 孙七 | 0 | NULL |
+------+--------+-------+-----------+
8.查询「李」姓⽼师的数量
SELECT COUNT(tid) from teacher where Tname LIKE '李%';
9.查询学过「张三」⽼师授课的同学的信息
1.查询张三老师的tid
SELECT tid from teacher WHERE Tname = '张三';
2.根据张三老师的tid查询他的课程编号cid
select CId FROM course as c join
(SELECT tid from teacher WHERE Tname = '张三') AS t
on c.TId = t.tid
3.根据cid查询所学的学生编号sid
SELECT sid from sc join
(
select CId FROM course
join (SELECT tid from teacher WHERE Tname = '张三') AS t
on course.TId = t.tid
) as c
on sc.cid = c.cid;
4.根据sid查询学生信息
SELECT student.* from student join
(
SELECT sid from sc join
(
select CId FROM course
join (SELECT tid from teacher WHERE Tname = '张三') AS t
on course.TId = t.tid
) as c
on sc.cid = c.cid
) as s
on student.sid = s.sid;
10.查询没有学全所有课程的同学的信息
##10.查询没有学全所有课程的同学的信息
1.查询一共有几门
SELECT count(cid) as num1 from course;
2.查询学生学了几门课
SELECT sid,count(sid) as num2 from sc
GROUP BY sid;
3.查询学了所有课程的学生编号
SELECT s.sid from
(
SELECT sid,count(sid) as num2 from sc
GROUP BY sid
) as s
join
(SELECT count(cid) as num1 from course) as c
on s.num2 = c.num1;
4.查询所有不在范围内的学生信息
SELECT stu.* from student as stu
where stu.sid not in
(
SELECT s.sid from
(
SELECT sid,count(sid) as num2 from sc
GROUP BY sid
) as s
join
(SELECT count(cid) as num1 from course) as c
on s.num2 = c.num1
);
11.查询至少有一门课与学号为" 01 "的同学所学相同的同学的信息
select distinct stu.*
from student as stu
join sc on sc.sid = stu.sid
where sc.cid in (select cid from sc where sid = '01');
+------+--------+---------------------+------+
| SId | Sname | Sage | Ssex |
+------+--------+---------------------+------+
| 01 | 赵雷 | 1990-01-01 00:00:00 | 男 |
| 02 | 钱电 | 1990-12-21 00:00:00 | 男 |
| 03 | 孙风 | 1990-12-20 00:00:00 | 男 |
| 04 | 李云 | 1990-12-06 00:00:00 | 男 |
| 05 | 周梅 | 1991-12-01 00:00:00 | 女 |
| 06 | 吴兰 | 1992-01-01 00:00:00 | 女 |
| 07 | 郑竹 | 1989-01-01 00:00:00 | 女 |
+------+--------+---------------------+------+
7 rows in set (0.00 sec)
12.查询和" 01 "号的同学学习的课程完全相同的其他同学的信息
select s2.sid,student.sname
from sc as s1
join sc as s2
on s1.cid = s2.cid and s1.sid = '01' and s2.sid != '01'
join student on s2.sid = student.sid
group by s2.sid,student.sname
having count(s2.cid) = (select count(*) from sc where sid = '01');
+------+--------+
| sid | sname |
+------+--------+
| 02 | 钱电 |
| 03 | 孙风 |
| 04 | 李云 |
+------+--------+
#### 关于sql_mode
> sql_mode是MySQL数据中的一个环境变量
> 定义了MySQL应该支持的sql语法,数据校验等# 指定列分组,而不是用全部列 select stu.sid,stu.sname,count(sc.cid) from student as stu left join sc on sc.sid = stu.sid group by stu.sid;
# ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'chuange.stu.Sname' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
# 标准的SQL select stu.sid,stu.sname,count(sc.cid) from student as stu left join sc on sc.sid = stu.sid group by stu.sid,stu.sname;
# 查看当前数据库中的sql_mode select @@sql_mode; +-------------------------------------------------------------------------------------------------------------------------------------------+ | @@sql_mode | +-------------------------------------------------------------------------------------------------------------------------------------------+ | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION | +-------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
ONLY_FULL_GROUP_BY
-- 针对grouo by 聚合操作,如果在select中的列,没有在group by中出现,那么将认为sql不合法# 更改mysql数据库的sql_mode 1. 临时修改(服务器重启后失效) set @@sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'; 2. 修改mysql配置文件(重启后生效) my.cnf 在my.cnf的[mysqld]的下面去配置 [mysqld] xxx = xxx sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';
总结建议:
1. 建议开启,符合SQL标准
2. 在MySQL中有一个函数 any_value(filed),允许返回非分组字段(和关闭only_full_group_by模式相同)
使用 any_value 函数
select stu.sid,any_value(stu.sname),any_value(stu.sage),count(sc.cid)
from student as stu
left join sc on sc.sid = stu.sid
group by stu.sid;
13.查询没学过"张三"⽼师讲授的任⼀⻔课程的学⽣姓名
--参考第9题
SELECT * from student
where sid not in
(
SELECT sid from sc join
(
select CId FROM course
join (SELECT tid from teacher WHERE Tname = '张三') AS t
on course.TId = t.tid
) as c
on sc.cid = c.cid
);
14.查询两门及其以上不及格课程的同学的学号,姓名及其平均成绩
select stu.sid,stu.sname,round(avg(sc.score),2) as avg_score
from student as stu
join sc on stu.sid = sc.sid
where sc.score < 60
group by stu.sid,stu.sname having count(sc.cid) >= 2;
15.检索" 01 "课程分数⼩于 60,按分数降序排列的学⽣信息
select stu.*,sc.score
from sc join student as stu
on sc.sid = stu.sid
where sc.cid = '01' and sc.score < 60
order by sc.score desc;
16.按平均成绩从⾼到低显示所有学⽣的所有课程的成绩以及平均成绩
16.按平均成绩从高到低显示所有学生的所有课程的成绩以及平均成绩
方法一:
select sc.*,s2.avg_score
from sc
join (select sid,avg(score) as avg_score from sc group by sid) as s2
on sc.sid = s2.sid
order by s2.avg_score desc,sc.sid;
+------+------+-------+-----------+
| SId | CId | score | avg_score |
+------+------+-------+-----------+
| 07 | 03 | 98.0 | 93.50000 |
| 07 | 02 | 89.0 | 93.50000 |
| 01 | 01 | 80.0 | 89.66667 |
| 01 | 02 | 90.0 | 89.66667 |
| 01 | 03 | 99.0 | 89.66667 |
| 05 | 01 | 76.0 | 81.50000 |
| 05 | 02 | 87.0 | 81.50000 |
| 03 | 03 | 80.0 | 80.00000 |
| 03 | 01 | 80.0 | 80.00000 |
| 03 | 02 | 80.0 | 80.00000 |
| 02 | 03 | 80.0 | 70.00000 |
| 02 | 01 | 70.0 | 70.00000 |
| 02 | 02 | 60.0 | 70.00000 |
| 04 | 02 | 30.0 | 33.33333 |
| 04 | 03 | 20.0 | 33.33333 |
| 04 | 01 | 50.0 | 33.33333 |
| 06 | 03 | 34.0 | 32.50000 |
| 06 | 01 | 31.0 | 32.50000 |
+------+------+-------+-----------+
方法二:
select
stu.sname,
a.score as '语文',
b.score as '数学',
c.score as '英语',
avg(d.score) as '平均成绩'
from student as stu
left join sc as a on stu.sid = a.sid and a.cid = '01'
left join sc as b on stu.sid = b.sid and b.cid = '02'
left join sc as c on stu.sid = c.sid and c.cid = '03'
left join sc as d on stu.sid = d.sid
group by stu.sname,语文,数学,英语
order by 平均成绩 desc;
+--------+--------+--------+--------+--------------+
| sname | 语文 | 数学 | 英语 | 平均成绩 |
+--------+--------+--------+--------+--------------+
| 郑竹 | NULL | 89.0 | 98.0 | 93.50000 |
| 赵雷 | 80.0 | 90.0 | 99.0 | 89.66667 |
| 周梅 | 76.0 | 87.0 | NULL | 81.50000 |
| 孙风 | 80.0 | 80.0 | 80.0 | 80.00000 |
| 钱电 | 70.0 | 60.0 | 80.0 | 70.00000 |
| 李云 | 50.0 | 30.0 | 20.0 | 33.33333 |
| 吴兰 | 31.0 | NULL | 34.0 | 32.50000 |
| 孙七 | NULL | NULL | NULL | NULL |
| 李四 | NULL | NULL | NULL | NULL |
| 赵六 | NULL | NULL | NULL | NULL |
| 张三 | NULL | NULL | NULL | NULL |
+--------+--------+--------+--------+--------------+
11 rows in set (0.00 sec)
以如下形式显示:课程 ID,课程 name,最⾼分,最低分,平均分,及格率,中等率,优良率,优秀率 及格为>=60,中等为:70-80,优良为:80-90,优秀为:>=90
要求输出课程号和选修⼈数,查询结果按⼈数降序排列,若⼈数相同,按课程号升序排列
CASE WHEN sc.score >= 60 THEN 1 ELSE 0 END
相当于编程中 if
if sc.score >= 60:
return 1
else:
return 0
select
sc.cid,
c.cname,
max(sc.score) as '最高分',
min(sc.score) as '最低分',
round(avg(sc.score),2) as '平均分',
count(sc.cid) as '选修人数',
sum(CASE WHEN sc.score >= 60 THEN 1 ELSE 0 END) / count(sc.cid) as '及格率',
sum(CASE WHEN sc.score >= 70 and sc.score < 80 THEN 1 ELSE 0 END) / count(sc.cid) as '中等率',
sum(CASE WHEN sc.score >= 80 and sc.score < 90 THEN 1 ELSE 0 END) / count(sc.cid) as '优良率',
sum(CASE WHEN sc.score >= 90 THEN 1 ELSE 0 END) / count(sc.cid) as '优秀率'
from sc join course as c on sc.cid = c.cid
group by sc.cid,c.cname
order by '选修人数' desc,sc.cid;
+------+--------+-----------+-----------+-----------+--------------+-----------+-----------+-----------+-----------+
| cid | cname | 最高分 | 最低分 | 平均分 | 选修人数 | 及格率 | 中等率 | 优良率 | 优秀率 |
+------+--------+-----------+-----------+-----------+--------------+-----------+-----------+-----------+-----------+
| 01 | 语文 | 80.0 | 31.0 | 64.50 | 6 | 0.6667 | 0.3333 | 0.3333 | 0.0000 |
| 02 | 数学 | 90.0 | 30.0 | 72.67 | 6 | 0.8333 | 0.0000 | 0.5000 | 0.1667 |
| 03 | 英语 | 99.0 | 20.0 | 68.50 | 6 | 0.6667 | 0.0000 | 0.3333 | 0.3333 |
+------+--------+-----------+-----------+-----------+--------------+-----------+-----------+-----------+-----------+
3 rows in set (0.00 sec)
如果想用%,可以用concat()拼接,例如concat(1,'+',1,'=',2)返回1+1=2
-- 按照各学科进行分组,计算平均成绩
select cid,round(avg(score),2) as avg_sc from sc group by cid;
+------+--------+
| cid | avg_sc |
+------+--------+
| 01 | 64.50 |
| 02 | 72.67 |
| 03 | 68.50 |
+------+--------+
-- 按照各学科的平均成绩,做自联结,进行比较
mysql> select s1.* ,s2.*
-> from
-> (select cid,round(avg(score),2) as avg_sc from sc group by cid) as s1
-> join
-> (select cid,round(avg(score),2) as avg_sc from sc group by cid) as s2;
+------+--------+------+--------+
| cid | avg_sc | cid | avg_sc |
+------+--------+------+--------+
| 01 | 64.50 | 01 | 64.50 |
| 02 | 72.67 | 01 | 64.50 |
| 03 | 68.50 | 01 | 64.50 |
-- | 01 | 64.50 | 02 | 72.67 |
| 02 | 72.67 | 02 | 72.67 |
-- | 03 | 68.50 | 02 | 72.67 |
-- | 01 | 64.50 | 03 | 68.50 |
| 02 | 72.67 | 03 | 68.50 |
| 03 | 68.50 | 03 | 68.50 |
+------+--------+------+--------+
select s1.* ,s2.*
from
(select cid,round(avg(score),2) as avg_sc from sc group by cid) as s1
join
(select cid,round(avg(score),2) as avg_sc from sc group by cid) as s2
on s1.avg_sc >= s2.avg_sc;
+------+--------+------+--------+
| cid | avg_sc | cid | avg_sc |
+------+--------+------+--------+
| 01 | 64.50 | 01 | 64.50 | 01
| 02 | 72.67 | 01 | 64.50 | 01
| 03 | 68.50 | 01 | 64.50 | 01
| 02 | 72.67 | 02 | 72.67 | 02
| 02 | 72.67 | 03 | 68.50 | 03
| 03 | 68.50 | 03 | 68.50 | 03
+------+--------+------+--------+
6 rows in set (0.00 sec)
-- 按照s2进行分组,统计s1的平均分出现的次数,
select s2.cid,s2.avg_sc,count(distinct s1.avg_sc) as rank
from
(select cid,round(avg(score),2) as avg_sc from sc group by cid) as s1
join
(select cid,round(avg(score),2) as avg_sc from sc group by cid) as s2
on s1.avg_sc >= s2.avg_sc
group by s2.cid,s2.avg_sc
order by rank
+------+--------+------+
| cid | avg_sc | rank |
+------+--------+------+
| 02 | 72.67 | 1 |
| 03 | 68.50 | 2 |
| 01 | 64.50 | 3 |
+------+--------+------+
-- 当修改完数据之后
update sc set score = 104.0 where sid = 01 and cid = 01;
insert into sc values('07','04',62.0);
----
+------+--------+------+
| cid | avg_sc | rank |
+------+--------+------+
| 02 | 72.67 | 1 |
| 01 | 68.50 | 2 |
| 03 | 68.50 | 2 |
| 04 | 62.00 | 3 |
+------+--------+------+
select cid,round(avg(score),2) as avg_sc from sc group by cid order by avg_sc desc;
+------+--------+
| cid | avg_sc |
+------+--------+
| 02 | 72.67 |
| 03 | 68.50 |
| 01 | 64.50 |
+------+--------+
-- @i 是sql中定义变量的意思
select b.cid,b.avg_sc,@i := @i+1 as rank
from (select @i := 0) as a,
(
select cid,round(avg(score),2) as avg_sc
from sc group by cid
order by avg_sc desc
) as b;
-- 同上一题,修改完数据后
+------+--------+------+
| cid | avg_sc | rank |
+------+--------+------+
| 02 | 72.67 | 1 |
| 01 | 68.50 | 2 |
| 03 | 68.50 | 3 |
| 04 | 62.00 | 4 |
+------+--------+------+
4 rows in set (0.00 sec)
20.查询学⽣的总成绩,并进⾏排名,总分重复时保留名次空缺
21.查询学⽣的总成绩,并进⾏排名,总分重复时不保留名次空缺
22.统计各科成绩各分数段⼈数:课程编号,课程名称,[100-85],[85-70],[70-60],[60-0]及所占百分⽐
23.查询各科成绩前三名的记录
24.查询每⻔课程被选修的学⽣数
25.查询出只选修两⻔课程的学⽣学号和姓名
26.查询男⽣、⼥⽣⼈数
27.查询名字中含有「⻛」字的学⽣信息
28.查询同名同性学⽣名单,并统计同名⼈数
29.查询 1990 年出⽣的学⽣名单
30.查询每⻔课程的平均成绩,结果按平均成绩降序排列,平均成绩相同时,按课程编号升序排列
31.查询平均成绩⼤于等于 85 的所有学⽣的学号、姓名和平均成绩
32.查询课程名称为「数学」,且分数低于 60 的学⽣姓名和分数
33.查询所有学⽣的课程及分数情况(存在学⽣没成绩,没选课的情况)
34.查询任何⼀⻔课程成绩在 70 分以上的姓名、课程名称和分数
35.查询不及格的课程
36.查询课程编号为 01 且课程成绩在 80 分以上的学⽣的学号和姓名
37.求每⻔课程的学⽣⼈数
38.成绩不重复,查询选修「张三」⽼师所授课程的学⽣中,成绩最⾼的学⽣信息及其成绩
39.成绩有重复的情况下,查询选修「张三」⽼师所授课程的学⽣中,成绩最⾼的学⽣信息及其成绩
40.查询不同课程成绩相同的学⽣的学⽣编号、课程编号、学⽣成绩
41.查询每⻔课程成绩最好的前两名
42.统计每⻔课程的学⽣选修⼈数(超过 5 ⼈的课程才统计)。
43.检索⾄少选修两⻔课程的学⽣学号
44.查询选修了全部课程的学⽣信息
45.查询各学⽣的年龄,只按年份来算
46.按照出⽣⽇期来算,当前⽉⽇ < 出⽣年⽉的⽉⽇则,年龄减⼀
TIMESTAMPDIFF() 从⽇期时间表达式中减去间隔
https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html
47.查询本周过⽣⽇的学⽣
返回⽇期从范围内的数字⽇历星期1到53
48.查询下周过⽣⽇的学⽣
49.查询本⽉过⽣⽇的学⽣
50.查询下⽉过⽣⽇的学⽣
建表语句:https://blog.csdn.net/weixin_43372836/article/details/107815785
1.⽤⼀条SQL语句查询出每⻔课都⼤于80分的学⽣姓名
-- 先根据学员进行分组,看每个人的最低分
select name,min(score) from mst_stu group by name;
+--------+------------+
| name | min(score) |
+--------+------------+
| 张三 | 75 |
| 李四 | 76 |
| 王五 | 81 |
+--------+------------+
-- 在使用分组过滤 having 筛选出最低分大于80
select name,min(score) as min_sc from mst_stu group by name having min_sc > 80;
-- 最终只需要符合要求的学员姓名
select name from mst_stu group by name having min(score) > 80;
2.比昨天温度高的ID
查找与之前(昨天的)⽇期相⽐温度更⾼的所有⽇期的 Id。
select * from mst_weather;
+----+------------+-------------+
| id | date | temperature |
+----+------------+-------------+
| 1 | 2022-04-01 | 20 |
| 2 | 2022-04-02 | 25 |
| 3 | 2022-04-03 | 21 |
| 4 | 2022-04-04 | 24 |
+----+------------+-------------+
select s1.*,s2.*
from mst_weather as s1
join mst_weather as s2
+----+------------+-------------+----+------------+-------------+
| id | date | temperature | id | date | temperature |
+----+------------+-------------+----+------------+-------------+
-- | 1 | 2022-04-01 | 20 | 1 | 2022-04-01 | 20 |
| 2 | 2022-04-02 | 25 | 1 | 2022-04-01 | 20 |
-- | 3 | 2022-04-03 | 21 | 1 | 2022-04-01 | 20 |
-- | 4 | 2022-04-04 | 24 | 1 | 2022-04-01 | 20 |
-- | 1 | 2022-04-01 | 20 | 2 | 2022-04-02 | 25 |
-- | 2 | 2022-04-02 | 25 | 2 | 2022-04-02 | 25 |
-- | 3 | 2022-04-03 | 21 | 2 | 2022-04-02 | 25 |
-- | 4 | 2022-04-04 | 24 | 2 | 2022-04-02 | 25 |
-- | 1 | 2022-04-01 | 20 | 3 | 2022-04-03 | 21 |
-- | 2 | 2022-04-02 | 25 | 3 | 2022-04-03 | 21 |
-- | 3 | 2022-04-03 | 21 | 3 | 2022-04-03 | 21 |
| 4 | 2022-04-04 | 24 | 3 | 2022-04-03 | 21 |
-- | 1 | 2022-04-01 | 20 | 4 | 2022-04-04 | 24 |
-- | 2 | 2022-04-02 | 25 | 4 | 2022-04-04 | 24 |
-- | 3 | 2022-04-03 | 21 | 4 | 2022-04-04 | 24 |
-- | 4 | 2022-04-04 | 24 | 4 | 2022-04-04 | 24 |
+----+------------+-------------+----+------------+-------------+
16 rows in set (0.00 sec)
select s1.id
from mst_weather as s1
join mst_weather as s2
on datediff(s1.date,s2.date) = 1
and s1.temperature > s2.temperature;
+----+
| id |
+----+
| 2 |
| 4 |
+----+
2 rows in set (0.00 sec)
3.查询每个主播的最⼤level以及对应的最⼩gap
(注意:不是每个主播的最⼤level和最⼩gap
select * from mst_zhubo;
+----------+-------+------+
| zhubo_id | level | gap |
+----------+-------+------+
| 123 | 8 | 20 |
| 123 | 9 | 40 |
| 123 | 9 | 30 |
| 246 | 6 | 30 |
| 246 | 6 | 20 |
+----------+-------+------+
5 rows in set (0.00 sec)
-- 先查询每个主播的最大 level
select zhubo_id,max(level) from mst_zhubo group by zhubo_id;
+----------+------------+
| zhubo_id | max(level) |
+----------+------------+
| 123 | 9 |
| 246 | 6 |
+----------+------------+
2 rows in set (0.00 sec)
--在这个基础上,查询出每个主播所有符合最大level的数据
select * from mst_zhubo where (zhubo_id,level) in (select zhubo_id,max(level) from mst_zhubo group by zhubo_id)
+----------+-------+------+
| zhubo_id | level | gap |
+----------+-------+------+
| 123 | 9 | 40 |
| 123 | 9 | 30 |
| 246 | 6 | 30 |
| 246 | 6 | 20 |
+----------+-------+------+
4 rows in set (0.01 sec)
-- 在这个基础上,按照主播分组,求最小的gap
select zhubo_id,level,min(gap)
from mst_zhubo
where (zhubo_id,level) in (select zhubo_id,max(level) from mst_zhubo group by zhubo_id)
group by zhubo_id,level;
+----------+-------+----------+
| zhubo_id | level | min(gap) |
+----------+-------+----------+
| 123 | 9 | 30 |
| 246 | 6 | 20 |
+----------+-------+----------+
2 rows in set (0.01 sec)
4.课程数据行转列(使用case when 或 if )
下表是每个课程class_id对应的年级(共有primary、middle、high三个),以及某种⽐率rate
请写出SQL查询出如下形式的表:
select * from mst_class;
+----------+---------+------+
| class_id | grade | rate |
+----------+---------+------+
| abc123 | primary | 70% |
| abc123 | middle | 65% |
| abc123 | high | 72% |
| hjkk86 | primary | 69% |
| hjkk86 | middle | 63% |
| hjkk86 | high | 74% |
+----------+---------+------+
select class_id,
max(CASE WHEN grade = 'primary' THEN rate ELSE 0 END) as 'primary',
max(CASE WHEN grade = 'middle' THEN rate ELSE 0 END) as 'middle',
max(CASE WHEN grade = 'high' THEN rate ELSE 0 END) as 'high'
from mst_class
group by class_id;
+----------+---------+--------+------+
| class_id | primary | middle | high |
+----------+---------+--------+------+
| abc123 | 70% | 65% | 72% |
| hjkk86 | 69% | 63% | 74% |
+----------+---------+--------+------+
2 rows in set (0.01 sec)
-- 使用IF()
select class_id,
max(IF(grade = 'primary',rate,0)) as 'primary',
max(IF(grade = 'middle',rate,0)) as 'middle',
max(IF(grade = 'high',rate,0)) as 'high'
from mst_class
group by class_id;
同类型练习:
怎么把这样⼀个表
查成这样⼀个结果
5.update中的SQL查询
有两个表A和B,均有key和value两个字段,如果B的key在A中也有,就把B的value换为A中对应的 value
-- 先按照题设计表
create table `mst_a`(`key` varchar(10),`value` varchar(10));
create table `mst_b`(`key` varchar(10),`value` varchar(10));
insert into mst_a values('A','aaa'),('B','bbb'),('C','ccc');
insert into mst_b values('D','ddd'),('E','eee'),('A','abc');
select * from mst_a;
+------+-------+
| key | value |
+------+-------+
| A | aaa |
| B | bbb |
| C | ccc |
+------+-------+
3 rows in set (0.00 sec)
select * from mst_b;
+------+-------+
| key | value |
+------+-------+
| D | ddd |
| E | eee |
| A | abc |
+------+-------+
3 rows in set (0.00 sec)
-- 先查询出哪个key符合要求?
select mst_a.key,mst_a.value from mst_a join mst_b on mst_a.key = mst_b.key;
-- update mst_b set value = ? where key = ?
-- update mst_b as up ,(?) as b set up.value = ? where up.key = ?
update mst_b as up,(
select mst_a.key,mst_a.value from mst_a join mst_b on mst_a.key = mst_b.key
) as b
set up.value = b.value where up.key = b.key
-- 注意事项
-- update 后面是可以进行任何查询语句,这个作用等同于 from
-- update 更新表,不能在set和where中用于子查询
-- update 也可以对多个表进行更新 (sqlserver不行)
6.设计表,关系如下:教师、班级、学⽣、科室、科室与教师为⼀对多关系,教师与班级为多对多关系, 班级与学⽣为⼀对多关系,科室中需体现层级关系。
1.写出各张表的逻辑字段
教师 mst_Teacher +-----+-----------+------+ | Tid | Tname | Kid | +-----+-----------+------+ | 1 | 王⽼师 | 1 | | 2 | 张⽼师 | 2 | | 3 | 孙⽼师 | 3 | | 4 | 李⽼师 | 3 | | 5 | 伊⽼师 | 4 | +-----+-----------+------+ CREATE TABLE `mst_teacher` ( `Tid` int PRIMARY KEY AUTO_INCREMENT, `Tname` varchar(10), `Kid` int ); insert into mst_teacher VALUES(1,'王⽼师',1),(2,'张⽼师',2),(3,'孙⽼师',3), (4,'李⽼师',3),(5,'伊⽼师',4); 班级 mst_cla +-----+-------+ | Cid | Cname | +-----+-------+ | 1 | 1班 | | 2 | 2班 | | 3 | 3班 | +-----+-------+ CREATE TABLE `mst_cla` ( `Cid` int PRIMARY KEY AUTO_INCREMENT, `Cname` varchar(10) ); insert into mst_cla VALUES(1,'1班'),(2,'2班'),(3,'3班'); 教师&班级 mst_tc +----+------+------+ | id | Tid | Cid | +----+------+------+ | 1 | 3 | 1 | | 2 | 3 | 2 | | 3 | 3 | 3 | | 4 | 4 | 1 | | 5 | 4 | 2 | | 6 | 4 | 3 | +----+------+------+ CREATE TABLE `mst_tc` ( `id` int PRIMARY KEY AUTO_INCREMENT, `Tid` int,`Cid` int ); insert into mst_tc VALUES(1,3,1),(2,3,2),(3,3,3),(4,4,1),(5,4,2),(6,4,3); 学⽣ mst_St +-----+--------+------+ | SId | Sname | Cid | +-----+--------+------+ | 1 | 赵雷 | 1 | | 2 | 钱电 | 1 | | 3 | 孙⻛ | 1 | | 4 | 李云 | 2 | | 5 | 周梅 | 2 | | 6 | 吴兰 | 3 | | 7 | 郑⽵ | 3 | +-----+--------+------+ CREATE TABLE `mst_St` ( `SId` int PRIMARY KEY AUTO_INCREMENT, `Sname` varchar(20),`Cid` int ); insert into mst_St VALUES(1,'赵雷',1),(2,'钱电',1),(3,'孙⻛',1),(4,'李云',2), (5,'周梅',2),(6,'吴兰',3),(7,'郑⽵',3); 科室 mst_ks +-----+-------------+------+ | Kid | Kname | Pid | +-----+-------------+------+ | 1 | 校⻓室 | 0 | | 2 | 教学处 | 1 | | 3 | ui办公室 | 2 | | 4 | h5办公室 | 2 | +-----+-------------+------+ CREATE TABLE `mst_ks` ( `Kid` int PRIMARY KEY AUTO_INCREMENT, `Kname` varchar(20),`Pid` int ); insert into mst_ks VALUES(1,'校⻓室',0),(2,'教学处',1),(3,'ui办公室',2),(4,'h5办公 室',2);
事务(Transaction)是由⼀系列对系统中数据进⾏访问与更新的操作所组成的⼀个程序执⾏逻辑单元。
- 事务的语法
- 事务的特性
- 事务并发问题
- 事务隔离级别
- 不同隔离级别的锁的情况(了解)
- 隐式提交(了解)
1. start transaction;/ begin;
2. commit; 使得当前的修改确认
3. rollback; 使得当前的修改被放弃
1). 原⼦性(Atomicity)
事务的原⼦性是指事务必须是⼀个原⼦的操作序列单元。事务中包含的各项操作在⼀次执⾏过程中,只允许出现两种状态之⼀。
- 全部执⾏成功
- 全部执⾏失败
事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执⾏过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发⽣⼀样。也就是说事务是⼀个不可分割的整体,就像化学中学过的原⼦,是物质构成的基本单位。
2). ⼀致性(Consistency)
事务的⼀致性是指事务的执⾏不能破坏数据库数据的完整性和⼀致性,⼀个事务在执⾏之前和执⾏之后,数据库都必须处以⼀致性状态。
⽐如:如果从A账户转账到B账户,不可能A账户已经扣了钱,⽽B账户没有加钱。3). 隔离性(Isolation)
事务的隔离性是指在并发环境中,并发的事务是互相隔离的。也就是说,不同的事务并发操作相同的数据时,每个事务都有各⾃完整的数据空间。
⼀个事务内部的操作及使⽤的数据对其它并发事务是隔离的,并发执⾏的各个事务是不能互相⼲扰的。
隔离性分4个级别,下⾯会介绍。
4). 持久性(Duration)
事务的持久性是指事务⼀旦提交后,数据库中的数据必须被永久的保存下来。即使服务器系统崩溃或服务器宕机等故障。只要数据库重新启动,那么⼀定能够将其恢复到事务成功结束后的状态。
- 脏读:读取到了没有提交的数据, 事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据。
- 不可重复读:同⼀条命令返回不同的结果集(更新).事务 A 多次读取同⼀数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同⼀数据时,结果 不⼀致。
- 幻读:重复查询的过程中,数据就发⽣了量的变化(insert, delete)。
create table users(
id int auto_increment primary key,
name varchar(10),
age int,
account int
)engine = innodb default charset=utf8mb4;
insert into users values(null,'张三',25,10000),(null,'李四',20,100),(null,'王
五',23,0);
4种事务隔离级别从上往下,级别越⾼,并发性越差,安全性就越来越⾼。 ⼀般数据默认级别是 读以提交或可重复读。
create table users(
id int auto_increment primary key,
name varchar(10),
age int,
account int
)engine = innodb default charset=utf8mb4;
insert into users values(null,'张三',25,10000),(null,'李四',20,100),(null,'王五',23,0);
基本事务操作(innodb 支持,MyISAM不支持事务)
1. start transaction;/ begin;
2. commit; 使得当前的修改确认
3. rollback; 使得当前的修改被放弃
查看当前会话中事务的隔离级别
select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |//可重复读
+-----------------+
1 row in set, 1 warning (0.93 sec)
设置当前会话中的事务隔离级别
set session transaction isolation level read uncommitted;
//设置 当前会话 事务 隔离级别 读未提交
Query OK, 0 rows affected (0.00 sec)
读未提交,该隔离级别允许脏读取,其隔离级别是最低的。换句话说,如果⼀个事务正在处理某⼀数 据,并对其进⾏了更新,但同时尚未完成事务,因此还没有提交事务;⽽以此同时,允许另⼀个事务也 能够访问该数据。
脏读示例:
在事务A和事务B同时执⾏时可能会出现如下场景:
余额应该为1500元才对。请看T5时间点,事务A此时查询的余额为0,这个数据就是脏数据,他是事务B 造成的,很明显是事务没有进⾏隔离造成的。
读已提交是不同的事务执⾏的时候只能获取到已经提交的数据。 这样就不会出现上⾯的脏读的情况了。 但是在同⼀个事务中执⾏同⼀个读取,结果不⼀致
不可重复读示例
可是解决了脏读问题,但是还是解决不了可重复读问题。
事务A其实除了查询两次以外,其它什么事情都没做,结果钱就从1000变成0了,这就是不可重复读的 问题。
可重复读就是保证在事务处理过程中,多次读取同⼀个数据时,该数据的值和事务开始时刻是⼀致的。 因此该事务级别限制了不可重复读和脏读,但是有可能出现幻读的数据。
幻读
幻读就是指同样的事务操作,在前后两个时间段内执⾏对同⼀个数据项的读取,可能出现不⼀致的结 果。
诡异的更新事件
顺序读是最严格的事务隔离级别。它要求所有的事务排队顺序执⾏,即事务只能⼀个接⼀个地处理,不 能并发。
1. 读未提交(RU): 有⾏级的锁,没有间隙锁。它与RC的区别是能够查询到未提交的数据。
2. 读已提交(RC):有⾏级的锁,没有间隙锁,读不到没有提交的数据。
3. 可重复读(RR):有⾏级的锁,也有间隙锁,每次读取的数据都是⼀样的,并且没有幻读的情况。
4. 序列化(S):有⾏级锁,也有间隙锁,读表的时候,就已经上锁了
DQL:查询语句
DML:写操作(添加,删除,修改)
DDL:定义语句(建库,建表,修改表,索引操作,存储过程,视图)
DCL:控制语⾔(给⽤户授权,或删除授权)
DDL(Data Define Language):都是隐式提交。
隐式提交:执⾏这种语句相当于执⾏commit; DDL
https://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html
⽬前使⽤的⼤多数SQL语句都是针对⼀个或多个表的单条语句。并⾮所有操作都这么简单,经常 会有⼀个完整的操作需要多条语句 才能完成。
例如以下的情形。
- 为了处理订单,需要核对以保证库存中有相应的物品。
- 如果库存有物品,需要预定以便不将它们再卖给别的⼈, 并减少可⽤的物品数量以反映正确的库存量。
- 库存中没有的物品需要订购,这需要与供应商进⾏某种交互。
执⾏这个处理需要针对许多表的多条MySQL语句。可能需要执⾏的具体语句及其次序也不是固定的。
那么,怎样编写此代码?可以单独编写每条语句,并根据结果有条件地执⾏另外的语句。
在每次需要这个处理时(以及每个需要它的应⽤中)都必须做这些⼯作。
可以创建存储过程。
- 存储过程简单来说,就是为以后的使⽤⽽保存 的⼀条或多条MySQL语句的集合。
- 储存过程是⼀组为了完成特定功能的SQL语句集,经过编译之后存储在数据库中,在需要时直接调⽤。
- 存储过程就像脚本语⾔中函数定义⼀样。
优点:
- 可以把⼀些复杂的sql进⾏封装,简化复杂操作
- 保证了数据的完整性,防⽌错误
- 简单的变动只需要更改存储过程的代码即可
- 提⾼性能。因为使⽤存储过程⽐使⽤单独的SQL语句要快。(预先编译)
缺点:
- 存储过程的编写⽐SQL语句复杂
- ⼀般可能还没有创建存储过程的权限,只能调⽤
- 业务逻辑不要封装在数据库⾥⾯,应该由应⽤程序(JAVA、Python、PHP)处理。
- 让数据库只做它擅⻓和必须做的,减少数据库资源和性能的消耗。
- 维护困难,⼤量业务逻辑封装在存储过程中,造成业务逻辑很难剥离出来。动A影响B。
- ⼈员也难招聘,因为既懂存储过程,⼜懂业务的⼈少。使⽤困难。
在电信、银⾏业、⾦融⽅⾯以及国企都普遍使⽤存储过程来熟悉业务逻辑,但在互联⽹中相对较少。
\d // 修改MySQL默认的语句结尾符 ; 改为 //
create procedure 创建语句
BEGIN 和 END语句 ⽤来限定存储过程体
-- 定义存储过程
\d //
create procedure p1()
begin
set @i=10;
while @i<90 do
insert into users values(null,concat('user:',@i),@i,0);
set @i=@i+1;
end while;
end;
//
call p()//
可以改为;结尾 \d;
call p();
show create procedure p1\G;
drop procedure p1;
MySQL语句在需要时被执⾏,存储过程也是如此
但是,如果你想要某条语句(或某些语句)在事件发⽣时⾃动执⾏,怎么办呢?
例如:
- 每当增加⼀个顾客到某个数据库表时,都检查其电话号码格式是否正确;
- 每当订购⼀个产品时,都从库存数量中减去订购的数量;
- ⽆论何时删除⼀⾏,都在某个存档表中保留⼀个副本。
所有这些例⼦的共同之处是它们都需要在某个表发⽣更改时⾃动处理。这确切地说就是触发器
- 触发器是MySQL响应写操作(增、删、改)⽽⾃动执⾏的⼀条或⼀组定义在BEGIN和END之间的MySQL语句
- 或可理解为:提前定义好⼀个或⼀组操作,在指定的SQL操作前或后来触发指定的SQL⾃动执⾏
- 触发器就像是JavaScript中的事件⼀样
举例: 定义⼀个update语句,在向某个表中执⾏insert添加语句时来触发执⾏,就可以使⽤触发器
CREATE TRIGGER
trigger_name
trigger_time
trigger_event
ON tbl_name
FOR EACH ROW trigger_stmt
说明:
# trigger_name:触发器名称
# trigger_time:触发时间,可取值:BEFORE或AFTER
# trigger_event:触发事件,可取值:INSERT、UPDATE或DELETE。
# tb1_name:指定在哪个表上
# trigger_stmt:触发处理SQL语句。
--查看所有的 触发器
show triggers\G;
--删除触发器
drop trigger trigger_name;
注意:如果触发器中SQL有语法错误,那么整个操作都会报错
-- 创建⼀个删除的触发器,在users表中删除数据之前,往del_users表中添加⼀个数据
-- 1,复制当前的⼀个表结构
create table del_users like users;
-- 2,创建 删除触发器 注意在创建删除触发器时,只能在删除之前才能获取到old(之前的)数据
\d //
create trigger
deluser
before delete
on users
for each row
begin
insert into del_users values(old.id,old.name,old.age,old.account);
end;
//
\d ;
-- 3 删除users表中的数据去实验
tips:
- 在INSERT触发器代码内,可引⽤⼀个名为NEW的虚拟表,访问被 插⼊的⾏;
- 在DELETE触发器代码内,可以引⽤⼀个名为OLD的虚拟表,访问被删除的⾏;
- OLD中的值全都是只读的,不能更新。
- 在AFTER DELETE的触发器中⽆法获取OLD虚拟表
- 在UPDATE触发器代码中
- 可以引⽤⼀个名为OLD的虚拟表访问更新以前的值
- 可以引⽤⼀个名为NEW的虚拟表访问新 更新的值;
⽤触发器来实现数据的统计
-- 1.创建⼀个表, users_count ⾥⾯有⼀个 num的字段 初始值为0或者是你当前users表中的count
-- 2,给users表创建⼀个触发器
-- 当给users表中执⾏insert添加数据之后,就让users_count⾥⾯num+1,
-- 当users表中的数据删除时,就让users_count⾥⾯num-1,
-- 想要统计users表中的数据总数时,直接查看 users_count
视图是虚拟的表。与包含数据的表不⼀样,视图只包含使⽤时动态检索数据的查询。
视图仅仅是⽤来查看存储在别处的数据的⼀种设施或⽅法。
视图本身不包含数据,因此它们返回的数据是从其他表中检索出来的。
在添加或更改这些表中的数据时,视图将返回改变过的数据。
因为视图不包含数据,所以每次使⽤视图时,都必须处理查询执⾏时所需的任⼀个检索。
如果你⽤多个联结和过滤创建了复杂的视图或者嵌套了视图,可能会发现性能下降得很厉害。
1. 重⽤SQL语句。
2. 简化复杂的SQL操作。在编写查询后,可以⽅便地重⽤它⽽不必知道它的基本查询细节。
3. 使⽤表的组成部分⽽不是整个表。
4. 保护数据。可以给⽤户授予表的特定部分的访问权限⽽不是整个表的访问权限。
5. 更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。
6. 注意:视图不能索引,也不能有关联的触发器或默认值。
创建视图:
create view v_users as select id,name,age from users where age >= 25 and age
<= 35;
-- Query OK, 0 rows affected (0.00 sec)
view视图的帮助信息:
? view
ALTER VIEW
CREATE VIEW
DROP VIEW
查看当前库中所有的视图
show tables; --可以查看到所有的表和视图
show table status where comment='view'; --只查看当前库中的所有视图
删除视图v_t1:
drop view v_t1;
索引类似图书的目录索引,可以提高数据检索的效率,降低数据库的I0成本。
ps:MySQL在300万条记录左右性能开始逐渐下降,虽然官方文档说500~800w记录,
MySQL官方对索引的定义为:
- 索引(Index)是帮助MySQL高效获取数据的数据结构。
- 我们可以简单理解为:快速查找排好序的一-种数据结构。
所以在回答这类问题时,可以从两个地方说
- 1.索引类型图书的目录,可以提高数据检索的效率
- 2.索引其实就是一-种排好序的数据结构。
关于数据结构的内容我们在下节课中将为大家详细讲解
- 主键索引
- 唯一索引
- 普通索引
- 组合索引
- 全文索引
效率从上到下降低,普通索引和组合索引差不多
即主索引,根据主键建立索引,不允许重复,不允许空值;
用来建立索引的列的值必须是唯一-的,允许空值
用表中的普通列构建的索引,没有任何限制,不轻易用
用大文本对象的列构建的索引
5.6之前的版本中,全文索引只能用于MyISAM存储引擎
5.6及以后的版本,MyISAM 和InnoDB均支持全文索引
在5.8之前的MySQL中,全文索引只对英文有用,对中文还不支持
用多个列组合构建的索引,这多个列中的值不允许有空值
组合索引的“最左原则”。要用右边的必须先用左边的,比如下图中要用phone必须先用email
- 索引就像是一本书的目录是为了提高数据检索速度
- 在MySQL中有不同的索引类型,要求和效率也各不一样
只有memory (内存)存储引擎支持哈希索引,哈希索引用索引列的值计算该值的hashCode,然后在hashCode相应的位置存执该值所在行数据的物理位置,因为使用散列算法,因此访问速度非常快,但是一个值只能对应一个hashCode, 而且是散列的分布方式,因此哈希索引不支持范围查找和排序的功能
正常情况下,如果不指定索引的类型,那么一般是指B+Tree索引(或者B+Tree索引)
存储引擎以不同的方式使用B+ Tree索引。性能也各有不同,但是InnoDB按照原数据格式进行存储。
不过呢,其实B+TREE就是B+树,而B+树呢就是通过B树演变过来的,所以如果想知道什么是B+树,那么就要先了解-下b树,要了解b树呢就得知道二叉树,这些都是数据结构的内容。
那数据结构又是啥呢?
数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者
存储效率。数据结构往往同高效的检索算法和索引技术有关。
顺序查找:就是从第一个元素开始,按索引顺序遍历待查找序列,直到找出给定目标或者查找失败
缺点:效率低-需要遍历整个待查序列
二分法查找:也称为折半法,是-种在有序数组中查找特定元素的搜索算法。
如何实现二分查找呢?
二叉树也存在缺点,就是当数据是顺序插入时就会改变树的形态(在非完全二叉树的时候)
平衡二叉树经过条件的控制,再通过旋转的方式,完成树的平衡,不过,旋转次数过多。
在平衡二又树稳定性的基础上,再优化一下, 减少旋转次数,保证树的平衡性。
树的查找性能取决于树的高度,让树尽可能平衡,就是为了降低树的高度。
当数据存在内存中,红黑树效率非常高,但是文件系统和数据库都是存在硬盘上的,如果数据量大的话,不一定能一次性加载到内存。
所以一棵树都无法一次性加载进内存, 又如何谈查找。
因此就出现了专为磁盘等存储设备而设计的一-种平衡多路查找树,也就是B树
与红黑树相比,在相同的的节点的情况下,一颗B/B+树的高度远远小于红黑树的高度
B树即平衡查找树,一般理解为平衡多路查找树,也称为B-树、B_ 树。
B树是-种自平衡树状数据结构,-一般较多用在存储系统上,比如数据
库或文件系统。
- 规则:
(1)排序方式:所有节点关键字是按递增次序排列,并遵循左小右大原则;
(2)M阶B树的子节点数:非叶节点的子节点数>1,且<=M ,且M>=2,空树除外(注:M阶代表一个树节点最多有多少个查找路径,M=M路,当M=2则是2叉树,M=3则是3叉);
(3)关键字数:枝节点的关键字数量大于等于ceil(m/2)-1个且小于等于M-1个(注:ceil()是个朝正无穷方向取整的函数 如ceil(1.1)结果为2);
(4)所有叶子节点均在同一层、叶子节点除了包含了关键字和关键字记录的指针外也有指向其子节点的指针只不过其指针地址都为null对应下图最后一层节点的空格子;
- B树的查询流程:
如上图我要从上图中找到E字母,查找流程如下
(1)获取根节点的关键字进行比较,当前根节点关键字为M,E
(2)拿到关键字D和G,D
(3)拿到E和F,因为E=E 所以直接返回关键字和指针信息(如果树结构里面没有包含所要查找的节点则返回null);
- B树的插入节点流程
定义一个5阶树(平衡5路查找树;),现在我们要把3、8、31、11、23、29、50、28 这些数字构建出一个5阶树出来;
遵循规则:
(1)节点拆分规则:当前是要组成一个5路查找树,那么此时m=5,关键字数必须<=5-1(这里关键字数>4就要进行节点拆分);
(2)排序规则:满足节点本身比左边节点大,比右边节点小的排序规则;
先插入 3、8、31、11
再插入23、29
再插入50、28
- B树节点的删除
规则:
(1)节点合并规则:当前是要组成一个5路查找树,那么此时m=5,关键字数必须大于等于ceil(5/2)(这里关键字数<2就要进行节点合并);
(2)满足节点本身比左边节点大,比右边节点小的排序规则;
(3)关键字数小于二时先从子节点取,子节点没有符合条件时就向向父节点取,取中间值往父节点放;
特点:B树相对于平衡二叉树的不同是,
每个节点包含的关键字增多了,特别是在B树应用到数据库中的时候,数据库充分利用了磁盘块的原理(磁盘数据存储是采用块的形式存储的,每个块的大小为4K,每次IO进行数据读取时,同一个磁盘块的数据可以一次性读取出来)把节点大小限制和充分使用在磁盘快大小范围;
把树的节点关键字增多后树的层级比原来的二叉树少了,减少数据查找的次数和复杂度;
1.查找效率不均衡
2.范围查找需要中序遍历
3.每一个叶子结点上都带有数据在计算机中,所有与空间相关的东西都是按照块(block)进行存取和操作的
每次读取都意味着一次I/O, 假设计算机中每个块的大小为4K,行的大小为1k,索引的大小为0.06K
如果需要寻址遍历的次数多,就意味着更多的I0
B+树是在B树的基础上作出的演变,那么大家可以看一下,B+树和B树的区别
1.B+树只有叶子节点存储数据
2.非叶子结点起到了索引的作用
3.所有叶子结点使用链表相连
1.磁盘读写代价更低
B树的数据和索引都在同-一个节点.上,那么每个块中包含的索引是少量的,如果想要取出比较深层的数据,意味着要读取更多的块,才能得到想要的索引和数据,那么就增加了I0次数而B+树中每个块能存储的索引是B树的很多倍,那么获取比较深层的数据,也只需要读取少量的块就可以,那么就减少了磁盘的I0次数
2.随机I0的次数更少
B+树的优势是什么?
随机I/O是指读写操作时间连续,但访问地址不连续,时长约为10ms。
顺序I/0是指读取和写入操作基于逻辑块逐个连续访问来自相邻地址的数据,时长约为0.1ms
在相同情况下,B树要进行更多的随机I0,而B+树需要更多的顺序I0,因此B+树,效率也更快
3.查询速度更稳定
由于B+Tree非叶子节点不存储数据(data) , 因此所有的数据都要查询至叶子节点,而叶子节点的高度都是相同的,因此所有数据的查询速度都是一样的。
总结
在数据库中,索引是提高数据的检索速度的,而索引是基于B+Tree的数据结构实现的。
而使用B+Tree的好处是:
1.降低了磁盘读写代价
2.顺序I/O提高效率
3.查询速度更稳定
索引又分为聚簇索引和非聚簇索引两种。
在索引的分类中,我们可以按照索引的键是否为主键来分为“主索引” 和“辅助索引”
使用主键键值建立的索引称为“主索引”,其它的称为“辅助索引”。
因此主索引只能有一个,辅助索引可以有很多个。
以上关于索引原理和聚簇与非聚簇索引都是以InnoDB表引擎为基础
⾄此,我们介绍的都是InnoDB存储引擎中的索引⽅案,为了内容的完整性,以及各位可能在⾯试的时候遇到这类的问题,我们有必要再简单介绍⼀下MyISAM存储引擎中的 索引⽅案。我们知道InnoDB中索引即数据,也就是聚簇索引的那棵B+树的叶⼦节点中已经把所有完整的⽤户记录都包含了,⽽MyISAM的索引⽅案虽然也使⽤树形结构,但 是却将索引和数据分开存储:
我们知道InnoDB中索引即数据,也就是聚簇索引的那棵B+树的叶子节点中已经把所有完整的数据都包含了,
而MyISAM的索引方案虽然也使用树形结构,但是却将索引和数据分开存储:也就是把索引信息单独存到-个文件中,这个文件称为索引文件
MyISAM会单独为表的主键创建一个索引, 只不过在索引的叶子节点中存储的不是完整的数据记录,而是主键值+行号的组合。也就是先通过索引找到对应的行号,再通过行号去找对应的记录!其它非主键索引也是一样的, 这种情况我们称为’回行’。所以在MyISAM中所有的索引都是非聚簇索引,也叫二级索引
- 数据存储方式:
- InnoDB由两种文件组成,表结构,数据和索引
- MyISAM由三种文件组成, 表结构、数据、索引
- 索引的方式:
- 索引的底层都是基于B+ Tree的数据结构建立
- InnoDB中主键索引为聚簇索引,辅助索引是非聚簇索引
- MyISAM中数据和索引存在不同的文件中,因此都是非聚簇索引
- 事务的支持:
- InnoDB支持事务
- MyISAM不支持事务
- 数据库的索引就是为了提高数据检索速度
- 而数据库的索引就是基于B+Tree的数据结构实现的
- 在InnoDB中主键是聚簇索引而辅助索引是非聚簇索引
- 在MyISAM中主键索引和辅助索引都是非聚簇索引
在理解了MySQL中的索引类型及了解了索引的原理之后,我们就要知道索引是为了提高检索性能。
那么如何更好的合理使用索引,并且对一些执行较慢的sql进行优化也是我们必须掌握的一种技能,
因此本节课,我们结合索引的类型和原理,来-起看一下关于慢查询与SQL优化
MySQL的慢查询,全名是慢查询日志,
是MySQL提供的一-种 日志记录,用来记录在MySQL中响应时间超过阀值的语句。
默认情况下,MySQL数据库并不启动慢查询日志,需要手动来设置这个参数。
如果不是调优需要的话,一般不建议启动该参数,开启慢查询日志会或多或少带来一定 的性能影响。
慢查询日志可用于查找需要很长时间才能执行的查询,因此是优化的候选者。
创建表,插入测试数据
查看慢查询日志
一条 查询语句在经过MySQL查询优化器的各种基于成本和规则的优化会后生成一个所谓的执行计划
这个执行计划展示了接下来具体执行查询的方式,比如多表连接的顺序是什么,对于每个表采用什么访问方法来具体执行查询等等。
MySQL为我们提供了EXPLAIN语句来帮助我们查看某个语句的具体执行计划。
使用Explanin分析一下SQL
使用主键查询
给name字段添加索引
大家看到,索引能给数据检索提高的效率非常明显
那么是否意味着我们只要尽可能多的去建立索引就可以了呢?
每建立一个索引都会建立一棵B+树,并且需要维护,这是很费性能和存储空间的。
适当建立索引
1.创建并使用自增数字来建立主键索引.
2.经常作为where条件的字段建立索引
3.添加索引的字段尽可能的保持唯一-性
4.可考虑使用联合索引并进行索引覆盖
联合索引的索引覆盖(多个字段组合成了一个联合索引,在查询时,所要的字段和查询条件中的索引是-致)
注意索引绝不是加的越多越好(1.索引会占空间.2.索引|会影响写入性能)
合理使用索引
MySQL索引通常是被用于提高WHERE条件的数据行匹配时的搜索速度,
在索引的使用过程中,存在一些使用细节和注意事项。
因为不合理的使用可能会导致建立了索引之后,不一定就使用上了索引
1.不要在列上使用函数和进行计算
2.隐式转换可能影响索引失效
当查询条件左右两侧类型不匹配的时候会发生隐式转换,隐式转换带来的影响就是可能导致索引失效而进行全表扫描。
3.ike语句的索引失效问题
1.多个单列索引并不是最佳选择
事实上, MySQL只能使用一个单列索引。这样既浪费了空间,又没有提高性能(因为需要回行)为了提高性能,可以使用复合索引保证列都被索引覆盖。
使用联合索引
2.复合索引的最左前缀原则
查询条件中使用了复合索引的第一个字段,索引才会被使用。因此,在复合索引|中索弓|列的顺序至关重要。如果不是按照索引的最左列开始查找,则无法使用索引。
3.尽可能达成索引覆盖
如果一个索引包含所有需要的查询的字段的值,直接根据索引的查询结果返回数据,而无需读表,能够极大的提高性能。因此,可以定义一个让索引包含的额外的列,即使这个列对于索引而言是无用的。
- 1. SQL语句的优化
- 1.避免嵌套语句(子查询)
- 2.避免多表查询(复杂查询简单化)
- 2.索引优化
- 1.适当建立索引|
- 2.合理使用索引