【问题标题】:Oracle SQL analytical queryOracle SQL 分析查询
【发布时间】:2019-02-07 02:30:14
【问题描述】:
SQL> 从演示中选择 LAST_UPDATED_DATE, ODOMETER; LAST_UPDA 里程表 --------- ---------- 05-OCT-18 47174.77 08-OCT-18 18 年 12 月 12 日 50246.37 15-OCT-18 18 年 10 月 19 日 53743.11 18 年 10 月 21 日 18 年 10 月 22 日 18 年 10 月 25 日 58789.22 选择了 8 行。

我需要确定里程表值在哪里为空,这必须使用 SQL 来完成。我的想法是——

  1. 获取里程表的上一个和下一个非空值以及天数之间的差异,我可以使用它来计算每天平均行驶距离。

例如在这种情况下,(50246.37 - 47174.77) / (12-OCT-18 - 05-OCT-18) = ~ 439

  1. 现在有了每天的平均值,计算天数之间的差,然后乘以平均值。

例如,(08-OCT-18 - 05-OCT-18) = 3 天,3 天 439 * 3 = 1317。因此,08-Oct-18 的值可以是 47174.77+1317 = 48491.77

现在,我需要帮助来编写 SQL 代码。

任何帮助将不胜感激。

【问题讨论】:

  • 您应该能够将 LAG 或 LAST_VALUE 与 CASE WHEN 结合使用。
  • 如果第一个(或最后一个)日期没有里程表读数,你会怎么做?或者你能保证第一行和最后一行总是有读数吗?

标签: sql oracle analytics


【解决方案1】:

您可以使用累积的最大值和最小值获取上一行和下一行(这假设里程表仅在一个方向上运行)。其余的只是算术插值的算术:

select d.last_updated_date, d.odometer,
       (case when d.odometer is not null then d.odometer
             else prev_o + (next_o - prev_o) * (last_updated_date - prev_lud) / (next_lud - prev_lud)
        end)
from (select d.*,
             max(case when odometer is not null then last_updated_date end) over (order by last_updated_date) as prev_lud,
             max(odometer) over (order by last_updated_date) as prev_o,
             min(case when odometer is not null then last_updated_date end) over (order by last_updated_date desc) as next_lud,
             min(odometer) over (order by last_updated_date desc) as next_o
      from demo d
     ) d;

【讨论】:

  • +1 用于观察 MAX 和 MIN 就足够了。关于效率的注意事项:由于某种原因,一旦对行进行了 ASC 排序,对相同的行进行 DESC 排序对于 Oracle 来说并不是一件小事;相反,它需要很长时间。因此,最好在任何地方都使用 ASC,并使用加窗子句(当前行之间的行和无限制的后续行)来获取“未来”数字。
【解决方案2】:

我会这样做。它可能有助于解决其他类似问题(线性插值),其中不能假设“值”随时间增加。对于里程表,这个假设非常合理,而 Gordon Linoff 的解决方案更简单。我为其他“数量”可能随时间下降和上升的应用程序提供此解决方案。

with
  sample_data(last_updated_date, odometer) as (
    select to_date('05-OCT-18', 'dd-MON-rr'), 47174.77 from dual union all
    select to_date('08-OCT-18', 'dd-MON-rr'), null     from dual union all
    select to_date('12-OCT-18', 'dd-MON-rr'), 50246.37 from dual union all
    select to_date('15-OCT-18', 'dd-MON-rr'), null     from dual union all
    select to_date('19-OCT-18', 'dd-MON-rr'), 53743.11 from dual union all
    select to_date('21-OCT-18', 'dd-MON-rr'), null     from dual union all
    select to_date('22-OCT-18', 'dd-MON-rr'), null     from dual union all
    select to_date('25-OCT-18', 'dd-MON-rr'), 58789.22 from dual
  )
, prep(last_updated_date, odometer, prev_date, next_date, prev_odo, next_odo) as (
    select last_updated_date, odometer,
           case when odometer is null
                then max(nvl2(odometer, last_updated_date, null))
                     over (order by last_updated_date) end,
           case when odometer is null
                then min(nvl2(odometer, last_updated_date, null))
                     over (order by last_updated_date 
                     rows between 1 following and unbounded following) end,
           last_value(odometer ignore nulls) over (order by last_updated_date),
           first_value(odometer ignore nulls) over (order by last_updated_date 
                                rows between 1 following and unbounded following)
    from   sample_data
  )
