Mysql多表和窗口函数

1.多表查询--自关联查询

# 格式: select * from A join A on 条件;   或者  select * from A left join A on 条件;
# 自关联查询的用法和 内连接, 外连接等操作一模一样, 只不过是: 表自己关联自己.
# 应用场景: 分类表(多级), 行政区域表(3级, 省市区).
# 查询结果: 跟上述多表查询结果一样, 如果是内连接就是交集, 如果是左外就是: 左表全集+交集, 如果是右外: 右表全集 + 交集.



# 1. 准备数据源.
drop table areas;
create table areas(     # 区域表
    id varchar(30) not null primary key,        # (省, 市, 县区)自身id, 主键, 自增
    title varchar(30),                          # (省, 市, 县区)名称
    pid varchar(30)                             # 当前的(省, 市, 县区)的 父级id
);
​
# 2. 查询河南省的信息.
select * from areas where id = '410000';
​
# 3. 查询河南省所有的市的信息.
select * from areas where pid = '410000';
​
# 4. 查询新乡市各县区的信息.
select * from areas where pid = '410700';
​
# 5. 查询河南省,所有的市的, 所有县区的信息.
select
    province.id, province.title,        # 省的信息
    city.id, city.title,                # 市的信息
    county.id, county.title             # 县区的信息
from areas county       # 县区
    join areas city     # 市
    join areas province # 省
on
    county.pid = city.id
and
    city.pid = province.id
where
    province.title='河南省';

2.多表查询--子查询

# 概述: 子查询指的是 1个SQL语句的查询条件 需要依赖 另1个SQL语句的执行结果.

#                 主查询(父查询)             子查询

# 格式: select * from 表A where 字段 = (select 列 from 表A/表B); # 查询结果: 取决于你写的SQL.

# 需求: 获取河南省所有的市的信息.
# 方式1: 自关联查询实现, 实际开发中的写法.
select * from areas p join areas c on p.id = c.pid where p.title = '河南省';
​
# 方式2: 子查询实现.
# 步骤1: 查询出河南省的id
select * from areas where title = '河南省';
​
# 步骤2: 根据上述的id, 查询出该省所有的市.
select * from areas where pid = '410000';
​
# 步骤3: 合并上述的内容, 最终写法为:
select * from areas where pid = (select id from areas where title = '河南省');

3.扩展--表关系分析

因为MySQL属于关系型数据库, 所以MySQL中的数据表与数据表之间是有关系的, 常见的关系有:

  • 一对多

    • 1个部门可以有多个员工, 但是1个员工只能属于1个部门

    • 1种分类可以有多个商品, 但是1个商品只能属于1个分类

  • 多对多

    • 1个学生可以选择多门课程, 1门课程也可以被多个学生选择

    • 1个订单可以包含多个商品, 1个商品也可以出现在多个订单中

  • 一对一

    • 1个人只有1个身份证号

    • 1个公司只能有1个注册地址

  • 问题: 如何分析表关系呢?

  • 答案: 通过ER模型图.

  •  

4.多表建表案例--一对多

建表原则: 在多的一方新建一列, 充当外键列, 去关联一的一方的主键列.

 

# 0. 切库.
create database day04;
use day04;
show tables;
​
# 1. 建表.
# 1.1 创建分类表, 主表.
drop table category;
create table category(
    cid int primary key auto_increment, # 分类id,主键,自增
    cname varchar(20)       # 分类名称
);
​
# 1.2 创建商品表, 外表.
drop table product;
create table product(
    pid int primary key auto_increment, # 商品id
    pname varchar(20),      # 商品名称
    price double,           # 商品价格
    cid int                 # 商品所属的分类id
);
​
# 2. 添加外键约束.
alter table product add constraint fk01 foreign key (cid) references category(cid);
# 删除约束
# alter table product drop  foreign key fk01;
​
# 3. 添加表数据
# 3.1 添加数据到 分类表, 主表.
insert into category value (null, '电脑'), (null, '手机'), (null, '相机'), (null, '手表');
​
# 3.2 添加数据到 商品表, 外表.
insert into product value (null, 'Iphone100', 200, 2);
insert into product value (null, '虫洞OG80', 100, 10);        # 因为没有分类id10, 所以报错.
​
# 4. 查询表数据.
# 4.1 查询 分类表, 主表.
select * from category;
​
# 4.2 查询 商品表, 外表.
select * from product;

5.多表建表案例--多对多

建表原则: 新建中间表, 该表至少有两列, 充当外键列, 分别去关联多的两方的主键列.

 

