Вычислить значение строки на основе предыдущих и фактических значений строки

9

Привет всем и спасибо за вашу помощь.
У меня следующая ситуация: таблица с названием операторов, которая содержит поля id (int), stmnt_date (date), debit (double), credit (double) и balance (double) Структура моего стола

Я хочу рассчитать остаток по следующим правилам:

Баланс первой строки (в хронологическом порядке ) = дебет - кредит, а для остальных строк

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

Как вы можете видеть на картинке выше, строки не упорядочены по дате, поэтому я дважды использовал слово в хронологическом порядке, чтобы подчеркнуть важность значения stmnt_date.

Спасибо большое за помощь.

Мохамед Анис Дахмани
источник
Вы можете объединить поля дебет и кредит в одно поле? Если это так, то вы можете использовать отрицательные значения как дебет и положительные значения как кредит.
Майк
1
Для будущих вопросов (так как на этот вопрос уже дан ответ), отправляйте код в тексте, а не в печатном виде. Также включите CREATE TABLEзаявления и образцы данных (с INSERT).
ypercubeᵀᴹ
В знак уважения к вашему ответу @ypercube, для тех, кто читает это, я добавил ниже пример CREATE TABLE и INSERT 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)
ypercubeᵀᴹ
источник
6

Я думаю, вы могли бы попробовать следующее:

set @balance := 0;

SELECT stmnt_date, debit, credit, (@balance := @balance + (debit - credit)) as Balance
FROM statements
ORDER BY stmnt_date;
TolMera
источник
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;
QuickJoe
источник
-1

Например, в MSSQL:

Используйте оператор with () для генерации CTE. По сути, это временный результирующий набор, который будет показывать значение каждой строки. Вы можете использовать math в операторе with, чтобы создать столбец в конце, используя math, чтобы показать, что сумма строки равна DEBIT-CREDIT. В вашем операторе with вам нужно назначить номера строк каждой строке, используйте предложение OVER WITH () для упорядочения по stmnt_date.

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

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

chrlsuk
источник
1
Вопрос о MySQL. Хотя неплохо (наоборот) предоставить код того, как это можно сделать с помощью CTE или оконных функций в СУБД, которые его имеют (например, Postgres, SQL-Server, DB2, Oracle, ... список длинный), вы должен по крайней мере предоставить код о том, как сделать это в MySQL.
ypercubeᵀᴹ