Calcola il valore di riga in base ai valori di riga precedenti ed effettivi


9

Ciao a tutti e grazie per il vostro aiuto.
Ho la seguente situazione: una tabella chiamata istruzioni che contiene i campi id (int), stmnt_date (data), debito (doppio), credito (doppio) e saldo (doppio) Struttura del mio tavolo

Voglio calcolare il saldo seguendo queste regole:

Il saldo della prima riga (in ordine cronologico ) = debito - credito e per il resto delle righe

saldo riga corrente = saldo riga cronologicamente precedente + debito riga corrente - credito riga corrente

Come puoi vedere nell'immagine sopra, le righe non sono organizzate per data ed è per questo che ho usato la parola cronologicamente due volte per enfatizzare l'importanza del valore stmnt_date.

Grazie mille per il tuo aiuto.


Sei in grado di unire i campi di debito e credito in un campo? In tal caso, è possibile utilizzare valori negativi come debito e valori positivi come credito.
Mike

1
Per domande future (dato che è stata data una risposta), inserisci il codice nel testo, non nelle schermate di stampa. Includere anche le CREATE TABLEdichiarazioni e i dati di esempio (con INSERT).
ypercubeᵀᴹ

In omaggio alla tua risposta @ypercube, per chiunque stia leggendo questo ho aggiunto un esempio CREATE TABLE e INSERT sotto dba.stackexchange.com/a/183207/131900
Zack Morris

Risposte:


8

Supponendo che stmnt_dateabbia un UNIQUEvincolo, questo sarebbe abbastanza facile con le funzioni finestra / analitica:

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 ;

Sfortunatamente, MySQL non ha (ancora) implementato funzioni analitiche. Puoi risolvere il problema con SQL rigoroso, unendoti alla tabella (che dovrebbe essere piuttosto inefficiente sebbene funzioni al 100%) o usando una specifica funzionalità MySQL, variabili (che sarebbero abbastanza efficienti ma dovresti testarlo durante l'aggiornamento di mysql, per essere sicuri che i risultati siano ancora corretti e non alterati da un miglioramento dell'ottimizzazione):

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 ;

Con i tuoi dati, si otterrà:

+------------+-------+--------+---------+
| 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

Penso che potresti provare quanto segue:

set @balance := 0;

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

2

La risposta di ypercube è piuttosto spettacolare (non avevo mai visto una creazione di variabili all'interno di una singola query tramite una selezione fittizia del genere), quindi ecco l'istruzione CREATE TABLE per comodità.

Per immagini di dati tabulari in Google Ricerca immagini, puoi utilizzare https://convertio.co/ocr/ o https://ocr.space/ per convertirlo in un documento di testo. Quindi, se l'OCR non ha rilevato correttamente le colonne e disponi di un Mac, utilizza TextWrangler con il tasto opzione premuto per eseguire una selezione rettangolare e spostare le colonne. La combinazione di editor SQL come Sequel Pro , TextWrangler e un foglio di calcolo come Google Docs rende estremamente efficiente la gestione di dati tabulari separati da tabulazioni.

Se potessi inserire tutto questo in un commento, lo farei, quindi per favore non votare questa risposta.

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

Tavoli che si uniscono da soli non è molto veloce su tavoli di grandi dimensioni. Quindi, gestendo questo compito su PostgreSQL, ho deciso di utilizzare la funzione trigger per calcolare il "bilanciamento" dei campi memorizzati. Tutti i calcoli vengono eseguiti una sola volta per ogni riga.

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

Ad esempio, in MSSQL:

Utilizzare un'istruzione with () per generare un CTE. Questo è essenzialmente un set di risultati temporaneo che mostrerà il valore di ogni riga. Puoi usare la matematica nell'istruzione with per creare una colonna alla fine, usando la matematica per mostrare che il totale della riga è DEBIT-CREDIT. Nella tua dichiarazione with dovrai assegnare i numeri di riga a ciascuna riga, usa la clausola OVER di WITH () per ordinare in base a stmnt_date.

Quindi, unisci ricorsivamente la tabella su se stessa, usando a.ROWNUMBER = b.ROWNUMBER-1 o +1 che ti permetterà di fare riferimento a a.total + b.total = totale di questa riga e della riga precedente.

Apprezzo che non sto fornendo il codice, tuttavia questo è il metodo pratico per raggiungere questo obiettivo. Posso fornire il codice se richiesto :)


1
La domanda riguarda MySQL. Sebbene non sia male (al contrario) fornire il codice di come ciò potrebbe essere fatto con CTE o funzioni di finestra in DBMS che lo hanno (come Postgres, SQL-Server, DB2, Oracle, ... l'elenco è lungo), tu dovrebbe almeno fornire il codice su come eseguire questa operazione in MySQL.
ypercubeᵀᴹ
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.