【SQL解惑】谜题15:找出最近两次工资

本文详细介绍了如何使用SQL查询技巧来检索员工薪资表中的最新记录及其前一条记录,包括使用视图、自连接、窗口函数等多种方法。
解惑一:
1、创建表和插入数据
create table Salaries
(
emp_name char(10) not null,
sal_date date not null,
sal_amt decimal (8,2) not null,
primary key (emp_name,sal_date)
)
insert into Salaries
values ('Tom','1996-06-20',500.00),
         ('Tom','1996-08-20',700.00),
         ('Tom','1996-10-20',800.00),
         ('Tom','1996-12-20',900.00),
         ('Dick','1996-06-20',500.00),
         ('Harry','1996-07-20',500.00),
         ('Harry','1996-09-20',700.00)
go
2、创建视图
create view Salaries2(emp_name,sal_date,sal_amt)
as select s0.emp_name,s0.sal_date,MAX(s0.sal_amt)
       from Salaries as s0,Salaries as s1
      where s0.sal_date <= s1.sal_date
        and s0.emp_name = s1.emp_name
      group by s0.emp_name,s0.sal_date
   having COUNT(*) <= 2
go
解惑二:
select s0.emp_name,s0.sal_date,s0.sal_amt,s1.sal_date,s1.sal_amt
  from Salaries as s0,Salaries as s1
 where s0.emp_name = s1.emp_name
   and s0.sal_date = (select MAX(s2.sal_date)
                                    from Salaries as s2
                                 where s0.emp_name = s2.emp_name)
   and s1.sal_date = (select MAX(s3.sal_date)
                                    from Salaries as s3
                                 where s0.emp_name = s3.emp_name
                                   and s3.sal_date < s0.sal_date)
union all
select s4.emp_name,max(s4.sal_date),MAX(s4.sal_amt),null,null
  from Salaries as s4
 group by s4.emp_name
having COUNT(*) = 1
解惑三:
1、语句块一(获取表的每个人的最大日期):
(1)from子句获取Salaries表数据;
(2)groupby子句将表数据按emp_name分组
(3)select子句选择emp_name和最大的sal_date
select w.emp_name,MAX(w.sal_date) as maxdate
                          from Salaries as w
                         group by w.emp_name
2、语句块二(获取表的每个人的最大日期和第二大日期):
(1)from子句获取上面语句块一的数据;
(2)left outer join子句左外连接表Salaries,当表a的emp_name与表x的emp_name且表a的日期(即最大日期)小于表x的日期时就添加到表a中;
(3)groupby子句将数据按emp_name和maxdate进行分组;
(4)select语句选择表a的emp_name,表a的maxdate(即最大的日期),表x的最大日期(即除了最大日期之外的第二大日期)
select a.emp_name,a.maxdate,max(x.sal_date) as maxdate2
  from a
  left outer join Salaries as x
    on a.emp_name = x.emp_name and a.maxdate > x.sal_date
 group by a.emp_name,a.maxdate
3、语句块三:
(1)from子句获取上面语句块二的数据;
(2)left outer join子句左外连接表Salaries,当表y与表z的emp_name和sal_date相等时则左外连接。
(3)select子句获取名字、最近日期、最近日期的工资、最近第二次的日期、最近第二次日期的工资
select b.emp_name,b.maxdate,y.sal_amt,b.maxdate2,z.sal_amt
  from b
  left outer join Salaries as y
    on b.emp_name = y.emp_name and b.maxdate  = y.sal_date
  left outer join Salaries as z
    on b.emp_name = z.emp_name and b.maxdate2 = z.sal_date
4、完整语句
select b.emp_name,b.maxdate,y.sal_amt,b.maxdate2,z.sal_amt
  from (select a.emp_name,a.maxdate,max(x.sal_date) as maxdate2
              from (select w.emp_name,MAX(w.sal_date) as maxdate
                          from Salaries as w
                         group by w.emp_name) as a
            left outer join Salaries as x
            on a.emp_name = x.emp_name and a.maxdate > x.sal_date
         group by a.emp_name,a.maxdate) as b
  left outer join Salaries as y
    on b.emp_name = y.emp_name and b.maxdate  = y.sal_date
  left outer join Salaries as z
    on b.emp_name = z.emp_name and b.maxdate2 = z.sal_date
解惑四:
1、使用解惑一的视图,通过日期的比较,只有前一个日期小于或等于后一个日期中的统计次数为小于等于2的即为前两个数。
例如:
日期为2017.1.1,2017.1.2,2017.1.3,2017.1.4。
那么按照公式的要求,自联结后日期进行比较并分组,那么存在如下分组。

2017.1.1<=2017.1.1
2017.1.2<=2017.1.2
2017.1.3<=2017.1.3
2017.1.4<=2017.1.4
2017.1.1<=2017.1.2
2017.1.2<=2017.1.3
2017.1.3<=2017.1.4

