2

我正在尝试找出 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(previous row),但我不知道如何将其放入 SQL 中。

4

3 回答 3

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;

可能包含错误,尚未测试。

于 2013-09-19T15:21:48.293 回答
1

他们不能有负余额

那触发了我的记忆:-)

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

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

添加 2 个“新”并减去 x“已使用”配额,如果它小于零,则使用零代替。

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

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                           

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

于 2013-09-19T16:29:12.137 回答
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

于 2013-09-19T17:52:17.887 回答