# 1. 建表.
# 1.1 创建学生表, 主表.
create table student(
    sid int primary key auto_increment, # id,主键,约束
    sname varchar(20),  # 学生姓名
    gender varchar(10), # 性别
    address varchar(20) # 地址
);
​
# 1.2 创建选修课表, 主表.
create table course(
    cid int primary key auto_increment,
    cname varchar(20)   # 选修课名
);
​
# 1.3 创建中间表(学生和选修课)表, 外表.
create table stu_course(
    # 核心细节1: 伪主键, id, 非空, 唯一, 自增
    id int not null unique auto_increment,
    sid int,
    cid int
    # primary key (sid, cid)        # 建表的时候, 指定联合主键.
);
​
# 2. 添加外键约束.
# 2.1 添加 学生表 和 中间表的 约束.
alter table stu_course add constraint fk_s_sc foreign key (sid) references student(sid);
# 2.2 添加 课程表 和 中间表的 约束.
alter table stu_course add constraint fk_c_sc foreign key (cid) references course(cid);
​
# 核心细节2: 设置中间表的联合主键, 不允许出现 相同学生选了相同课的情况.
alter table stu_course add primary key(sid, cid);
​
# 3. 添加表数据
# 3.1 添加数据到 学生表, 主表.
insert into student value
    (null, '乔峰', '男', '契丹'),
    (null, '虚竹', '男', '登封'),
    (null, '段誉', '男', '大理'),
    (null, '杨过', '男', '郑州');
​
# 3.2 添加数据到 课程表, 主表.
insert into course value (null, 'Py大数据'),(null, 'Java'),(null, '前端'),(null, '测试');
​
# 3.3 添加数据到 中间表, 外表.
# 需求1: 乔峰 学习Py大数据 和 Java
insert into stu_course value(null, 1, 1), (null, 1, 2);
​
# 需求2: 虚竹 Java, 测试
insert into stu_course value(null, 2, 2), (null, 2, 4);
​
# 需求3: 乔峰学习Py大数据.
insert into stu_course value(null, 1, 1);
​
# 4. 查询表数据.
# 4.1 查询 学生表, 主表.
select * from student;
​
# 4.2 查询 课程表, 主表.
select * from course;
​
# 4.3 查询 中间表, 外表.
select * from stu_course;
​
# 扩展需求: 找到每个学生选修了哪些课程, 要求显示: 学生id, 学生姓名, 学生地址, 选修课id, 选修课名.
select
       s.sid, s.sname, s.address, c.cid, c.cname
from
     student s, course c, stu_course sc
where
      s.sid = sc.sid and c.cid = sc.cid;

6.窗口函数--相关概述

窗口函数详解:
    函数:
        函数也叫方法, 指的是程序内容已经定义好的功能, 我们直接拿来用即可, 相当于程序准备好的工具, 我们传入指定值, 就可以获取到既定的结果.
        例如: select ceil(3.1);       => 4
    概述:
        窗口函数指的是 over()函数, 它相当于给表新增一列, 至于新增的内容是什么, 取决于你的 窗口函数 结合了 什么函数一起使用.
    问题: 能和窗口函数一起使用的函数有哪些呢?
    答案:
        情境1: 窗口函数 + 聚合函数(count, sum, max, min, avg)
        情境2: 窗口函数 + 排序函数(row_number, rank, dense_rank, ntile)
        情境3: 窗口函数 + 其他函数(lag, lead, first_value, last_value)
    格式:
        可以结合窗口函数一起使用的函数 over(partition by 分组的列 order by 排序列 rows between 起始行 and 结束行)
    细节:
        1. 窗口函数是MySQL8.0的特性, 你的MySQL必须是8.X及其以上版本才可以使用.
        2. 窗口函数相当于给表新增一列, 至于加的内容是什么, 取决于 结合了什么函数一起使用.
        3. 如果over()里边什么都不写, 默认操作的是: 表中 该列所有的数据.
        4. 如果写了partition by(表示分组): 则默认操作 组内所有的数据.
        5. 如果写了order by(表示排序):     则默认操作 组内第一行 至 当前行的数据.
        6. 如果要执行操作数据的范围, 即: 从哪一行开始, 到哪一行结束, 可以使用 rows between 起始行 and 结束行, 它主要涉及的参数如下:
            unbound preceding    表示第1行
            n preceding            n表示数字, 表示向上n行, 例如: 3 preceding 表示 向上 3行.
            current row            表示 当前行
            n following            n表示数字, 表示向下n行, 例如: 3 following 表示 向下 3行.
            unbound following    表示最后1行

