有许多研究人员在复活节岛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_id
、unicorn_id
和observation_date
或上分别是唯一的lastseen
。
有时,管理数据输出的Cobold [原文如此] 会稍微出错,并重新输出相同的数据两次。在这种情况下,我更新lastseen
而不是创建新记录。我只在每列都相同的情况下这样做
不幸的是,研究人员并不完全了解第三范式。每个月他们都会上传前几个月对几只独角兽的观察,即使没有进行新的观察。他们使用new observation_date
执行此操作,这意味着将新记录插入到表中。
我有一个单独的created
和lastseen
完整的可追溯性,因为研究人员有时会延迟提交一些观察结果。这些是由数据库创建的,不是提交信息的一部分。
这是一些示例数据(部分更改了列名,以便在没有滚动条的情况下使其适合)。
+--------+--------+-----------+------------+------- ----+---------+--------+ | 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_babies
drunk
observation_date
last_observation_date
lastseen
我需要这样做,因为我有许多与此表相关的复杂的独角兽相关查询;研究人员每月上传大约 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 月 10日lastseen
创建的记录不正确。
+--------+--------+-----------+------------+------- ----+---------+--------+--------------+ | 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. 在摩艾周围放牧