Club Builder - Foros de discusión Club Builder
Foro de C++Builder en español
 
 FAQFAQ   BuscarBuscar   MiembrosMiembros   Grupos de UsuariosGrupos de Usuarios   RegistrarseRegistrarse 
 PerfilPerfil   Entra para ver tus mensajes privadosEntra para ver tus mensajes privados   EntrarEntrar 

Problemas con campos calculados, cambian de valor!

 
Publicar nuevo tema   Responder al tema    Foros de discusión -> Bases de Datos
Ver tema anterior :: Ver tema siguiente  
Autor Mensaje
Mensaje    Asunto: Problemas con campos calculados, cambian de valor! Responder citando

Buenas una vez más, he estado leyendo y leyendo y poco a poco voy avanzando en mi proyecto Very Happy.

Vereis he vsito que en un IBDataSet puedo "crear" campos calculados que no se almacenan físicamente en la tabla. Pues bien, he creado uno para mi tabla detalles "Detalles-presupuesto", para que calcule el subtotal del precio de un artículo por el número de unidades.
Para definirilo segui los pasos que encontre por el foro y el magnífico libro "The Dark Side of...": primreo creo el campo, le pongo un nombre y digo que es calculado. Luego en el evento OnCalcFields tengo puesto lo siguiente:

Código:

  frmNuevoPres->dataPresRec->FieldByName("Subtotal")->Value = edUnidades->Text.ToInt() * frmRecambios->dataRecambios->FieldByName("rec_pventa")->Value;


Donde dataPresRec es el IBDataSet detalles (detalles presupuesto), y dataRecambios sería la tabla artículos. Para que coja adecuadamente el artículo seleccionado, en el formulario del nuevo presupuesto, tengo puesto un dblookupcombobox apuntando a dicha tabla artículos, así al seleccionar un artículo ya se me posiciona en ese artículo y me devuelve el precio de venta para ese artículo y se podría calcular.

Para poder ver el contenido de la tabla detalles utilizo un dbgrid que contiene todos los campos de la tabla detalles mas el campo calculado.

En fin, al insertar el primer artículo, perfecto me calcula el Subtotal, inserto un segundo artículo perfecto, tambien me calcula el subtotal... pero ojo, me modifica el resto de subtotales!. Es decir, si en el primer artículo el SubTotal salía 56€, se almacena bien, sale en el grid el detalle más el subtotal de 56€, pero si vuelvo a insertar otro artículo y por ejemplo el subtotal es de 80€, tambien me lo guarda bien... pero el 56€ del artículo anterior me lo modifica y me pone 80€.

¿Que estoy haciendo mal?. ¿O acaso entendí mal la función de los campos calculados?

bueno, gracias una vez mas a todos y saludos!

EDITO; Por si os sirve como pista, si quiero editar un presupuesto ya dado, el campo Subtotal aparece 0 en cada línea de detalle... no entiendo que pasa Sad
Volver arriba
Ver perfil de usuario Enviar mensaje privado
Mensaje    Asunto: Responder citando

Hola ercea

Yo trabajo mucho con campos calculados y nunca he tenido ese problema. Algo anda mal por ahi en cuanto el enfoque y uso de los campos calculados.

Imaginemos el siguiente escenario:

Clasica situación maestro-detalle (factura)

Tabla Maestra
=========
IdFac
Fecha
SubTotal
IVA
Total

Tabla detalle
=========
Id
IdFac
IdPro
Cantidad
Precio

Supongamos que en la tabla detalle por cuestiones de almacenamiento no deseas guardar el importe de cada linea de detalle, es decir, el valor de multiplicar la cantidad de productos por su precio. En este caso has decidido usar un campo calculado en el IBDataset, asi que lo agregas (ese paso ya lo tienes), por lo que el IBDataSet quedaria asi:

IBDetalles
=========
Id
IdFac
IdPro
Cantidad
Precio
Importe (campo calculado)

En el evento OnCalcFields del IBDataset de los detalles agregas las siguientes lineas.

Código:
DataSet->FieldByName("Importe")->Value = DataSet->FieldByName("Cantidad")->Value  * DataSet->FieldByName("Precio")->Value;


El resultado por cada linea de detalle que tu agregues o modifiques seria:

Código:

Id     IdFac     IdPro     Cantidad     Precio     Importe (campo calculado)
==========================================================================
1        1             1          10      10.00     100.00
2        1             2          5       30.00     150.00
3        1             3          1       100.00    100.00


Si tu modificas ya sea la cantidad o el precio de cada linea, el campo caluclado hara su tarea y se actualizará con el importe correcto.

A grandes rasgos este es uno de los usos mas comunes de los campos calculados.

Si pudieras darnos más información sobre lo que intentas hacer será mucho mejor.

Saludos
_________________
Quien hace una pregunta teme parecer un ignorante durante cinco minutos, quien no pregunta se mantiene ignorante toda la vida.
Volver arriba
Ver perfil de usuario Enviar mensaje privado Enviar email MSN Messenger
Mensaje    Asunto: Responder citando

Hola Ercea:

Me he dado cuenta que tienes algunos problemas con respecto a los campos cualculados, por lo que veo usas InterBase o Firebird, bien lo que me gusta comentar siempre, es si me puedes dar mas detalles tecnicos, a veces cuando estas tratando de explicar con detalles técnicos, la solucion viene a la cabeza.

tienes un campo calculado dentro de un IBDataSet, si has visto el Libro de "The Darks Side of BCB" que comentas que si lo has echo, indica que son campos que se utilizan para mostrar información, que NO debe almacenarse en una tabla, una regla general de diseños de bases de datos dice " Todos quellos valores, que sean calculados por otros campos, NO deben ser almacenados como atributos o campos de una tabla", en este caso los resultados de una factura por estos lados de LatinoAmerica (México) se realizan 3 calculos con respecto a una factura (por que la factura?, por que es el mas claro ejemplo de una relacion Maestro - Detalle)

tu vendes 10 productos, a estos productos que te dan un GRAN TOTAL que es el pago de la factura, se divide en 2 opciones mas
SUB TOTAL y 15% de impuestos (IVA)

GRAN TOTAL = SUB TOTAL + IVA

Estos son campos calculados (espero no estar saliendo del tema)
ahora bien, los campos calculados deben y siempre declararse DENTRO de las tablas en el Motor de Base de Datos. para evitar las contrariedades que estas teniendo.


si estas manejando Interbase o Firebird que es lo que pienso puedes crear un campo calculado dentro de la tabla, como ejemplo pong una tabla para el calculo de creditos de algunos clientes de un sistema que he estado realizando

Código:
CREATE TABLE CREDITO_DETALLE (
    CREDITO_ID INTEGER,
    SALDO_CREDITO FLOAT,
    SALDO_APAGAR FLOAT,
    ABONO FLOAT,
    SALDO_ACUBRIR COMPUTED BY (SALDO_APAGAR - ABONO),
    FECHAMOVIMIENTO DATE,
    ABONO_ID INTEGER NOT NULL);


como puedes ver el campo SALDO_ACUBRIR es un campo calculado declarado dentro de la tabla, usando la opcion COMPUTED BY.


En tu caso, usando un Detalle-presupuesto que es la tabla a la cual le creas el campo, quiero pensar e imaginar que los campos que tienes por que son los MAS habitualesr para generar el presupuesto son:

--------Campos que NO son considerados Calculados --------
Id_Presupuesto // campo para hacer la relacion con la tabla maestra
Id_Detalle // campo de incremento para cada uno de los registros del detalle
Id_Producto // para saber que producto es
Precio_Producto // este campo SI deves almacenarlo
Cantidad // tambien para realizar el calculo
--------Campos que SI son considerados Calculados --------
SubTotal // este es un campo calculado de la tabla o del IBDataSet


Reitero en tu campo Precio_Producto, ya que es el que NO se debe perder, cuando generas el evento OnCalc de la base
recuerda que estas posicionado en un registro de los articulos, por lo tanto al calcular de nuevo los SubTotales, te va a tomar siempre el valor rec_pventa donde te encuentres, por ello debes almancenar el valor del rec_pventa en la tabla detalles_presupuesto, quedando de la siguiente manera
Código:
Precio_Producto -> frmRecambios->dataRecambios->FieldByName("rec_pventa")->Value;
Cantidad -> edUnidades->Text.ToInt()