7.窗口函数--结合聚合函数使用

# 切换数据库.
use day04;
show tables ;
​
# 员工表
CREATE TABLE employee
(
    id     int,         # 编号
    name   varchar(20), # 姓名
    deg    varchar(20), # 职位
    salary int,         # 工资
    dept   varchar(20)  # 部门
);
​
insert into employee value (1201, 'gopal', 'manager', 50000, 'TP');
insert into employee value (1202, 'manisha', 'cto', 50000, 'TP');
insert into employee value (1203, 'khalil', 'dev', 30000, 'AC');
insert into employee value (1204, 'prasanth', 'dev', 30000, 'AC');
insert into employee value (1206, 'kranthi', 'admin', 20000, 'TP');
​
# 网站点击量表
create table website_pv_info
(
    cookieid   varchar(20), # 用户id
    createtime varchar(20), # 访问时间
    pv         int          # 页面浏览量
);
​
insert into website_pv_info value ('cookie1', '2018-04-10', 1);
insert into website_pv_info value ('cookie1', '2018-04-11', 5);
insert into website_pv_info value ('cookie1', '2018-04-12', 7);
insert into website_pv_info value ('cookie1', '2018-04-13', 3);
insert into website_pv_info value ('cookie1', '2018-04-14', 2);
insert into website_pv_info value ('cookie1', '2018-04-15', 4);
insert into website_pv_info value ('cookie1', '2018-04-16', 4);
insert into website_pv_info value ('cookie2', '2018-04-10', 2);
insert into website_pv_info value ('cookie2', '2018-04-11', 3);
insert into website_pv_info value ('cookie2', '2018-04-12', 5);
insert into website_pv_info value ('cookie2', '2018-04-13', 6);
insert into website_pv_info value ('cookie2', '2018-04-14', 3);
insert into website_pv_info value ('cookie2', '2018-04-15', 9);
insert into website_pv_info value ('cookie2', '2018-04-16', 7);
​
# 网站访问记录表
create table website_url_info
(
    cookieid   varchar(20), # 用户id
    createtime varchar(20), # 访问时间
    url        varchar(20)  # 访问的url页面
);
insert into website_url_info value ('cookie1', '2018-04-10 10:00:02', 'url2');
insert into website_url_info value ('cookie1', '2018-04-10 10:00:00', 'url1');
insert into website_url_info value ('cookie1', '2018-04-10 10:03:04', '1url3');
insert into website_url_info value ('cookie1', '2018-04-10 10:50:05', 'url6');
insert into website_url_info value ('cookie1', '2018-04-10 11:00:00', 'url7');
insert into website_url_info value ('cookie1', '2018-04-10 10:10:00', 'url4');
insert into website_url_info value ('cookie1', '2018-04-10 10:50:01', 'url5');
insert into website_url_info value ('cookie2', '2018-04-10 10:00:02', 'url22');
insert into website_url_info value ('cookie2', '2018-04-10 10:00:00', 'url11');
insert into website_url_info value ('cookie2', '2018-04-10 10:03:04', '1url33');
insert into website_url_info value ('cookie2', '2018-04-10 10:50:05', 'url66');
insert into website_url_info value ('cookie2', '2018-04-10 11:00:00', 'url77');
insert into website_url_info value ('cookie2', '2018-04-10 10:10:00', 'url44');
insert into website_url_info value ('cookie2', '2018-04-10 10:50:01', 'url55');

案例:

# 案例1: 窗口函数初体验.
# 不使用窗口函数, 直接用传统的 聚合函数.
select sum(salary) total_salary from employee;

# 需求: 统计每个所有员工的工资, 并将其展示在每个员工数据的最后.
select *, sum(salary) over() as total_salary from employee;

窗口函数案例 窗口 + 聚合 

# 补充概述: PV: Page View 页面浏览量, UV: user view, 用户访问数 IP: ip(独立访客)访问数
# 例如: 我通过我电脑的谷歌浏览器访问了京东的10个页面, 通过Edge浏览器访问了京东的7个页面, 请问: PV, UV, IP分别是多少?
# 答案: IP: 1个, UV: 2个, PV: 17个

# 案例2: 演示 窗口函数 + 聚合函数一起使用.
# 需求:求出网站总的pv数 所有用户所有访问加起来
select sum(pv) from website_pv_info;

# 如果over()里边什么都不写, 默认操作的是: 表中 该列所有的数据.
select *, sum(pv) over() total_pv from website_pv_info;

