Oracle SQL 将日期维度表与另一个关于日期值的表连接起来
Posted
技术标签:
【中文标题】Oracle SQL 将日期维度表与另一个关于日期值的表连接起来【英文标题】:Oracle SQL join date dimension table with another table on date value 【发布时间】:2021-03-04 08:03:21 【问题描述】:我有一个包含所有日期的日期维度表和另一个包含特定日期的项目值的表。 例如 (a) Date_Dim 表
|Full_Date |
|-----------|
| .... |
|1-jan-2021 |
|2-Jan-2021 |
|3-jan-2021 |
| ... |
(b) Item_value 表
|P_Date | ITEM | Value |
|-----------:|:------|-------:|
|20-Dec-2020 |AA1 |9 |
|1-jan-2021 |AA1 |10 |
|1-jan-2021 |AA2 |100 |
| ... | ... | ... |
我正在尝试为 date_dim 表中的每个日期构建一个包含 item_value 表中每个项目的最新值的事实表。即每天物品的价值。 例如
|Full_date | ITEM | Value |
|-----------:|-------:|------:|
|31-Dec-2020 |AA1 | 9 |
|31-Dec-2020 |AA2 | null |
|1-Jan-2021 |AA1 | 10 |
|1-Jan-2021 |AA2 | 100 |
|2-Jan-2021 |AA1 | 10 |
|2-Jan-2021 |AA2 | 100 |
|3-Jan-2021 |AA1 | 10 |
|3-Jan-2021 |AA2 | 100 |
|4-Jan-2021 |AA1 | 10 |
|4-Jan-2021 |AA2 | 100 |
请问如何构建这个查询? 我尝试了以下但不工作
选择 full_date,p_date,item,value 从昏暗日期 左外连接 item_value on full_date=p_date;
不确定 max(p_date) over (partition by ...) 是否有效。
谢谢
【问题讨论】:
【参考方案1】:您可以使用分区外连接然后聚合:
WITH date_dim ( full_date ) AS (
SELECT DATE '2020-12-31' + LEVEL - 1 AS full_Date
FROM DUAL
CONNECT BY DATE '2020-12-31' + LEVEL - 1 <= DATE '2021-01-04'
)
SELECT item,
full_date,
MAX( value ) KEEP ( DENSE_RANK LAST ORDER BY p_date ) AS value
FROM date_dim d
LEFT OUTER JOIN item_value i
PARTITION BY ( i.item )
ON ( d.full_date >= i.p_date )
GROUP BY item, full_date
其中,对于样本数据:
CREATE TABLE item_value ( P_Date, ITEM, Value ) AS
SELECT DATE '2020-12-20', 'AA1', 9 FROM DUAL UNION ALL
SELECT DATE '2021-01-01', 'AA1', 10 FROM DUAL UNION ALL
SELECT DATE '2021-01-01', 'AA2', 100 FROM DUAL;
输出:
项目 |完整日期 |价值 :--- | :-------- | ----: AA1 | 20 年 12 月 31 日 | 9 AA1 | 21 年 1 月 1 日 | 10 AA1 | 21 年 1 月 2 日 | 10 AA1 | 21 年 1 月 3 日 | 10 AA1 | 21 年 1 月 4 日 | 10 AA2 | 20 年 12 月 31 日 | 空 AA2 | 21 年 1 月 1 日 | 100 AA2 | 21 年 1 月 2 日 | 100 AA2 | 21 年 1 月 3 日 | 100 AA2 | 21 年 1 月 4 日 | 100
注意:您不需要存储date_dim
维度表;它可以即时生成,并减少执行(昂贵的)IO 操作从硬盘读取表的需要。
db小提琴here
【讨论】:
【参考方案2】:您可以使用分析函数LEAD
为您的ITEM
表简单地添加一个有效区间
select
P_DATE,
lead(P_DATE-1,1,(select max(full_date) from date_dim)) over (partition by ITEM order by P_DATE) P_DATE_TO,
ITEM, VALUE
from item_value
;
P_DATE P_DATE_TO ITE VALUE
------------------- ------------------- --- ----------
20.12.2020 00:00:00 31.12.2020 00:00:00 AA1 9
01.01.2021 00:00:00 04.01.2021 00:00:00 AA1 10
01.01.2021 00:00:00 04.01.2021 00:00:00 AA2 100
在某些情况下,这对于您的用例来说已经足够了,因为您可以在给定的 date
上查询特定 ITEM
的 VALUE
select VALUE from item_value_hist h where ITEM = 'AA2'
and <query_date> BETWEEN h.P_DATE and h.P_DATE_TO
注意,有效期是包含的,因为我们为P_DATE_TO
从相邻的P_DATE
中减去一天。您应该注意DATE
s 有一个时间组件。
如果您想要 ITEM per DAY 概览,您必须首先添加 缺少的早期历史记录和 VALUE
的 NULL
select
(select min(full_date) from date_dim) P_DATE, min(P_DATE)-1 P_DATE_TO, ITEM, null VALUE
from item_value
group by ITEM
having min(P_DATE) > (select min(full_date) from date_dim)
P_DATE P_DATE_TO ITE VALUE
------------------- ------------------- --- -----
31.12.2020 00:00:00 31.12.2020 00:00:00 AA2
比简单的外部联接到你的维度表匹配从你的有效期间隔
with item as (
select
P_DATE,
lead(P_DATE-1,1,(select max(full_date) from date_dim)) over (partition by ITEM order by P_DATE) P_DATE_TO,
ITEM, VALUE
from item_value
union all
select
/* add the missing early history without a VALUE */
(select min(full_date) from date_dim) P_DATE, min(P_DATE)-1 P_DATE_TO, ITEM, null VALUE
from item_value
group by ITEM
having min(P_DATE) > (select min(full_date) from date_dim)
)
select dt.full_date, item.ITEM, item.VALUE from item
join date_dim dt
on dt.full_date between item.P_DATE and item.P_DATE_TO
order by item.ITEM, dt.full_date
FULL_DATE ITE VALUE
------------------- --- ----------
31.12.2020 00:00:00 AA1 9
01.01.2021 00:00:00 AA1 10
02.01.2021 00:00:00 AA1 10
03.01.2021 00:00:00 AA1 10
04.01.2021 00:00:00 AA1 10
31.12.2020 00:00:00 AA2
01.01.2021 00:00:00 AA2 100
02.01.2021 00:00:00 AA2 100
03.01.2021 00:00:00 AA2 100
04.01.2021 00:00:00 AA2 100
【讨论】:
【参考方案3】:两步:
-
交叉连接日期和项目。如果您没有 item 表(您应该),请连接 item_value 表中的不同项目。
使用
OUTER APPLY
获取FROM
子句中的值或使用FETCH FIRST ROW ONLY
使用子查询的SELECT
子句中的值。
查询:
select
d.full_date,
i.item,
(
select iv.value
from Item_value iv
where iv.item = i.item
and iv.p_date <= d.full_date
order by iv.p_date desc
fetch first row only
) as value
from dim_date d
cross join (select distinct item from item_value) i
order by d.full_date, i.item;
【讨论】:
【参考方案4】:您可以使用cross join
后跟left join
以引入现有值来生成日期和项目的完整列表。然后可以使用last_value()
或lag()
填写值:
select d.p_date, i.item,
coalesce(v.value,
lag(v.value ignore nulls) over (partition by i.item order by d.p_date)
) as value
from date_dim d cross join
(select distinct iv.item from item_value iv) i left join
item_value iv
on iv.p_date = d.p_date and iv.item = i.item;
您也可以使用join
在值表中添加“结束”日期来执行此操作:
select d.p_date, i.item,
coalesce(v.value,
lag(v.value ignore nulls) over (partition by i.item order by d.p_date)
) as value
from date_dim d cross join
(select distinct iv.item from item_value iv) i left join
(select iv.*,
lead(p_date) over (partition by item order by p_date) as next_p_date
from item_value iv
) iv
on i.item = iv.item and
d.p_date >= iv.p_date and
(iv.next_p_date is null or d.p_date < iv.next_p_date);
【讨论】:
以上是关于Oracle SQL 将日期维度表与另一个关于日期值的表连接起来的主要内容,如果未能解决你的问题,请参考以下文章
Oracle:将两个表与一个公共列加上第二个表中的一个附加列(最新生效日期)连接以选择其他列