Ir al contenido



Foto

DBGrid agregar columna saldo


  • Por favor identifícate para responder
19 respuestas en este tema

#1 Gaston

Gaston

    Member

  • Miembros
  • PipPip
  • 49 mensajes

Escrito 16 octubre 2016 - 08:16

Hola, no encuentro la forma de obtener una columna saldo que funcione bien.

La tabla tiene campos: fecha, idprov, detalle, debe, haber (tiene más campos pero no son relevantes para el caso).

 

Ejemplo:

fecha         idprov     detalle         debe       haber

01/10/16       5         xxxxxx            20              0

01/10/16       5         xxxxxx             0             15

01/10/16       5         xxxxxx            50              0

01/10/16       5         xxxxxx            30              0

01/10/16       5         xxxxxx              0            40

 

Nótese que cuando hay un importe en el debe, no lo hay en el haber. Necesito:

 

fecha         idprov     detalle         debe       haber     saldo

01/10/16       5         xxxxxx            20              0          20

01/10/16       5         xxxxxx             0             15            5

01/10/16       5         xxxxxx            50              0          55

01/10/16       5         xxxxxx            30              0          85

01/10/16       5         xxxxxx              0            40          45

 

He intentado:


sql
  1. SELECT c.fecha, c.idprov, c.debe, c.detalle,c.debe, SUM(c.total-c.debe) AS saldo
  2. FROM compras AS c ORDER BY c.idprov, c.fecha;

Esto me da como resultado en la grid sólo la última fila con con el saldo.

 

Si al código le quito SUM y dejo c.total -c.debe me muestra todas las filas pero en la columna saldo me repite el importe en positivo si está en el debe o negativo si está en haber.

 

También intenté:


sql
  1. SELECT c.fecha, c.idprov, c.debe, c.detalle, (c.total-c.debe) AS saldo,
  2. SUM(c.total-c.debe) AS acumula,
  3. FROM compras AS c
  4. ORDER BY c.idprov, c.fecha;

Pero también me arroja solo la última fila, calculo que debido a la utilización de la función SUM.

 

Hace horas que estoy buscando por la web pero no hay caso.

 

Saludos.

 

Olvidé decir que utilizo SQlite con Zeos.


  • 0

#2 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 585 mensajes
  • LocationArgentina

Escrito 16 octubre 2016 - 11:52

Eso es porque las funciones agregadas trabajan sobre grupos de datos (group by). Cuando no hay group by, sería la tabla entera. Vamos, cuando hacemos select count nos da el total de filas retornadas y nos parece muy lógico verdad? :)

Creo que tu problema no se puede resolver con una consulta sql.

Yo lo resolvería usando un campo calculado
  • 0

#3 Gaston

Gaston

    Member

  • Miembros
  • PipPip
  • 49 mensajes

Escrito 17 octubre 2016 - 03:08

La verdad no entiendo, ahora le agregué una columna saldo a la tabla con default value=0 pero cuando intento recorrer el ZQuery con un WHILE NOT ZQuery.Eof y actualizar la columna saldo con ZConnection.ExecuteDirect me actualiza el primer registro, pero no sale del WHILE, tampoco se cuelga pero tengo que interrumpir el programa para que no se me queme el disco...
 
Es frustrante, algo que con Clipper hacían en 5 minutos hace más de 10 horas que estoy luchando, no le agarro la vuelta y se supone que es algo sencillo.
 


delphi
  1. procedure TfrmCtaCte.btnVerClick(Sender: TObject);
  2. var
  3. sqltxt:string;
  4. vSaldo:Real;
  5. vidcompras:integer;
  6. begin
  7. vProvID:=cmbProv1.KeyValue;
  8. sqltxt:='SELECT * FROM compras WHERE idprov='+IntToStr(vProvID)+' ORDER BY fecha;';
  9. vidcompras:=ZQcompras.FieldByName('id').AsInteger;
  10. ZQcompras.Close;
  11. ZQcompras.SQL.Clear;
  12. ZQcompras.SQL.Text:=sqltxt;
  13. ZQcompras.Open;
  14. ZQcompras.Active:=true; //hasta aca funciona agregando el dbgrid refresh
  15.  
  16.   //vSaldo:=ZQcompras.FieldByName('total').AsCurrency-ZQcompras.FieldByName('debe').AsCurrency;
  17.   While NOT ZQcompras.EOF do
  18.   begin
  19.   //  vSaldo:=vSaldo+ZQcompras.FieldByName('total').AsCurrency-ZQcompras.FieldByName('debe').AsCurrency;
  20.     ZConnection1.ExecuteDirect('UPDATE compras SET saldo=''10'' WHERE id='+IntToStr(vidcompras)+';');
  21.     Next;
  22.     vidcompras:=ZQcompras.FieldByName('id').AsInteger;
  23.   end;                        
  24.  
  25. ZQcompras.Refresh;
  26. DBGrid1.Refresh;
  27. end;

