6

有许多研究人员在复活节岛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') )
     );

该表在 observer_idunicorn_idobservation_date或上分别是唯一的lastseen

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

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

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

这是一些示例数据(部分更改了列名,以便在没有滚动条的情况下使其适合)。

+--------+--------+-----------+------------+------- ----+---------+--------+
| OBS_ID | UNI_ID | 创建 | 最后一次 | OBS_DATE | #婴儿 | 醉酒 |
+--------+--------+-----------+------------+------- ----+---------+--------+
| 1 | 1 | 2011 年 11 月 1 日 | 2011 年 11 月 1 日 | 11 年 10 月 31 日 | 10 | n |
| 1 | 2 | 2011 年 11 月 1 日 | 2011 年 11 月 1 日 | 11 年 10 月 31 日 | 10 | n |
| 1 | 3 | 2011 年 11 月 1 日 | 2011 年 11 月 1 日 | 11 年 10 月 31 日 | 10 | n |
| 1 | 6 | 11 年 11 月 10 日 | 11 年 11 月 10 日 | 2011 年 11 月 7 日 | 0 | n |
| 1 | 1 | 11 年 11 月 17 日 | 11 年 11 月 17 日 | 11 年 4 月 9 日 | 10 | n |
| 1 | 2 | 11 年 11 月 17 日 | 11 年 11 月 17 日 | 11 年 4 月 9 日 | 10 | n |
| 1 | 3 | 11 年 11 月 17 日 | 11 年 11 月 17 日 | 11 年 4 月 9 日 | 10 | n |
| 1 | 6 | 11 年 11 月 17 日 | 11 年 11 月 17 日 | 11 年 11 月 17 日 | 0 | n |
| 1 | 6 | 2011 年 12 月 1 日 | 2011 年 12 月 1 日 | 2011 年 12 月 1 日 | 0 | n |
| 1 | 6 | 2012 年 1 月 1 日 | 2012 年 1 月 1 日 | 2012 年 1 月 1 日 | 3 | n |
| 1 | 6 | 2012 年 2 月 1 日 | 2012 年 2 月 1 日 | 2012 年 2 月 1 日 | 0 | n |
| 1 | 6 | 2012 年 3 月 1 日 | 2012 年 3 月 1 日 | 2012 年 3 月 1 日 | 0 | n |
| 1 | 6 | 01-APR-12 | 01-APR-12 | 01-APR-12 | 0 | n |
| 1 | 1 | 12 年 4 月 19 日 | 12 年 4 月 19 日 | 12 年 4 月 19 日 | 7 | 是 |
| 1 | 2 | 12 年 4 月 19 日 | 12 年 4 月 19 日 | 12 年 4 月 19 日 | 7 | 是 |
| 1 | 3 | 12 年 4 月 19 日 | 12 年 4 月 19 日 | 12 年 4 月 19 日 | 7 | 是 |
| 1 | 6 | 2012 年 5 月 1 日 | 2012 年 5 月 1 日 | 2012 年 5 月 1 日 | 0 | n |
+--------+--------+-----------+------------+------- ----+---------+--------+

我想对这些观察进行部分非规范化,以便如果收到具有相同observer_id,和(有效负载)但更新的unicorn_id记录,我会更新表中的新列,而不是插入新记录。在这种情况下,我仍然会更新。no_of_babiesdrunk observation_datelast_observation_datelastseen

我需要这样做,因为我有许多与此表相关的复杂的独角兽相关查询;研究人员每月上传大约 1000 万次带有新日期的旧观测数据,而我每月收到大约 900 万条真正的新记录。我已经跑了一年,已经有 2.25 亿个独角兽观察。由于我只需要知道每个有效负载组合的最后观察日期,我宁愿大量减小表的大小并为自己节省大量时间来全面扫描它。

这意味着该表将变为:

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') )
     );

并且存储在表中的数据如下所示;last_observation_date如果观察只被“看到”一次,是否为空都没有关系。我在加载数据时不需要帮助,只需将当前表部分非规范化为如下所示。

