在PostgreSQL查询中遇到问题

时间:2018-05-28 09:14:09

标签: sql postgresql greatest-n-per-group

我有以下表格:

create table employee(id int, name varchar(50), fname varchar(50));

create table rank (id int, name varchar(50));

create table promotion(id int, dt date, from_rank_id int, to_rank_id int, employee_id int,
constraint fk_pro_emp foreign key(employee_id) references employee(id),
constraint fk_pro_rank_f foreign key(from_rank_id) references rank(id),
constraint fk_pro_rank_t foreign key(to_rank_id) references rank(id));

insert into employee values(1, 'John', 'Roy'), (2, 'Kane', 'Williamson'), (3, 'Yasin', 'Khan'), (4, 'Dwayne', 'Brain');

insert into rank values(1, 'A'), (2, 'B'), (3, 'C'), (4, 'D'), (5, 'E');

--One person can have many promotions so I insert two records for the same person
insert into promotion values(1, '2010-01-01', 1, 2, 1), (2, '2015-01-01', 2, 3, 1);

--I insert three promotions for the second employee
insert into promotion values(4, '2011-11-23', 1, 2, 2), (5, '2012-04-05', 2, 3, 2), (6, '2013-12-30', 3, 4, 2);

--I insert one record for the third person
insert into promotion values(7, '2015-10-21', 3, 4, 3);

--The last person does not have promotion  

现在我想从员工中选择记录及其最新(最长促销日期)促销记录 所需的输出是:

EMP_ID      Name      Father_Name     Pro_Date    From_Rank    To_Rank  
   1        John         Roy          2015-01-01     B            C  
   2        Kane       Williamson     2013-12-30     C            D  
   3        Yasin        Khan         2015-10-21     C            D  
   4        Dwayne       Brain         <Null>       <Null>      <Null>   

提前感谢您的帮助。

3 个答案:

答案 0 :(得分:1)

在Postgres中解决问题的常用方法是使用(专有)distinct on()运算符。

以下查询返回每位员工的最新促销:

select distinct on (p.employee_id) p.employee_id
from promotion p
order by p.employee_id, p.dt desc

这可以与rank表连接以获取等级名称:

select distinct on (p.employee_id) p.employee_id, p.dt as promotion_date, fr.name as from_rank, tr.name as to_rank
from promotion p
  join rank tr on tr.id = p.to_rank_id
  join rank fr on fr.id = p.from_rank_id
order by p.employee_id, p.dt desc

您的表格设置允许在没有from_rank_idto_rank_id的情况下插入促销,因为这些列可以为空。如果确实允许(我怀疑),则需要将这些连接更改为外连接。至少应将to_rank_id定义为NOT NULL,但可能两者都定义为employee

该查询又可以加入select e.*, t.* from employee e left join ( select distinct on (p.employee_id) p.employee_id, p.dt as promotion_date, fr.name as from_rank, tr.name as to_rank from promotion p join rank tr on tr.id = p.to_rank_id join rank fr on fr.id = p.from_rank_id order by p.employee_id, p.dt desc ) t on t.employee_id = e.id order by e.id; 表:

IWorker

在线示例:http://rextester.com/NWBDFL38394

答案 1 :(得分:0)

您可以使用LATERAL加入和子查询,使用dt为给定employee_id选择最高ORDER BY dt DESC LIMIT 1的促销,以获取曾晋升过一次的员工以及促销数据。 UNION ALL从未晋升过的员工,您可以使用NOT EXISTS (SELECT * FROM promotion ...)进行检查。

SELECT e.id "EMP_ID",
       e.name "Name",
       e.fname "Father_Name",
       x.dt "Pro_Date",
       x.rank_before "From_Rank",
       x.rank_after "To_Rank"
       FROM employee e
            CROSS JOIN LATERAL (SELECT p.dt,
                                       rb.name rank_before,
                                       ra.name rank_after
                                       FROM promotion p
                                            LEFT JOIN rank rb
                                                      ON rb.id = p.from_rank_id
                                            LEFT JOIN rank ra
                                                      ON ra.id = p.to_rank_id
                                       WHERE p.employee_id = e.id
                                       ORDER BY p.dt DESC
                                       LIMIT 1) x
UNION ALL
SELECT e.id "EMP_ID",
       e.name "Name",
       e.fname "Father_Name",
       NULL "Pro_Date",
       NULL "From_Rank",
       NULL "To_Rank"
       FROM employee e
       WHERE NOT EXISTS (SELECT *
                                FROM promotion p
                                WHERE p.employee_id = e.id);

答案 2 :(得分:0)

试试这个 -

SELECT e.id, e.name, e.fname, p3.dt pro_date, p3.name2 From_Rank, p3.name1 To_Rank
FROM employee e
left join (select p.dt dt, p.employee_id, r.name name1, r2.name name2
           from promotion p
           inner join (select max(dt) dt, employee_id
                       from promotion
                       group by employee_id) p2
                       on p.dt = p2.dt
          left join rank r on p.to_rank_id = r.id
          left join rank r2 on p.from_rank_id = r2.id) p3 
on e.id = p3.employee_id

http://www.sqlfiddle.com/#!17/bd7e4/50