Обчисліть значення рядка на основі попередніх та фактичних значень рядків


9

Привіт усім і дякую за допомогу.
У мене є така ситуація: таблиця з назвою заяви, яка містить поля id (int), stmnt_date (дата), дебет (подвійний), кредит (подвійний) та баланс (подвійний) Структура мого столу

Я хочу обчислити залишок за такими правилами:

Баланс першого ряду ( хронологічно ) = дебет - кредит та для решти рядків

поточний баланс рядка = хронологічно попередній баланс рядка + поточний дебет рядка - поточний кредит рядка

Як ви бачите на малюнку над рядками, не впорядковані за датою, і тому я вжив слово хронологічно двічі, щоб підкреслити важливість значення stmnt_date.

Велике спасибі за вашу допомогу.


Чи можете ви об'єднати дебетові та кредитні поля в одне поле? Якщо так, то ви можете використовувати негативні значення як дебетові, а позитивні - як кредитні.
Майк

1
Для майбутніх питань (оскільки на це відповіли), поштовий індекс у тексті, а не на екранах для друку. Також включайте CREATE TABLEвиписки та зразкові дані (з INSERT).
ypercubeᵀᴹ

На знак вашої відповіді @ypercube, для тих, хто читає це, я додав приклад СТВОРИТИ ТАБЛИЦЮ та ВСТАВИТИ нижче dba.stackexchange.com/a/183207/131900
Зак Морріс

Відповіді:


8

Якщо припустити, що stmnt_dateце UNIQUEобмеження, це буде досить легко з віконними / аналітичними функціями:

SELECT 
    s.stmnt_date, s.debit, s.credit,
    SUM(s.debit - s.credit) OVER (ORDER BY s.stmnt_date
                                  ROWS BETWEEN UNBOUNDED PRECEDING
                                           AND CURRENT ROW)
        AS balance
FROM
    statements AS s
ORDER BY
    stmnt_date ;

На жаль, MySQL не (ще) не реалізував аналітичні функції. Ви можете вирішити проблему або за допомогою строгого SQL, самостійно приєднавшись до таблиці (яка має бути досить неефективною, хоча працює 100%), або використовуючи певну функцію MySQL, змінні (що було б досить ефективно, але вам доведеться перевірити її під час оновлення mysql, щоб бути впевненим, що результати все ще є правильними та не піддаються певним покращенням оптимізації):

SELECT 
    s.stmnt_date, s.debit, s.credit,
    @b := @b + s.debit - s.credit AS balance
FROM
    (SELECT @b := 0.0) AS dummy 
  CROSS JOIN
    statements AS s
ORDER BY
    stmnt_date ;

З вашими даними, це призведе до:

+------------+-------+--------+---------+
| stmnt_date | debit | credit | balance |
+------------+-------+--------+---------+
| 2014-05-15 |  3000 |      0 |    3000 |
| 2014-06-17 | 20000 |      0 |   23000 |
| 2014-07-16 |     0 |   3000 |   20000 |
| 2014-08-14 |     0 |   3000 |   17000 |
| 2015-02-01 |  3000 |      0 |   20000 |
+------------+-------+--------+---------+
5 rows in set (0.00 sec)

6

Я думаю, ви можете спробувати наступне:

set @balance := 0;

SELECT stmnt_date, debit, credit, (@balance := @balance + (debit - credit)) as Balance
FROM statements
ORDER BY stmnt_date;

2

Відповідь ypercube є досить вражаючою (я ніколи не бачив створення змінної в межах одного запиту через манекен, що вибирається таким чином), ось ось для створення зручності висловлювання CREATE TABLE.

Для табличних зображень даних у пошуку зображень Google ви можете використовувати https://convertio.co/ocr/ або https://ocr.space/ для перетворення їх у текстовий документ. Тоді, якщо OCR не виявив стовпці належним чином, і у вас є Mac, використовуйте TextWrangler при натиснутій клавіші опцій, щоб виконати прямокутний вибір і перемістити стовпчики навколо. Поєднання редактора SQL на зразок Sequel Pro , TextWrangler та електронної таблиці, як Google Docs, робить справу з табличними табличними даними, розділеними вкладками, надзвичайно ефективно.

Якби я міг все це залишити в коментарі, я б хотів, тому, будь ласка, не підтримуйте цю відповідь.