select   last_updated_date,
         nvl( odometer,
              round(prev_odo + (next_odo - prev_odo) * 
                   (last_updated_date - prev_date) / (next_date - prev_date), 2)
            ) as odometer
from     prep
order by last_updated_date
;

输出

LAST_UPDATED_DATE   ODOMETER
----------------- ----------
05-OCT-18           47174.77
08-OCT-18           48491.17
12-OCT-18           50246.37
15-OCT-18           51744.97
19-OCT-18           53743.11
21-OCT-18           55425.15
22-OCT-18           56266.17
25-OCT-18           58789.22

【讨论】:

    【解决方案3】:

    这是一个查询,它将为您提供缺少的值。它使用两个常规连接来定位前一个和下一个里程表值可用的记录。

    SELECT
        d.last_update_date,
        d0.odometer 
            + (d1.odometer - d0.odometer) * ( d.last_update_date - d0.last_update_date ) 
            / ( d1.last_update_date - d0.last_update_date ) odometer
    FROM
        demo d
        INNER JOIN demo d0 ON d0.last_update_date = (
            SELECT MAX(last_update_date) 
            FROM demo 
            WHERE odometer IS NOT NULL AND last_update_date < d.last_update_date
        )
        INNER JOIN demo d1 ON d1.last_update_date = (
            SELECT MIN(last_update_date) 
            FROM demo 
            WHERE odometer IS NOT NULL AND last_update_date > d.last_update_date
        )
    WHERE d.odometer IS NULL;
    

    这个DB Fiddle demo返回:

     LAST_UPDATE_DATE | ODOMETER
     :--------------- | ----------:
     08-OCT-18        | 48491.17
     15-OCT-18        | 51744.97
     21-OCT-18        | 55425.15
     22-OCT-18        | 56266.17
    

    10 月 8 日的值似乎正是您所期望的。


    如果您希望实际更新表以添加缺失值,您可以使用 Oracle MERGE 语法,如图所示是 this db fiddle

    MERGE INTO demo target 
    USING (
        SELECT
            d.last_update_date,
            d0.odometer 
                + (d1.odometer - d0.odometer) * ( d.last_update_date - d0.last_update_date ) 
                / ( d1.last_update_date - d0.last_update_date ) odometer
        FROM
            demo d
            INNER JOIN demo d0 ON d0.last_update_date = (
                SELECT MAX(last_update_date) 
                FROM demo 
                WHERE odometer IS NOT NULL AND last_update_date < d.last_update_date
            )
            INNER JOIN demo d1 ON d1.last_update_date = (
                SELECT MIN(last_update_date) 
                FROM demo 
                WHERE odometer IS NOT NULL AND last_update_date > d.last_update_date
            )
        WHERE d.odometer IS NULL
    ) src ON (src.last_update_date = target.last_update_date)
    WHEN MATCHED THEN UPDATE SET target.odometer = src.odometer;
    

    【讨论】:

      【解决方案4】:

      您可以使用OUTER APPLY(从 Oracle 12c 开始)通过 ODOMETER 获取上一行和下一行。然后使用公式进行线性插值。

      select
        demo.last_updated_date,
        coalesce
        (
          demo.odometer,
          prev.odometer + ( (next.odometer - prev.odometer) *
                            (demo.last_updated_date - prev.last_updated_date) /
                            (next.last_updated_date - prev.last_updated_date) )
        ) as odometer
      from demo
      outer apply
      (
        select *
        from demo d
        where d.last_updated_date < demo.last_updated_date
        and d.odometer is not null
        order by d.last_updated_date desc
        fetch first row only
      ) prev
      outer apply
      (
        select *
        from demo d
        where d.last_updated_date > demo.last_updated_date
        and d.odometer is not null
        order by d.last_updated_date desc
        fetch first row only
      ) next
      order by demo.last_updated_date;
      

      结果(四舍五入):

      LAST_UPDATED_DATE |里程表 ------------------+--------- 18 年 5 月 5 日 | 47174.77 08-OCT-18 | 48916.94 18 年 12 月 12 日 | 50246.37 15-OCT-18 | 52217.80 18 年 10 月 19 日 | 53743.11 18 年 10 月 21 日 | 55425.15 18 年 10 月 22 日 | 56266.17 18 年 10 月 25 日 | 58789.22

      演示:https://dbfiddle.uk/?rdbms=oracle_18&fiddle=54cc9d4b7dd5793e1c0025627fd929de

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-01
        • 2012-06-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多