Calculer la valeur de ligne sur la base des valeurs de ligne précédentes et réelles

9

Salut tout le monde et merci pour votre aide.
J'ai la situation suivante: une table appelée instructions qui contient les champs id (int), stmnt_date (date), débit (double), crédit (double) et solde (double) Structure de ma table

Je veux calculer le solde en suivant ces règles:

Le solde de la première ligne ( chronologiquement ) = débit - crédit et pour le reste des lignes

solde de ligne actuel = solde de ligne précédent chronologiquement + débit de ligne actuel - crédit de ligne actuel

Comme vous pouvez le voir sur l'image ci-dessus, les lignes ne sont pas organisées par date et c'est pourquoi j'ai utilisé le mot chronologiquement deux fois pour souligner l'importance de la valeur stmnt_date.

Merci beaucoup pour votre aide.

Mohamed Anis Dahmani
la source
Pouvez-vous joindre les champs débit et crédit en un seul champ? Si c'est le cas, vous pouvez utiliser des valeurs négatives comme débit et des valeurs positives comme crédit.
Mike
1
Pour les questions futures (puisque cela a été répondu), code postal dans le texte, pas les écrans d'impression. Incluez également les CREATE TABLEinstructions et les exemples de données (avec INSERT).
ypercubeᵀᴹ
En hommage à votre réponse @ypercube, pour tous ceux qui lisent ceci, j'ai ajouté un exemple CREATE TABLE et INSERT ci-dessous dba.stackexchange.com/a/183207/131900
Zack Morris

Réponses:

8

En supposant que cela stmnt_datea une UNIQUEcontrainte, ce serait assez facile avec les fonctions fenêtre / analytique:

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 ;

Malheureusement, MySQL n'a pas (encore) implémenté de fonctions analytiques. Vous pouvez résoudre le problème soit avec du SQL strict, en joignant automatiquement la table (qui devrait être plutôt inefficace bien que fonctionnant à 100%) ou en utilisant une fonctionnalité MySQL spécifique, des variables (qui seraient assez efficaces mais vous devrez les tester lors de la mise à niveau de mysql, pour être sûr que les résultats sont toujours corrects et non altérés par certaines améliorations d'optimisation):

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 ;

Avec vos données, il en résultera:

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

Je pense que vous pouvez essayer ce qui suit:

set @balance := 0;

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

réponse de ypercube est assez spectaculaire (je n'avais jamais vu de création de variable dans une seule requête via une sélection factice comme celle-ci), voici donc l'instruction CREATE TABLE pour votre commodité.

Pour les images de données tabulaires dans Google Image Search, vous pouvez utiliser https://convertio.co/ocr/ ou https://ocr.space/ pour le convertir en document texte. Ensuite, si l'OCR n'a pas détecté correctement les colonnes et que vous avez un Mac, utilisez TextWrangler avec la touche d'option enfoncée pour effectuer une sélection rectangulaire et déplacer les colonnes. La combinaison d'un éditeur SQL comme Sequel Pro , TextWrangler et d'une feuille de calcul comme Google Docs rend le traitement des données tabulaires séparées par des tabulations extrêmement efficace.

Si je pouvais mettre tout cela dans un commentaire, je le ferais, alors ne votez pas cette réponse.

-- 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
la source
1

Tables à jonction automatique ce n'est pas très rapide sur les grandes tables. Donc, face à cette tâche sur PostgreSQL, j'ai décidé d'utiliser la fonction de déclenchement pour calculer "l'équilibre" des champs stockés. Tous les calculs ne se produisent qu'une seule fois pour chaque ligne.

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
la source
-1

Dans, par exemple, MSSQL:

Utilisez une instruction with () pour générer un CTE. Il s'agit essentiellement d'un jeu de résultats temporaire qui affichera la valeur de chaque ligne. Vous pouvez utiliser les mathématiques dans l'instruction with pour créer une colonne à la fin, en utilisant les mathématiques pour montrer que le total de la ligne est DEBIT-CREDIT. Dans votre instruction with, vous devrez attribuer des numéros de ligne à chaque ligne, utilisez la clause OVER de WITH () pour classer par stmnt_date.

Ensuite, joignez récursivement la table sur elle-même, en utilisant a.ROWNUMBER = b.ROWNUMBER-1 ou +1 qui vous permettra de référencer a.total + b.total = total de cette ligne et de la ligne précédente.

J'apprécie de ne pas fournir le code mais c'est la méthode pratique pour y parvenir. Je peux fournir du code sur demande :)

chrlsuk
la source
1
La question concerne MySQL. Bien qu'il ne soit pas mauvais (au contraire) de fournir le code de la façon dont cela pourrait être fait avec les CTE ou les fonctions de fenêtre dans les SGBD qui en disposent (comme Postgres, SQL-Server, DB2, Oracle, ... la liste est longue), vous devrait au moins fournir du code sur la façon de procéder dans MySQL.
ypercubeᵀᴹ