部分非正规化的独角兽观察

Ben*_*Ben 6 sql oracle gaps-and-islands

复活节岛1上,有许多研究人员正在观察世界上最后剩下的独角兽.每天研究人员都会记录他们看到的独角兽,目击日期,每只独角兽的婴儿数量,以及在发生瞄准时他们是否喝醉了.它们被单独上传到一个中心位置,然后每天向我发出一个平面文件,显示所有新的观察结果.

我有一个看起来像这样的表来包含信息:

create table unicorn_observations (
     observer_id number not null
   , unicorn_id number not null
   , created date not null -- date the record was inserted into the database
   , lastseen date not null -- date the record was last seen
   , observation_date date not null
   , no_of_babies number not null
   , drunk varchar2(1) not null
   , constraint pk_uo primary key ( observer_id, unicorn_id, created )
   , constraint chk_uo_babies check ( no_of_babies >= 0 )
   , constraint chk_uo_drunk check ( drunk in ('y','n') )
     );
Run Code Online (Sandbox Code Playgroud)

该表是分开独特 observer_id,unicorn_idobservation_datelastseen.

有时管理数据输出的Cobold [sic]会略微出错并重新输出两次相同的数据.在这种情况下,我更新lastseen而不是创建新记录.我在每列相同的情况下这样做

不幸的是,研究人员并不完全了解第三种正常形式.他们每个月都会上传几个独角兽的前几个月的观察结果,即使没有新的观察结果.他们使用new 执行此操作observation_date,这意味着将新记录插入到表中.

由于研究人员有时会提交一些观察结果,因此我有一个单独的created,lastseen完整的可追溯性.这些是由数据库创建的,不是提交信息的一部分.

这是一些示例数据(部分更改了列名称,以使其适合没有滚动条).

+--------+--------+-----------+-----------+-----------+---------+-------+
| OBS_ID | UNI_ID |  CREATED  | LASTSEEN  | OBS_DATE  | #BABIES | DRUNK |
+--------+--------+-----------+-----------+-----------+---------+-------+
|      1 |      1 | 01-NOV-11 | 01-NOV-11 | 31-OCT-11 |      10 | n     |
|      1 |      2 | 01-NOV-11 | 01-NOV-11 | 31-OCT-11 |      10 | n     |
|      1 |      3 | 01-NOV-11 | 01-NOV-11 | 31-OCT-11 |      10 | n     |
|      1 |      6 | 10-NOV-11 | 10-NOV-11 | 07-NOV-11 |       0 | n     |
|      1 |      1 | 17-NOV-11 | 17-NOV-11 | 09-APR-11 |      10 | n     |
|      1 |      2 | 17-NOV-11 | 17-NOV-11 | 09-APR-11 |      10 | n     |
|      1 |      3 | 17-NOV-11 | 17-NOV-11 | 09-APR-11 |      10 | n     |
|      1 |      6 | 17-NOV-11 | 17-NOV-11 | 17-NOV-11 |       0 | n     |
|      1 |      6 | 01-DEC-11 | 01-DEC-11 | 01-DEC-11 |       0 | n     |
|      1 |      6 | 01-JAN-12 | 01-JAN-12 | 01-JAN-12 |       3 | n     |
|      1 |      6 | 01-FEB-12 | 01-FEB-12 | 01-FEB-12 |       0 | n     |
|      1 |      6 | 01-MAR-12 | 01-MAR-12 | 01-MAR-12 |       0 | n     |
|      1 |      6 | 01-APR-12 | 01-APR-12 | 01-APR-12 |       0 | n     |
|      1 |      1 | 19-APR-12 | 19-APR-12 | 19-APR-12 |       7 | y     |
|      1 |      2 | 19-APR-12 | 19-APR-12 | 19-APR-12 |       7 | y     |
|      1 |      3 | 19-APR-12 | 19-APR-12 | 19-APR-12 |       7 | y     |
|      1 |      6 | 01-MAY-12 | 01-MAY-12 | 01-MAY-12 |       0 | n     |
+--------+--------+-----------+-----------+-----------+---------+-------+

