8

我需要做一些非常奇怪的事情,那就是创建虚假记录以填补产品价格发布日期之间的空白。

实际上,我的情况比这要复杂一些,但我已经简化为产品/日期/价格。

假设我们有这张表:

create table PRICES_TEST
(
   PRICE_DATE    date          not null,
   PRODUCT       varchar2(13) not null,
   PRICE         number
);

alter table PRICES_TEST 
  add constraint PRICES_TEST_PK
    primary key (PRICE_DATE, PRODUCT);

有了这些记录:

insert into PRICES_TEST values (date'2012-04-15', 'Screw Driver', 13);
insert into PRICES_TEST values (date'2012-04-18', 'Screw Driver', 15);

insert into PRICES_TEST values (date'2012-04-13', 'Hammer', 10);
insert into PRICES_TEST values (date'2012-04-16', 'Hammer', 15);
insert into PRICES_TEST values (date'2012-04-19', 'Hammer', 17);

选择记录将返回给我:

PRICE_DATE                PRODUCT       PRICE                  
------------------------- ------------- ---------------------- 
13-Apr-2012 00:00:00      Hammer        10                     
16-Apr-2012 00:00:00      Hammer        15                     
19-Apr-2012 00:00:00      Hammer        17                     
15-Apr-2012 00:00:00      Screw Driver  13                     
18-Apr-2012 00:00:00      Screw Driver  15                     

假设今天是 2012 年 4 月 21 日,我需要一个每天重复每个价格的视图,直到发布新价格。像这样:

PRICE_DATE                PRODUCT       PRICE                  
------------------------- ------------- ---------------------- 
13-Apr-2012 00:00:00      Hammer        10                     
14-Apr-2012 00:00:00      Hammer        10                     
15-Apr-2012 00:00:00      Hammer        10                     
16-Apr-2012 00:00:00      Hammer        15                     
17-Apr-2012 00:00:00      Hammer        15                     
18-Apr-2012 00:00:00      Hammer        15                     
19-Apr-2012 00:00:00      Hammer        17                     
20-Apr-2012 00:00:00      Hammer        17                     
21-Apr-2012 00:00:00      Hammer        17                     
15-Apr-2012 00:00:00      Screw Driver  13                     
16-Apr-2012 00:00:00      Screw Driver  13                     
17-Apr-2012 00:00:00      Screw Driver  13                     
18-Apr-2012 00:00:00      Screw Driver  15                     
19-Apr-2012 00:00:00      Screw Driver  15                     
20-Apr-2012 00:00:00      Screw Driver  15                     
21-Apr-2012 00:00:00      Screw Driver  15                     

任何想法如何做到这一点?我不能真正使用其他辅助表、触发器或 PL/SQL 编程,我真的需要使用视图来做到这一点。

我认为这可以使用 oracle 分析来完成,但我对此并不熟悉。我试图阅读这个http://www.club-oracle.com/articles/analytic-functions-i-introduction-164/但我根本没有明白。

4

4 回答 4

6

您可以使用语法创建行生成器语句CONNECT BY LEVEL,与表中的不同产品交叉连接,然后将其外部连接到您的价格表。最后一步是使用该LAST_VALUE函数并IGNORE NULLS重复价格,直到遇到新值,并且由于您想要一个视图,因此使用以下CREATE VIEW语句:

create view dense_prices_test as
select
    dp.price_date
  , dp.product
  , last_value(pt.price ignore nulls) over (order by dp.product, dp.price_date) price
from (
      -- Cross join with the distinct product set in prices_test
      select d.price_date, p.product
      from (
            -- Row generator to list all dates from first date in prices_test to today
            with dates as (select min(price_date) beg_date, sysdate end_date from prices_test)
            select dates.beg_date + level - 1 price_date 
            from dual
            cross join dates
            connect by level <= dates.end_date - dates.beg_date + 1
            ) d
      cross join (select distinct product from prices_test) p
     ) dp
left outer join prices_test pt on pt.price_date = dp.price_date and pt.product = dp.product;
于 2012-04-21T00:23:46.647 回答
5

