【问题标题】:Oracle SQL: Detecting breaks in continual spansOracle SQL:检测连续跨度中的中断
【发布时间】:2011-01-26 03:47:27
【问题描述】:

我有下表,我正在尝试检测其跨度有中断的产品。

Product     | unit_Cost | price start date |    price end date
--------------------------------------------------------------------------
product 1     15.00         01/01/2011      03/31/2011
product 1     15.00         04/01/2011      06/31/2011
product 1     15.00         07/01/2011      09/31/2011
product 1     15.00         10/01/2011      12/31/2011

product 2     10.00         01/01/2011      12/31/2011

product 3     25.00         01/01/2011      06/31/2011
product 3     25.00         10/01/2011      12/31/2011

所以在这里我希望它报告 product3,因为我们缺少跨度

07/01/2011 - 09/31/2011

关于如何做到这一点的任何想法?

编辑: 甲骨文版:10g

Create Table Statement

CREATE TABLE Sandbox.TBL_PRODUCT
(
  PRODUCT_ID        VARCHAR2(13 BYTE),   
  PRODUCT           VARCHAR2(64 BYTE),
  UNIT_COST         NUMBER,
  PRICE_START_DATE  DATE,
  PRICE_END_DATE    DATE
)

编辑 2 开始日期和结束日期不能重叠

编辑 3 跨度可以是任意两个日期,只要 price_end_date >= price_start_date。包括 Equal,因为产品可以销售一天。

【问题讨论】:

  • 我们需要您的查询,以及可能涉及的表的 CREATE TABLE 语句,以便能够为您提供帮助。还有 Oracle 的版本……为什么人们会支持一个不完整的问题?!​​
  • 开始日期和结束日期可以重叠吗?
  • 你总是关心一年吗?假设我有一年的前 6 个月,但到年底什么都没有——这可能吗?例如,假设产品 3 只有第一条记录。应该举报吗?

标签: sql oracle


【解决方案1】:

试试这个(使用 LEAD 解析函数):

SELECT *
  FROM (
                SELECT a.*, LEAD(price_start_date,1,NULL) OVER(PARTITION BY product ORDER BY price_end_date) next_start_date 
         FROM Product a
       )
WHERE (price_end_date + 1)<> next_start_date

设置示例

        CREATE TABLE PRODUCT
          (
            PRODUCT   VARCHAR2(100 BYTE),
            UNIT_COST NUMBER,
            START_DATE DATE,
            END_DATE DATE
          );

        INSERT INTO Product VALUES('product 1','15.00',TO_DATE('01/01/2011','MM/DD/RRRR'),TO_DATE('03/31/2011','MM/DD/RRRR'));
        INSERT INTO Product VALUES('product 1','15.00',TO_DATE('04/01/2011','MM/DD/RRRR'),TO_DATE('06/30/2011','MM/DD/RRRR'));
        INSERT INTO Product VALUES('product 1','15.00',TO_DATE('07/01/2011','MM/DD/RRRR'),TO_DATE('09/30/2011','MM/DD/RRRR'));
        INSERT INTO Product VALUES('product 1','15.00',TO_DATE('10/01/2011','MM/DD/RRRR'),TO_DATE('12/31/2011','MM/DD/RRRR'));
        INSERT INTO Product VALUES('product 2','10.00',TO_DATE('01/01/2011','MM/DD/RRRR'),TO_DATE('12/31/2011','MM/DD/RRRR'));
        INSERT INTO Product VALUES('product 3','25.00',TO_DATE('01/01/2011','MM/DD/RRRR'),TO_DATE('06/30/2011','MM/DD/RRRR'));
        INSERT INTO Product VALUES('product 3','25.00',TO_DATE('10/01/2011','MM/DD/RRRR'),TO_DATE('12/31/2011','MM/DD/RRRR'));

SELECT *
  FROM (
                SELECT a.*, LEAD(start_date,1,NULL) OVER(PARTITION BY product ORDER BY start_date) next_start_date 
                 FROM Product a
              )
WHERE (end_date + 1)<> next_start_date

编辑:更新了查询以考虑下一个开始日期和当前结束日期,以避免数据分布出现问题。

【讨论】:

  • 为什么要在开始日期上增加 3 个月?您是否假设数据是按季度组织的?我不确定这个假设是否合理。假设产品 3 对缺失的差距还有一条记录?您的查询仍将其报告为丢失。我认为你的方法很好,只是需要调整。我认为您应该在每个 end_date 中添加一天,并根据您的 next_start_date 进行检查。
  • @DCookie:是的,我假设数据是按季度组织的。由于 OP 正在谈论跨度,因此应该使用一个预定义的时间段。如果 OP 可以提供有关跨度的更多信息,则可以轻松调整查询以适应跨度。
  • 除非你知道数据不是这样组织的,因为产品 2 有一个全年的记录,而产品 3 有一个半年的记录。
  • 插入产品值('产品 4','35.00',TO_DATE('01/01/2011','MM/DD/RRRR'),TO_DATE('06/30/2011' ,'MM/DD/RRRR'));插入产品值('product 4','35.00',TO_DATE('7/01/2011','MM/DD/RRRR'),TO_DATE('12/31/2011','MM/DD/RRRR' ));被检测为休息,即使不是我会更新问题
  • 也许最好写下下一个开始日期必须是结束日期的第二天?
