Calcular o valor da linha com base nos valores anteriores e reais

9

Olá a todos e obrigado por sua ajuda.
Eu tenho a seguinte situação: uma tabela chamada instruções que contém os campos id (int), stmnt_date (data), débito (duplo), crédito (duplo) e saldo (duplo) Estrutura da minha mesa

Quero calcular o saldo seguindo estas regras:

O saldo da primeira linha ( cronologicamente ) = débito - crédito e para o restante das linhas

saldo da linha atual = saldo da linha cronologicamente anterior + débito da linha atual - crédito da linha atual

Como você pode ver na figura acima, as linhas não são organizadas por data e é por isso que usei a palavra cronologicamente duas vezes para enfatizar a importância do valor stmnt_date.

Muito obrigado pela sua ajuda.

Mohamed Anis Dahmani
fonte
Você pode associar os campos de débito e crédito em um campo? Nesse caso, você pode usar valores negativos como débito e valores positivos como crédito.
6135 Mike
11
Para perguntas futuras (como isso foi respondido), poste o código no texto, não nas telas de impressão. Inclua também as CREATE TABLEinstruções e os dados de amostra (com INSERT).
usar o seguinte comando
Em homenagem a sua resposta @ypercube, para quem lê este eu adicionei um CREATE TABLE e exemplo INSERIR abaixo dba.stackexchange.com/a/183207/131900
Zack Morris

Respostas:

8

Supondo que haja stmnt_dateuma UNIQUErestrição, isso seria bastante fácil com as funções analíticas / de janela:

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 ;

Infelizmente, o MySQL ainda não implementou funções analíticas. Você pode resolver o problema com SQL estrito, ingressando automaticamente na tabela (que deve ser bastante ineficiente, embora funcione 100%) ou usando um recurso específico do MySQL, variáveis ​​(que seriam bastante eficientes, mas você teria que testá-lo ao atualizar o mysql, para ter certeza de que os resultados ainda estão corretos e não prejudicados por alguma melhoria na otimização):

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 ;

Com seus dados, isso resultará em:

+------------+-------+--------+---------+
| 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ᵀᴹ
fonte
6

Eu acho que você poderia tentar o seguinte:

set @balance := 0;

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

A resposta do ypercube é bastante espetacular (eu nunca tinha visto uma criação de variável em uma única consulta por meio de um dummy select assim), então aqui está a instrução CREATE TABLE para sua conveniência.

Para imagens de dados tabulares na Pesquisa de imagens do Google, você pode usar https://convertio.co/ocr/ ou https://ocr.space/ para convertê-las em um documento de texto. Então, se o OCR não detectou as colunas corretamente e você possui um Mac, use o TextWrangler com a tecla de opção pressionada para executar uma seleção retangular e mover as colunas. A combinação de editor SQL como Sequel Pro , TextWrangler e uma planilha como o Google Docs torna extremamente eficiente o tratamento de dados tabulares separados por tabulações.

Se eu pudesse colocar tudo isso em um comentário, por favor, não vote mais nesta resposta.

-- 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)
*/
Zack Morris
fonte
1

Mesas auto-adesivas não são muito rápidas em mesas grandes. Então, lidando com esta tarefa no PostgreSQL, decidi usar a função trigger para calcular o campo armazenado "balance". Todos os cálculos acontecem apenas uma vez para cada linha.

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
fonte
-1

Em, por exemplo, MSSQL:

Use uma instrução with () para gerar um CTE. Este é essencialmente um conjunto de resultados temporário que mostrará o valor de cada linha. Você pode usar math na instrução with para criar uma coluna no final, usando math para mostrar que o total da linha é DEBIT-CREDIT. Na sua declaração with, você precisará atribuir números de linha a cada linha, use a cláusula OVER de WITH () para ordenar por stmnt_date.

Em seguida, junte recursivamente a tabela em si mesma, usando a.ROWNUMBER = b.ROWNUMBER-1 ou +1, o que permitirá que você consulte o a.total + b.total = total desta linha e da linha anterior.

Aprecio que não estou fornecendo o código, mas este é o método prático para conseguir isso. Eu posso fornecer código, se solicitado :)

chrlsuk
fonte
11
A questão é sobre o MySQL. Embora não seja ruim (pelo contrário) fornecer código de como isso pode ser feito com CTEs ou funções de janela no DBMS que o possui (como Postgres, SQL-Server, DB2, Oracle, ... a lista é longa), você deve pelo menos fornecer código sobre como fazer isso no MySQL.
ypercubeᵀᴹ