我想部分denormalise这些意见,这样,如果一个新的记录与相同的接收observer_id,unicorn_id,no_of_babiesdrunk(有效载荷),但有一个新的 observation_date我在桌子上,更新新列last_observation_date的,而不是插入新记录.我仍然会lastseen在这种情况下更新.

我需要这样做,因为我有许多连接到此表的复杂的独角兽相关查询; 研究人员每月大约10万次上传旧日期的旧观察结果,我每月收到大约9m的新记录.我已经跑了一年,已经有225米的独角兽观察.因为我只需要知道每个有效负载组合的最后观察日期,我宁愿大量减小表的大小并节省自己大量的时间来全扫描它.

这意味着该表将变为:

create table unicorn_observations (
     observer_id number not null
   , unicorn_id number not null
   , created date not null -- date the record was inserted into the database
   , lastseen date not null -- date the record was last seen
   , observation_date date not null
   , no_of_babies number not null
   , drunk varchar2(1) not null
   , last_observation_date date
   , constraint pk_uo primary key ( observer_id, unicorn_id, created )
   , constraint chk_uo_babies check ( no_of_babies >= 0 )
   , constraint chk_uo_drunk check ( drunk in ('y','n') )
     );
Run Code Online (Sandbox Code Playgroud)

存储在表格中的数据如下所示; last_observation_date如果观察只被"看到"一次,那么是否为空并不重要.我在加载数据时不需要帮助,只是将当前表的部分非规范化看起来像这样.

+--------+--------+-----------+-----------+-----------+---------+-------+-------------+
| OBS_ID | UNI_ID |  CREATED  | LASTSEEN  | OBS_DATE  | #BABIES | DRUNK | LAST_OBS_DT |
+--------+--------+-----------+-----------+-----------+---------+-------+-------------+
|      1 |      6 | 10-NOV-11 | 01-DEC-11 | 07-NOV-11 |       0 | n     | 01-DEC-11   |
|      1 |      1 | 01-NOV-11 | 17-NOV-11 | 09-APR-11 |      10 | n     | 31-OCT-11   |
|      1 |      2 | 01-NOV-11 | 17-NOV-11 | 09-APR-11 |      10 | n     | 31-OCT-11   |
|      1 |      3 | 01-NOV-11 | 17-NOV-11 | 09-APR-11 |      10 | n     | 31-OCT-11   |
|      1 |      6 | 01-JAN-12 | 01-JAN-12 | 01-JAN-12 |       3 | n     |             |
|      1 |      6 | 01-FEB-12 | 01-MAY-12 | 01-FEB-12 |       0 | n     | 01-MAY-12   |
|      1 |      1 | 19-APR-12 | 19-APR-12 | 19-APR-12 |       7 | y     |             |
|      1 |      2 | 19-APR-12 | 19-APR-12 | 19-APR-12 |       7 | y     |             |
|      1 |      3 | 19-APR-12 | 19-APR-12 | 19-APR-12 |       7 | y     |             |
+--------+--------+-----------+-----------+-----------+---------+-------+-------------+

显而易见的答案

select observer_id as obs_id
     , unicorn_id as uni_id
     , min(created) as created
     , max(lastseen) as lastseen
     , min(observation_date) as obs_date
     , no_of_babies as "#BABIES"
     , drunk
     , max(observation_date) as last_obs_date
  from unicorn_observations
 group by observer_id
        , unicorn_id
        , no_of_babies
        , drunk
Run Code Online (Sandbox Code Playgroud)

不起作用,因为它忽略了2012年1 月1 对独角兽6的3个独角兽婴儿的单一观察; 这反过来意味着11月lastseen10 创建的记录不正确.