Esto: SET saldo=''10'' lo puse como prueba solamente.
 
Cualquier sugerencia será bienvenida, por más mínima que sea. Gracias.


  • 0

#4 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 585 mensajes
  • LocationArgentina

Escrito 17 octubre 2016 - 08:26

Sigue con el depurador la invocacion al metodo Next dentro del While. Estas invocando al Next de la clase TForm


Editado por Agustin Ortu, 17 octubre 2016 - 08:26 .

  • 0

#5 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 13.394 mensajes
  • LocationMéxico

Escrito 17 octubre 2016 - 08:34

Hola

Yo haría dos cambios en tu código.
 

delphi
  1. procedure TfrmCtaCte.btnVerClick(Sender: TObject);
  2. var
  3. sqltxt:string;
  4. vSaldo:Real;
  5. vidcompras:integer;
  6. begin
  7. vProvID:=cmbProv1.KeyValue;
  8. sqltxt:='SELECT * FROM compras WHERE idprov='+IntToStr(vProvID)+' ORDER BY fecha;';
  9. vidcompras:=ZQcompras.FieldByName('id').AsInteger;
  10. ZQcompras.Close;
  11. ZQcompras.SQL.Clear;
  12. ZQcompras.SQL.Text:=sqltxt;
  13. ZQcompras.Open;
  14. // ZQcompras.Active:=true; //No es necesaria ya que previamente la has abierto con ZQCompras.Open
  15.  
  16.   //vSaldo:=ZQcompras.FieldByName('total').AsCurrency-ZQcompras.FieldByName('debe').AsCurrency;
  17.   While NOT ZQcompras.EOF do
  18.   begin
  19.   //  vSaldo:=vSaldo+ZQcompras.FieldByName('total').AsCurrency-ZQcompras.FieldByName('debe').AsCurrency;
  20.     ZConnection1.ExecuteDirect('UPDATE compras SET saldo=''10'' WHERE id='+IntToStr(vidcompras)+';');
  21. //Cambiamos em Next como lo comenta Agustín
  22.     ZQcompras.Next;
  23.     vidcompras := ZQcompras.FieldByName('id').AsInteger;
  24.   end;                        
  25.  
  26. ZQcompras.Refresh;
  27. DBGrid1.Refresh;
  28. end;

 
Solo no entiendo que quieres hacer con el Next si al final solo tendrás el ID del último registro....

Saludos
  • 1

#6 Delphius

Delphius

    Advanced Member

  • Administrador
  • 5.772 mensajes
  • LocationArgentina

Escrito 17 octubre 2016 - 11:36

Estoy tratando de entender el hilo.

 

Por lo que he visto, entiendo que la tabla compras además de idprov hay otro campo llamado id que actúa de clave primaria.

Al parecer lo que buscas es que te vaya calculando el saldo acumulado.

 

De ser así, me parece más apropiado realizarlo por medio de un Procedimiento Almacenado que regrese el conjunto de datos o bien, si es que SQLite no los soporta, hacerlo por la vía de sistema.

¿Porqué por un SP? Porque para obtener el saldo acumulado es necesario "mirar el registro anterior". Básicamente el SP debe aplicar este proceso:

 

1. Ejecutar un SELECT de los registros requeridos y filtrados (por id como tu caso)

2. Por cada registro devuelto:

2.1. Calcular el saldo parcial (debe - haber)

2.2. En una variable temporal vamos guardando el saldo acumulado: saldoactual = saldoactual + saldoparcial. NOTA: inicialmente saldoactual = 0 antes de entrar al paso 2.

2.3. Regresamos/isertamos el registro actual en el cojunto de datos que va a devolver el SP y agremos este valor actual

 

Tengo que admitir que estoy oxidado en SQL como para armar a modo de ejemplo un SP. Hace un tiempo que no he tenido que volver a meter mano a SPs en Firebird... siempre se me afloja el tornillo sobre PSQL.

 