-- DROP TABLE statements;

CREATE TABLE IF NOT EXISTS statements (
  id integer NOT NULL AUTO_INCREMENT,
  stmnt_date date,
  debit integer not null default 0,
  credit integer not null default 0,
  PRIMARY KEY (id)
);

INSERT INTO statements
(stmnt_date  , debit, credit) VALUES
('2014-06-17', 20000, 0     ),
('2014-08-14', 0    , 3000  ),
('2014-07-16', 0    , 3000  ),
('2015-02-01', 3000 , 0     ),
('2014-05-15', 3000 , 0     );

-- this is slightly modified from ypercube's (@b := 0 vs @b := 0.0)
SELECT 
    s.stmnt_date, s.debit, s.credit,
    @b := @b + s.debit - s.credit AS balance
FROM
    (SELECT @b := 0) AS dummy 
CROSS JOIN
    statements AS s
ORDER BY
    stmnt_date ASC;

/* result
+------------+-------+--------+---------+
| stmnt_date | debit | credit | balance |
+------------+-------+--------+---------+
| 2014-05-15 |  3000 |      0 |    3000 |
| 2014-06-17 | 20000 |      0 |   23000 |
| 2014-07-16 |     0 |   3000 |   20000 |
| 2014-08-14 |     0 |   3000 |   17000 |
| 2015-02-01 |  3000 |      0 |   20000 |
+------------+-------+--------+---------+
5 rows in set (0.00 sec)
*/

1

Самостійне приєднання таблиць на великих столах не дуже швидко. Отже, вирішуючи це завдання на PostgreSQL, я вирішив використовувати тригерну функцію для обчислення збереженого поля "баланс". Усі розрахунки відбуваються лише один раз для кожного ряду.

DROP TABLE IF EXISTS statements;

CREATE TABLE IF NOT EXISTS statements (
  id BIGSERIAL,
  stmnt_date TIMESTAMP,
  debit NUMERIC(18,2) not null default 0,
  credit NUMERIC(18,2) not null default 0,
  balance NUMERIC(18,2)
);

CREATE OR REPLACE FUNCTION public.tr_fn_statements_balance()
RETURNS trigger AS
$BODY$
BEGIN

    UPDATE statements SET
    balance=(SELECT SUM(a.debit)-SUM(a.credit) FROM statements a WHERE a.stmnt_date<=statements.stmnt_date)
    WHERE stmnt_date>=NEW.stmnt_date;

RETURN NULL;
END;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

CREATE TRIGGER tr_statements_after_update
  AFTER INSERT OR UPDATE OF debit, credit
  ON public.statements
  FOR EACH ROW
  EXECUTE PROCEDURE public.tr_fn_statements_balance();


INSERT INTO statements
(stmnt_date  , debit, credit) VALUES
('2014-06-17', 20000, 0     ),
('2014-08-14', 0    , 3000  ),
('2014-07-16', 0    , 3000  ),
('2015-02-01', 3000 , 0     ),
('2014-05-15', 3000 , 0     );


select * from statements order by stmnt_date;

-1

Наприклад, MSSQL:

Використовуйте оператор with () для створення CTE. Це по суті тимчасовий набір результатів, який показуватиме значення кожного рядка. Ви можете використовувати математику в операторі with для створення стовпця в кінці, використовуючи математику, щоб показати загальний рядок - DEBIT-CREDIT. У вашому операторі із заявою вам потрібно буде призначити номери рядків кожному рядку, використовуйте пункт OVER для слова AND () для впорядкування по stmnt_date.

Потім рекурсивно приєднайте таблицю до себе, використовуючи a.ROWNUMBER = b.ROWNUMBER-1 або +1, що дозволить вам посилати a.total + b.total = загальну кількість цього рядка та попереднього рядка.

Я вдячний, що не надаю код, однак це практичний метод для досягнення цього. Я можу надати код за запитом :)


1
Питання стосується MySQL. Хоча це непогано (навпаки) надати код того, як це можна зробити за допомогою CTE або віконних функцій у СУБД, у яких є (наприклад, Postgres, SQL-сервер, DB2, Oracle, ... список довгий), ви повинен хоча б надати код про те, як це зробити в MySQL.
ypercubeᵀᴹ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.