+--------+--------+-----------+-----------+-----------+---------+-------+-------------+
| OBS_ID | UNI_ID |  CREATED  | LASTSEEN  | OBS_DATE  | #BABIES | DRUNK | LAST_OBS_DT |
+--------+--------+-----------+-----------+-----------+---------+-------+-------------+
|      1 |      1 | 01-NOV-11 | 17-NOV-11 | 09-APR-11 |      10 | n     | 31-OCT-11   |
|      1 |      2 | 01-NOV-11 | 17-NOV-11 | 09-APR-11 |      10 | n     | 31-OCT-11   |
|      1 |      3 | 01-NOV-11 | 17-NOV-11 | 09-APR-11 |      10 | n     | 31-OCT-11   |
|      1 |      6 | 10-NOV-11 | 01-MAY-12 | 07-NOV-11 |       0 | n     | 01-MAY-12   |
|      1 |      6 | 01-JAN-12 | 01-JAN-12 | 01-JAN-12 |       3 | n     | 01-JAN-12   |
|      1 |      1 | 19-APR-12 | 19-APR-12 | 19-APR-12 |       7 | y     | 19-APR-12   |
|      1 |      2 | 19-APR-12 | 19-APR-12 | 19-APR-12 |       7 | y     | 19-APR-12   |
|      1 |      3 | 19-APR-12 | 19-APR-12 | 19-APR-12 |       7 | y     | 19-APR-12   |
+--------+--------+-----------+-----------+-----------+---------+-------+-------------+

如果没有一些程序逻辑,我现在没有看到这样做的方法,即循环.我宁愿在这种情况下避免循环,因为我必须完全扫描225米行表260次(不同created日期的数量).即使使用lag(),并lead()因为有每麒麟观测的不确定量将需要递归.

有没有办法在单个SQL语句中创建此数据集?

表规范和示例数据也在SQL Fiddle中.


尝试更好的解释:

该问题是如何保持东西是真实的.2012年1月1日,独角兽6号有3个婴儿.

看看GROUP BY创建的"表"中的独角兽6; 如果我试图在1 月1 找到婴儿的数量,我会得到两条记录,这是一个矛盾.

+--------+--------+-----------+-----------+-----------+---------+-------+-------------+
| OBS_ID | UNI_ID |  CREATED  | LASTSEEN  | OBS_DATE  | #BABIES | DRUNK | LAST_OBS_DT |
+--------+--------+-----------+-----------+-----------+---------+-------+-------------+
|      1 |      6 | 10-NOV-11 | 01-MAY-12 | 07-NOV-11 |       0 | n     | 01-MAY-12   |
|      1 |      6 | 01-JAN-12 | 01-JAN-12 | 01-JAN-12 |       3 | n     | 01-JAN-12   |
+--------+--------+-----------+-----------+-----------+---------+-------+-------------+

但是,我只想要一行,就像在第二个表中一样.在这里,对于任何时间点,最多只有一个"正确"值,因为独角兽6有0个婴儿的两个时间段在它有3个时被分成两行.

+--------+--------+-----------+-----------+-----------+---------+-------+-------------+
| OBS_ID | UNI_ID |  CREATED  | LASTSEEN  | OBS_DATE  | #BABIES | DRUNK | LAST_OBS_DT |
+--------+--------+-----------+-----------+-----------+---------+-------+-------------+
|      1 |      6 | 10-NOV-11 | 01-DEC-11 | 07-NOV-11 |       0 | n     | 01-DEC-11   |
|      1 |      6 | 01-JAN-12 | 01-JAN-12 | 01-JAN-12 |       3 | n     |             |
|      1 |      6 | 01-FEB-12 | 01-MAY-12 | 01-FEB-12 |       0 | n     | 01-MAY-12   |
+--------+--------+-----------+-----------+-----------+---------+-------+-------------+

在moai周围放牧

Ale*_*ole 1

根据我认为您正在尝试做的事情,主要是根据您对 unicorn 6 的具体问题的更新,我认为这会得到您想要的结果。它不需要递归leadand lag,但需要两层。