La otra alternativa es hacerlo por la vía sistema. Los campos calculados como lo que propone Agustín pueden ayudar. En tu DBGrid defines el campo calculado y mantienes alguna variable global para llevar la cuenta acumulado. Luego haces que el campo calculado lea el valor y recalcule en base al parcial.

Otra forma si no usas un DBGrid ni campos calculados y/o persistentes es tener un StringGrid normal y hacer las cuentas solos.

 

Sea la forma que elijas debes tener una variable temporal que lleve el acumulado. Esto es para "mirar hacia atrás".

 

Espero que se entienda.

 

Saludos,


  • 1

#7 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 585 mensajes
  • LocationArgentina

Escrito 17 octubre 2016 - 01:41

El DBGrid no tiene nada que ver en este problema. Lo mas propio es darle un DataSet "masticado" (como diriamos los Argentinos) al DBGrid para que pueda mostrar lo que corresponda. 

 

Con masticado me refiero a que la aplicacion deberia agregar ese campo extra con el valor correspondiente en cada fila. Hay muchas maneras de hacerlo.

 

El Stored Procedure es una. Probablemente sea la mas "optima". Digo "optima" y no optima porque si bien seguro sea mas rapido que iterar un DataSet, tambien le metes mas carga al servidor. Ademas, tenes que manejar PSQL como dice Delphius. Y el PSQL no es tan estandar entre los distintos DBMS. Mi punto de vista sobre este tema sigue siendo el mismo que desde el dia #1, prefiero no tener una dependencia a la implementacion de SP de la BD y limitarme al estandar SQL para poco mas que select, delete, update e insert. De este modo puedo evitar dolores de cabeza a la hora de cambiar de BD. Y esta bueno poder agarrar y decir, a este tipo le meto una "basecita SQLite o Firebird embebido" y a este "mostro" le meto un Oracle, o compartir esa BD MySQL que usa el server Apache

 

Otra forma es como ya enuncie hace rato, el campo calculado. El campo calculado es una agregado que se le hace a un DataSet, que es invisible para la BD, y permite agregar una "columna virtual" a una tabla. No es muy complicado de programar, basta con meter un simple hook OnCalcFields en el DataSet. Hasta se puede crear el campo calculado en tiempo de diseño

Pero por lo general me parecen demasiado cosa. El caso de uso ideal para los campos calculados es cuando necesitamos que ese campo cambie su valor dinamicamente. Pero si es una resta y simplemente presentamos datos, y el valor esta fijo, me parece que esta de mas.Hay que tener en cuenta que los campos calculados se calculan bastante seguido. Osea que en una tabla grande, andar recalculando una constante podria ser un cuello de botella

 

Y tambien se podria hacer algo como lo siguiente, que es super facil, no vas a tener el overhead de los campos calculados, pero vas a tener que recorrer una vez el DataSet inicial, el que te retorna el query, y eso por supuesto tiene su costo

 

La idea es usar una tabla de memoria, por ejemplo un TClientDataSet en Delphi. Se que en Lazarus no esta la misma clase pero hay algunas que se le asemejan. Sino me equivoco una se llamaba TBuffDataSet. Lo importante es que es un TDataSet. Lo que haces entonces es crearte esta tabla de memoria, le pones las mismas columnas que la tabla de la BD (en realidad las que necesitas mostrar), le agregas ademas una columna "saldo" y luego es cuestion de recorrer e ir copiando lo que te dio el query y al mismo tiempo calculando el Saldo

 

Seguramente hay mas formas de hacerlo; a veces es cuestion de creatividad


  • 1

#8 Delphius

Delphius

    Advanced Member

  • Administrador
  • 5.772 mensajes
  • LocationArgentina

Escrito 17 octubre 2016 - 04:40

Yo también soy de que, para casos como éstos, en donde es mejor darle todo masticado.

Aunque prefiero y tiendo en lo posible en delegar las cosas al servidor de base de datos (que para eso está).

 

El problemilla de SQLite es que no soporta SPs asi que no queda otra que ir a una solución vía sistema.

Es muy importante lo que ha señalado Agustín. Los campos calculados se van "recalculando" cada tiempo, cuando se cambia el cursor, cada vez que se abre o cierra el dataset, etc. En esta situación en donde el cálculo es "fijo" e inmutable (y con mas razón dependiente de los registros anteriores) me parece sano que se utilice el método casero y simplemente hacer uno el trabajo. Es decir disponer de un SELECT para traer el conjunto de datos para luego mostrar la data y el campo saldo en un control como StringGrid (que dicho sea de paso no corre el peligro de alterar algún campo y estropear los datos originales ya que no es un db-ware)

 

