Calcular el valor de la fila en función de los valores de fila anteriores y reales

9

Hola a todos y gracias por vuestra ayuda.
Tengo la siguiente situación: una tabla llamada declaraciones que contiene campos id (int), stmnt_date (fecha), débito (doble), crédito (doble) y saldo (doble) Estructura de mi mesa

Quiero calcular el saldo siguiendo estas reglas:

El saldo de la primera fila ( cronológicamente ) = débito - crédito y para el resto de las filas

saldo de fila actual = saldo de fila cronológicamente anterior + débito de fila actual - crédito de fila actual

Como puede ver en la imagen de arriba, las filas no están ordenadas por fecha y es por eso que usé la palabra cronológicamente dos veces para enfatizar la importancia del valor stmnt_date.

Muchas gracias por su ayuda.

Mohamed Anis Dahmani
fuente
¿Puede unir los campos de débito y crédito en un campo? Si es así, puede usar valores negativos como débito y valores positivos como crédito.
Mike
1
Para futuras preguntas (ya que esto ha sido respondido), publique el código en el texto, no en las pantallas de impresión. Incluya también las CREATE TABLEdeclaraciones y los datos de muestra (con INSERT).
ypercubeᵀᴹ
En homenaje a su respuesta @ypercube, para cualquiera que lea esto, agregué un ejemplo CREAR TABLA e INSERTAR a continuación dba.stackexchange.com/a/183207/131900
Zack Morris

Respuestas:

8

Asumiendo que stmnt_datetiene una UNIQUErestricción, esto sería bastante fácil con las funciones de ventana / análisis:

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 ;

Desafortunadamente, MySQL (todavía) no ha implementado funciones analíticas. Puede resolver el problema con SQL estricto, uniéndose a la tabla (que debería ser bastante ineficiente aunque funcione al 100%) o utilizando una característica específica de MySQL, variables (que serían bastante eficientes pero tendría que probarlo) al actualizar mysql, para asegurarse de que los resultados siguen siendo correctos y no están alterados por alguna mejora de optimización):

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 sus datos, dará como resultado:

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

Creo que podrías probar lo siguiente:

set @balance := 0;

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

La respuesta de ypercube es bastante espectacular (nunca había visto una creación variable dentro de una sola consulta a través de una selección ficticia como esa), así que aquí está la declaración CREATE TABLE para su conveniencia.

Para imágenes de datos tabulares en Google Image Search, puede usar https://convertio.co/ocr/ o https://ocr.space/ para convertirlo en un documento de texto. Luego, si el OCR no detectó columnas correctamente, y tiene una Mac, use TextWrangler con la tecla de opción presionada para realizar una selección rectangular y mover las columnas. La combinación del editor SQL como Sequel Pro , TextWrangler y una hoja de cálculo como Google Docs hace que el manejo de datos tabulares separados por tabulaciones sea extremadamente eficiente.

Si pudiera poner todo esto en un comentario, lo haría, así que por favor no vote esta respuesta.

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

Las mesas de unión automática no son muy rápidas en las mesas grandes. Entonces, al tratar con esta tarea en PostgreSQL, decidí usar la función de disparo para calcular el "saldo" del campo almacenado. Todos los cálculos ocurren solo una vez para cada fila.

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

En, por ejemplo, MSSQL:

Use una instrucción with () para generar un CTE. Este es esencialmente un conjunto de resultados temporal que mostrará el valor de cada fila. Puede usar las matemáticas en la instrucción with para crear una columna al final, usando las matemáticas para mostrar que el total de la fila es DEBIT-CREDIT. En su declaración with necesitará asignar números de fila a cada fila, use la cláusula OVER de WITH () para ordenar por stmnt_date.

Luego, unir recursivamente la tabla sobre sí mismo, utilizando a.ROWNUMBER = b.ROWNUMBER-1 o +1, lo que le permitirá referir a.total + b.total = total de esta fila y la fila anterior.

Aprecio que no estoy proporcionando el código, sin embargo, este es el método práctico para lograrlo. Puedo proporcionar el código si se solicita :)

chrlsuk
fuente
1
La pregunta es sobre MySQL. Si bien no es malo (por el contrario) proporcionar un código de cómo se podría hacer esto con CTE o funciones de ventana en DBMS que lo tienen (como Postgres, SQL-Server, DB2, Oracle, ... la lista es larga), usted al menos debería proporcionar un código sobre cómo hacer esto en MySQL.
ypercubeᵀᴹ