Oracle SELECT 查询:配对日期时折叠空值

Posted

技术标签:

【中文标题】Oracle SELECT 查询:配对日期时折叠空值【英文标题】:Oracle SELECT query: collapsing null values when pairing up dates 【发布时间】:2010-05-20 19:29:00 【问题描述】:

我有以下 Oracle 查询:

SELECT id,
       DECODE(state, 'Open', state_in, NULL) AS open_in,
       DECODE(state, 'Not Open', state_in, NULL) AS open_out,
FROM (
       SELECT id,
              CASE WHEN state = 'Open'
                   THEN 'Open'
                   ELSE 'Not Open'
              END AS state,
              TRUNC(state_time) AS state_in
       FROM ...
     )

这给了我如下数据:

id  open_in              open_out
1   2009-03-02 00:00:00
1                        2009-03-05 00:00:00
1   2009-03-11 00:00:00
1                        2009-03-26 00:00:00
1                        2009-03-24 00:00:00
1                        2009-04-13 00:00:00

我想要的是这样的数据:

id  open_in              open_out
1   2009-03-02 00:00:00  2009-03-05 00:00:00
1   2009-03-11 00:00:00  2009-03-24 00:00:00

也就是说,保留所有唯一的id/open_in 对,并与open_in 之后最早的open_out 配对。对于给定的id,可以有任意数量的唯一open_in 值,以及任意数量的唯一open_out 值。唯一的 id/open_in 可能没有匹配的 open_out 值,在这种情况下,open_out 应该是该行的 null

我觉得一些分析函数,可能是LAGLEAD,在这里会很有用。也许我需要MINPARTITION 一起使用。

【问题讨论】:

我使用有限的接口(没有直接的数据库访问),所以我不确定确切的版本,但根据我已经能够使用的功能,我猜可能有 10 个。我无权访问EXPLAIN PLAN,只能访问SELECT 您的预期输出中可能存在拼写错误 - 不应该是 2009-03-26 而不是 24 号吗? 如果你发出“select * from v$version”并且你有权限这样做,你可以看到确切的版本。 @OMG Ponies:不,3/24 是正确的,因为它是 3/11 之后的最早日期。 3/26 在 3/11 之后首先列出,是的,但随后是 3/24 和 4/13。 @Rob:所以请帮助我,我以前尝试过,但它从来没有奏效,但它现在就奏效了。也许我们的数据库管理员很慷慨!我有 10 克。 【参考方案1】:

它可以做得更简单一点。首先让我们创建一个示例表:

SQL> create table mytable (id,state,state_time)
  2  as
  3  select 1, 'Open', date '2009-03-02' from dual union all
  4  select 1, 'Closed', date '2009-03-05' from dual union all
  5  select 1, 'Open', date '2009-03-11' from dual union all
  6  select 1, 'Shut down', date '2009-03-26' from dual union all
  7  select 1, 'Wiped out', date '2009-03-24' from dual union all
  8  select 1, 'Demolished', date '2009-04-13' from dual
  9  /

Table created.

数据等于您的选择语句的输出:

SQL> SELECT id,
  2         DECODE(state, 'Open', state_in, NULL) AS open_in,
  3         DECODE(state, 'Not Open', state_in, NULL) AS open_out
  4  FROM (
  5         SELECT id,
  6                CASE WHEN state = 'Open'
  7                     THEN 'Open'
  8                     ELSE 'Not Open'
  9                END AS state,
 10                TRUNC(state_time) AS state_in
 11         FROM mytable
 12       )
 13  /

        ID OPEN_IN             OPEN_OUT
---------- ------------------- -------------------
         1 02-03-2009 00:00:00
         1                     05-03-2009 00:00:00
         1 11-03-2009 00:00:00
         1                     26-03-2009 00:00:00
         1                     24-03-2009 00:00:00
         1                     13-04-2009 00:00:00

6 rows selected.

下面是稍微简单的查询:

SQL> select id
  2       , min(case when state = 'Open' then state_time end)  open_in
  3       , min(case when state != 'Open' then state_time end) open_out
  4    from ( select id
  5                , state
  6                , state_time
  7                , max(x) over (partition by id order by state_time) grp
  8             from ( select id
  9                         , state
 10                         , state_time
 11                         , case state
 12                           when 'Open' then
 13                             row_number() over (partition by id order by state_time)
 14                           end x
 15                      from mytable
 16                  )
 17         )
 18   group by id
 19       , grp
 20   order by id
 21       , open_in
 22  /

        ID OPEN_IN             OPEN_OUT
---------- ------------------- -------------------
         1 02-03-2009 00:00:00 05-03-2009 00:00:00
         1 11-03-2009 00:00:00 24-03-2009 00:00:00

2 rows selected.

问候, 抢。

【讨论】:

哦,那好多了!实际上,我发现了我的一个错误,如果有多个唯一的id's,LAG 的东西就会消失并且日期不会正确对齐。 :( 我将在完整的、不受限制的查询中试用您的查询,但我认为它会起作用。谢谢! 您能添加您对查询的解释吗?我正在研究它,试图了解它在做什么,但很高兴听到你的想法。 要了解它在做什么,我建议您从内到外执行这三个部分。换句话说:最里面的查询只为“打开”记录分配不同的数字。这些数字代表组。现在应该将“未打开”记录分配给这些组之一。这是通过最大分析函数完成的。现在这六条记录的 grp 值为 1、1、3、3、3 和 3,它们在最后的 group by 中使用。希望这会有所帮助。【参考方案2】:

我认为 Stack Overflow 一定是鼓舞人心的,或者至少它可以帮助我更清晰地思考。折腾了一天,终于搞定了:

SELECT id,
       open_in,
       open_out
FROM (
       SELECT id,
              open_in,
              LAG(open_out, times_opened) OVER (PARTITION BY id
                                                ORDER BY open_out DESC
                                                NULLS LAST) AS open_out
       FROM (
              SELECT id,
                     open_in,
                     open_out,
                     COUNT(DISTINCT open_in) OVER (PARTITION BY id)
                       AS times_opened
              FROM (
                     SELECT id,
                            DECODE(state, 'Open', state_in, NULL) AS open_in,
                            DECODE(state, 'Not Open', state_in, NULL)
                              AS open_out
                     FROM (
                            SELECT id,
                                   CASE WHEN state = 'Open'
                                        THEN 'Open'
                                        ELSE 'Not Open'
                                   END AS state,
                                   TRUNC(au_time) AS state_in
                            FROM ...
                          )
                   )
            )
     )
WHERE open_in IS NOT NULL

更新: 看起来这并不完全有效。它适用于我的问题中的示例,但是当有多个唯一的 id 时,LAG 的东西会发生变化并且日期并不总是对齐。 :(

【讨论】:

以上是关于Oracle SELECT 查询:配对日期时折叠空值的主要内容,如果未能解决你的问题,请参考以下文章

oracle日期时间范围查询

oracle 查询时间段每一天日期。

ORACLE---Unit02: Oracle字符串操作 Oracle数值操作 Oracle日期操作 空值操作

mysql 如何实现Oracle中的这种日期查询?

数据类型为“日期”的 Oracle sql 查询

Oracle中日期作为条件的查询