No creo que te resulte complicado diseñar un algoritmo que lo haga. Ya antes di unas pistas de como encararlo.

 

Saludos,


  • 1

#9 Gaston

Gaston

    Member

  • Miembros
  • PipPip
  • 49 mensajes

Escrito 17 octubre 2016 - 06:19

Eureka! 


delphi
  1. procedure TfrmCtaCte.btnVerClick(Sender: TObject);
  2. var
  3. sqltxt:string;
  4. vSaldo:Real;
  5. vidcompras:integer;
  6. begin
  7. vProvID:=cmbProv1.KeyValue;
  8. sqltxt:='SELECT * FROM compras WHERE idprov='+IntToStr(vProvID)+' ORDER BY fecha;';
  9. vidcompras:=ZQcompras.FieldByName('id').AsInteger;
  10.  
  11. ZQcompras.Close;
  12. ZQcompras.SQL.Clear;
  13. ZQcompras.SQL.Text:=sqltxt;
  14. ZQcompras.Open;
  15. vidcompras:=ZQcompras.FieldByName('id').AsInteger;
  16. vSaldo:=ZQcompras.FieldByName('total').AsCurrency-ZQcompras.FieldByName('debe').AsCurrency;
  17. ShowMessage(CurrToStr(vSaldo));
  18. ZConnection1.ExecuteDirect('UPDATE compras SET saldo='''+CurrToStr(vSaldo)+''' WHERE id='+IntToStr(vidcompras)+';');
  19. vSaldo:=ZQcompras.FieldByName('total').AsCurrency-ZQcompras.FieldByName('debe').AsCurrency;
  20. While NOT ZQcompras.EOF do
  21. begin
  22. vSaldo:=vSaldo+ZQcompras.FieldByName('total').AsCurrency-ZQcompras.FieldByName('debe').AsCurrency;
  23. ZConnection1.ExecuteDirect('UPDATE compras SET saldo='''+CurrToStr(vSaldo)+''' WHERE id='+IntToStr(vidcompras)+';');
  24. ZQcompras.Next;
  25. vidcompras:=ZQcompras.FieldByName('id').AsInteger;
  26. end;
  27. ZQcompras.Refresh;
  28. DBGrid1.Refresh;
  29. end; 

La clave era algo tan sencillo como hacer el next sobre el query.... y luego arreglar todo lo demás. Como bien señaló Agustín estaba haciendo el next sobre el form.

Egostar: toda la razón del mundo.

Delphius, pensé en un stringgrid pero el tema, es que luego esa query tiene que ir a parar LazReport y no sé como hacerlo, conseguí poco y nada de documentación de ese componente, estoy "a los ponchazos" y "atando todo con alambre", el viernes tengo que presentar esta "Demo" para conseguir el trabajo, para el cual contaría con 6 meses y el cual encararía de otra forma.

 

Les estoy muy agradecido a todos, tema recontra solved.

 

P.D.: Si me quieren recomendar algún libro, por ahí uno de Delphi aplicado a bases de datos SQL, porque de Lazarus hay uno solo y ya lo compré.


  • 0

#10 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 585 mensajes
  • LocationArgentina

Escrito 17 octubre 2016 - 06:55

Libro de Delphi? La Cara Oculta de Delphi. Tengo entendido que el enlace es legal ya que el autor Ian Marteens lo ha cedido a ClubDelphi. Los moderadores pueden eliminar el link si estoy equivocado

 

Por otra parte, cual es el problema con el LazReport? Nunca lo he usado, pero seguramente sea orientado a TDataSets. Por lo tanto, lo que debe llegar al Report es, el Query si, pero a su vez es un DataSet

 

El DBGrid usa un DataSet y muestra los datos en un control estilo rejilla

El TStringGrid no tiene nada "automatico", simplemente es una rejilla que contiene y muestra una matriz de strings. No sabe leer automaticamente una fuente de datos

Un Reporte tambien usa un DataSet y "sabe", o tiene componentes que se colocan sobre el, que pueden leer y mostrar campos de un DataSet

 

Como ves, todo gira alrededor de TDataSet. No caigas en la falsa ilusion de que si no tenes el DBGrid te quedas sin programa. El verdadero poder esta en el TDataSet. El resto simplemente son "presentadores". Algunos mas faciles de usar que otros

 

Nada te impide utilizar un TListView por ejemplo y mostrar los datos de un DataSet en pantalla. Es un pequeño ciclo que recorre el DataSet y va colocando las propiedades necesarias. Es un poquillo de trabajo, nada del otro mundo

 

Luego, es cuestion de mandarle el mismo DataSet al Reporte para que haga su trabajo

 

Lo unico que tenes que hacer es mantener una referencia (una variable) a dicho DataSet, e irselo pasando a quien lo necesite


Editado por Agustin Ortu, 17 octubre 2016 - 06:57 .

  • 1

#11 Gaston

Gaston

    Member

  • Miembros
  • PipPip
  • 49 mensajes

Escrito 17 octubre 2016 - 07:31

Sí es legal, me lo había descargado ya pero no tuve tiempo, veo que ese es camino.

LazReport, efectivamente se alimenta de un DataSet, hasta ahora lo he usado sin mayores problemas para listados simples. El problema, o mejor dicho, mi problema, es la falta de guías completas de algunos componentes de Lazarus, como el caso de LazReport.

Me anoto lo de TListView, parece interesante.

 

 

 

Como ves, todo gira alrededor de TDataSet. 

 

Comienzo a darme cuenta eso.

 

Saludos. 


  • 0

#12 Delphius

Delphius

    Advanced Member

  • Administrador
  • 5.772 mensajes
  • LocationArgentina

Escrito 17 octubre 2016 - 07:38

Tu código puede pulirse un poco más. A simple vista rápida puedo decirte lo siguiente:

1. Después de abrir el Query con el SELECT y antes de empezar a operar por seguridad se recomienda aplicar un .First para obligar al dataset a ubicarse en el primer registro. Se que con el Open() se posiciona en el 1er registro, pero es habitual llevar un código seguro. Sobre todo cuando se va a iterar como en el while... antes del while, es importante asegurarse de que esté en el primer registro.

 

2. Te sugiero usar parámetros. Te evitas tener que lidiar con aplicas conversiones innecesarias. Por ejemplo en el UPDATE:


sql
  1. UPDATE compras SET saldo = :elSaldo WHERE id = :elID

Como puedes ver, se usa dos parámetros, y para ello se antepone los dos puntos.

 

Y para acceder a ellos se hace mediante ParamByName(), similar a como los para los campos. De este modo le puedes pasar por medio de AsCurrency el valor exacto y dejarle al componente que haga el trabajo.

 

Aunque no estoy seguro de si ExecuteDirect permite parámetros, y si es que el Zconnection tiene para ese caso el ParamByName. Mi primer pensamiento es que no lo soporta.

Una alternativa para este caso, y que al menos para mi quizá sea más segura y apropiada que usar ese ExecuteDirect, es emplear otro Query adicional que ejecute esta consulta UPDATE.

 

Basicamente es trabajar con 2 query, una para traer el conjunto original. Y una 2da adicional para aplicar las actualizaciones sobre este conjunto.

 

3. la variable vsaldo debería ser Currency y no del tipo real. Puede darte problemas de precisión más adelante. Currency es la vía segura cuando se trata de asuntos monetarios.

4. No te olvides de aplicar un Commit para finalizar la transacción con todas las operaciones de forma segura.

 

Y como dijo Agustín, los componentes data-ware como son el DBGrid, DBEdit, etc. no son más que un intermediario hacia el TDataSet. Quien tiene los datos es en realidad el TDataSet, ya que es la fuente que interactúa con la base de datos. Los diferentes Querys, Tables, StoredProcs, etc de las diferentes suites no dejan de ser un hijos descendientes de un TDataSet.

Los componentes visuales simplemente le piden el dataset con el que están vinculados que les pase el/los valor/es y para aplicarle cambios le envían la orden al dataset correspondiente.

 

Respecto a material de lectura, no se cuanto más te aporte La Cara Oculta de Delphi respecto al libro de Lazarus que tu compraste. Desconozco el contenido de ese libro de Lazarus.

La Cara Oculta de Delphi 4 es una referencia obligada para quien se meta en el mundo Object Pascal. Su contenido lo podrás sin problemas extrapolar a Lazarus. Pero como dije: no se que tanto contenido "nuevo" o "adicional" y que ya no esté dicho en el libro de Lazarus te pudiera dar.

Leer ambos no te va a hacer daño, te va a enriquecer más seguro. Lo que tiene de bueno La Cara Oculta es que pone mucho énfasis en el área de conectarse a base de datos. Ese plus es lo que estás buscando seguro, aunque me pregunto si es que en el libro de Lazarus no tendrá también sus capítulos sobre el tema.

 

En última, y lo que te recomiendo, es que cuando sea una duda muy puntual sobre ciertos componentes de Lazarus, oque  por aquí no le encontremos la vuelta, lo más sano es acudir al foro oficial. Yo en su momento tuve dudas muy puntuales sobre el uso de TBGRABitmap y y me respondieron muy amablemente ¡Incluso el propio creador fue quien salió en mi ayuda!. Tiene una comunidad muy participativa. Eso si: es en inglés, y si te cuesta un poco el idioma San Traductor de Google mal que bien se da a entender bastante bien.

 

Saludos,


  • 1

#13 cram

cram

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 774 mensajes
  • LocationMisiones, Argentina

Escrito 17 octubre 2016 - 08:03

Si quieres que aparezca el resultado de la función sum en cada una de las filas de la tabla, podrías usar una expresión como la siguiente:


sql
  1. SELECT * FROM
  2. (SELECT c.fecha, c.idprov, c.debe, c.detalle,c.debe),
  3. (SELECT SUM(COMPRAS.total-COMPRAS.debe) AS SUM_DIF FROM COMPRAS)
  4. FROM COMPRAS C
  5. ORDER BY c.idprov, c.fecha;

Disculpa puede contener errores, pero esa es la idea. Se trata de un select compuesto de otros select.

 

Saludos


  • 1

#14 cram

cram

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 774 mensajes
  • LocationMisiones, Argentina

Escrito 17 octubre 2016 - 08:06

No leí todo el hilo, más bien solo el problema.

Te recomiendo que la solución a los problemas de este tipo soluciones desde el código SQL y no desde el código del programa, te ahorrarás problemas y ganarás facilidad y flexibilidad.


  • 1

#15 Delphius

Delphius

    Advanced Member

  • Administrador
  • 5.772 mensajes
  • LocationArgentina

Escrito 17 octubre 2016 - 08:37

No he probado tu propuesta cram, mi pregunta es si en verdad esa consulta devuelve la diferencia acumulada.

Fíjate que en el ejemplo del primer post de Gaston en Saldo se va acumulando la diferencia entre el debe y el haber registro a registro.

 

Eso me hace dudar de si es posible llevar de forma "pura" en SQL sin recurrir a un SP.

 

Saludos,


  • 1

#16 Gaston

Gaston

    Member

  • Miembros
  • PipPip
  • 49 mensajes

Escrito 17 octubre 2016 - 10:01

Hola cram, te agradezco to código SQL, ni bien pueda lo pruebo y cuento como anduvo, ahora estoy con poco tiempo, pero cuando termine esta demo lo haré sin falta. De hecho lo intenté hacer algo parecido pero no me funciono, seguramente por mi limitado SQL.

 

Delphius, estuve hasta ahora luchando con el tema que muy bien anticipaste, lástima que no entré al foro antes, me volví loco con el formato, es decir, el código que puse funcionaba perfecto hasta que se encontraba con la presencia de números con decimales. Resumiendo lo solucioné con DecimalSeparator, pero la próxima probaré los parámetros :saldo como me sugieres pues parece mucho más simple, y tomo nota de los demás como .First y la posibilidad de trabajar con 2 querys. Estoy en ese foro también, entiendo perfectamente el inglés pero me cuesta escribirlo sin San Google... El libro de Lazarus, la verdad me sirvió mucho, pero no es un buen libro y la traducción al español es un desastre en todo sentido. A bases de datos le dedica un capítulo, de ahí que utilizo Zeos, de hacer los ejercicios del libro. "Iniciar con Lazarus y Free Pascal" es el libro, abarca demasiados temas, y ya los últimos temas parece que fueron escritos para que figuren en el índice.


delphi
  1. procedure TfrmCtaCte.btnVerClick(Sender: TObject);
  2. var
  3. sqltxt,updatetxt:string;
  4. vSaldo:Real;
  5. vidcompras:integer;
  6. sSaldo:string;
  7. begin
  8. vProvID:=cmbProv1.KeyValue;
  9. sqltxt:='SELECT * FROM compras WHERE idprov='+IntToStr(vProvID)+' ORDER BY fecha;';
  10. vidcompras:=ZQcompras.FieldByName('id').AsInteger;
  11. vSaldo:=0;
  12. ZQcompras.Close;
  13. ZQcompras.SQL.Clear;
  14. ZQcompras.SQL.Text:=sqltxt;
  15. ZQcompras.Open;
  16. vidcompras:=ZQcompras.FieldByName('id').AsInteger;
  17.  
  18. While NOT ZQcompras.EOF do
  19. begin
  20. vSaldo:=vSaldo+ZQcompras.FieldByName('total').AsFloat-ZQcompras.FieldByName('debe').AsFloat;
  21. DecimalSeparator:='.';
  22. sSaldo:= FloatToStrF(vSaldo,ffFixed,12,2);
  23. updatetxt:='UPDATE compras SET saldo='+sSaldo+' WHERE id='+IntToStr(vidcompras)+';';
  24. ShowMessage(FloatToStr(vSaldo)+' '+sSaldo+' '+updatetxt);
  25. ZConnection1.ExecuteDirect('UPDATE compras SET saldo='+sSaldo+' WHERE id='+IntToStr(vidcompras)+';');
  26. ZQcompras.Next;
  27. vidcompras:=ZQcompras.FieldByName('id').AsInteger;
  28. end;
  29. DecimalSeparator:=',';
  30. ZQcompras.Refresh;
  31. DBGrid1.Refresh;
  32. end;

Dejo es código porque nunca se sabe, por ahí le puede servir a alguien, si va en contra de alguna regla dejar tanto código, por favor me avisan.

 

Un dato que se me pasó por alto es que en la DB SQLite los campos (columnas) total, debe están definidos como REAL (12,2).

 

Saludos.


  • 0

#17 cram

cram

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 774 mensajes
  • LocationMisiones, Argentina

Escrito 18 octubre 2016 - 01:38

No he probado tu propuesta cram, mi pregunta es si en verdad esa consulta devuelve la diferencia acumulada.

Fíjate que en el ejemplo del primer post de Gaston en Saldo se va acumulando la diferencia entre el debe y el haber registro a registro.

 

Eso me hace dudar de si es posible llevar de forma "pura" en SQL sin recurrir a un SP.

 

Saludos,

 

Si Delphius, en efecto es lo que hace, muestra un único valor. Al parecer entendí mal. Aún así es posible utilizar una sentencia SQL, Claro que es mejor usar un procedimiento, como dices.


  • 0

#18 Gaston

Gaston

    Member

  • Miembros
  • PipPip
  • 49 mensajes

Escrito 18 octubre 2016 - 09:14

La verdad es que el código funciona y sirve para salir del apuro, pero cabe destacar que con apenas 100 registros (filas) demora unos 2 segundos desde que el usuario hace click hasta que se actualiza el DBGrid, por lo tanto, si no cierran el hilo, cuando pueda, me gustaría aportar una mejor solución.

 

Saludos.


  • 0

#19 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 585 mensajes
  • LocationArgentina

Escrito 19 octubre 2016 - 08:14

Sip, lo que te conviene hacer es desconectar temporalmente el DataSet del grid, hacer los cambios, y después conectar

Podés hacer eso de dos formas DataSet.DisableControls, procesar, DataSet.EnableControls

En tu caso como el cálculo se hace una sola vez, y el DataSet no tiene sentido sin ese cálculo, lo propio sería:

Obtener datos (realizar el query)
Procesar el saldo
Conectar el DataSet al grid

El último paso es simplemente un DataSource.DataSet := qry;

Obviamente que no tenés que tener esa conexión establecida en tiempo de diseño

En realidad el costo está en que el grid recorre y se vuelve a dibujar cada vez que cambia algo
  • 1

#20 Gaston

Gaston

    Member

  • Miembros
  • PipPip
  • 49 mensajes

Escrito 14 noviembre 2016 - 09:20

Lo prometido es deuda, he mejorado la técnica para calcular saldos, esta vez con un programa para llevar el control de bancos. El tema es simple, no recalcular los saldos recorriendo el ZQuery con un While Do que tarda un tiempo fenomenal, sino hacerlo con SQL. Me explico, estaba en un promedio de 10 registros por segundo, en una tabla que va a crecer en torno a los 1.000 registros por mes, indudablemente me iban a pegar una merecida patada. Haciendo todo con SQL con 100 registros no tarda ni un segundo.

La tabla reg pertenece a la DB bancos y tiene campos como: ID, FECHA, DEBITOS, CREDITOS Y SALDO. Si no menciono esto lo que sigue no se entenderá.

Primero llegué a la conclusión de que cuando se agrega un registro con fecha por ej. 05/11/2016 los saldos anteriores no hay que tocarlos sencillamente porque no se ven afectados. En base a eso decidí hacer una query para obtener el saldo del último registro a esa fecha que necesito para calcular el saldo del registro que voy a agregar mediante un zquery.insert porque el tiempo que demanda agregar una fila es inapreciable y el método me resulta cómodo.

Hasta ahí la mitad del problema resuelto, pero así como los saldos anteriores a la fecha del nuevo registro no afectan en nada, pasa lo contrario con los registros posteriores, a todos hay que sumarles o restarle el nuevo registro, y ahí venía el problema, resuelto ahora como verán en el código, con un simple UPDATE de SQL que no tarda nada.


delphi
  1. procedure TfrmReg.btnAgregarClick(Sender: TObject);
  2. var
  3. sqltxt:String;
  4. elbanco:Integer;
  5. hastafecha:string;
  6. saldoAgregar:Currency;
  7. saldoUpdate:Currency;
  8. begin
  9. hastafecha:=FormatDateTime('YYYY-MM-DD',dtpFecha.Date);
  10. elbanco:=cmbBanco.KeyValue; //Código de banco, integer
  11. sqltxt:='SELECT fecha, detalle, saldo FROM reg where banco=:elbanco AND fecha<=:hastafecha '+
  12. 'order by fecha,id;';
  13. DataM.ZQa.SQL.Text:=sqltxt;
  14. DataM.ZQa.Params.ParamByName('elbanco').AsInteger:=elbanco;
  15. DataM.ZQa.Params.ParamByName('hastafecha').AsString:=hastafecha;
  16. DataM.ZQa.Open;
  17. DataM.ZQa.Last;
  18. // Con esto encontré el saldo inmediatamente anterior
  19. saldoUpdate:=edDebito.Value-edCredito.Value; // Obtengo el número a sumar al saldo a las filas con fecha posterior
  20. saldoAgregar:=DataM.ZQa.FieldByName('saldo').AsCurrency+(edDebito.Value-edCredito.Value); // El saldo del registro a agregar
  21. DataM.ZQa.Close;
  22.  
  23. DataM.ZQReg.Close;
  24. DataM.ZQReg.Open;
  25. DataM.ZQReg.Insert;
  26. DataM.ZQReg.FieldByName('banco').AsInteger:=cmbBanco.KeyValue;
  27. DataM.ZQReg.FieldByName('fecha').AsDateTime:=dtpFecha.Date;
  28. DataM.ZQReg.FieldByName('cheque').AsInteger:=edCheque.Value;
  29. DataM.ZQReg.FieldByName('subcuenta').AsInteger:=cmbSubCta.KeyValue;
  30. DataM.ZQReg.FieldByName('detalle').AsString:=edDetalle.Text;
  31. DataM.ZQReg.FieldByName('vto').AsDateTime:=DTPVto.Date;
  32. DataM.ZQReg.FieldByName('debitos').AsCurrency:=edDebito.Value;
  33. DataM.ZQReg.FieldByName('creditos').AsCurrency:=edCredito.Value;
  34. DataM.ZQReg.FieldByName('saldo').AsCurrency:=saldoAgregar;
  35. DataM.ZQReg.Post;
  36.  
  37. //Aquí la actualización del resto de las filas con fecha posterior al agregado
  38. sqltxt:='UPDATE reg SET saldo=saldo+:saldoUpdate WHERE banco=:elbanco AND fecha>:hastafecha;';
  39. DataM.ZQa.SQL.Text:=sqltxt;
  40. DataM.ZQa.Params.ParamByName('elbanco').AsInteger:=elbanco;
  41. DataM.ZQa.Params.ParamByName('hastafecha').AsString:=hastafecha;
  42. DataM.ZQa.Params.ParamByName('saldoUpdate').AsCurrency:=saldoUpdate;
  43. DataM.ZQa.Open;
  44. DataM.ZQa.Close;
  45. end;

Luego el código para cuando se produce una modificación o una baja es similar.

Aclaro que he quitado algo de código referido a la validación de datos y actualización de un DBGrid para que no "distraiga". 

 

Saludos.


  • 0