有许多研究人员在unicorns 1 上观察世界上最后剩下的Easter Island。每天研究人员都会记录他们看到的独角兽,目击日期,每只独角兽的婴儿数量,以及在发生瞄准时他们是否喝醉了。它们被单独上传到一个中心位置,然后每天向我发出一个平面文件,显示所有新观察结果。
我有一个包含这些信息的表:
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 [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 | +--------+--------+-----------+-----------+-----------+---------+-------+
我想对这些观察结果进行部分非规范化处理,以便在收到的新记录中使用相同的observer_id
,unicorn_id
,no_of_babies
和drunk
(有效负载)但使用较新的 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') )
);
并且表中存储的数据如下所示;如果观察仅被“看到”一次,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
不起作用,因为它忽略了2012年1月1日 st 对3只独角兽婴儿的单独观察;这反过来意味着11月10日 th 创建的记录的lastseen
不正确。
+--------+--------+-----------+-----------+-----------+---------+-------+-------------+ | 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 st 上找到婴儿的数量,我会得到两条记录,这是一个矛盾。
+--------+--------+-----------+-----------+-----------+---------+-------+-------------+ | 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 | +--------+--------+-----------+-----------+-----------+---------+-------+-------------+
<子> 1。在moai周围放牧
答案 0 :(得分: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;
答案 1 :(得分:1)
基于我认为您正在尝试做的事情,主要是关于与unicorn 6的具体问题的更新,我认为这会得到您想要的结果。它不需要递归lead
和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的三个记录,但第三个的lastseen
和observation_date
与你的样本相反,所以我不确定我是否仍然不理解那。我假设您希望在每个分组中保留最早的observation_date
和最新lastseen
,理由是它在添加新记录时会发生什么,但我不确定。 ..
因此,最里面的查询从表中获取原始数据,并为lead
和 lag
获取observation_date
和no_of_babies
和drunk
列使用略有不同的分区。 order by
是rownum
以后可以使用的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
,在下一步中获得并用于之后的序列中。仅为了简洁的独角兽6:
lead
如果lag
或observation_date
值发生了变化,则下一级会消除num_of_babies
的{{1}}和drunk
值 - 您只是特别提及分裂婴儿数,但我认为你也想分开清醒。在此之后,null
或first_obs_date
last_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
都为非空的行来消除这些中间值。在该过滤集合中,有第二层lead
和lag
来获取每个日期的第一个或最后一个值 - 这就是我不确定是否正确,因为它与你的样品。
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
最后,过滤掉没有last_obs_dt
的剩余行。
现在我等着看我误解了哪个位...... * 8-)
在对lead
和lag
排序进行更正后,针对独角兽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
在原始数据是否按此顺序输入时会发生什么声音,或者在添加新记录的情况下您会做什么在将来。
答案 2 :(得分: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来测试它)。但是,它应该让你知道如何做到这一点。
基本上,您首先会找到您感兴趣的期间的所有开始和结束记录。然后您将每个开始记录加入到同一分组中的下一个结束记录中。
更新:我的原始代码没有检查开始后是否结束了。我解决了这个问题。
Ben指出, Update2:,not exists
子句在这里会很慢。另一个帮助我加快速度的方法是分两步完成:先找到所有可能的配对,然后分别选择正确的配对。
在这种情况下,在临时表或子查询中,将每个obs_start
加入到每个可能正确的obs_end
。
然后,在这些配对中,为每个obs_end
选择最早obs_start
的配对。