Page 1 of 2
Facturas con consecutivos con acceso a varias terminales
Posted: Thu Aug 26, 2021 10:58 am
by Adolfredo Martinez
Buena como puede controlar uno, cuando varias personas intentan guardar una factura en el mismo instante en mysql o en sql. Intente con un contador en una tabla pero siempre se crea varias con el mismo numero cuando se intenta acceder al mismo tiempo desde 2 pc. Hay alguna forma o algun algoritmo para controlar eso y que se asignen consecutivos validos sin perder consecutivos en el camino?. Osea bloquear el registro, cuando el termine libéralo, para que otro lo tome, sin perder el consecutivo.
Diciendo de otra forma, para que me entienda:
Estoy generando un consecutivo trayendo el último de la tabla y sumandole 1, el problema es que si se realiza la petición mas de una vez al mismo tiempo, el código seria el mismo para las peticiones que se hagan.
Gracias por la atención prestada.
Adolfredo Martinez
Re: Facturas con consecutivos con acceso a varias terminales
Posted: Thu Aug 26, 2021 11:11 am
by karinha
Re: Facturas con consecutivos con acceso a varias terminales
Posted: Thu Aug 26, 2021 4:21 pm
by Armando
Adolfredo:
No te sirve un campo auto incrementable para el número de folio? y
tal vez apoyándote en una transacción
Saludos
Re: Facturas con consecutivos con acceso a varias terminales
Posted: Thu Aug 26, 2021 5:22 pm
by carlos vargas
Code: Select all | Expand
STATIC FUNCTION Cobr_Grabar( lNuevo )
LOCAL lGrabado := FALSE
LOCAL cWhere
oServer:lThrowError := TRUE
TRY
oServer:BeginTransaction()
IF lNuevo .and. ( nNumero := IncCount( "control", "cont_cobr" ) ) > 0
oServer:Insert2( "cobradores", { { "num_cobr" , nNumero }, ;
{ "nombre" , cNombre }, ;
{ "cedula" , cCedula }, ;
{ "ciudad" , cCiudad }, ;
{ "direccion" , cDireccion }, ;
{ "telefonos" , cTelefonos }, ;
{ "fdi" , dFechaI }, ;
{ "activo" , lActivo }, ;
{ "fdr" , dFechaR }, ;
{ "nota" , cNota } } )
ELSE
cWhere := "num_cobr=" + Var2Str( nNumero )
oServer:Update2( "cobradores", { { "nombre" , cNombre }, ;
{ "cedula" , cCedula }, ;
{ "ciudad" , cCiudad }, ;
{ "direccion" , cDireccion }, ;
{ "telefonos" , cTelefonos }, ;
{ "fdi" , dFechaI }, ;
{ "activo" , lActivo }, ;
{ "fdr" , dFechaR }, ;
{ "nota" , cNota } }, cWhere )
ENDIF
oServer:Commit()
oQryCobr:Refresh()
lGrabado := TRUE
CATCH oError
ShowError( oError )
oServer:Rollback()
END
oServer:lThrowError := FALSE
IF lGrabado
IIf( lNuevo, ( oBrw:GoBottom(), oBrw:Refresh() ), oBrw:RefreshCurrent() )
ELSE
oBrw:GoTop()
ENDIF
RETURN lGrabado
Code: Select all | Expand
FUNCTION IncCount( cTable, cField )
LOCAL oQryTmp
LOCAL nCount := 0
oQryTmp := oServer:Query( "SELECT " + cField + " FROM " + cTable + " FOR UPDATE" )
IF oQryTmp:RecCount() >= 0
nCount := oQryTmp:FieldGet( 1 ) + 1
oServer:Execute( "UPDATE " + cTable + " SET " + cField + " = " + Var2Str( nCount ) )
ENDIF
oQryTmp:End()
RETURN nCount
CREATE TABLE `control` (
`cont_usua` BIGINT(10) UNSIGNED NOT NULL DEFAULT '0',
`cont_ruta` BIGINT(10) UNSIGNED NOT NULL DEFAULT '0',
`cont_cobr` BIGINT(10) UNSIGNED NOT NULL DEFAULT '0',
`cont_clie` BIGINT(10) UNSIGNED NOT NULL DEFAULT '0',
`cont_pres` BIGINT(10) UNSIGNED NOT NULL DEFAULT '0',
`cont_abon` BIGINT(10) UNSIGNED NOT NULL DEFAULT '0',
`cont_docu` BIGINT(10) UNSIGNED NOT NULL DEFAULT '0',
`my_recno` BIGINT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`my_recno`) USING BTREE
)
COLLATE='latin1_spanish_ci'
ENGINE=InnoDB
Re: Facturas con consecutivos con acceso a varias terminales
Posted: Fri Aug 27, 2021 12:06 am
by Adolfredo Martinez
Gracias por responder.
Carlos Vargas, gracias por esa líneas de código, me pondré a la mano, les avisare como me fue.
Re: Facturas con consecutivos con acceso a varias terminales
Posted: Sat Sep 04, 2021 9:47 pm
by Adolfredo Martinez
Con el ejemplo de Carlos, logre solucionar el problema del consecutivo de la factura. Pero tengo otra inquietud:
Como logro si dos terminales la A: esta insertando o borrando Facturas del cliente 100. Pero en la terminal B: selecciono el mismo cliente pero haciendo otra actividad ya sea borrando una factura o modificándola. Como logro bloquear el terminal B, hasta que termine la terminal A. mando le un mensaje que esta ocupada por otro usuario. Para cuando entra la terminal B, ya encuentre la modificacion hecha por la terminal A.
Re: Facturas con consecutivos con acceso a varias terminales
Posted: Wed Sep 08, 2021 1:47 pm
by armando.lagunas
Estimado:
lo que yo utilizo en SQL es un campo lógico (0-1) como una bandera, terminal A: toma el registro y le cambio el estado a ese campo inmediatamente a 0 (ocupado) y cuando termino los cambios realizados en el registro le devuelvo el estado 1 (Activo), entonces en la terminal B verifica el estado de este campo antes de hacer cualquier cosa en el registro.
lo otro es realizar procedimiento almacenado en la base de datos estableciendo SET TRANSACTION ISOLATION LEVEL READ COMMITTED que es lo que permite bloquear temporalmente registros en una base de datos SQL
busca en internet como utilizarlo hay varios LEVEL para analizar y utilizar
Espero que te sirva
Saludos
Re: Facturas con consecutivos con acceso a varias terminales
Posted: Wed Sep 08, 2021 5:42 pm
by Adolfredo Martinez
Gracias Armando.
Me pondré mano a la obra, soy nuevo en esto de Mysql, gracias algunos amigos del fórum, he salido adelante.
Re: Facturas con consecutivos con acceso a varias terminales
Posted: Wed Sep 08, 2021 10:31 pm
by JoseAlvarez
Saludos Adolfredo.
Yo lo hago asi y nunca he tenido problemas. Funciona con cualquier numero de estaciones en la red.
Code: Select all | Expand
cTabla:= _cPrefijo+'_docventa' //nombre de tu tabla
cQuery:=''
cQuery:="SELECT * FROM "+ cTabla + " FOR UPDATE; " // seleccionamos el UNICO registro de la tabla con la clausula FOR UPDATE
oQuery:= _oSqlConex:Query( cQuery ) // este "FOR UPDATE" hace un bloqueo y ninguna otra estacion puede accesar
// mientras se realiza la operacion de sumar uno al consecutivo y tomarlo
// en variable de memoria
nNumeroFactura:=oQuery:numero_factura+1 // Sumamos 1 al valor del contador guardado en la tabla
cQuery:=''
cQuery:="Update "+cTabla+" Set " // aqui actualizamos el valor del campo en la tabla ya incrmentado en 1
cQuery+="numero_factura:="+alltrim(str(nNumeroFactura))+"; "
_oSqlConex:Execute( cQuery )
_oSqlConex:End // al cerrar la conexcion, se libera el registro para los demas usuarios.
La clausula FOR UPDATE es la que hace toda la magia. Por supuesto, siempre debes hacer el SELECT primero.
En mi caso uso una tabla con un solo registro por eso el SELECT no lleva un WHERE, puedes adaptarlo a tu situacion.
Re: Facturas con consecutivos con acceso a varias terminales
Posted: Thu Sep 09, 2021 12:47 pm
by TecniSoftware
Hola Jose
Queria hacerte una pregunta porque me dejaste con dudas:
_oSqlConex:End // al cerrar la conexcion, se libera el registro para los demas usuarios.
El registro se libera al cerrar _oSqlConex o al cerrar oQuery ???
Porque si tuvieras que cerrar la _oSqlConex deberias volver a reconectarte a cada rato para poder seguir trabajando.
Muchos saludos
Alejandro
Re: Facturas con consecutivos con acceso a varias terminales
Posted: Thu Sep 09, 2021 7:02 pm
by JoseAlvarez
Saludos alejandro.
Hago mis sistemas para que puedan trabajar con datos hospedados tanto local como Remoto (en la nube).
Por eso mi filosofia de trabajo es:
1.- conectar
2.- consultar/actualizar
3.- desconectar
Localmente puedes tener una conexion con MYSQL largo rato o puedes configurarlo para eso, pero remotamente (en la nube) el servidor te desconecta a los 30seg. de inactividad. Por lo menos en el servidor que yo uso (hostgator).
Por eso conecto y desconecto a cada rato sin importar si estoy trabajando en cualquiera de las formas. Mis sistemas asi quedan listos para trabajar local o remoto y se configura al momento de instalarlos.
Cuando voy a hacer varias actualizaciones las hago con transacciones y alli conecto y desconecto solo una vez, ya que mientras se ejecutan las operaciones el servidor se mantiene activo.
Cuando utilizas FOR UPDATE se libera el registro al cerrar la conexcion, o al realizar un COMMIT en el caso de usar transacciones.
Disculpen por no haber colocado un ejemplo menos personalizado.
Gracias por la observacion.
Un Abrazo.
Re: Facturas con consecutivos con acceso a varias terminales
Posted: Fri Sep 10, 2021 4:31 pm
by carlos vargas
Alejandro,
La idea es, si en lugar de cerrar la conexion, utilizas transacciones
al realizar el commit o el rollback, se libera el registro seleccionado con el SELECT ... FOR UPDATE...
salu2
carlos vargas
Re: Facturas con consecutivos con acceso a varias terminales
Posted: Mon Sep 13, 2021 11:56 am
by VictorCasajuana
armando.lagunas wrote:Estimado:
lo que yo utilizo en SQL es un campo lógico (0-1) como una bandera, terminal A: toma el registro y le cambio el estado a ese campo inmediatamente a 0 (ocupado) y cuando termino los cambios realizados en el registro le devuelvo el estado 1 (Activo), entonces en la terminal B verifica el estado de este campo antes de hacer cualquier cosa en el registro.
lo otro es realizar procedimiento almacenado en la base de datos estableciendo SET TRANSACTION ISOLATION LEVEL READ COMMITTED que es lo que permite bloquear temporalmente registros en una base de datos SQL
busca en internet como utilizarlo hay varios LEVEL para analizar y utilizar
Espero que te sirva
Saludos
Hola Armando.
Este tema siempre me ha intrigado y a veces me pongo a buscar info pero nunca llego a buen puerto. En tu caso lo tienes bien controlado ya que modificas la tabla. No me gusta mucho tener que modificar la estructura de datos para este fin pero se podría aceptar como solución. Pero me surge una duda, si el terminal A toma el registro, cambias la bandera a 1 y se cierra el programa sin pasar por el proceso de guardar la ficha, que haces? por ejemplo se puede apagar el ordenador, error de ejecución, etc...
aquí hay una buena charla que tuvimos hace meses al respecto:
https://forum.modharbour.app/viewtopic.php?f=25&t=259
Re: Facturas con consecutivos con acceso a varias terminales
Posted: Mon Sep 13, 2021 4:06 pm
by JoseAlvarez
VictorCasajuana wrote:armando.lagunas wrote:Estimado:
lo que yo utilizo en SQL es un campo lógico (0-1) como una bandera, terminal A: toma el registro y le cambio el estado a ese campo inmediatamente a 0 (ocupado) y cuando termino los cambios realizados en el registro le devuelvo el estado 1 (Activo), entonces en la terminal B verifica el estado de este campo antes de hacer cualquier cosa en el registro.
lo otro es realizar procedimiento almacenado en la base de datos estableciendo SET TRANSACTION ISOLATION LEVEL READ COMMITTED que es lo que permite bloquear temporalmente registros en una base de datos SQL
busca en internet como utilizarlo hay varios LEVEL para analizar y utilizar
Espero que te sirva
Saludos
Hola Armando.
Este tema siempre me ha intrigado y a veces me pongo a buscar info pero nunca llego a buen puerto. En tu caso lo tienes bien controlado ya que modificas la tabla. No me gusta mucho tener que modificar la estructura de datos para este fin pero se podría aceptar como solución. Pero me surge una duda, si el terminal A toma el registro, cambias la bandera a 1 y se cierra el programa sin pasar por el proceso de guardar la ficha, que haces? por ejemplo se puede apagar el ordenador, error de ejecución, etc...
aquí hay una buena charla que tuvimos hace meses al respecto:
https://forum.modharbour.app/viewtopic.php?f=25&t=259
Hola Victor, saludos.
Veo que el tema es recurrente, por lo que me tome el tiempo para hacer dos ejemplos sencillos que a mi me han funcionado muy bien. Jamas he tenido problema con mis sistemas en este aspecto.
Lo primero es aclarar que toda la magia la hace la instruccion
SELECT.. FOR UPDATE... y esto significa 3 cosas:
1.- Siempre hay que hacer un
SELECT... FOR UPDATE previo al
UPDATE ... SET eso es lo que nos va a bloquear el registro seleccionado.
sin permitir que nadie mas tenga acceso a el.
2.- Para que
SELECT... FOR UPDATE funcione debemos tener el
AUTOCOMMIT en 0 (OFF o FALSE) segun la version de mysql o mariadb.
si no se desea tener configurado de manera permanente, se puede activar o desactivar mientras hacemos la operacion.
3.- En caso de no querer usar el AUTOCOMMIT, la otra opcion mas comoda y tambien valida, es usar una transaccion. El caso o gusto particular de cada quien indicara
cual sistema usar, si
AUTOCOMMIT o
TRANSACCION.
Aca dejo los ejemplos:
USANDO AUTOCOMMITCode: Select all | Expand
# include FIVEWIN.CH
Function Fnct_Prueba()
Local oQuery, cQuery, cTabla
Local nNuevoNumero
_oSqlConex:=TDolphinSrv():New( _cHost, _cUser, _cPasswordUser,,, _cDataBase,bSQLCnxErr() ) // mis variables de conexion son publicas.
// cada quien debe manejar esta parte
if _oSqlConex:lError // segun su forma de trabajar.
_oSqlConex:End()
return .f.
endif
cQuery := "SET AUTOCOMMIT=0 ; " // colocamos el AUTOCOMMIT temporalmente en 0 (desactivado)
oQuery := _oSqlConex:Execute( cQuery ) // de esta forma AUTOCOMMIT funciona solo con la sesion actual
// en caso de que no este configurado por defecto
cTabla := _cPrefijo+'_docventatemp'
cQuery := ''
cQuery := "SELECT numerotemp_documento FROM "+ cTabla +" FOR UPDATE ; " // hacemos el SELECT... FOR UPDATE para bloquear la fila de
oQuery := _oSqlConex:Query( cQuery ) // la tabla
nNuevoNumero:=oQuery:numerotemp_documento+1 // aumentamos en 1 el contador consecutivo
cQuery :=''
cQuery :="Update "+cTabla+" Set "
cQuery +="numerotemp_documento:="+alltrim(str(nNuevoNumero))+" ; " // actualizamos la tabla
_oSqlConex:Execute( cQuery )
//msginfo('Stop') // activar si se desea chequear la tabla con phpmyadmin, heidi, navicat, etc. durante el proceso.
cQuery:="commit ; " // hacemos commit para actualizar la tabla.
oQuery:= _oSqlConex:Execute( cQuery )
cQuery := "SET AUTOCOMMIT=1 ; " // Colocamos el autocommit de nuevo a su estado activo
oQuery := _oSqlConex:Execute( cQuery )
_oSqlConex:End() // cerramos la conexion
return nNuevoNumero // nNuevoNumero es el numero asignado a la nueva factura que estamos emitiendo ...
Usando TransaccionesCode: Select all | Expand
# include FIVEWIN.CH
Function Fnct_Prueba()
Local oQuery, cQuery, cTabla
Local nNuevoNumero
_oSqlConex:=TDolphinSrv():New( _cHost, _cUser, _cPasswordUser,,, _cDataBase,bSQLCnxErr() ) // mis variables de conexion son publicas.
// cada quien debe manejar esta parte
if _oSqlConex:lError // segun su forma de trabajar.
_oSqlConex:End()
return .f.
endif
_oSqlConex:BeginTransaction() // comenzamos la transaccion
cTabla := _cPrefijo+'_docventatemp'
cQuery := ''
cQuery := "SELECT numerotemp_documento FROM "+ cTabla +" FOR UPDATE ; " // hacemos el SELECT... FOR UPDATE para bloquear la fila de
oQuery := _oSqlConex:Query( cQuery ) // la tabla
nNuevoNumero:=oQuery:numerotemp_documento+1 // aumentamos en 1 el contador consecutivo
cQuery :=''
cQuery :="Update "+cTabla+" Set "
cQuery +="numerotemp_documento:="+alltrim(str(nNuevoNumero))+" ; " // actualizamos la tabla
_oSqlConex:Execute( cQuery )
//msginfo('Stop') // activar si se desea chequear durante el proceso.
_oSqlConex:CommitTransaction()
_oSqlConex:End() // cerramos la conexion
return nNuevoNumero // nNuevoNumero es el numero asignado a la nueva factura que estamos emitiendo ...
En ambos casos, el registro se libera al ejecutarse el
COMMIT.
Esto es solo una idea de como lo hago, cada quien puede adaptarlo a su modo.
Espero haber ayudado. Quizas algun colega le consiga fallas o pueda sugerir mejoras. Todo es bienvenido. Pero como comento, jamas he tenido problemas con esta manera de obtener consecutivos sin que se repitan, no importa el numero de estaciones que accesen al mismo tiempo la tabla.
Re: Facturas con consecutivos con acceso a varias terminales
Posted: Tue Sep 14, 2021 6:28 am
by VictorCasajuana
Vale, me había confundido. Pensaba que el bloqueo que comentabas al principio ( 0 en el campo lógico ) lo utilizabas cuando un usuario, por ejemplo, abría una ficha para modificar los datos, para que el resto de usuarios no pudiesen modificar dicha ficha o al menos saber de su edición por otro usuario.
Lo que expones es correcto, pero ya es a nivel de transacciones dentro de un proceso automático el cual no está controlado por el usuario.