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 上查询特定 ITEMVALUE

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中减去一天。您应该注意DATEs 有一个时间组件。

如果您想要 ITEM per DAY 概览,您必须首先添加 缺少的早期历史记录VALUENULL

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:将两个表与一个公共列加上第二个表中的一个附加列(最新生效日期)连接以选择其他列

需要有关 SQL 中复杂 Join 语句的帮助

在 SQL 上创建具有特定日期的时间维度

如何将一个字段中的日期与另一字段中的时间结合起来 - MS SQL Server

sql 在满足特定条件时将一个表与另一个表合并。

Oracle中几个关于日期方面的SQL实例