2017.1.1<=2017.1.3
2017.1.2<=2017.1.4


2017.1.1<=2017.1.4



通过上面的比对可了解到,只要分组成员少于或等于2的,那么对应的就是最近的两天
create view Salaries2(emp_name,sal_date,sal_amt)
as select s0.emp_name,s0.sal_date,MAX(s0.sal_amt)
       from Salaries as s0,Salaries as s1
      where s0.sal_date <= s1.sal_date
        and s0.emp_name = s1.emp_name
      group by s0.emp_name,s0.sal_date
   having COUNT(*) <= 2
2、选择数据
(1)选择其中的最大的数据。
select s1.emp_name,s1.sal_date,s1.sal_amt,s2.sal_date,s2.sal_amt
  from Salaries2 as s1,Salaries2 as s2
 where s1.emp_name = s2.emp_name
   and s1.sal_date > s2.sal_date
(2)选择其中数据量为1次的数据,即只有一天的数据。
select emp_name,MAX(sal_date),MAX(sal_amt),null,null
  from Salaries2
 group by emp_name
having COUNT(*) = 1
(3)将两种数据合并
select s1.emp_name,s1.sal_date,s1.sal_amt,s2.sal_date,s2.sal_amt
  from Salaries2 as s1,Salaries2 as s2
 where s1.emp_name = s2.emp_name
   and s1.sal_date > s2.sal_date
 union all
select emp_name,MAX(sal_date),MAX(sal_amt),null,null
  from Salaries2
 group by emp_name
having COUNT(*) = 1

解惑五:
1、语句块一:
(1)from子句获取表Salaries为表1的数据;
(2)innerjoin表Salaries为表2的数据,根据两表的emp_name和表2的sal_date进行自联接;
(3)当表2的sal_date数据为获取表Salaries为表4的数据,且表4的emp_name和表1的emp_name相等的时候,表4的sal_date都小于表1的每一条sal_date的时候,当上一步的数据为空值时,则直接获取表2的sal_date即可。
以下为具体例子:
(1)左边为表1和表2内连接的结果,右边为表4的数据;
(2)第一个,当s1_sal_date的数据1996-6-20与表4的所有数据进行比较,都是小于或大于表4的数据,因此没有满足的数据可以返回,因此取本身这条数据的s2_sal_date;
(3)第二个,当s1_sal_date的数据1996-10-20与表4的所有数据进行比较,存在数据1996-10-20大于1996-6-20和1996-8-20,因此再这两个数据中取最大值,选择对应的s2_sal_date即可。
总结:以下的语句是可以查找出,每一行日期与之邻近的上一个日期
select (...)
  from Salaries as s1
 inner join Salaries as s2
    on s2.emp_name = s1.emp_name
   and s2.sal_date = coalesce((select MAX(s4.sal_date)
                               from Salaries as s4
                              where s4.emp_name = s1.emp_name
                                 and s4.sal_date < s1.sal_date),s2.sal_date)
2、语句块二:
(1)以语句块一的数据为基础;
(2)where子句中的子查询筛选数据:当表1的数据与表3的数据中的emp_name相等时时,如果表3的每一个sal_date日期都大于表1的sal_date日期时,则选择数据;
(3)当子查询没有任何数据时,才返回对应的sal_date。
当表1的1996-6-20与表3的所有sal_date比较时,存在1996-8-20、1996-10-20、1996-12-20大于表1的数据,因此在exists子查询中会返回数据;
当表1的1996-12-20与表3的所有sal_date比较时,不存在任何数据大于表1的数据,因此在exists子查询中不会返回任何数据;
同时where子句中的是not exists(true)时,则不返回数据;同时where子句中的是not exists(false)时,则返回数据。
select s1.emp_name as s1_emp_name,
         s1.sal_date as s1_sal_date,
         s1.sal_amt as s1_sal_amt,
         s2.emp_name as s2_emp_name,
         s2.sal_date as s2_sal_date,
         s2.sal_amt as s2_sal_amt
  from Salaries as s1
 inner join Salaries as s2
    on (...)
 where not exists(select *
                    from Salaries as s3
                   where s3.emp_name = s1.emp_name
                     and s3.sal_date > s1.sal_date) 
3、语句块三
(1)select子句选择表1的emp_name、sal_date、sal_amt
(2)如果表1的数据与表2的数据不相同时,则直接选择表2的数据;
如果表1的数据与表2的数据相同时,则证明该行数据就只有1个日期而已,则直接为空即可。
select s1.emp_name,s1.sal_date as curr_date,s1.sal_amt as curr_amt,
         case when s2.sal_date <> s1.sal_date then s2.sal_date end as prev_date,
         case when s2.sal_date <> s1.sal_date then s2.sal_amt end as prev_amt
  from Salaries as s1
 inner join Salaries as s2
    on s2.emp_name = s1.emp_name
   and s2.sal_date = coalesce((select MAX(s4.sal_date)
                               from Salaries as s4
                              where s4.emp_name = s1.emp_name
                                and s4.sal_date < s1.sal_date),s2.sal_date)
 where not exists(select *
                   from Salaries as s3
                  where s3.emp_name = s1.emp_name
                    and s3.sal_date > s1.sal_date)       