+--------+--------+-----------+------------+------- ----+---------+--------+--------------+
| OBS_ID | UNI_ID | 创建 | 最后一次 | OBS_DATE | #婴儿 | 醉酒 | LAST_OBS_DT |
+--------+--------+-----------+------------+------- ----+---------+--------+--------------+
| 1 | 6 | 11 年 11 月 10 日 | 2011 年 12 月 1 日 | 2011 年 11 月 7 日 | 0 | n | 2011 年 12 月 1 日 |
| 1 | 1 | 2011 年 11 月 1 日 | 11 年 11 月 17 日 | 11 年 4 月 9 日 | 10 | n | 11 年 10 月 31 日 |
| 1 | 2 | 2011 年 11 月 1 日 | 11 年 11 月 17 日 | 11 年 4 月 9 日 | 10 | n | 11 年 10 月 31 日 |
| 1 | 3 | 2011 年 11 月 1 日 | 11 年 11 月 17 日 | 11 年 4 月 9 日 | 10 | n | 11 年 10 月 31 日 |
| 1 | 6 | 2012 年 1 月 1 日 | 2012 年 1 月 1 日 | 2012 年 1 月 1 日 | 3 | n | |
| 1 | 6 | 2012 年 2 月 1 日 | 2012 年 5 月 1 日 | 2012 年 2 月 1 日 | 0 | n | 2012 年 5 月 1 日 |
| 1 | 1 | 12 年 4 月 19 日 | 12 年 4 月 19 日 | 12 年 4 月 19 日 | 7 | 是 | |
| 1 | 2 | 12 年 4 月 19 日 | 12 年 4 月 19 日 | 12 年 4 月 19 日 | 7 | 是 | |
| 1 | 3 | 12 年 4 月 19 日 | 12 年 4 月 19 日 | 12 年 4 月 19 日 | 7 | 是 | |
+--------+--------+-----------+------------+------- ----+---------+--------+--------------+

显而易见的答案

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

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

+--------+--------+-----------+------------+------- ----+---------+--------+--------------+
| OBS_ID | UNI_ID | 创建 | 最后一次 | OBS_DATE | #婴儿 | 醉酒 | LAST_OBS_DT |
+--------+--------+-----------+------------+------- ----+---------+--------+--------------+
| 1 | 1 | 2011 年 11 月 1 日 | 11 年 11 月 17 日 | 11 年 4 月 9 日 | 10 | n | 11 年 10 月 31 日 |
| 1 | 2 | 2011 年 11 月 1 日 | 11 年 11 月 17 日 | 11 年 4 月 9 日 | 10 | n | 11 年 10 月 31 日 |
| 1 | 3 | 2011 年 11 月 1 日 | 11 年 11 月 17 日 | 11 年 4 月 9 日 | 10 | n | 11 年 10 月 31 日 |
| 1 | 6 | 11 年 11 月 10 日 | 2012 年 5 月 1 日 | 2011 年 11 月 7 日 | 0 | n | 2012 年 5 月 1 日 |
| 1 | 6 | 2012 年 1 月 1 日 | 2012 年 1 月 1 日 | 2012 年 1 月 1 日 | 3 | n | 2012 年 1 月 1 日 |
| 1 | 1 | 12 年 4 月 19 日 | 12 年 4 月 19 日 | 12 年 4 月 19 日 | 7 | 是 | 12 年 4 月 19 日 |
| 1 | 2 | 12 年 4 月 19 日 | 12 年 4 月 19 日 | 12 年 4 月 19 日 | 7 | 是 | 12 年 4 月 19 日 |
| 1 | 3 | 12 年 4 月 19 日 | 12 年 4 月 19 日 | 12 年 4 月 19 日 | 7 | 是 | 12 年 4 月 19 日 |
+--------+--------+-----------+------------+------- ----+---------+--------+--------------+

我目前看不到没有一些程序逻辑(即循环)的方法。在这种情况下,我宁愿避免循环,因为我必须全扫描 225m 行表 260 次(不同created日期的数量)。即使使用lag()andlead()也需要递归,因为每个独角兽的观察量是不确定的。

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

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


尝试更好的解释:

问题是在某些事情为真时保持不变。2012 年 1 月 1 日,独角兽 6 生了 3 个婴儿。

只看 GROUP BY 创建的“表”中的独角兽 6;如果我试图找出 1 月 1的婴儿数量,我将返回两条记录,这是矛盾的。

+--------+--------+-----------+------------+------- ----+---------+--------+--------------+
| OBS_ID | UNI_ID | 创建 | 最后一次 | OBS_DATE | #婴儿 | 醉酒 | LAST_OBS_DT |
+--------+--------+-----------+------------+------- ----+---------+--------+--------------+
| 1 | 6 | 11 年 11 月 10 日 | 2012 年 5 月 1 日 | 2011 年 11 月 7 日 | 0 | n | 2012 年 5 月 1 日 |
| 1 | 6 | 2012 年 1 月 1 日 | 2012 年 1 月 1 日 | 2012 年 1 月 1 日 | 3 | n | 2012 年 1 月 1 日 |
+--------+--------+-----------+------------+------- ----+---------+--------+--------------+

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

