视频:SQL进阶教程系列为'CodeWithMosh'全套10小时教程
(红字部分不保对)
MySQL workbench左侧Schemas显示当前所有databases,包含各自的tables,views,stored procedures,functions等。
双击其中一个database 该database加粗 等价于 use 'sql_store'
tables中每个table旁边最右边的表格 查看完整表格 等价于 select * from orders
tables中每个table旁的扳手符号 查看该表格的列名和属性
PK主键 NN非空值 AI增加记录时自动增一 Default当没有值时的默认值
自己创建的名字可以不用加反引号 (牛客网上的答案也是这样)
选出属性,可以进行加减乘除模计算. select points+10 from table1;
对日期做大小比较条件 需要加上引号 >'2000-08-30'
and or多个条件 A and (B or C) and的运算优先级高于 or
between的使用 where points between 1000 and 3000;表示[1000,3000]
REGEXP运算符 正则表达式:搜索字符串时非常强大
WHERE address LIKE '%trail%';
WHERE address REGEXP 'trail'; #表示包含trail
WHERE address REGEXP '^trail'; #表示必须以trail开头
WHERE address REGEXP 'trail$'; #表示必须以trail结尾
WHERE address REGEXP 'trail|flower' #表示要么包含有trail要么包含flower |前后不能加空格
WHERE address REGEXP '[GIM]e' #表示包含要么Ge 要么 Ie 要么Me
WHERE address REGEXP '[a-h]e' # [ ] - 表示字符范围
#举例 以EN或O结尾
where name REGEXP 'EN$|O$'
NULL运算符 获取缺失的数据 is null 或者 is not null进行条件判断
ORDER BY select A,B from table order by C,D order by的属性不一定在select之中,也可以排序
order by的属性也可以不是列名,算术表达式 比如 按照 单价*数量 排序
select *, unit_price*quantity as total_price
from table1
order by total_price #上面as过了,可以直接写成total_price而不是算式表达式
LIMIT 通过添加偏移量,在给数据分页时很有用
limit 6,3 表示第7到第9条数据 6表示偏移量
JOIN
(INNER) JOIN取两表交集
SELECT
order_id,
o.customer_id, --两个表都有customer_id 需要加前缀 否则报错列不明确
--下面给orders起了别名,其他所有位置都要用别名
full_name
from orders o --orders customers作为前缀有很多 改个名字 这里as可省略
join customers c
on o.order_id=c.order_id
跨数据库JOIN
--当前数据库是A 想连接数据库B中的product表
SELECT *
from orders o
join B.product p --需要给不在当前数据库中的表格前加上 数据库. 作为前缀
on o.order_id=p.order_id
self join 注意每个表都要加别名 不然select列不准确; select相同的属性 最好也要as
自连接无法使用using
SELECT --找到员工对应的上司
e.employee_id,
e.first_name,
m.first_name as manager
FROM employees e
JOIN employees m --join 完了之后select id,员工名,上司名
ON e.reports_to = m.employee_id;
多表连接
select * from A
join B
on A.aa=B.bb
join C
on A.xx=C.tt;
有复合主键时的JOIN 使用复合连接条件 SELECT * FROM A JOIN B ON (条件1) AND (条件2)
隐式连接语法 (尽量不要使用)
select *
from orders o
join customers c
on o.id = c.id;
--隐式连接语法
select *
from orders o,customers c
where o.id=c.id;
OUTER JOIN 即LEFT JOIN; RIGHT JOIN 希望将左or右表格中的内容全部输出即使不满足ON的条件
outer join也可以多表连接 避免使用right join 否则会容易搞不清楚怎么连接表格的
A left join B on xx left join C on yy
outer join也可以自连接
比如上面self join的代码中加上left 则可以将CEO的信息也输出,其manager为null
--多表外连接 Exercise 注意区分JOIN和LEFT JOIN的内部逻辑
SELECT
o.order_date,
o.order_id,
c.first_name,
s.name as shipper,
os.name AS status
FROM orders o
JOIN customers c
ON o.customer_id = c.customer_id
LEFT JOIN shippers s
ON o.shipper_id = s.shipper_id
JOIN order_statuses os
ON o.status=os.order_status_id
ORDER BY status,o.order_id;
NATURAL JOIN 系统自己看着办:基于两个表相同的列连接 不建议使用
select * from orders o natural join customers c
CROSS JOIN 连接第一个表的每条记录和第二个表的每条记录 相互连接
select * from orders o cross join products p 显式
等价于 select * from orders, products 隐式交叉连接
USING
当两个表格中对应的列名相同时,可以使用USING等价替换ON语句 内外连接都可使用
ON o.customer_id = c.customer_id 等价于 USING (customer_id)
连接条件中添加多列(存在多个主键的情况) USING (order_id,product_id) 用,将相同的列隔开写入()
USING 后面的列名要加括号
UNION
合并多段查询记录 from同一个表或不同的表
--以2019年为分界点 查询订单状态 使用UNION
SELECT
order_id,
order_date,
'Active' AS status --这里直接添加一列属性,值全为字符串Active
FROM orders
WHERE order_date >='2019-01-01'
UNION
SELECT
order_id,
order_date,
'Archived' AS status
FROM orders
WHERE order_date <'2019-01-01';
INSERT INTO录入数据
按照create table时列的顺序录入属性值
当列的属性是AI auto_increment (一般是主键),使用DEFAULT 录入该属性
当列的属性不要求不为空NN,可以用NULL或DEFAULT录入该属性,则会自动补充为Default的内容
或可以自定义属性输入顺序 没有提到的属性会按照NULL或Default的值录入(见前面的笔记)
一次录入多行数据
--以shippers表格为例 列属性 shipper_id AI , name NN
INSERT INTO shippers(name) -- 别的表格中有多个属性,按顺序添加
VALUES ('ZBQ'), -- 一个括号中为一行数据
('AAA'),
('BBB');
插入分层行 (一次往多个表格中插入数据)
以store数据库为例,当存在一组订单时,会涉及到多个表格 orders order_items 母子关系
orders为母,order_items为子
orders表的一行可以在order_items中有一行或多行 一个订单可以有多个产品
LAST_INSERT_ID()
一个表格使用AUTO_INCREMENT(AI)属性为主键(PK)列生成唯一的整数值。当录入一行新数据到该表中时,MySQL会自动生成一个唯一的整数,并将其作为主键列的值。
通过使用 LAST_INSERT_ID函数获取生成的序列号,并此数字值使用在下一个语句中,例如:将其值作为新行的一个值插入到相关表中。
INSERT INTO orders (customer_id,order_date,status)
VALUES (1,'2019-10-02',1) ; -- 添加一笔新的订单 使用customers表格中已存在的顾客id
-- SELECT LAST_INSERT_ID() 得到新插入数据的主键值 根据这个主键 到对应的表格中录入新数据
INSERT INTO order_items
VALUES (LAST_INSERT_ID(),5,100,3.12)
(LAST_INSERT_ID(),6,10,1.11); -- 一笔order数据 对应 两种产品
-- last_insert_id()这个位置的列 在属性中其实是AI,但这里说明也可以直接赋值
-- order_items这个表格有两个主键
创建表复制 将一个表格复制到另外一个表格中 CREATE TABLE AS
CREATE TABLE orders_archived AS
SELECT * FROM orders -- 表格中数据完全复制但是PK AI都没有设置 -- 属于subquery
INSERT INTO orders_archived --使用选择语句作为插入语句中的子查询
SELECT *
FROM orders
WHERE order_date < '2019-01-01';
更新数据 UPDATE table1 SET
更新数据可以直接赋值,也可以使用表达式,或直接赋列名
UPDATE invoices
SET
payment_total = invoice_total*0.5,
payment_date = due_date
WHERE invoice_id=3; --where client_id in (3,4) in的使用
改变where条件就可以多行修改 但是workbench会warning 因为设置中选了safe updates 一次只更新一条数据,防止意外更新或删除一些记录。warning不是报错不影响结果 建议还是把safe updates勾上
subquery :在另一段sql语句中的select语句
恢复数据库: file——open SQL script 找到创建数据库的sql文件重新打开 全部执行
聚合函数 一般用于数值型属性的列 也可以用于日期和字符串 如max用于日期 表示最近的日期
列中的空值NULL不会被聚合函数包含在内计算
--得到表格中所有记录条数 不管是不是空值
select count(*) from table1; -- 得到结果的排头会是 count(*) 所以一般还要as一下
聚合函数不止可以作用于列名上,也可以作用于表达式 比如 SUM(money_total*1.1)
select
'First half of 2019' as data_range,
sum(invoice_total) as total_sales,
sum(payment_total) as total_payments,
sum(invoice_total)-sum(payment_total) as what_we_expect
-- 这里不能使用total_sales - total_payments 而是使用sum(total_sales-total_payments)
from invoices
where invoice_date between '2019-01-01' and '2019-06-30'
GROUP BY
默认状态下是按照GROUP BY中指定的列进行排序的
group by的列名不可以使用别名
HAVING 在GROUP BY 分组后进行筛选数据, 而WHERE是在分组前进行条件判断
HAVING中出现的列名必须出现在select之中 可以使用别名,但WHERE可以使用任何列名
-- 在分组后加入判断条件 (按照id分组求得各自的总销售并找到大于500的)
-- 但优先级:where > group by 但是还没有分组 不能确定按照分组求得的总销售额 所以where无法使用
select
client_id,
SUM(invoices_total) as total_sales
from invoices
group by client_id
having total_sales>500;
WITH ROLLUP 运算符 只能应用于聚合值的列
如果用多列进行分组,则会对每一组的总计以及整个结果集都进行计算
select
client_id,
SUM(invoices_total) as total_sales
from invoices
group by client_id
with rollup; -- 将total_sales那一列的总和 放在新增一行的对应列上 client_id对应列为NULL
(写exercise的时候注意 payment_method 还是payment_id 不然金额怎么都对不上)
子查询subquery 不止可以在where中 也可以在select或from中
subquery vs joins
两种等价写法 --课堂上的例子 一开始没有写出来
select * from clients
where client_id not in (
select distinct client_id from invoices
);
select * from clients
join invoices using (client_id)
where client_id is NULL
ALL 表示所有
-- select invoices larger than max invoices of client_id =3
--原始方法
select * from invoices
where invoice_total >
(select max(invoice_total) from invoices where client_id =3);
-- 使用all关键字
select * from invoices
where invoice_total >
ALL(select invoice_total from invoices where client_id =3);
-- 不加all时,select子查询语句中会得到不唯一的结果
-- 加all 根据前面的大于号 大于all括号中的所有结果
SOME ANY 表示任一
-- 找出有至少两张发票的client_id count和group by使用不熟练
select client_id
from invoices
group by client_id
having count(*) >=2 ; --聚合函数可以不在select列中 且在having中可以出现
correlated subqueries
-- 找出工资超过他们本部门平均工资的员工
select employee_id from
(select * from employees
join
(select avg(salary) as avg ,office_id from employees
group by office_id) -- 这里会报错 每一个派生出来的表都要有自己的别名
using office_id )
where salary > avg
-- 正解 疯狂套娃
select employee_id from
(select * from employees e1
join
(select avg(salary) as AVGsalary ,office_id from employees
group by office_id) as e2
using (office_id)) as e3
where salary > AVGsalary;
-- 关联子查询 子查询和外查询存在相关性 理解for循环
select employee_id
from employees e
where salary >(
select avg(salary)
from employees
where office_id = e.office_id
)
exists运算符 where exits 类比 if判断语句
-- 找出有发票的客户
select * from clients c
where client_id in (
select distinct client_id from invoices
)
select * from clients c
where exits(
select client_id from invoices
where client_id = c.client_id --这里用了相关子查询 将内查询和外查询 关联
)
-- exits 并没有像in那样 返回一个结果集 而是 true\false
select中的子查询
-- average_invoices and differences
--错解
select invoice_id,
invoice_total,
avg(invoice_total) as average_invoices,
invoice_total - avg(invoice_total) as differences -- 这里不能使用average_invoices 别名
from invoices group by invoice_id; -- 直接使用avg 一定要 group by 但要的是全体平均
-- Aggregate function in SELECT statement without GROUPBY will return one row only
--正解
select invoice_id,
invoice_total,
(select avg(invoice_total) from invoices) as average_invoices,
invoice_total - (select average_invoices) --不能直接使用别名 要么把子查询复制过来
-- 要么select 别名
from invoices group by invoice_id;
-- 练习 ! 自己写出来哒 six!
select c.client_id, name,
(select sum(invoice_total) from invoices where client_id = c.client_id) as total_sales,
(select avg(invoice_total) from invoices) as average_invoices,
(select total_sales) - (select average_invoices) as differences
-- 这里可以直接 (select total_sales - avgrage_invoices)
from clients c;
from子句的子查询 在from中使用子查询 必须给其起一个别名
-- 可以将上一个练习得到的表格 用在from中
select * from (
select c.client_id, name,
(select sum(invoice_total) from invoices where client_id = c.client_id) as total_sales,
(select avg(invoice_total) from invoices) as average_invoices,
(select total_sales) - (select average_invoices) as differences
-- 这里可以直接 (select total_sales - avgrage_invoices)
from clients c
) as sales_summary
-- 这么长的from子查询 可以通过视图 保存在数据库中 后面会学
内置函数:用以处理数值,日期时间,字符串值
数值函数
ROUND(num) 四舍五入数字 ROUND(num,a) a:四舍五入的精度 a取1 保留一位小数做四舍五入
TRUNCATE 截断数字 truncate(num,a) 从小数点后截取a位 如 5.1234 2 结果为5.12
CEILING FLOOR上(下)限函数 ceiling(num) floor(num)返回大于(小于) 等于这个数字的最小(大)整数
ABS(num) 绝对值
RAND(0,1) 生成0,1之间的随机数
字符串函数
LENGTH('SDF') 字符串长度 UPPER() LOWER()
LTRIM RTRIM 移除字符串左(右)空白字符 TRIM 删除前后的空白字符
LEFT('abcd',2) 返回字符串左侧的几个字符 RIGHT 同
SUBSTRING('abcde',3,2) 得到一个字符串中任意位置的字符or串 3表示起始位置 2为长度
起始位置从1开始算 若第三个参数不写 则默认到字符串最后
LOCATE('aa','bbaaacc') 返回第一个参数(字符or字符串)的匹配位置
重复也返回出现的第一个位置 不区分大小写 找不到要匹配的字符 则返回0
REPLACE('abcdefg','abcd','zbq') 将第一个参数中出现的第二个参数替换为第三个参数
CONCAT 连接两个or多个字符串 concat(first_name,' ',last_name)
日期和时间函数
NOW() 当前日期和时间 CURDATE() 当前日期 CURTIME() 当前时间 不要忘记()
一些用来提取特定日期或时间的构成元素
YEAR(NOW()) MONTH DAY HOUR MINITE SECOND同 返回结果为int
DAYNAME(NOW()) 返回星期几 字符串类型 MONTHNAME同
EXTRACT(DAY FROM NOW())
-- 查询当年的订单
select * from orders
where year(now())=year(order_date)
时间和日期的格式化 改变时间日期的格式
DATE_FORMAT TIME_FORMAT
select date_format(now(),'%y')
-- %y 表示两位的年份 %Y 表示4位的年份 %m 两位的月份 %M 月份的名称June %d 日期
select date_format(now(),'%m %d %y')
select time_format(now(),'%H:%i %p')
-- %H小时 %i分钟 %p pm/am
计算日期和时间
-- date_add 或者 date_sub 在现有日期上做出改变
select date_add(now(),interval 1 day) -- 在现有日期上增加一天
-- interval 1 year 增加一年
-- -1 减掉一年 或 使用 date_sub()
-- datediff 计算两个日期之间的间隔 返回天数
select datediff('2021-01-20','2023-01-20') -- 前减后 结果 -730
-- time_to_sec 返回从0点计算的秒数
select time_to_sec('09:00')
-- 计算时间间隔 返回秒数
select time_to_sec('09:00')-time_to_sec('09:02') -- 结果 -120
将NULL值换成某个内容 IFNULL(列名,'内容') 将空的列换成内容
或者 COALESEC(列名,'xxx','内容') 返回一堆值中第一个非空的的值
SELECT order_id,
ifnull(shipper_id,'not_assigned') as shipper
from orders;
SELECT order_id,
coalesec(shipper_id,comments,'not_assigned') as shipper
-- comments是orders表中的一列 有的为空有的不为空
from orders;
IF IF(expression,first,second) expression 为true ,取first值 否则取second值
字符串 空值 日期 数字
-- 改写union那个部分的例子
--以2019年为分界点 查询订单状态 使用UNION
SELECT
order_id,
order_date,
IF(YEAR(order_date) = year(now()),'Active','Archived') AS status
FROM orders;
-- exercise
select product_id,name from products
join
(select product_id, count(product_id)from order_items group by product_id)as table1
using(product_id) ; -- ??????????为什么这里得不到join后的结果
--使用if 再join -- ??????? 母鸡
select product_id,name from products
join
(select product_id, count(product_id) as times,if(count(product_id)>1,'many','once') as status from order_items group by product_id) as aaa
using(product_id); --这里要注意 if表达式中无法使用别名
-- 我的笨蛋方法 用到关联查询
select product_id,name,
'many' as status
from products p
where
(select count(product_id) from order_items o where p.product_id=o.product_id )>1
union
select product_id,name,
'once' as status
from products p
where
(select count(product_id) from order_items o where p.product_id=o.product_id )=1 ;
-- 关联查询不止可以放在where中 直接放在select中 再结合if使用
select product_id,name,
if((select count(product_id) from order_items o where p.product_id=o.product_id )>1 ,'many','once') as status
from products p ;
-- 视频正解
select product_id ,name, count(*) as orders
--, if balbala
from products
join order_items using(product_id)
group by product_id,name;
CASE 运算符
当测试表达式有多个 if就不够用了
-- 今年的active 去年的last year 之前的archived
SELECT
order_id,
CASE
WHEN YEAR(order_date) = year(now()) THEN 'Active'
WHEN YEAR(order_date) = year(now())-1 THEN 'LAST YEAR'
WHEN YEAR(order_date) < year(now())-1 THEN 'Archived'
ELSE 'Future'
END AS status -- 使用end关键字关闭case语句块
FROM orders;
--EXERCISE 金银铜 case的规则是符合即输出
CASE
WHEN points>=3000 THEN 'Active'
WHEN points>2000 THEN 'Last year' -- 不需要写>2000 <3000 到第二行已经能说明<3000了
END AS XX
将某段查询保存为视图,后面使用该查询时不用反复写
create view sales_by_client as
select
c.client_id,
c.name
SUM(invoices_total) as total_sales
from clients c
join invoices i
using (client_id)
group by client_id,name
-- 在左侧 该数据库下的Views中,可以找到 sales_by_client 可以把它当作表格 select其中数据
-- 视图不存储数据 存储数据的是表格