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_inopen_out 值之前,现在我想要new_innew_outopen_inopen_outfixed_infixed_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 值的主要内容,如果未能解决你的问题,请参考以下文章

oracle中怎么查询字段中的某个值

在oracle中怎么查一个表中的的一个字段的重复数据

oracle 查询某个字段有多条

SQL 查询不同字段的最大值

怎么在oracle中查询出所有的视图view?

oracle查询重复数据