我想我有一个解决方案,使用增量方法来获得 CTE 的最终结果:

with mindate as
(
  select min(price_date) as mindate from PRICES_TEST
)
,dates as
(
  select mindate.mindate + row_number() over (order by 1) - 1 as thedate from mindate,
    dual d connect by level <= floor(SYSDATE - mindate.mindate) + 1
)
,productdates as
(
  select p.product, d.thedate
  from (select distinct product from PRICES_TEST) p, dates d
)
,ranges as
(
  select
    pd.product,
    pd.thedate,
    (select max(PRICE_DATE) from PRICES_TEST p2
     where p2.product = pd.product and p2.PRICE_DATE <= pd.thedate) as mindate
    from productdates pd
)
select 
    r.thedate,
    r.product,
    p.price
from ranges r
inner join PRICES_TEST p on r.mindate = p.price_date and r.product = p.product
order by r.product, r.thedate
  • mindate检索数据集中最早的可能日期
  • dates生成从最早可能日期到今天的日期日历。
  • productdates交叉连接所有可能日期的所有可能产品
  • ranges确定在每个日期应用的价格日期
  • inner join最终查询链接哪个价格日期适用于实际价格,并通过条件过滤掉没有相关价格日期的日期

演示:http ://www.sqlfiddle.com/#!4/e528f/126

于 2012-04-21T00:23:35.133 回答
4

我对 Wolf 的出色回答做了一些更改。

我将子查询因式分解 ( WITH) 替换为connect by. 这使代码更简单一些。(尽管这种类型的代码一开始看起来很奇怪,所以这里可能不会有很大的收获。)

最重要的是,我使用了分区外连接,而不是交叉连接和外连接。分区外连接也有点奇怪,但它们正是针对这种情况的。这使代码更简单,并且应该提高性能。

select
    price_dates.price_date
    ,product
    ,last_value(price ignore nulls) over (order by product, price_dates.price_date) price
from
(
    select trunc(sysdate) - level + 1 price_date
    from dual
    connect by level <= trunc(sysdate) -
        (select min(trunc(price_date)) from prices_test) + 1
) price_dates
left outer join prices_test
    partition by (prices_test.product)
    on price_dates.price_date = prices_test.price_date;
于 2012-04-21T04:30:34.167 回答
1

我刚刚意识到@Wolf 和@jonearles 的改进不会返回我需要的确切结果,因为列出所有日期的行生成器不会按产品生成范围。如果产品 A 的第一个价格晚于产品 B 的任何价格,则产品 A 的第一个上市日期仍然必须相同。但他们确实帮助我进一步工作并获得了预期的结果:

我从更改 @wolf 的日期范围选择器开始:

select min(price_date) beg_date, sysdate end_date from prices_test

对此:

select min(PRICE_DATE) START_DATE, sysdate as END_DATE, PRODUCT 
from PRICES_TEST group by sysdate, PRODUCT

但是,不知何故,每个产品的行数在每个级别都呈指数级重复增长。我只是在外部查询中添加了一个 distinct。最后的选择是这样的:

select
  DP.PRICE_DATE,
  DP.PRODUCT,
  LAST_VALUE(PT.PRICE ignore nulls) over (order by DP.PRODUCT, DP.PRICE_DATE) PRICE
from (
  select distinct START_DATE + DAYS as PRICE_DATE, PRODUCT 
  from 
  (
    -- Row generator to list all dates from first date of each product to today
    with DATES as (select min(PRICE_DATE) START_DATE, sysdate as END_DATE, PRODUCT from PRICES_TEST group by sysdate, PRODUCT)
    select START_DATE, level - 1 as DAYS, PRODUCT
    from DATES
    connect by level < END_DATE - START_DATE + 1
    order by 3, 2
  ) d order by 2, 1
) DP
left outer join prices_test pt on pt.price_date = dp.price_date and pt.product = dp.product;

@Mellamokb 解决方案实际上是我真正需要的,而且肯定比我的 noobie 解决方案更好。

感谢大家不仅在这方面帮助我,而且还向我展示了“with”和“connect by”等功能。

于 2012-04-23T17:59:26.110 回答