# 需求: 求出每个用户总pv数
# 方式1: sum() + group by 一起使用.
select cookieid, sum(pv) total_pv from website_pv_info group by cookieid;

# 方式2: 聚合函数 + 窗口函数一起使用.
# 细节: 如果写了partition by(表示分组): 则默认操作 组内所有的数据.
select *, sum(pv) over(partition by cookieid) total_pv from website_pv_info;

# 细节: 如果写了order by(表示排序):     则默认操作 组内第一行 至 当前行的数据.
select *, sum(pv) over(partition by cookieid order by createtime) total_pv from website_pv_info;
# 上述的代码, 等价于如下的内容:
select *, sum(pv) over(partition by cookieid order by createtime rows between unbounded preceding and current row ) total_pv from website_pv_info;

# 需求: 统计每个cookieID的pv(访问量), 只统计: 当前行及 向前3行 向后1行
select *, sum(pv) over(partition by cookieid order by createtime rows between 3 preceding and 1 following ) total_pv from website_pv_info;

窗口函数--结合排序函数使用

# 需求: 根据点击量(pv)做排名, 组内排名.
# 这里的排序函数指的是: row_number(), rank(), dense_rank(), 它们都可以做排名, 不同的是, 对相同值的处理结果.
# 例如: 数据是100, 90, 90, 60, 则: row_number是: 1, 2, 3, 4,  rank: 1, 2, 2, 4,  dense_rank: 1, 2, 2, 3
select
       *,
        row_number() over(partition by cookieid order by pv) rn1,       # 根据cookieid分组, 根据pv进行排序(升序)
        rank() over(partition by cookieid order by pv) rn2,             # 根据cookieid分组, 根据pv进行排序(升序)
        dense_rank() over(partition by cookieid order by pv) rn3        # 根据cookieid分组, 根据pv进行排序(升序)
from website_pv_info;

# 需求: 根据cookieID进行分组, 获取每组点击量最高的前2名数据, 这个就是经典的案例: 分组求TopN
# Step1: 根据cookieID进行分组, 根据点击量进行排名.
select *, dense_rank() over (partition by cookieid order by pv desc) rn from website_pv_info;

# 细节: where只能筛选表中已经有的列(数据)
# Step2: 把上述的查询结果当做一张表, 然后从中获取我们要的数据即可.
select * from (
    select *, dense_rank() over (partition by cookieid order by pv desc) rn from website_pv_info
) t1 where rn <= 2;

# ntile(数字,表示分成几份)  采用均分策略, 每份之间的差值不超过1, 优先参考最小的那个部分, 即: 7分成3份, 则是: 3, 2, 2
select *, ntile(2) over(partition by cookieid order by pv desc) rn from website_pv_info;

窗口函数--结合其它函数使用

# 1. LAG 用于统计窗口内往上第n行值
# 需求: 显示用户上一次的访问时间.
select *, lag(createtime, 1) over(partition by cookieid order by createtime) lag1 from website_url_info;

# 根据cookieID分组, createtime升序排序, 获取当前行 向上2行的createtime列的值, 找不到就用默认值(夯哥)填充.
select *, lag(createtime, 2, '1970-01-01 00:00:00') over(partition by cookieid order by createtime) lag1 from website_url_info;

# 2. LEAD 用于统计窗口内往下第n行值
select *, LEAD(createtime, 1) over(partition by cookieid order by createtime) lead1 from website_url_info;

# 3. FIRST_VALUE 取分组内排序后,截止到当前行,第一个值
select *, first_value(createtime) over(partition by cookieid order by createtime) lead1 from website_url_info;

# 4. LAST_VALUE  取分组内排序后,截止到当前行,最后一个值
select *, last_value(createtime) over(partition by cookieid order by createtime) lead1 from website_url_info;

# 5. 上述的其它函数, 你只需要注意lag()函数即可, 它在实际开发中的应用场景是: 判断某个账号(用户)是否连续登陆n天.
select * from website_pv_info;
# 改造下值.
update website_pv_info set createtime='2018-04-17' where cookieid = 'cookie1' and createtime='2018-04-16';

# Step1: 获取当前日期, 往前数的第6个日期, 即为一周的初始日期.
select
       *,
       lag(createtime, 6) over(partition by cookieid order by createtime) lag1
from website_pv_info;

# 扩展: 通过 datediff可以计算两个日期的差值, 即: 前边的日期 - 后边的日期.
select datediff('2018-04-17', '2018-04-10');        # 7

