这样的事情可能比联接更有效。唉,我没有找到避免扫描表两次的方法。
注意:我没有使用 ORDER BY 子句(事实上,如果我有排序会很奇怪,因为我在日期上使用了 to_char 来格式化它们)。如果您在进一步处理中需要它,最好不要将日期包装在 to_char 中。
with
input_data ( id, animal_id, transfer_date, status_from, status_to) as (
select 100, 5265, to_date('01-Jul-2016', 'dd-Mon-yyyy'), null, 'P' from dual union all
select 101, 5265, to_date('22-Jul-2016', 'dd-Mon-yyyy'), 'P' , 'A' from dual union all
select 102, 5265, to_date('26-Jul-2016', 'dd-Mon-yyyy'), 'A' , 'B' from dual union all
select 103, 5265, to_date('06-Aug-2016', 'dd-Mon-yyyy'), 'B' , 'A' from dual
)
select animal_id,
lag (status_to) over (partition by animal_id order by transfer_date) as status,
to_char(lag (transfer_date) over (partition by animal_id order by transfer_date),
'dd-Mon-yyyy') as start_date,
to_char(transfer_date - 1, 'dd-Mon-yyyy') as end_date
from input_data
union all
select animal_id,
max(status_to) keep (dense_rank last order by transfer_date),
to_char(max(transfer_date), 'dd-Mon-yyyy'),
null
from input_data
group by animal_id
;
ANIMAL_ID STATUS START_DATE END_DATE
---------- ------ -------------------- --------------------
5265 30-Jun-2016
5265 P 01-Jul-2016 21-Jul-2016
5265 A 22-Jul-2016 25-Jul-2016
5265 B 26-Jul-2016 05-Aug-2016
5265 A 06-Aug-2016
添加:说明其工作原理。首先,有一个“WITH 子句”来从 OP 的消息中创建输入数据;这是一种标准技术,任何不熟悉 Oracle 11.1 中引入的因式子查询(CTE、WITH 子句)的人都会通过阅读它/它们来为自己(以及我们其他人!)带来很多好处。
查询将来自两个来源的行连接在一起。在一个分支中,我使用lag() 解析函数;它根据“order by”子句中的列排序,按“partition by”子句中的列对每个组内的行进行排序。因此,例如,lag(status_to) 将查看同一 animal_id 中的所有行,它会按 transfer_date 对它们进行排序,并且对于每一行,它将从 PREVIOUS 行中选择 status_to(因此“lag ”)。工会的其余部分的工作方式类似。
我对联合有第二部分...正如您在原始帖子中看到的那样,有四行,但输出必须有五行。一般来说,这表明解决方案中的某处将需要某种联合(直接且明显地如在我的解决方案中,或通过自联接或以任何其他方式)。在这里,我只是最后一个状态的另一行(仍然是“当前”)。我使用dense_rank last,在每个组中(如何在 GROUP BY 中显示),只选择transfer_date 的最后一行。
要了解查询的工作原理,首先注释掉union all 和select... group by animal_id 行并运行剩下的内容可能会有所帮助。这将显示查询的第一部分的作用。然后取消注释这些行,而是注释第一部分,从第一个 select animal_id 到 union all(注释这两行以及两者之间的所有内容)。再次运行查询,这将只显示每个 animal_id 的最后一行。
当然,在示例中提供的 OP 只有一个animal_id;如果您愿意,您可以添加更多具有不同animal_id 的行(例如在 WITH 子句中)。只是现在partition by animal_id 和group by animal_id 变得重要了;只有一个 animal_id 就不需要它们(例如,如果所有行都已被 WHERE animal_id = 5265 在子查询中的其他位置过滤)。
添加 #2 - OP 已请求此版本的另一个版本 - 如果不需要第一行怎么办?然后查询更容易编写和阅读。下面我将不再复制 CTE(WITH 子句),并且不再将日期包含在 to_date() 中。不需要GROUP BY,而且我没有对行进行排序(但如果需要,OP 可以这样做)。
select animal_id,
status_to as status,
transfer_date as start_date,
lead(transfer_date) over (partition by animal_id order by transfer_date) - 1
as end_date
from input_data
;