运行总...扭曲

时间:2013-09-19 14:44:38

标签: sql postgresql

我试图找出SQL为每日配额系统执行运行总计。系统就像这样......

每天用户获得2个“消费品”的配额。如果他们全部使用它们,第二天他们会得到另一个2.如果他们以某种方式过度使用它们(使用超过2个),第二天他们仍然得到2(他们不能有负余额)。如果他们不全部使用它们,剩余部分将持续到第二天(可以进入下一天等等)。

以下是用作验证的数据图表。它被列为当天的配额,当天使用的金额,当天结束时留下的金额:

2 - 2 - 0
2 - 0 - 2
4 - 3 - 1
3 - 0 - 3
5 - 7 - 0
2 - 1 - 1
3 - 0 - 3
5 - 2 - 3
5 - 1 - 4
6 - 9 - 0

开始使用的SQL将是:

WITH t(x, y) AS (
  VALUES (2, '2013-09-16'),
              (0, '2013-09-17'),
              (3, '2013-09-18'),
              (0, '2013-09-19'),
              (7, '2013-09-20'),
              (1, '2013-09-21'),
              (0, '2013-09-22'),
              (2, '2013-09-23'),
              (1, '2013-09-24'),
              (9, '2013-09-25')
)

对于我的生活,尝试递归语句和窗口聚合,我无法弄清楚如何使它工作(但我当然可以看到模式)。

它应该类似于2 - x + SUM(上一行),但我不知道如何将其放入SQL中。

3 个答案:

答案 0 :(得分:2)

尝试创建自定义聚合函数,如:

CREATE FUNCTION quota_calc_func(numeric, numeric, numeric) -- carry over, daily usage and daily quota
RETURNS numeric AS 
$$
  SELECT GREATEST(0, $1 + $3 - $2);
$$
LANGUAGE SQL STRICT IMMUTABLE;

CREATE AGGREGATE quota_calc( numeric, numeric ) -- daily usage and daily quota
(
    SFUNC = quota_calc_func,
    STYPE = numeric,
    INITCOND = '0'
);

WITH t(x, y) AS (
  VALUES (2, '2013-09-16'),
              (0, '2013-09-17'),
              (3, '2013-09-18'),
              (0, '2013-09-19'),
              (7, '2013-09-20'),
              (1, '2013-09-21'),
              (0, '2013-09-22'),
              (2, '2013-09-23'),
              (1, '2013-09-24'),
              (9, '2013-09-25')
)
SELECT x, y, quota_calc(x, 2) over (order by y)
FROM t;

可能包含错误,尚未对其进行测试。

答案 1 :(得分:1)

  

他们不能有负余额

这引发了我的记忆: - )

10年前我在Teradata系统上遇到了类似的问题。

可以使用递归轻松实现逻辑,对于每一行都执行:

  

如果小于零,则添加2“new”和减去x“used”配额   改为使用零。

我不记得我是如何找到这个解决方案的,但我终于使用简单的累积总和实现了它:

SELECT
  dt.*, 
  CASE -- used in following calculation, this is just for illustration
     WHEN MIN(quota_raw) OVER (ORDER BY datecol ROWS UNBOUNDED PRECEDING) >= 0 THEN 0 
     ELSE MIN(quota_raw) OVER (ORDER BY datecol ROWS UNBOUNDED PRECEDING)
  END AS correction,
  quota_raw
  - CASE
       WHEN MIN(quota_raw) OVER (ORDER BY datecol ROWS UNBOUNDED PRECEDING) >= 0 THEN 0 
       ELSE MIN(quota_raw) OVER (ORDER BY datecol ROWS UNBOUNDED PRECEDING)
    END AS quote_left
FROM
 (
   SELECT quota, datecol, 
      SUM(quota) OVER (ORDER BY datecol ROWS UNBOUNDED PRECEDING) AS quota_used,
      2*COUNT(*) OVER (ORDER BY datecol ROWS UNBOUNDED PRECEDING) AS quota_available,
      quota_available - quota_used AS quota_raw 
   FROM t
 ) AS dt
ORDER BY datecol                           

秘诀就是移动的最小“校正”,它将负面结果调整为零。

答案 2 :(得分:0)

简单的递归cte解决方案,假设你在日期中没有间隙:

with recursive cte as (
    select
        t.dt,
        2 as quote_day,
        t.quote_used,
        greatest(2 - t.quote_used, 0) as quote_left
    from t
    where t.dt = '2013-09-16'
    union all
    select
        t.dt,
        2 + c.quote_left as quote_day,
        t.quote_used,
        greatest(2 + c.quote_left - t.quote_used, 0) as quote_left
    from cte as c
        inner join t on t.dt = c.dt + 1  
)
select *
from cte

<强> sql fiddle demo

另一种解决方案 - 累积聚合:

with cte1 as (
    select
        dt, quote_used,
        sum(2 - quote_used) over(order by dt asc) as quote_raw
    from t
), cte2 as (
    select
        dt, quote_used, quote_raw,
        least(min(quote_raw) over(order by dt asc), 0) as quote_corr
    from cte1
)
select
    dt,
    quote_raw - quote_corr + quote_used as quote_day,
    quote_used,
    quote_raw - quote_corr as quote_left
from cte2

<强> sql fiddle demo