# Step2: 获取当前时间, 及它的前6个的登陆时间 的差值.
select * from (
    select
           *,
           lag(createtime, 6) over(partition by cookieid order by createtime) lag1
    from website_pv_info
) t1 where datediff(createtime, lag1) = 6;      # 10, 11, 12, 13, 14, 15, 16

SQL语句总结

# --------------------------------- DDL操作 数据库  CURD ---------------------------------
# 1. 创建数据库  *
create database 数据库名 charset '码表';      # utf8, gbk

# 2. 查看数据库.
show databases ;

# 3. 切换数据库    *
use 数据库名;

# 4. 删除数据库.
drop database 数据库名;

# 5. 修改数据库的码表.
alter database 数据库名 charset '码表';

# --------------------------------- DDL操作 数据表  CURD ---------------------------------
# 1. 创建数据表. *
create table 数据表名(
    id int primary key auto_increment,
    列名1 数据类型 [约束],
    列名1 数据类型 [约束],
    ......
);

# 2. 删除数据表. *
drop table 数据表名;
truncate table 数据表名;

# 3. 修改数据表名
rename table 旧表名 to 新表名;

# 4. 查看数据表. *
show tables;
desc 数据表名;

# --------------------------------- DDL操作 字段  CURD ---------------------------------
# 1. 添加字段.
alter table 数据表名 add 列名 数据类型 [约束];
# 2. 删除字段.
alter table 数据表名 drop 列名;

# 3. 修改字段.
alter table 数据表名 modify 列名 数据类型 [约束];
alter table 数据表名 change 旧列名 新列名 数据类型 [约束];

# 4. 查询字段  *
desc 表名;

# --------------------------------- DML操作 表数据  增删改(更新), 全部掌握 ---------------------------------
# 1. 添加表数据
insert into 表名 value(值1, 值2...);        # 个数, 类型, 一致.
insert into 表名 value(值1, 值2...),(值1, 值2...),(值1, 值2...);

# 2. 修改表数据.
update 表名 set 列1 = 值, 列2 = 值 where 条件;

# 3. 删除表数据.
delete from 表名 where 条件;

# --------------------------------- DQL操作 表数据  查询, 全部掌握 ---------------------------------
# 1. 简单查询, 比较运算符, 逻辑运算符, 模糊查询, 范围查询, 空值过滤.
select * from 表名 where id > 3;
select * from 表名 where id > 3 and id <= 5;
select * from 表名 where name like '夯%';
select * from 表名 where age between 10 and 30 ;
select * from 表名 where age in (1, 3, 6) ;
select * from 表名 where name is not null;

# 2. 聚合查询
# count(), sum(), max(), min(), avg()

# 3. 排序
# order by 列 asc/desc

# 4.  分组 group by

# 5. 分页查询 limit 起始索引, 每页的数据条数.

# 6. 一个完整的(单表)查询
select
    distinct 列1, 列2 as 别名
from
    表名
where
    组前筛选
group by
    分组的列
having
    组后筛选
order by
    排序的列 asc/desc
limit 起始索引, 每页的数据条数;

# --------------------------------- 多表 查询, 全部掌握(除了交叉查询, 子查询) ---------------------------------
# 1. 交叉查询,  笛卡尔积.
select * from A, B;
# 2. 内连接, 交集.
select * from A join B on 条件;       # 显式.
select * from A, B where 条件;        # 隐式.

# 3. 外连接
select * from A left join B on 条件;  # 左表全集 + 交集

# 4. 自关联查询.
select * from A a1, A a2, A a3 where a1.pid = a2.id and a2.pid = a3.id;

# 5. 子查询
select * from A where id = (select id from B where 条件);

# --------------------------------- 窗口函数, 重点掌握 排序函数  ---------------------------------
# 格式
select
       *,
       #   聚合函数---------------------, 排序函数-------------------------------, 其它函数-------------------------------
       count()/sum()/avg()/max()/min()/row_number()/rank()/dense_rank()/ntile()/lag()/lead()/first_value()/last_value()
           over(partition by 分组的列 order by 排序的列 [asc/desc] rows between 起始行 and 结束行 )
from 表名;

# 窗口函数 = 给表新增1列, 至于增加的内容是什么, 取决于窗口函数结合什么函数一起使用.
# over()啥都不写, 默认是全局聚合(统计该列所有数据), 写了partition by则是分组聚合(统计组内所有), 写了order by则是统计: 组内第1行 至 当前行.
# 其他的关于行数的限定: unbound preceding 第1行,  n preceding 向上n行, current row 当前行, n following 向下n行, unbound following 最后1行.

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值