解惑六:
1、创建视图
(1)from子句获取表Salaries的数据
(2)leftouterjoin表Salaries,当表1的emp_name与表2的emp_name相同时,且表1的日期大于表2的日期。相当于获取所有日期与所有比该日期小的集合,如果没有则为空。
create view SalaryHistory(emp_name,curr_date,curr_amt,prev_date,prev_amt)
as
select s0.emp_name,s0.sal_date as curr_date,
                           s0.sal_amt as curr_amt,
                           s1.sal_date as prev_date,
                           s1.sal_amt as prev_amt
  from Salaries as s0
  left outer join Salaries as s1 on s0.emp_name = s1.emp_name
  and s0.sal_date > s1.sal_date
go
2、查询数据
(1)from子句获取数据
(2)where子句筛选表1的最大日期,表1代表所有日期的最大日期,表2代表所有日期的邻近日期的最大日期。筛选出来的日期即为表的最大日期和最大的邻近日期。
select s0.emp_name,
         s0.curr_date,
         s0.curr_amt,
         s0.prev_date,
         s0.prev_amt
  from SalaryHistory as s0
 where s0.curr_date = (select MAX(curr_date)
                                     from SalaryHistory as s1
                                    where s0.emp_name = s1.emp_name)
   and s0.prev_date =  (select MAX(prev_date)
                                       from SalaryHistory as s2
                                      where s0.emp_name = s2.emp_name
                                           or s0.prev_date is null)
解惑七:
1、查询语句
(1)子查询中,是使用Rank函数将表Salaries按emp_name分组且按sal_date降序排列
(2)查询语句中只有leftouterjoin表插入第二名的日期且选择第一日期即可。
with SalaryRanks(emp_name,sal_date,sal_amt,pos)
as
(
select emp_name,sal_date,sal_amt,
         RANK() over(PARTITION BY emp_name order by sal_date desc)
  from Salaries
)                             
select C.emp_name,
         C.sal_date as curr_date,C.sal_amt as curr_amt,
         P.sal_date as prev_date,P.sal_amt as prev_amt
  from SalaryRanks as c
  left outer join SalaryRanks as P
    on P.emp_name = c.emp_name
   and P.pos = 2
 where c.pos = 1
解惑八:
1、查询语句
(1)子查询将表Salaries按emp_name分组按sal_date降序排列并赋值
(2)where子句筛序序号小于3的数据
(3)select子句根据序号1或者2来确定最近的一次和最近一次的前一次
select s1.emp_name,
         MAX(case when rn = 1 then sal_date else null end) as curr_date,
         MAX(case when rn = 1 then sal_amt else null end) as curr_amt,
         MAX(case when rn = 2 then sal_date else null end) as prev_date,
         MAX(case when rn = 2 then sal_amt else null end) as prev_amt
  from (select emp_name,sal_date,sal_amt,
                     RANK() over (PARTITION BY emp_name order by sal_date desc)
              from Salaries) as s1(emp_name,sal_date,sal_amt,rn)
 where rn < 3
 group by s1.emp_name
解惑九:
该方法与解惑七中一样
with cte(emp_name,sal_date,sal_amt,rn)
as
(
select emp_name,sal_date,sal_amt,
       ROW_NUMBER() over (PARTITION by emp_name order by sal_date desc) as rn
  from Salaries
)
select o.emp_name,
         o.sal_date as curr_date,o.sal_amt as curr_amt,
         i.sal_date as prev_date,i.sal_amt as prev_amt
  from cte as o
  left outer join cte as i
    on o.emp_name = i.emp_name and i.rn = 2
 where o.rn = 1
解惑十:
·1、该语句在MSSQL2010、MSSQL2012的版本才能运行
select emp_name,curr_date,curr_amt,prev_date,prev_amt
  from (select emp_name,
                     sal_date as curr_date,
                     sal_amt as curr_amt,
                     MIN(sal_amt) over (partition by emp_name
                                                  order by sal_date desc
                                                  rows between 1 following and following)
                     as prev_date,
                     min(sal_amt) over (partition by emp_name
                                                  order by sal_date desc
                                                  rows between 1 following and following)
                     as prev_amt,
                     row_number() over (partition by emp_name
                                                  order by sal_date desc)
                     as rn
              from Salaries) as dt 
 where rn = 1













评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值