select *
from (
    select observer_id, unicorn_id,
        case when first_obs_dt is null then created
            else lag(created) over (order by rn) end as created,
        case when last_obs_dt is null then lastseen
            else lead(lastseen) over (order by rn) end as lastseen,
        case when first_obs_dt is null then observation_date
            else lag(observation_date) over (order by rn)
            end as observation_date,
        no_of_babies,
        drunk,
        case when last_obs_dt is null then observation_date
            else null end as last_obs_dt
    from (
        select observer_id, unicorn_id, created, lastseen, 
            observation_date, no_of_babies, drunk,
            case when lag_no_babies != no_of_babies or lag_drunk != drunk
                or lag_obs_dt is null then null
                else lag_obs_dt end as first_obs_dt,
            case when lead_no_babies != no_of_babies or lead_drunk != drunk
                or lead_obs_dt is null then null
                else lead_obs_dt end as last_obs_dt,
            rownum rn
        from (
            select observer_id, unicorn_id, created, lastseen,
                observation_date, no_of_babies, drunk,
                lag(observation_date)
                    over (partition by observer_id, unicorn_id, no_of_babies,
                            drunk
                        order by observation_date) lag_obs_dt,
                lag(no_of_babies)
                    over (partition by observer_id, unicorn_id, drunk
                        order by observation_date) lag_no_babies,
                lag(drunk)
                    over (partition by observer_id, unicorn_id, no_of_babies
                        order by observation_date) lag_drunk,
                lead(observation_date)
                    over (partition by observer_id, unicorn_id, no_of_babies,
                        drunk
                    order by observation_date) lead_obs_dt,
                lead(no_of_babies)
                    over (partition by observer_id, unicorn_id, drunk
                        order by observation_date) lead_no_babies,
                lead(drunk)
                    over (partition by observer_id, unicorn_id, no_of_babies
                        order by observation_date) lead_drunk
            from unicorn_observations
            order by 1,2,5
        )
    )
    where first_obs_dt is null or last_obs_dt is null
)
where last_obs_dt is not null
order by 1,2,3,4;
Run Code Online (Sandbox Code Playgroud)

这使:

OBSERVER_ID UNICORN_ID CREATED   LASTSEEN  OBSERVATI NO_OF_BABIES D LAST_OBS_
----------- ---------- --------- --------- --------- ------------ - ---------
          1          1 17-NOV-11 01-NOV-11 09-APR-11           10 n 31-OCT-11
          1          1 19-APR-12 19-APR-12 19-APR-12            7 y 19-APR-12
          1          2 17-NOV-11 01-NOV-11 09-APR-11           10 n 31-OCT-11
          1          2 19-APR-12 19-APR-12 19-APR-12            7 y 19-APR-12
          1          3 17-NOV-11 01-NOV-11 09-APR-11           10 n 31-OCT-11
          1          3 19-APR-12 19-APR-12 19-APR-12            7 y 19-APR-12
          1          6 10-NOV-11 01-DEC-11 07-NOV-11            0 n 01-DEC-11
          1          6 01-JAN-12 01-JAN-12 01-JAN-12            3 n 01-JAN-12
          1          6 01-FEB-12 01-MAY-12 01-FEB-12            0 n 01-MAY-12

9 rows selected.
Run Code Online (Sandbox Code Playgroud)

它有独角兽 6 的三个记录,但第三个的lastseenobservation_date与您的示例相反,所以我不确定我是否仍然不理解这一点。我假设您想在每个分组中保留最早observation_date和最新的lastseen记录,因为这似乎是添加新记录时会发生的情况,但我不确定......

因此,最里面的查询从表中获取原始数据,并使用稍微不同的分区获取和leadlagobservation_date 列。以便稍后可以使用,在下一步中获得并用于之后的订购。为简洁起见,仅针对 unicorn 6:no_of_babiesdrunkorder byrownum

CREATED   LASTSEEN  OBSERVATI NO_OF_BABIES D LAG_OBS_D LAG_NO_BABIES L LEAD_OBS_ LEAD_NO_BABIES L
--------- --------- --------- ------------ - --------- ------------- - --------- -------------- -
10-NOV-11 10-NOV-11 07-NOV-11            0 n                           17-NOV-11              0 n
17-NOV-11 17-NOV-11 17-NOV-11            0 n 07-NOV-11             0 n 01-DEC-11              0 n
01-DEC-11 01-DEC-11 01-DEC-11            0 n 17-NOV-11             0 n 01-FEB-12              3 n
01-JAN-12 01-JAN-12 01-JAN-12            3 n                       0                          0
01-FEB-12 01-FEB-12 01-FEB-12            0 n 01-DEC-11             3 n 01-MAR-12              0 n
01-MAR-12 01-MAR-12 01-MAR-12            0 n 01-FEB-12             0 n 01-APR-12              0 n
01-APR-12 01-APR-12 01-APR-12            0 n 01-MAR-12             0 n 01-MAY-12              0 n
01-MAY-12 01-MAY-12 01-MAY-12            0 n 01-APR-12             0 n
Run Code Online (Sandbox Code Playgroud)

