Условная сумма в Oracle

Я пытаюсь сделать запрос с условной суммой. СУММА должна быть больше 15, после чего сбросьте ее. Так:

A | 3 | 3 
B | 7 | 10 
C | 6 | 16  -- ====
D | 5 | 5 
E | 9 | 14
F | 3 | 17  -- ====
G | 8 | 8

Как я могу это сделать?


person Fábio Almeida    schedule 22.10.2018    source источник
comment
Какую версию Oracle вы используете?   -  person Gordon Linoff    schedule 22.10.2018
comment
И как упорядочены строки - по алфавиту по первому столбцу? (Я предполагаю, что то, что вы показываете, является упрощением вашего реального случая.) Если это так, важно, чтобы первый столбец был ограничен NOT NULL. Тогда что должно произойти, если в первом столбце (столбце порядка) есть дубликаты? Или этого тоже гарантированно не будет?   -  person mathguy    schedule 22.10.2018
comment
Я использую Oracle 12.   -  person Fábio Almeida    schedule 22.10.2018
comment
Порядок примерно первый столбец. Это как код продукта. Этот столбец не может быть NULL.   -  person Fábio Almeida    schedule 22.10.2018
comment
Вам действительно нужна частичная сумма для каждой строки, или вы просто ищете способ разбить строки на группы, где сумма каждой группы является наименьшей суммой, превышающей 15? Хотя я не уверен, что это облегчает задачу :-)   -  person mbj    schedule 09.01.2019


Ответы (3)


В качестве альтернативы рекурсивному SQL вы также можете использовать предложение SQL MODEL. Лично я считаю, что это немного легче читать, чем рекурсивный SQL, хотя его сложнее написать (поскольку большинству людей, таких как я, нужно искать синтаксис).

-- "test_data" is just a substitute for your real table, which I don't have
-- it is just so people without your table can run this example and would
-- not be part of your real solution.
with test_data ( sort_col, addend ) as
( SELECT 'A', 3 FROM DUAL UNION ALL
 SELECT 'B', 7 FROM DUAL UNION ALL
 SELECT 'C', 6 FROM DUAL UNION ALL
 SELECT 'D', 5 FROM DUAL UNION ALL
 SELECT 'E', 9 FROM DUAL UNION ALL
 SELECT 'F', 3 FROM DUAL UNION ALL
 SELECT 'G', 8 FROM DUAL ),
-- Solution begins here
sorted_inputs ( sort_col, sort_order, addend, running_sum_max_15) as
( SELECT sort_col, row_number() over ( order by sort_col ) sort_order, addend, 0 from test_data )
SELECT sort_col, addend, running_sum_max_15
from sorted_inputs
model 
dimension by (sort_order)
measures ( sort_col, addend, running_sum_max_15 )
rules update
( running_sum_max_15[1] = addend[1],
  running_sum_max_15[sort_order>1] = 
          case when running_sum_max_15[CV(sort_order)-1] < 15 THEN 
             running_sum_max_15[CV(sort_order)-1] ELSE 0 END+addend[CV(sort_order)]
)

РЕЗУЛЬТАТЫ

+----------+--------+--------------------+
| SORT_COL | ADDEND | RUNNING_SUM_MAX_15 |
+----------+--------+--------------------+
| A        |      3 |                  3 |
| B        |      7 |                 10 |
| C        |      6 |                 16 |
| D        |      5 |                  5 |
| E        |      9 |                 14 |
| F        |      3 |                 17 |
| G        |      8 |                  8 |
+----------+--------+--------------------+
person Matthew McPeak    schedule 22.10.2018
comment
Спасибо. Я выбираю ваш, потому что он дешевле. - person Fábio Almeida; 23.10.2018
comment
Хорошо, раньше мне больше всего нравился способ расчета нарастающей суммы с использованием предложения MODEL. Пожалуйста, проверьте мой второй подход с MATCH_RECOGNIZE. - person Lukasz Szozda; 01.01.2019

Использование рекурсивного cte:

DROP TABLE tab;
CREATE TABLE tab
AS
SELECT 'A' as col1, 3 AS col2 FROM dual UNION ALL
SELECT 'B' as col1, 7 AS col2 FROM dual UNION ALL
SELECT 'C' as col1, 6 AS col2 FROM dual UNION ALL
SELECT 'D' as col1, 5 AS col2 FROM dual UNION ALL
SELECT 'E' as col1, 9 AS col2 FROM dual UNION ALL
SELECT 'F' as col1, 3 AS col2 FROM dual UNION ALL
SELECT 'G' as col1, 8 AS col2 FROM dual;

Фактический запрос:

WITH cte_r AS (
  SELECT t.*, ROW_NUMBER() OVER(ORDER BY t.col1) AS rn FROM tab t
), cte(col1, col2, total, rn) AS (
  SELECT col1, col2, col2 AS total, rn
  FROM cte_r
  WHERE rn = 1
  UNION ALL
  SELECT cte_r.col1, cte_r.col2,
       CASE WHEN cte.total >= 15 THEN 0 ELSE cte.total END + cte_r.col2 AS total,
       cte_r.rn
  FROM cte
  JOIN cte_r
    ON cte.rn = cte_r.rn-1
)
SELECT col1, col2, total
FROM cte
ORDER BY rn;

Выход:

┌──────┬──────┬───────┐
│ COL1 │ COL2 │ TOTAL │
├──────┼──────┼───────┤
│ A    │    3 │     3 │
│ B    │    7 │    10 │
│ C    │    6 │    16 │
│ D    │    5 │     5 │
│ E    │    9 │    14 │
│ F    │    3 │    17 │
│ G    │    8 │     8 │
└──────┴──────┴───────┘

db‹>демонстрация скрипки


Это решение не ограничивается только Oracle, но оно будет работать и с другими СУБД, такими как SQL Server/PosrgeSQL/MySQL 8.0/SQLite 3.25.

db‹>демонстрация скрипки — PostgreSQL

person Lukasz Szozda    schedule 22.10.2018

Достичь желаемого результата можно намного проще, чем рекурсивное CTE.

Oracle 12c поддерживает MATCH_RECOGNIZE, и он хорошо подходит для решения проблемы "пригонки к корзине":

SELECT Col1, col2, rolling_sum, bin_num
FROM T
MATCH_RECOGNIZE (
  ORDER BY col1
  MEASURES SUM(col2) ROLLING_SUM, MATCH_NUMBER() AS bin_num
  ALL ROWS PER MATCH
  AFTER MATCH SKIP PAST LAST ROW
  PATTERN ( A+ )
  DEFINE A AS SUM(col2) < 15 + A.col2);

db‹>демонстрация скрипки

Выход:

┌───────┬───────┬──────────────┬─────────┐
│ COL1  │ COL2  │ ROLLING_SUM  │ BIN_NUM │
├───────┼───────┼──────────────┼─────────┤
│ A     │    3  │           3  │       1 │
│ B     │    7  │          10  │       1 │
│ C     │    6  │          16  │       1 │
│ D     │    5  │           5  │       2 │
│ E     │    9  │          14  │       2 │
│ F     │    3  │          17  │       2 │
│ G     │    8  │           8  │       3 │
└───────┴───────┴──────────────┴─────────┘

Дополнительно: Ограничение промежуточный итог с MODEL

person Lukasz Szozda    schedule 01.01.2019