y en el evento OnCalc defines la operacion de la siguiente manera:

Código:
frmNuevoPres->dataPresRec->FieldByName("Subtotal")->Value = frmNuevoPres->dataPresRec->FieldByName("Cantidad")->Value * frmNuevoPres->dataPresRec->FieldByName("Precio_Producto")->Value;


Nota: el evento OnCalc genera un recorrido de la tabla donde estas, desde el primer registro hasta el ultimo algo asi como un:
Código:
While(! frmNuevoPres->dataPresRec->Eof)
{
  "SubTotal = Cantidad * Precio_Producto;"
  ....
}


pero si lo haces con el codigo que tienes esto queda imaginariamente asi:

Código:
While(! frmNuevoPres->dataPresRec->Eof)
{
  "frmNuevoPres->dataPresRec->FieldByName("Subtotal")->Value = edUnidades->Text.ToInt() * frmRecambios->dataRecambios->FieldByName("rec_pventa")->Value;"
  ....
}


y recuerda que si no te has movido en la tabla articulos ("frmRecambios->dataRecambios->FieldByName("rec_pventa")->Value;"")

este valor se calcula en todos los campos de la tabla Detalles-Presupuesto, modificandote asi TODOS los valores y asignadote el mismo valor en todos.

Saludos y espero que esta sea la solucion a tus demandas.
_________________
Cool
Volver arriba
Ver perfil de usuario Enviar mensaje privado Enviar email MSN Messenger
Mensaje    Asunto: Responder citando

Muchísimas gracias a los dos por proporcionarme tanta información. En primer lugar, sí, uso Firebird con componentes IBX.
Ahora estoy retocando una cosa que me he fijado en vuestras respuestas. Y es que en la tabla detalles no almacenaba el precio del recambio, sino que lo cogía de la tabla recambios.

Mis tablas maestra-detalles son :

presupuesto: pres_codigo, pres_clt, pres_fecha, pres_caducidad, pres_desc, pres_total, pres_aceptado

pres_detalles: pres_codigo, pres_rec, pres_unidades

Ambos habeis comentado sobre un id para los detalles, pero yo he pensado en poner como clave primaria el el codigo del presupuesto mas el codigo del recambio... no se si estareis de acuerdo en ello Rolling Eyes

Como veis no almaceno el precio... no sé supuse que no hacía falta porque teniendo el codigo de recambio podía averiguarlo... pero veo que es necesario almacenarlo xD. Además con lo último que has comentado César, entiendo ahora el porqué me sucede. Porque si hace un recorrido como dices, entonces coge el precio del último recambio seleccionado y lo multiplica por las unidades (que lo coge de un editbox) y por eso sale el mismo precio en todas las líneas.

Así que nada, ahora mismo añadiré el precio y el campo calculado dentro de la tabla y ya os comentaré los resultados, aunque seguro que ese es el fallo xD.

Gracias por vuestra dedicación Wink

saludos!

EDITO: Éxito, ahora si me almacena correctamente los datos Very Happy, gracias!!
Volver arriba
Ver perfil de usuario Enviar mensaje privado
Mensaje    Asunto: Responder citando

Que tal ercea, que bueno que has solucionado el problema.

Uno de los motivos de porque es necesario almacenar el precio en la tabla de detalles es el siguiente:

Imagina que has hecho una factura con un solo producto, el precio de dicho producto es de 50 (creo que trabajas con euros)... pues bien, resulta que ha sido necesario cambiar el precio del producto y ahora vale 70.

Si quisieras hacer una consulta de facturas ¿cual sería el precio del producto que has vendido en esa factura? ¿50 o 70?.

Al guardar el precio en la tabla detalles siempre sabras en cuanto fue vendido cada producto, independientemente del cambio de precios que se puedan dar.

Saludos!
_________________
Quien hace una pregunta teme parecer un ignorante durante cinco minutos, quien no pregunta se mantiene ignorante toda la vida.
Volver arriba
Ver perfil de usuario Enviar mensaje privado Enviar email MSN Messenger
Mensaje    Asunto: Responder citando

Tienes toda la razón buildero, no había caido en eso y hubiera cometido un grave error si no hubiera añadido el precio :S. Lo tendré en cuenta en próximas ocasiones Wink

gracias y saludos!
Volver arriba
Ver perfil de usuario Enviar mensaje privado
Mensaje    Asunto: Responder citando

Hola, me gustaría comentaros otra cosa sobre este tema de los presupeustos. Vereis tengo un problemilla y no sé exactamente el porqué me pasa.

Para la inserción/edición de presupuestos, tengo un dblookupcombobox para elegir cliente, otro para elegir el artículo (recambios), y luego los dbedits para el tema de descuento, unidades, etc. A parte tengo el famoso grid para los detalles, más dos botones uno para insertar un detalle en el grid (tabla) y otra para quitarlo. Mi idea fue que todo se trabaje con transacciones, es decir, voy añadiendo y eliminando artículos en un presupuesto dado, pero realmente hasta que no doy a "nuevo presupuesto" no hago un CommitRetaining() en el componente ibtransaction para que se almacene en la tabla.

Pues bien, cuando inserto, elijo las unidades y pulso el botón "añadir" y se añade al grid, perfecto. Si elijo ahora un artículo del grid y pulso "Quitar" se elimina del grid, perfecto.

Ahora bien, si vuelvo a elegir un artículo que ya estaba en el grid (pero lo habia eliminado), al pulsar el botón "añadir" me sale la siguiente excepcion:

"(...) raised exception class EIBInterBaseError with message "lock conflict on no wait transaction violation of FOREIGN KEY constraint "FK_PRES_REC_REC" on table "PRES_REC". Foreign key target does no exist"

FK_PRES_REC_REC es una restricción de integridad que creé en la tabla PRES_REC para indicar que el código del artículo (recambio) pertenece a la tabla Recambios.... y la verdad no entiendo este error :S. Os copio el código de "Añadir" y el de "quitar" respectivamente:

Añadir:

Código:
  float Subtotal;

  try{
    if(comboRecambios->Text == "")
      throw PresInvalido("Debe seleccionar un recambio");
    if(edUnidades->Text == "0" || edUnidades->Text == "")
      throw PresInvalido("Debe elegir las unidades");
    if(edUnidades->Text.ToInt() > frmRecambios->dataRecambios->FieldByName("rec_stock")->Value)
      throw PresInvalido("No hay stock suficiente, hay " + frmRecambios->dataRecambios->FieldByName("rec_stock")->Value + " unidades disponibles");
    if(!SQLPres->IsEmpty())//el SQL se ejecuta al seleccionar un recambio
      throw PresInvalido("El artículo seleccionado ya está en la lista");

//almacenamos el código de la tabla maestra
    dataPresRec->FieldByName("pres_codigo")->Value = frmPres->dataPres->FieldByName("pres_codigo")->Value;

  //almacenamos en la tabla detalles el artículo seleccionado
    dataPresRec->FieldByName("pres_precio")->Value = frmRecambios->dataRecambios->FieldByName("rec_pventa")->Value;
    dataPresRec->FieldByName("pres_unidades")->Value = edUnidades->Text.ToInt();
  //el código del artículo lo coge del dblookupcombobox de recambios
  //confirmamos la inserción (AQUI es cuando salta la excepción!!)
 
    dataPresRec->Post();

    Subtotal = gridDetalles->Fields[4]->Value;
//Y actualizamos el Total
    Total += Subtotal;
    TotalDesc = Total * (100 - edDesc->Text.ToDouble())/100;

    lblTotal->Caption = FormatFloat("0.00",TotalDesc);

    //restamos el stock
    frmRecambios->dataRecambios->Edit();
    frmRecambios->dataRecambios->FieldByName("rec_stock")->Value = frmRecambios->dataRecambios->FieldByName("rec_stock")->Value - edUnidades->Text.ToInt();
    frmRecambios->dataRecambios->Post();

//dejamos la tabla detalles preparada para la inserción de más artículos
    dataPresRec->Append();
    cmdQuitar->Enabled = true;
  }
  catch(Exception &E)
//el catch es un messagedlg indicando un error


Para la función Quitar tenemos:
Código:

  float Subtotal;

  if(!dataPresRec->IsEmpty()) //si no está vacía podemos eliminar
  {
    //cogemos el subtotal del campo calculado
    Subtotal = gridDetalles->Fields[4]->Value;

    //Restaurmaos el stock
    frmRecambios->dataRecambios->Edit();
    frmRecambios->dataRecambios->FieldByName("rec_stock")->Value = frmRecambios->dataRecambios->FieldByName("rec_stock")->Value + dataPresRec->FieldByName("pres_unidades")->Value;
    frmRecambios->dataRecambios->Post();

  //Borramos de la tabla PRES_REC (tabla detalles) el artículo seleccionado
    dataPresRec->Delete();

//Actualizamos el Total
    Total -= Subtotal;
    TotalDesc = Total * (100 - edDesc->Text.ToDouble())/100;

    lblTotal->Caption = FormatFloat("0.00",TotalDesc);

//Si la tabla se queda vacía deshabilitamos el botón Quitar
    if(dataPresRec->IsEmpty())
      cmdQuitar->Enabled = false;
  }
  else
    MessageDlgX("ATENCIÓN: La lista está vacía",mtError,TMsgDlgButtons()<<mbOK>dataPresRec->Append();


No sé, por más que miro... no sé qué puede fallar Embarassed , y lo curioso es eso, que salte con un artículo que antes estaba, pero lo elimino y lo vuelvo a insertar....

En fin, espero que podais ayudarme (una vez mas Embarassed ), gracias y saludos!

EDITO: Se me olvidó comentar, que el IBTransaction relacionado con la tabla detalles lo tenga en ReadCommited pero en AutoStopAction lo tengo puesto en None, así cuando ya tenga la lista preparada, pulso el botón "Nuevo Presupeusto" y entre otras cosas, confirmo con CommitRetaining() al IBTransaction de la tabla detalles.

Con tanto tocho, no sé si os he dado inforamción suficiente o he hablado demasiado Embarassed
Volver arriba
Ver perfil de usuario Enviar mensaje privado
Mensaje    Asunto: Responder citando

antes que le muevas a tu diseño de la base de datos, comentame como estas usando los siguiente componentes, para asi poder determinar el error que esta saltandote, mira yo uso los siguiente componentes, para que te des una idea,

Código:
1.- IBDataBase -

Código:
1.- IBTransaction -
propiedades:
DefaultAction = TACommitRetaining
DefaultDataBase = IBDataBase de arriba
AllowAutoStart = true
Params =
{
read_committed
rec_version
nowait
}

cuantas tablas uso cuantos IBDataSet pongo
Código:
(n) IBDataSet
con las propiedades siguientes
DataBase = Componente IBDataBase
Transaccion = Componete IBTransaccion (al tomar la base automaticamente se establece este componente)



y cada que hago post en una tabla uso la siguiente declaracion, es un poco de mas codigo pero me permite controlar mejor las transacciones, pues soy yo quien las abre pero el servidor las cierra cuando indico que realice la transaccion, el proceso CommitRetaining me sirve para grabar y mantener habierto el data set, si uso Commit se cierra y tengo que volver abrir la base de datos del componente IBDataBase

Código:
      IBDataSet1->Post();
      IBDataSet1->ApplyUpdates();
      IBDataSet1->Transaction->CommitRetaining();


comentame si es asi como las tienes?

Saludos y espero tus respuestas, o si hay alguna variación te recomiendo checar este post
_________________
Cool
Volver arriba
Ver perfil de usuario Enviar mensaje privado Enviar email MSN Messenger
Mensaje    Asunto: Responder citando

Hola César te comento (ese hilo que me has pasado también lo he leido xD):

1 - Tengo un componente IBDatabase en el formulario principal

2 - Para cada tabla de mi BD, tengo un IBDataSet con las propiedades InsertSQL, DeleteSQL, etc establecidas

3 - Para cada tabla (es decir para cada IBDataSet) tengo un IBTransaction. Todas, excepto la tabla detalles presupuesto, tienen las propiedades:

Código:

Read Commited con los parámetros: read_committed, rec_version, nowait y write (para que no haga falta hacer CommitRetaining)

AllowAutoStart = true
AutoStopAction = CommitRetaining
DefaultAction = CommitRetaining

El IBTransaction que tiene la tabla detalles presupuesto tiene las propiedades:

Código:

Read Commited conlos parámetros: read_committed, rec_version, nowait

AllowAutoStart = true
AutoStopAction = saNone
DefaultAction = CommitRetaining


A cada IBDataSet, le asigno su correspondiente IBTransaction. Entonces, en todas las tablas, menos en la tabla detalles, con hacer Post me basta para almacenar en la BD, mientras que en la tabla detalles, si te fijas en el código que he puesto más arriba, voy haciendo Post y Append conforme añado más artículos, y para quitarlos hago Delete y Append (para dejarlo preparado para añadir otro articulo después), y ya cuando pulso "Nuevo presupuesto" hago un CommitRetaining a la tabla detalles, y luego un Post a la tabla maestra.

No aplico ApplyUpdates, lo acabo de probar y me saltó una excepción de que no tenía CacheUpdates activado, lo activé y funciona, pero me sigue fallando lo de siempre (quitar y volver poner el mismo artículo falla).
¿Puede que el fallo esté a la hora de quitar artículos (boton quitar) o sino en la sentencia DeleteSQL de la tabla detalles? Rolling Eyes :

Código:

DELETE
FROM Pres_Rec
WHERE pres_codigo = :OLD_PRES_CODIGO AND pres_rec = :OLD_PRES_REC;


Bueno, creo que nome dejo nada, gracias por tu interés César Wink

saludos!
Volver arriba
Ver perfil de usuario Enviar mensaje privado
Mensaje    Asunto: Responder citando

En primer lugar siento el doble post. Tan solo comentaros que aparentemente se ha arreglado el error, y ya me deja reinsertar artículos en un mismo presupuesto... sin embargo el método para solucionarlo no me convence mucho.

Vereis, en principio recordad que mi tabla Detalles Presupuestos era:

pres_detalles: pres_codigo, pres_rec, pres_precio, pres_unidades

Entonces, tenía puesto como restricciones de integridad referencial que pres_codigo se una CF de la tabla Presupuestos, y que pres_rec es una CF de la tabla Recambios. Con esto así, me salía la excepción que os comente antes.

"(...) raised exception class EIBInterBaseError with message "lock conflict on no wait transaction violation of FOREIGN KEY constraint "FK_PRES_REC_REC" on table "PRES_REC". Foreign key target does no exist"

Observando el mensaje, pues me dio por eliminar la restriccion de integridad referencial de recambios, es decir he quitado la restricción que decía que pres_rec era CF de la tabla Recambios. Ahora añado por código, el campo pres_rec. Inserto artículos, los elimino... vuelvo a insertar un artículo eliminado... confirmo el presupuesto... y funciona!!

Pero no me convence esta solución por dos motivos: el primero que no sé exactamente el por qué me funciona... y el segundo motivo es por cuestiones de diseño... ¿hice bien en indicar como CF el campo pres_rec o hice mal en eliminarlo xD?

En fin, d emomento lo dejo como está que me está haciendo perder demasiado tiempo y tengo que hacer mas cosas.

gracias a todos como siempre, y saludos!!

EDITO: Vuelvo a postear por si a alguien le interesa. He solucionado por fin e problema con restricciones y todo. El problema, un cruce de transacciones.... tenia asignado a los datasets de presupuesto y detalles un mismo componete de transaccion... pero en la tabla articulos tenia su propio componente de transacción. Entonces, añadia un aticulo a la lista de detalles y decremetnaba el stock... esto implicaba activarse dos transacciones y al decrementar el stock salía esa excepción que os comenté en las anteriores respuestas.
Solución: asignar a todas las tablas el mismo componeten de transacción así solo se activa una en esas operaciones y puedo confirmar (con commit) o cancelar (con rollback) y se mantiene todo como estaba.

saludos!
Volver arriba
Ver perfil de usuario Enviar mensaje privado
Mostrar mensajes de anteriores:   
Cambiar a:  
No puedes publicar temas nuevos en este foro
No puedes responder a temas en este foro
No puedes editar tus mensajes en este foro
No puedes borrar tus mensajes en este foro
No puedes votar en las encuestas de este foro


Hosting WordPress Indedmedia desde 3,99 €
© 2002–2026 Club Builder · Funciona con phpBB.