如果 或值发生变化,下一个级别将清空leadlag值- 您仅特别提到了婴儿计数的拆分,但我假设您也想在清醒时进行拆分。此后,任何包含或的内容都是迷你范围的开始或结束。observation_datenum_of_babiesdrunknullfirst_obs_datelast_obs_date

CREATED   LASTSEEN  OBSERVATI NO_OF_BABIES D FIRST_OBS LAST_OBS_         RN
--------- --------- --------- ------------ - --------- --------- ----------
10-NOV-11 10-NOV-11 07-NOV-11            0 n           17-NOV-11          1
17-NOV-11 17-NOV-11 17-NOV-11            0 n 07-NOV-11 01-DEC-11          2
01-DEC-11 01-DEC-11 01-DEC-11            0 n 17-NOV-11                    3
01-JAN-12 01-JAN-12 01-JAN-12            3 n                              4
01-FEB-12 01-FEB-12 01-FEB-12            0 n           01-MAR-12          5
01-MAR-12 01-MAR-12 01-MAR-12            0 n 01-FEB-12 01-APR-12          6
01-APR-12 01-APR-12 01-APR-12            0 n 01-MAR-12 01-MAY-12          7
01-MAY-12 01-MAY-12 01-MAY-12            0 n 01-APR-12                    8
Run Code Online (Sandbox Code Playgroud)

现在可以忽略不是小范围开始或结束的任何内容,因为这些值要么与之前或之后的值相同,要么被之前或之后的值取代。这解决了不确定数量的观察问题 - 此时忽略多少并不重要。first_obs_dt因此,下一个级别通过过滤和均last_obs_dt非空的行来消除这些中间值。在该过滤集中,有第二层leadlag来获取每个日期的第一个或最后一个值 - 这是我不确定是否正确的一点,因为它与您的一个样本不匹配。

CREATED   LASTSEEN  OBSERVATI NO_OF_BABIES D LAST_OBS_
--------- --------- --------- ------------ - ---------
10-NOV-11 01-DEC-11 07-NOV-11            0 n
10-NOV-11 01-DEC-11 07-NOV-11            0 n 01-DEC-11
01-JAN-12 01-JAN-12 01-JAN-12            3 n 01-JAN-12
01-FEB-12 01-MAY-12 01-FEB-12            0 n
01-FEB-12 01-MAY-12 01-FEB-12            0 n 01-MAY-12
Run Code Online (Sandbox Code Playgroud)

最后,剩下的没有a的行last_obs_dt被过滤掉。

现在我会等着看我误解了哪些位...... *8-)


经过更正leadlag排序,独角兽 1 每个阶段的信息相同:

CREATED   LASTSEEN  OBSERVATI NO_OF_BABIES D LAG_OBS_D LAG_NO_BABIES L LEAD_OBS_ LEAD_NO_BABIES L
--------- --------- --------- ------------ - --------- ------------- - --------- -------------- -
17-NOV-11 17-NOV-11 09-APR-11           10 n                           31-OCT-11             10 n
01-NOV-11 01-NOV-11 31-OCT-11           10 n 09-APR-11            10 n
19-APR-12 19-APR-12 19-APR-12            7 y

CREATED   LASTSEEN  OBSERVATI NO_OF_BABIES D FIRST_OBS LAST_OBS_         RN
--------- --------- --------- ------------ - --------- --------- ----------
17-NOV-11 17-NOV-11 09-APR-11           10 n           31-OCT-11          1
01-NOV-11 01-NOV-11 31-OCT-11           10 n 09-APR-11                    2
19-APR-12 19-APR-12 19-APR-12            7 y                              3

CREATED   LASTSEEN  OBSERVATI NO_OF_BABIES D LAST_OBS_
--------- --------- --------- ------------ - ---------
17-NOV-11 17-NOV-11 09-APR-11           10 n 09-APR-11
19-APR-12 19-APR-12 19-APR-12            7 y 19-APR-12
Run Code Online (Sandbox Code Playgroud)

我不确定保留的数据应该发生什么observation_date,以及lastseen当原始数据像这样不按顺序输入时,或者在这种情况下您将在将来添加新记录时做什么。