【解决方案2】:

您也可以使用这种技术。它使用内部查询 (chronological_record) 为 TBL_PRODUCT 表中的每条记录分配一个排名(排名在每个 product 内按 start_date 排序)。

WITH
  chronological_record AS
  (
    SELECT
      product,
      unit_cost,
      start_date,
      end_date,
      (DENSE_RANK() OVER (PARTITION BY product ORDER BY start_date))
          AS chronological_order
    FROM
      TBL_PRODUCT
  )

SELECT
  earlier.product,
  (earlier.end_date + 1) AS missing_period_start_date,
  (later.start_date - 1) as missing_period_end_date
FROM
  CHRONOLOGICAL_RECORD earlier
  INNER JOIN
  CHRONOLOGICAL_RECORD later
    ON
        earlier.product = later.product
      AND
        (earlier.chronological_order + 1) = later.chronological_order
WHERE
  (earlier.end_date + 1) <> later.start_date

在您的示例中,子查询 (chronological_record) 将产生如下内容:

产品 |单位_成本 |开始日期 |结束日期 |按时间顺序 -------------------------------------------------- ---------------------- 产品 1 15.00 01/01/2011 03/31/2011 1 产品 1 15.00 04/01/2011 06/31/2011 2 产品 1 15.00 07/01/2011 09/31/2011 3 产品 1 15.00 10/01/2011 12/31/2011 4 产品 2 10.00 01/01/2011 12/31/2011 1 产品 3 25.00 01/01/2011 06/31/2011 1 产品 3 25.00 10/01/2011 12/31/2011 2

主查询的INNER JOIN 有效地将较早的记录与其下一个(按时间顺序)记录匹配。

【讨论】:

  • 当然可以,虽然与使用 lead(...)lag(...) 相比似乎有点漫长?
  • @araqnid:说实话,在今天之前我并不知道LEADLAG。感谢您的提示!
【解决方案3】:

假设您的表名为products,则您的开始日期列名为s,结束日期列名为e

create view max_interval as 
select product, 
max(e) - min(s) as max_interval 
from products group by product;


create view total_days as 
select product, 
sum( e - s ) + count(product) - 1 as total_days 
from products group by product  ;

那么这个查询会为您提供所有具有“缺失”跨度的产品:

select a.*, b.*
from max_interval a 
left outer join total_days b 
on (a.product = b.product)
where a.max_interval <> b.total_days;

由于两个视图中的 group by 是相同的,这当然可以组合成一个查询,尽管使解决方案不太清楚:

select product, 
max(e) - min(s) as max_interval, 
sum( e - s ) + count(product) - 1 as total_days 
from products group by product  
having max(e) - min(s) <> sum( e - s ) + count(product) - 1;

但正如 Stephanie Page 指出的那样,这是一种过早的优化;您不太可能经常扫描连续跨度中的中断。

【讨论】:

  • 有趣的方法,虽然它确实意味着扫描产品表两次。但是,它们不能组合成一个查询吗?
  • 您见过很多包含数十亿行的 Product 表吗?无论如何,对可能完全缓存的表进行 TWO 扫描有什么大不了的。 “差距”分析多久运行一次?您多久更新一次“产品”表?
【解决方案4】:

假设您修复了样本集中的错误日期,您可以对范围进行一些数学比较:

SELECT PRODUCT
FROM Sandbox.TBL_PRODUCT
HAVING SUM(PRICE_END_DATE - PRICE_START_DATE + 1) < MAX(PRICE_END_DATE) - MIN(PRICE_START_DATE) + 1
GROUP BY PRODUCT

哪个会返回:

PRODUCT                                                                         
-----------------
product 3                                                                       
1 row selected

【讨论】:

    【解决方案5】:

    您可以使用exists 子句过滤存在较早行的行,并使用not exist 子句查找前一行未在当前行加上一天结束的行。例如:

    select  *
    from    TBL_PRODUCT t1
    where   exists
            (
            select  *
            from    TBL_PRODUCT t2
            where   t2.PRODUCT = t1.PRODUCT
                    and t2.PRICE_END_DATE < t1.PRICE_START_DATE
            )
            and not exists
            (
            select  *
            from    TBL_PRODUCT t3
            where   t3.PRODUCT = t1.PRODUCT
                    and t3.PRICE_END_DATE + 1 = t1.PRICE_START_DATE
            );
    

    打印出来:

    PRODUCT          UNIT_COST PRICE_STA PRICE_END
    ----------------------- ---------- --------- ---------
    product 3           25 01-OCT-11 31-DEC-11
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-04-25
      • 1970-01-01
      • 1970-01-01
      • 2013-04-17
      • 2010-11-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多