Oracle SELECT 查询:在为不同字段配对日期时折叠 NULL 值
Posted
技术标签:
【中文标题】Oracle SELECT 查询:在为不同字段配对日期时折叠 NULL 值【英文标题】:Oracle SELECT query: collapsing NULL values when pairing up dates for different fields 【发布时间】:2010-06-23 13:53:31 【问题描述】:这个问题很像my previous question,但是稍微复杂一些。 Rob van Wijk's answer 完美解决了我的另一个问题,我一直以此为起点。我现在的问题是我正在为不同的字段旋转日期。在我关心获取给定id
的所有open_in
和open_out
值之前,现在我想要new_in
、new_out
、open_in
、open_out
、fixed_in
和fixed_out
每个id
。我有以下内容:
SELECT id,
state,
state_time,
MAX(new_row_num) OVER (PARTITION BY id ORDER BY state_time) AS new_row_group,
MAX(open_row_num) OVER (PARTITION BY id ORDER BY state_time) AS open_row_group,
MAX(fixed_row_num) OVER (PARTITION BY id ORDER BY state_time) AS fixed_row_group
FROM (
SELECT id,
state,
state_time,
CASE state
WHEN 'New'
THEN ROW_NUMBER() OVER (PARTITION BY id ORDER BY state_time)
END AS new_row_num,
CASE state
WHEN 'Open'
THEN ROW_NUMBER() OVER (PARTITION BY id ORDER BY state_time)
END AS open_row_num,
CASE state
WHEN 'Fixed'
THEN ROW_NUMBER() OVER (PARTITION BY id ORDER BY state_time)
END AS fixed_row_num
FROM ...
)
这给了我如下数据:
id state state_time new_row_group open_row_group fixed_row_group
1 New 2009-03-03 00:03:31 1
1 Closed 2009-03-04 04:15:27 1
2 New 2010-05-22 14:38:49 1
2 Open 2010-05-22 14:39:14 1 2
2 Fixed 2010-05-22 17:15:27 1 2 3
我想要如下数据:
id new_in new_out open_in open_out fixed_in fixed_out
1 2009-03-03 00:03:31 2009-03-04 04:15:27
2 2010-05-22 14:38:49 2010-05-22 14:39:14 2010-05-22 14:39:14 2010-05-22 17:15:27 2010-05-22 17:15:27
如何旋转数据以获取每个 id
的日期配对?
编辑:澄清一下,id
可以多次进入和离开一个状态。例如,id
可能从 New 到 Open 到 Fixed 到 Open 到 Fixed 到 Closed。在这种情况下,需要有尽可能多的行来保存所有状态时间,例如:
id new_in new_out open_in open_out fixed_in fixed_out
4 2009-01-01 00:00:00 2009-01-02 00:00:00 2009-01-02 00:00:00 2009-01-03 00:00:00 2009-01-03 00:00:00 2009-01-04 00:00:00
4 2009-01-04 00:00:00 2009-01-05 00:00:00 2009-01-05 00:00:00 2009-01-06 00:00:00
【问题讨论】:
【参考方案1】:莎拉,
以下是您的示例数据示例:
SQL> create table yourtable (id,state,state_time)
2 as
3 select 1, 'New', to_date('2009-03-03 00:03:31','yyyy-mm-dd hh24:mi:ss') from dual union all
4 select 1, 'Closed', to_date('2009-03-04 04:15:27','yyyy-mm-dd hh24:mi:ss') from dual union all
5 select 2, 'New', to_date('2010-05-22 14:38:49','yyyy-mm-dd hh24:mi:ss') from dual union all
6 select 2, 'Open', to_date('2010-05-22 14:39:14','yyyy-mm-dd hh24:mi:ss') from dual union all
7 select 2, 'Fixed', to_date('2010-05-22 17:15:27','yyyy-mm-dd hh24:mi:ss') from dual union all
8 select 3, 'New', date '2009-01-01' from dual union all
9 select 3, 'Open', date '2009-01-02' from dual union all
10 select 3, 'Fixed', date '2009-01-03' from dual union all
11 select 3, 'Open', date '2009-01-04' from dual union all
12 select 3, 'Fixed', date '2009-01-05' from dual union all
13 select 3, 'Closed', date '2009-01-06' from dual
14 /
Table created.
查询:
SQL> select id
2 , max(decode(state,'New',state_time)) new_in
3 , max(decode(state,'New',out_time)) new_out
4 , max(decode(state,'Open',state_time)) open_in
5 , max(decode(state,'Open',out_time)) open_out
6 , max(decode(state,'Fixed',state_time)) fixed_in
7 , max(decode(state,'Fixed',out_time)) fixed_out
8 from ( select id
9 , state
10 , state_time
11 , max(cnt) over (partition by id order by state_time) the_row
12 , lead(state_time) over (partition by id order by state_time) out_time
13 from ( select id
14 , state
15 , state_time
16 , count(*) over (partition by id,state order by state_time) cnt
17 from yourtable
18 )
19 )
20 group by id
21 , the_row
22 order by id
23 , the_row
24 /
ID NEW_IN NEW_OUT OPEN_IN OPEN_OUT FIXED_IN FIXED_OUT
---------- ------------------- ------------------- ------------------- ------------------- ------------------- -------------------
1 03-03-2009 00:03:31 04-03-2009 04:15:27
2 22-05-2010 14:38:49 22-05-2010 14:39:14 22-05-2010 14:39:14 22-05-2010 17:15:27 22-05-2010 17:15:27
3 01-01-2009 00:00:00 02-01-2009 00:00:00 02-01-2009 00:00:00 03-01-2009 00:00:00 03-01-2009 00:00:00 04-01-2009 00:00:00
3 04-01-2009 00:00:00 05-01-2009 00:00:00 05-01-2009 00:00:00 06-01-2009 00:00:00
4 rows selected.
要了解它的工作原理,请从内到外执行查询并检查中间结果集。如果您需要其他解释,请告诉我。
问候, 抢。
【讨论】:
耶!这很好用。我希望你能注意到这个问题,因为你回答了我最后一个 Oracle 问题。 :) 谢谢!我确实必须在你的之外添加一个额外的SELECT
,所以我可以有一个WHERE new_in IS NOT NULL OR open_in IS NOT NULL OR fixed_in IS NOT NULL
,因为我得到了很多空行。
对于各种max(decode(state,'New',state_time))
语句,我认为应该使用min
。我在使用max
时跳过了不同的状态时遇到了一些麻烦。【参考方案2】:
我不确定您希望如何处理同一状态对于 ID 重复多次的情况。以下答案采用简单的方法,假设您希望第一次设置状态和最后一次替换状态。
select id,
min(case state when 'New' then state_time else null end) as new_in,
max(case state when 'New' then out_state_time else null end) as new_out,
min(case state when 'Open' then state_time else null end) as open_in,
max(case state when 'Open' then out_state_time else null end) as open_out,
min(case state when 'Fixed' then state_time else null end) as fixed_in,
max(case state when 'Fixed' then out_state_time else null end) as fixed_out
from
(select id,
state,
state_time,
lead(state_time) over (partition by id
order by state_time) as out_state_time
from ...
)
group by id
lead
分析函数获取分区/顺序语句描述的下一行,因此这是找出状态何时更改的最简单方法。中间查询是基本的透视查询(将列转换为行)。
【讨论】:
好点。我更新了我的问题以解释我在寻找什么。 ID 肯定可以多次进入和退出一个状态,并且应该有多个行来适应这种情况。【参考方案3】:select news.id, news.state_time as new_in, min(not_news.state_time) as new_out
, min(opens.state_time) as open_in
, min(not_opens.state_time) as open_out
, min(closes.state_time) as close_in
, min(not_closed.state_time) as close_out
from
(SELECT id,
state,
state_time
from mytable
where state = 'New' ) news
left join
(SELECT id,
state,
state_time
from mytable
where state <> 'New' ) not_news on news.id = not_news.id and news.state_time <= not_news.state_time
left join
(SELECT id,
state,
state_time
from mytable
where state = 'Open' ) opens on news.id = opens.id and news.state_time <= opens.state_time
left join
(SELECT id,
state,
state_time
from mytable
where state not in ('New', 'Open' )) not_opens on news.id = opens.id and news.state_time <= opens.state_time and opens.state_time <= not_opens.state_time
left join
(SELECT id,
state,
state_time
from mytable
where state = 'Closed' ) closes on news.id = closes.id and news.state_time <= closes.state_time
left join
(SELECT id,
state,
state_time
from mytable
where state not in ('Closed' )) not_closed on news.id = not_closed.id and news.state_time <= closes.state_time and closes.state_time <= not_closed.state_time
group by news.id, news.state_time
order by id, news.state_time
我的测试数据(借自 Rob):
创建表 mytable (id,state,state_time) 作为 select 1, 'New', to_date('2009-03-03 00:03:31','yyyy-mm-dd hh24:mi:ss') from dual union all select 1, 'Closed', to_date('2009-03-04 04:15:27','yyyy-mm-dd hh24:mi:ss') from dual union all select 2, 'New', to_date('2010-05-22 14:38:49','yyyy-mm-dd hh24:mi:ss') from dual union all select 2, 'Open', to_date('2010-05-22 14:39:14','yyyy-mm-dd hh24:mi:ss') from dual union all select 2, 'Fixed', to_date('2010-05-22 17:15:27','yyyy-mm-dd hh24:mi:ss') from dual union all select 3, 'New', date '2009-01-01' from dual union all 选择 3, 'Open', date '2009-01-02' from dual union all 选择 3, 'Fixed', date '2009-01-03' from dual union all 选择 3, 'Open', date '2009-01-04' from dual union all 选择 3, 'Fixed', date '2009-01-05' from dual union all 选择 3, 'Closed', date '2009-01-06' from dual
查询结果:
ID NEW_IN NEW_OUT OPEN_IN OPEN_OUT CLOSE_IN CLOSE_OUT 1 2009 年 3 月 3 日 12:03:31 2009 年 3 月 4 日 4:15:27 2009 年 3 月 4 日 4:15:27 2 2010 年 5 月 22 日 2:38:49 2010 年 5 月 22 日 2:39:14 2010 年 5 月 22 日 2:39:14 2010 年 5 月 22 日 5:15:27 3 2009 年 1 月 1 日 2009 年 1 月 2 日 2009 年 1 月 2 日 2009 年 1 月 3 日 2009 年 1 月 6 日希望您能阅读以上内容,我在格式化时遇到了问题。
【讨论】:
您的联接有问题。在您使用“state ”的任何地方,您都会在稍后返回每一行。例如,使用海报的数据,您的第一个连接将返回 2 行 id=2。第一行将有 new_out = 2010-05-22 14:39:14(打开),第二行将有 new_out = 2010-05-22 17:15:27(固定)。如果要使用此方法,则需要确保这些不等式连接仅导致每个 ID 返回一行。 按 ID 和 news.state_time 分组并使用 min() 聚合函数消除了额外的行。 (我用的是“”)。我会发布一些测试数据和查询结果。 我不认为我完全理解莎拉想要什么。 Rob 的查询结果与我对问题的理解不完全相符。以上是关于Oracle SELECT 查询:在为不同字段配对日期时折叠 NULL 值的主要内容,如果未能解决你的问题,请参考以下文章