2

毎日の割り当てシステムの現在の合計を計算するための SQL を理解しようとしています。システムは次のように機能します...

ユーザーは毎日 2 つの「消耗品」のクォータを取得します。彼らがそれらをすべて使い果たした場合、翌日には別の2が得られます。何らかの理由でそれらを使いすぎた場合(2つ以上使用した場合)、翌日も2になります(マイナスの残高を持つことはできません)。それらがすべて使用されない場合、残りは翌日に持ち越されます(次の日に持ち越される可能性があります...)。

検証として使用するデータのグラフを次に示します。1 日のクォータ、その日に使用された量、1 日の終わりに残った量としてレイアウトされます。

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 に入れる方法がわかりません。

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

残高がマイナスになることはありません

それが私の記憶を引き起こしました:-)

Teradata システムで 10 年以上前に同様の問題がありました。

ロジックは、各行に対して再帰を使用して簡単に実装できます。

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 に答える