+--------+--------+-----------+------------+------- ----+---------+--------+--------------+
| OBS_ID | UNI_ID | 创建 | 最后一次 | OBS_DATE | #婴儿 | 醉酒 | LAST_OBS_DT |
+--------+--------+-----------+------------+------- ----+---------+--------+--------------+
| 1 | 6 | 11 年 11 月 10 日 | 2011 年 12 月 1 日 | 2011 年 11 月 7 日 | 0 | n | 2011 年 12 月 1 日 |
| 1 | 6 | 2012 年 1 月 1 日 | 2012 年 1 月 1 日 | 2012 年 1 月 1 日 | 3 | n | |
| 1 | 6 | 2012 年 2 月 1 日 | 2012 年 5 月 1 日 | 2012 年 2 月 1 日 | 0 | n | 2012 年 5 月 1 日 |
+--------+--------+-----------+------------+------- ----+---------+--------+--------------+

1. 在摩艾周围放牧

4

3 回答 3

2

试试这个。

with cte as
(
    select v.*,  ROW_NUMBER() over (partition by grp, unicorn_id order by grp, unicorn_id) rn
    from
    (
        select u.*, 
            ROW_NUMBER() over (partition by unicorn_id order by no_of_babies, drunk, created )
            -ROW_NUMBER() over (partition by unicorn_id order by created) as grp
        from unicorn_observations u
    ) v
) 
    select 
        observer_id, cte.unicorn_id, mincreated,maxlastseen,minobsdate,no_of_babies,drunk,maxobsdate
    from cte 
        inner join 
        (    
            select 
                unicorn_id, grp, 
                min(created) as mincreated,
                max(lastseen) as maxlastseen, 
                min(observation_date) as minobsdate,
                max(observation_date) as maxobsdate
            from cte 
            group by unicorn_id, grp
        ) v
        on cte.grp = v.grp
        and cte.unicorn_id = v.unicorn_id
    where rn=1  
    order by created;
于 2012-10-04T10:31:19.650 回答
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;

这使:

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.

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

因此,最里面的查询从表中获取原始数据,并使用稍微不同的分区为 和 列获取一个lead和。这是这样一个可以稍后使用,在下一步中获得并用于在之后的一个中订购。为了简洁起见,仅适用于独角兽 6:lagobservation_date 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

如果or值发生变化,下一个级别会清除leadandlag值- 您只具体提到了婴儿数量的拆分,但我假设您也想拆分清醒。在此之后,任何具有或者是迷你范围的开始或结束的东西。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

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

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

最后,没有 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

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

于 2012-10-04T22:53:14.757 回答
0

这种类型的问题可以通过首先在子查询中创建一些标志,然后使用它们来解决。

with obs_flags as (
    select 
       observer_id as obs_id
     , unicorn_id as uni_id
     , case when lag(observation_date) over (
           partition by unicorn_id, no_of_babies, drunk
           order by unicorn_id, observation_date
       ) is null then 1 else 0 end as group_start
     , case when lead(observation_date) over (
           partition by unicorn_id, no_of_babies,drunk
           order by unicorn_id, observation_date
       ) is null then 1 else 0 end as group_end
     , observation_date
     , no_of_babies
     , drunk
     , lastseen
     , created
  from unicorn_observations
)
select obs_start.obs_id
     , obs_start.uni_id
     , obs_start.created
     , obs_end.lastseen as lastseen
     , obs_start.observation_date
     , obs_start.no_of_babies as "#BABIES"
     , obs_start.drunk
     , obs_end.observation_date as last_obs_date
  from obs_flags obs_start
  join obs_flags obs_end on 
      obs_start.group_start = 1 and
      obs_end.group_end = 1 and
      obs_start.uni_id = obs_end.uni_id and
      obs_start.no_of_babies = obs_end.no_of_babies and
      obs_start.drunk = obs_end.drunk and
      obs_start.observation_date <= obs_end.observation_date and
      --Only join with the first end point we find:
      not exists (
          select * from obs_flags f where
              obs_start.uni_id = f.uni_id and
              obs_start.no_of_babies = f.no_of_babies and
              obs_start.drunk = f.drunk and
              f.group_end = 1 and
              f.observation_date < obs_end.observation_date and
              f.observation_date >= obs_start.observation_date
      );

这是一个复杂的问题;我可能没有完全满足您的要求(或者那里可能有错字。我没有 Oracle 来测试它)。但是,它应该让您了解如何完成它。

基本上,您首先找到您感兴趣的时期的所有开始和结束记录。然后将每个开始记录加入同一分组中的下一个结束记录。

更新:我的原始代码没有检查结束是否在开始之后。我修好了。

Update2:正如 Ben 指出的,not exists这里的子句会很慢。过去帮助我加快速度的另一种方法是分两步完成:首先找到所有潜在的配对,然后分别从中选择正确的配对。

在这种情况下,在临时表或子查询中将每个连接obs_start到每个可能正确obs_end的 .

obs_end然后,从这些配对中,选择每个配对中最早的那个obs_start

于 2012-10-04T09:46:03.533 回答