Conectándose con ADO a las bases de datos

6 comentarios

Como seguramente sabes, para conectarte a una Base de Datos de Firebird puedes utilizar varios métodos, uno de ellos es ADO (ActiveX Data Objects). Fue desarrollado por Microsoft y por lo tanto se usa principalmente con Windows.

Con ADO se pueden crear tablas, borrar tablas, insertar filas, modificar filas, borrar filas, consultar filas, etc., todas las operaciones normales.

El método más usado para conectarse a bases de datos de Firebird creo que es ODBC pero he leído que ADO es más rápido. No lo puedo afirmar ni negar porque aún no he realizado pruebas intensivas ni he encontrado alguna página donde las hayan hecho y muestren los resultados.

Lo primero que debes hacer si quieres conectarte por ADO es conseguir un proveedor, aquí encontrarás uno de ellos:

http://www.ibprovider.com/eng/documentation/firebird_interbase_odbc_drivers.html

ADO1

Captura 1. Si haces clic en la imagen la verás más grande

Después de descargarlo e instalarlo ya estará listo para ser usado.

Gracias a la colaboración de Jaume (ver los comentarios más abajo) también disponemos del IBOLE.DLL el cual dicen que es muy bueno. Puedes descargarlo desde:

http://www.mediafire.com/download/7hpf8ppyudpnn4k/IBOLE.dll

¿Cómo se utiliza ADO?

Primero, debes conectarte a la Base de Datos

Segundo, debes obtener un RecordSet, o sea un conjunto resultado con los datos que te interesan

Tercero, puedes manipular esos datos como desees

Cuarto, debes cerrar el RecordSet

Quinto, debes cerrar la conexión

Ejemplo. Mostrar los nombres de los clientes

En nuestra Base de Datos tenemos una tabla llamada CLIENTES y queremos obtener los nombres de los mismos, este pequeño programita desarrollado con Visual FoxPro cumplirá esa  tarea:

CLEAR

CLOSE ALL

CLEAR ALL

Private oSQL, oRecordSet

*--- Primero, se conecta a la Base de Datos

oSQL = CreateObject("ADODB.Connection")

with oSQL
   .Provider = "LCPI.IBProvider.3.Lite"
   .ConnectionString = "User=SYSDBA;password=masterkey;Data Source=E:\SQL\DATABASES\BLOG_DB.FDB;auto_commit=true"
   .Open()
endwith

*--- Segundo, se crea un RecordSet

oRecordSet = CreateObject("ADODB.RecordSet")

*--- Tercero, se obtienen los datos que nos interesan

oRecordSet.Open("Select * From CLIENTES",oSQL)

*--- Cuarto, se procesan los datos obtenidos

do while !oRecordSet.EOF
   ? oRecordSet.Fields.Item("CLI_NOMBRE").Value
   oRecordSet.MoveNext()
enddo

*--- Quinto, se cierran y se eliminan los objetos que se habían creado

oRecordSet.Close()

oSQL.Close()

Release oRecordSet, oSQL

Después de ejecutarlo el resultado obtenido será algo similar a esto:

ADO2

Captura 2. Si haces clic en la imagen la verás más grande

Como puedes ver, en la pantalla se mostraron los nombres de los 4 clientes cuyos datos estaban guardados en la tabla respectiva.

A diferencia de ODBC aquí no obtendrás un cursor para trabajar con él. Un cursor es una tabla .DBF temporal y desde mi punto de vista es más útil porque puede ser indexada, mostrada en un browse, etc. La forma de usar ADO me parece más antigua, más manual, aunque sobre gustos…

Desde luego que siempre puedes enviar el contenido de los recordsets a cursores o a tablas .DBF pero sería un trabajo adicional y ¿vale la pena? eso ya no lo sé porque dependerá de tus circunstancias.

Propiedades, métodos y eventos de ADO

ADO tiene muchísimas propiedades, métodos y eventos que puedes usar, en el ejemplo anterior se mostró solamente una pequeñísima parte de ellos. Si te interesa el tema hay varias páginas con información útil en Internet, una de ellas es la siguiente:

http://www.w3schools.com/ado/default.asp

Conclusión:

Si quieres, puedes usar ADO para conectarte a una Base de Datos pero yo hasta ahora no le he encontrado alguna ventaja. Quizás sea más rápido que ODBC y en ese caso sí podría justificarse conectarse mediante ADO, pero eso es algo que aún no he verificado.

Usar cursores es más rápido y más sencillo (para mí, claro) que estar obteniendo los datos fila por fila. Esto lo notarás principalmente cuando quieras imprimir informes porque mientras un cursor o una tabla .DBF ya están preparados para ser usados en informes, los recordsets no lo están y deberán prepararse previamente y habría que ver si vale la pena el esfuerzo.

De todas maneras, siempre es bueno tener varias alternativas para hacer cualquier cosa y disponer de ADO aumenta nuestras posibilidades de conexión a las bases de datos, así que bienvenido.

Descargas de archivos .DLL para usarlos como Proveedor de ADO

http://www.ibprovider.com/eng/documentation/firebird_interbase_odbc_drivers.html

http://www.mediafire.com/download/7hpf8ppyudpnn4k/IBOLE.dll

Artículos relacionados:

w3schools.com

El índice del blog Firebird21

Anuncios

Entendiendo subconsultas y tablas derivadas

6 comentarios

El lenguaje SQL nos permite escribir un SELECT en muchos lugares, veamos algunos ejemplos:

Ejemplo 1. Un SELECT simple

SELECT
   MiColumna1,
   MiColumna2
FROM
   MiTabla

En este caso se escribió solamente un SELECT, es útil para consultas sencillas pero cuando las consultas se complican ya no será suficiente.

Ejemplo 2. Un SELECT con una subconsulta

SELECT
   MiColumna1,
   (SELECT MiColumna2 FROM MiTabla2 WHERE MiCondición)
FROM
   MiTabla

En este ejemplo hemos reemplazado a MiColumna2 por una subconsulta, o sea por un SELECT dentro de otro SELECT. El SELECT que está entre paréntesis es la subconsulta. Fíjate que además del nombre de la tabla se escribió la cláusula WHERE ¿por qué eso? porque la subconsulta debe devolver una sola columna y una sola fila.

Si la subconsulta devuelve más de una columna entonces verás el error: “Count of column list and variable list do not match”. O sea que se esperaba una sola columna pero la subconsulta está devolviendo más de una columna.

Si devuelve una sola columna pero más de una fila entonces el mensaje de error que verás será: “Multiple rows in singleton select”. ¿qué significa esa frase? que la subconsulta debería devolver una sola fila pero está devolviendo más de una fila.

La cláusula WHERE sirve justamente para poner una condición que estás seguro que se cumple para una sola fila. Además de la cláusula WHERE también podrías usar, dependiendo del caso, las cláusulas FIRST 1, ó ROWS 1 ó DISTINCT o las funciones agregadas MAX(), MIN(), etc. Lo que debes recordar es que la subconsulta debe devolver una fila y solamente una fila. Puede devolver cero filas, y en ese caso el valor de la columna será NULL (en SQL un valor de NULL significa “valor desconocido”) pero jamás más de una fila.

Ejemplo 3. Un SELECT a continuación del FROM

SELECT
   MiColumna1,
   MiColumna2
FROM
   (SELECT MiColumna1, MiColumna2 FROM MiTabla)

Aquí el SELECT interno se escribió después de la cláusula FROM. En estos casos no se le llama subconsulta sino que se le llama tabla derivada.

Para que funcione, la cantidad de columnas de la tabla derivada siempre debe ser igual o mayor a la cantidad de columnas del SELECT principal, nunca puede ser menor (y a la tabla principal si lo deseas puedes agregarle columnas que tengan valores constantes, como 17, 21, ‘Asunción’, etc.). Y los nombres de las columnas deben coincidir, no puedes usar una columna en el SELECT principal que no hayas usado en la tabla derivada.

Fíjate que la subconsulta debe devolver una sola columna pero la tabla derivada puede devolver varias columnas.

Ejemplo 4. Una subconsulta en la cláusula GROUP BY

SELECT
   MiColumna1,
   (SELECT MiColumna2 FROM MiTabla2 WHERE MiCondición)
FROM
   MiTabla
GROUP BY
   MiColumna1,
   (SELECT MiColumna2 FROM MiTabla2 WHERE MiCondición)

En este caso, la subconsulta se usó también luego de la cláusula GROUP BY, eso es necesario porque todas las columnas que se incluyen en el SELECT principal y que no son funciones agregadas también deben encontrarse a continuación de la cláusula GROUP BY.

Ejemplo 5. Una subconsulta en la cláusula ORDER BY

SELECT
   MiColumna1,
   MiColumna2
FROM
   MiTabla
ORDER BY
   (SELECT MiColumna3 FROM MiTabla2 WHERE MiCondición)

Aquí como puedes ver la subconsulta se escribió a continuación de la cláusula ORDER BY y por lo tanto el SELECT principal será ordenado por el contenido de la subconsulta.

Ejemplo 6. Un subconsulta en el JOIN

SELECT
   MiColumna1,
   MiColumna2
FROM
   MiTabla
JOIN
   MiTabla2
      ON MiColumna = (SELECT MiOtraColumna FROM MiOtraTabla)

También puede usarse una subconsulta en el JOIN, sea éste del tipo que sea (INNER, LEFT, RIGHT, FULL, CROSS, NATURAL)

Ejemplo 7. Insertando, modificando o borrando filas desde una subconsulta

Si necesitas insertarle datos a una tabla y los datos que necesitas se encuentran en otra u otras tablas la forma más rápida es hacerlo con una subconsulta:

INSERT INTO
MiTabla
   (MiColumna1, MiColumna2, MiColumna3)
   SELECT MiValor1, MiValor2, MiValor3 FROM MiOtraTabla

También puedes usar una subconsulta para establecer la condición que necesitas para modificar o borrar filas. En general se usan subconsultas cuando las filas que se quiere insertar, modificar o borrar son muchas.

Conclusión:

Como seguramente habrás deducido luego de ver los ejemplos anteriores, las subconsultas pueden usarse en muchos lugares, prácticamente puedes usarlas en cualquier lugar donde necesites el valor de una columna.

Hay muchos ejemplos más que podría escribir pero con los anteriores supongo que ya tienes bien clara la idea.

¿Es conveniente usar subconsultas?

A veces sí, a veces no. En ocasiones es lo mejor que puedes hacer y en ocasiones hay mejores alternativas así que debes tratarlas como una herramienta más, algo que te puede ayudar a conseguir los resultados que estás buscando. Pero debes recordar que en general usar subconsultas es más lento que no usarlas y por lo tanto debes siempre verificar que no exista una mejor opción.

Artículos relacionados:

Tablas derivadas

El índice del blog Firebird21

Usando un “connection pool”

2 comentarios

Cuando la cantidad de usuarios es grande y los recursos del Servidor son pocos puedes utilizar un “connection pool” para que todos esos usuarios puedan estar conectados a pesar de los pocos recursos disponibles.

Esta es una técnica que hace años se utilizó bastante pero que está decayendo porque la memoria y las redes son relativamente baratas entonces suele ser más sencillo y más rápido invertir en hardware para aumentar los recursos del Servidor y de esa manera solucionar el problema.

Aunque claro, eso no siempre es posible, hay muchos casos en que usar un “connection pool” es la mejor alternativa. Por ejemplo si los usuarios que se conectan son miles o millones y lo hacen a través de Internet. Allí, tener un Servidor que pueda manejar esas miles o millones de conexiones puede ser demasiado costoso. Y entonces, un “connection pool” es la solución a los tres problemas típicos:

  1. La cantidad de conexiones que puede manejar el Servidor siempre es finita, es limitada
  2. Cada conexión demora en abrirse y en cerrarse, no es instantánea
  3. Cada conexión consume recursos (memoria) de la computadora donde se encuentra el Servidor

¿Qué es un “connection pool”?

Es una técnica para que un Servidor con pocos recursos pueda manejar muchísimas conexiones. De esta manera se solucionarían los tres problemas ennumerados arriba.

¿Cómo funciona?

Las aplicaciones (Contabilidad, Facturación, Sueldos, Ventas, etc.) envían sus solicitudes al “connection pool” y éste las envía al Servidor.

Lo normal es que las aplicaciones envíen sus solicitudes al Servidor. Pues aquí no ocurre así, sino que ocurre esto:

CONNECTION1

Diagrama 1. Si haces clic en la imagen la verás más grande

El “connection pool” mantiene varias conexiones abiertas permanentemente a la Base de Datos. Cuando una aplicación realiza una solicitud el programa verifica si alguna de esas conexiones está libre. Si lo está, la utiliza. Si no hay conexiones libres entonces realiza una nueva conexión.

Cuando una conexión ha estado inactiva mucho tiempo (digamos, 10 minutos) y es mayor que el número mínimo de conexiones entonces la cierra.

Ejemplo:

Una empresa tiene 1.000 usuarios que se conectan diariamente a su Base de Datos. En promedio cada día 950 de esos usuarios realizan 120 transacciones cada uno, cada una de esas transacciones demorando 100 milisegundos. Los restantes 50 usuarios realizan 40 transacciones diarias, cada una de esas transacciones demorando 900 milisegundos.

Entonces tenemos que:

950 * 120 * 100 / 1000 / 60 / 60 = 3 horas + 0,166666667 * 60= 3 horas y 10 minutos

50 * 40 * 900 / 1000 / 60 / 60 = 0, 50 * 60 = 30 minutos

Tiempo total = 3 horas y 10 minutos más 30 minutos = 3 horas y 40 minutos

El Servidor estuvo encendido durante 8 horas pero solamente estuvo activo 3 horas y 40 minutos.

El “connection pool” se configuró para mantener 50 conexiones abiertas. Si se necesitan más las abre pero cuando esas conexiones adicionales están inactivas durante más de 10 minutos, las cierra.

Entonces, cuando una aplicación envía una solicitud el “connection pool” verifica si hay alguna conexión inactiva. Si la hay, la utiliza. Si no la hay, abre una nueva conexión.

Después que el “connection pool” entregó el resultado de la solicitud a la aplicación no cierra la conexión sino que la deja libre para que pueda ser usada por otra solicitud.

De esta manera hay 50 conexiones abiertas permanentemente y quizás algunas más abiertas temporalmente. Cuando una aplicación realiza una solicitud el “connection pool” verifica si alguna de esas conexiones está inactiva. Si es así la utiliza, ahorrando tiempo y memoria, porque como ya estaba abierta no es necesario abrirla.

¿Cómo conectarse a la Base de Datos?

En este artículo habíamos visto tres formas de establecer conexión a la Base de Datos:

https://firebird21.wordpress.com/2013/12/23/sobre-conexiones-y-desconexiones/

cuando se usa un “connection pool” lo correcto es usar la opción número 3. es decir: abrir_la_conexión, realizar_la_tarea, cerrar_la_conexión

Conclusión:

Cuando los usuarios son muchos y los recursos del Servidor son pocos se puede invertir en hardware para aumentar esos recursos o utilizar un “connection pool”. Este es un programa que se encarga de administrar las conexiones a la Base de Datos. Mantiene siempre una cierta cantidad de conexiones abiertas y así cuando alguna aplicación envía una solicitud trata de usar una de esas conexiones abiertas. Si no es posible porque todas están ocupadas entonces abre una nueva conexión. Cuando una conexión ha estado inactiva durante mucho tiempo, la cierra.

Las aplicaciones no se conectan a la Base de Datos sino que se conectan al “connection pool”, el cual se encarga de enviar las solicitudes al Servidor y de recibir los resultados.

Para que esta técnica funcione las aplicaciones siempre deben abrir_la_conexión, realizar_la_tarea, cerrar_la_conexión porque si mantienen las conexiones abiertas esta técnica no funcionará.

Artículos relacionados:

Sobre conexiones y desconexiones

El índice del blog Firebird21

 

Sobre conexiones y desconexiones

7 comentarios

¿Cuándo conectarse y cuándo desconectarse de la Base de Datos?

Aquí tenemos varias posibilidades, analizaremos lo que implica cada una de ellas:

  1. Conectarse cuando la aplicación (Contabilidad, Facturación, Ventas, Sueldos, etc.) inicia y desconectarse cuando la aplicación finaliza
  2. Conectarse cuando el formulario se inicia y desconectarse cuando finaliza
  3. Conectarse para realizar una tarea y desconectarse cuando la tarea finaliza

¿Cuál elegir?

En general y en casi todos los casos la mejor opción es la número 1. ¿Por qué? porque tener una conexión abierta no puede causarle daño a la Base de Datos y eso de estar abriendo y cerrando conexiones sin necesidad solamente acarreará pérdida de tiempo. Abrir una conexión o cerrar una conexión no es instantáneo, toma unos cuantos milisegundos hacerlo. Puede parecer muy poco pero si lo multiplicas por la cantidad de veces que un usuario lo hace en un día y a ese resultado lo multiplicas por la cantidad de usuarios y a ese resultado lo multiplicas por la cantidad de días laborables del mes puedes encontrarte con muchos minutos o inclusive muchas horas desperdiciadas por estar abriendo y cerrando conexiones sin necesidad.

Pero hay casos en los que sí es conveniente abrir_conexión, realizar_tarea, cerrar_conexión ¿cuáles son esos casos? cuando la señal es débil o es mala o siempre se realiza por Internet. Por ejemplo si alguien está viajando y se conecta al Servidor de la empresa desde su notebook (también llamada laptop) y sufre frecuentes cortes de Internet. O cuando la conexión siempre es a través de Internet (el caso de las aplicaciones “en la nube”).

Pero cuando se trata de una red local que funciona bien no tiene sentido eso de abrir_conexión, realizar_tarea, cerrar_conexión porque ningún beneficio se obtiene de ello y sí perjuicios: por un lado pérdida de tiempo como ya habíamos visto y por el otro que no se podrían usar tablas GTT de conexión, solamente se podrían usar tablas GTT de transacción.

Conclusión:

Si la conexión a la Base de Datos es a través de una red local entonces siempre lo más conveniente es usar la opción 1.

La opción 3 suele ser la más conveniente cuando los cortes de conexión son frecuentes, eso podría suceder cuando alguien está viajando y usando una notebook para conectarse a la Base de Datos que se encuentra en el local de la empresa.

También habría que usar la opción 3. cuando los usuarios siempre o mayormente se conectan por Internet a la Base de Datos.

Lo mejor técnica entonces es que desde tu aplicación el usuario pueda elegir el método de conexión. Aquellos usuarios que lo hacen a través de una red local deberían tener habilitada la opción 1. y aquellos que lo hacen a través de Internet deberían tener habilitada la opción 3. Y siempre deberían tener la posibilidad de cambiarse de la una a la otra.

Artículo relacionado:

El índice del blog Firebird21

Consultas con columnas condicionales

2 comentarios

A veces en nuestras consultas necesitamos mostrar columnas cuyos contenidos provienen de otras tablas y además lo hacen en forma condicional.

Ejemplo:

En una tabla MOVIMCAB tenemos una columna llamada MVC_TIPMOV la cual nos indica el tipo de movimiento (‘ECM’ es una entrada por compras, ‘SVT’ es una salida por ventas, etc.) y donde también guardamos el Identificador del Proveedor (cuando se trata de una compra) o el Identificador del Cliente (cuando se trata de una venta).

Ahora, necesitamos una consulta que nos muestre el nombre del Proveedor o el nombre del Cliente, según corresponda. Para ello haremos uso de la construcción CASE … WHEN … END

SELECT
   MVC_FECHAX,
   MVC_NRODOC,
   MVC_TIPMOV,
   CASE
      WHEN MVC_TIPMOV = 'ECM' THEN (SELECT PRO_NOMBRE FROM PROVEEDORES WHERE PRO_IDENTI = MVC_IDEPRO)
      WHEN MVC_TIPMOV = 'SVT' THEN (SELECT CLI_NOMBRE FROM CLIENTES    WHERE CLI_IDENTI = MVC_IDECLI)
   END AS NOMBRE
FROM
   MOVIMCAB
WHERE
   MVC_TIPMOV IN ('ECM', 'SVT')

Y este es el resultado (parcial, por supuesto) que obtuvimos:

CONDICIONAL1

Captura 1. Si haces clic en la imagen la verás más grande

Como puedes ver, en la columna NOMBRE tenemos el nombre de un Proveedor (cuando se trata de una compra) o el nombre de un Cliente (cuando se trata de una venta). Y para saber de cual de ellos se trata podemos mirar el contenido de la columna MVC_TIPMOV.

ERROR: Multiple rows in singleton select

Si ves ese mensaje de error significa que el SELECT interno devolvió más de una fila pero debería haber devuelto solamente una fila (o ninguna, pero jamás más de una).

¿Cuál es el problema?

Que si el SELECT devuelve varias filas el Firebird no puede saber cual de esas filas es la correcta. Por ejemplo, si el SELECT devuelve el nombre de 500 clientes ¿cuál de esos 500 nombres es el que debe mostrar? imposible que el Firebird pueda saberlo y por lo tanto te muestra un mensaje de error.

Para evitar el error siempre tienes que asegurarte que la condición que pusiste en la cláusula WHERE limite el resultado a una sola fila.

En el ejemplo de arriba se usó el Identificador del Proveedor o del Cliente. Como esos identificadores son únicos y jamás pueden repetirse entonces estamos seguros de que jamás obtendremos el error “multiple rows in singleton select”. Sin embargo cuando no usas identificadores ni claves primarias ni claves únicas la posibilidad de encontrar ese error siempre existirá.

Artículo relacionado:

El índice del blog Firebird21

Usando transacciones con acceso exclusivo a tablas

5 comentarios

Hay algo que debes tener muy claro: en Firebird no se recomienda que una transacción tenga el acceso exclusivo a una o más tablas. Si normalmente necesitas tener acceso exclusivo a las tablas evidentemente aún no entiendes como funciona Firebird y deberías leer mucho sobre las transacciones y su manejo.

Sin embargo esta regla tiene algunas (poquísimas) excepciones. Una de esas excepciones es cuando necesitas numerar documentos en forma consecutiva y sin que falte algún número. O asignar códigos consecutivos y sin que existan códigos faltantes.

Para casos como los anteriores no puedes simplemente usar un generador (también llamado secuencia) porque si la transacción termina con un ROLLBACK el generador no regresa a su valor anterior. Recuerda que los generadores están afuera de las transacciones.

Ejemplo (usando un generador):

  • El último número de Factura guardado es el 12956
  • Se inicia una transacción, como usa un generador asigna el valor 12957 a la nueva Factura, pero por algún motivo la Factura no pudo ser grabada y la transacción termina con un ROLLBACK.
  • Se inicia otra transacción, la cual ahora asigna el valor 12958 a la nueva Factura. Esta transacción sí finalizó con un COMMIT

Luego cuando se ejecuta un SELECT se ve que la siguiente Factura después de la 12956 es la 12958. ¿Y la Factura 12957? ¿Qué pasó con ella? No está, no existe, no la encontramos ni buscándola con lupa.

El problema que ocurrió aquí fue causado por haber usado un generador para numerar a las Facturas. Y es que los generadores jamás retroceden en su valor, siempre van al siguiente, sin importar como haya terminado la transacción (con un COMMIT o con un ROLLBACK). Eso es así porque los generadores son independientes de las transacciones, están afuera de ellas.

Bueno, en realidad sí se puede alterar el valor de un generador pero no es aconsejable hacerlo porque es muy propenso a crear conflictos.

La solución: acceso exclusivo a una tabla

Para casos como los del ejemplo anterior es que Firebird nos da la posibilidad de acceder a las tablas en forma exclusiva. De esta manera encontrar la solución es muy fácil:

  1. En una tabla (llamada por ejemplo CONFIGURACION) tenemos guardado el último número de Factura
  2. Abrimos una transacción con la cláusula RESERVING y por lo tanto todas las tablas que se nombren después de la cláusula RESERVING se abrirán en forma exclusiva.
  3. Le sumamos 1 (uno) al último número de Factura que tenemos guardado en la tabla CONFIGURACION
  4. Utilizamos ese número para numerar a nuestra nueva Factura
  5. Ejecutamos los INSERTs correspondientes a nuestras tablas cabecera y detalles de Facturas
  6. Cerramos la transacción

Si la transacción finalizó exitosamente con un COMMIT, todo bien, siguiendo el ejemplo anterior ahora el último número de Factura guardado en la tabla CONFIGURACION será el 12957.

Si la transacción finalizó con un ROLLBACK tampoco hay problema, porque el último número de Factura volverá a su valor anterior de 12956.

O sea que sin importar como termine la transacción (con un COMMIT o con un ROLLBACK) en la tabla de CONFIGURACION siempre tendremos registrado el último número correcto de Factura.

¿Es necesario usar una tabla adicional?

Una pregunta que quizás te hayas hecho es:

¿Es necesario usar una tabla adicional (llamada CONFIGURACION en nuestro ejemplo), no podríamos simplemente buscar el último número de Factura guardado en nuestra tabla cabecera de Ventas y sumarle 1 (uno)?

Es necesario usar una tabla adicional. Buscar el último número de Factura guardado en la tabla cabecera de Ventas y sumarle (1) uno sería una muy mala idea. ¿Por qué? Porque si varios usuarios están guardando ventas podría ocurrir algo como:

  • El usuario Juan busca el último número de Factura, halla que es el 12956, le suma 1 y su Factura será la 12957
  • El usuario Ricardo busca el último número de Factura, halla que es el 12956, le suma 1 y su Factura será la 12957
  • El usuario Andrés busca el último número de Factura, halla que es el 12956, le suma 1 y su Factura será la 12957
  • El usuario Ricardo termina su transacción con un COMMIT, todo bien para él, ningún problema
  • El usuario Andrés quiere terminar su transacción con un COMMIT pero obtiene un error: “Número de Factura ya existe”
  • El usuario Juan quiere terminar su transacción con un COMMIT pero obtiene un error: “Número de Factura ya existe”

¿Se puede solucionar? Claro, como la transacción del usuario Juan encontró que el número de Factura ya existía entonces vuelve a leer el último número de Factura (que ahora será el 12957) y vuelve a sumarle 1 (uno). Si tiene suerte esta vez podrá finalizar con un COMMIT. Pero … ¿y si un milisegundo antes la transacción de Andrés hizo lo mismo? Pues la transacción de Juan no podrá finalizar con un COMMIT. Y tendrá que volver a leer el último número de Factura guardado en la tabla cabecera de Ventas (que ahora será el 12958), tendrá que volver a sumarle 1 (uno) y tendrá que volver a intentar el COMMIT … sólo para descubrir que ahora la transacción de Matías ya usó ese número. Y seguirá así hasta que por fin en algún momento pueda finalizar con su dichoso COMMIT.

Si son solamente dos o tres personas las que están registrando las ventas entonces es muy poco probable que te encuentres con esta clase de conflictos, pero ¿y son cientos o miles las personas que están insertando filas en la misma tabla y al mismo tiempo?

Pues allí sí que tendrás problemas mayúsculos. O le muestras un mensaje al usuario: “Estimado, no puedo grabar tu venta porque por un tonto error de mi parte permití que ese mismo número de Factura lo grabe otra persona” o haces que la transacción continúe intentando el COMMIT hasta lograrlo … alguna vez.

Entonces … ¿por qué cornos complicarte la vida si la solución es tan sencilla? Utilizas una tabla donde registras el último número de Factura y listo.

Es cierto que esa tabla puede también estar bloqueada por otra transacción pero en cuanto se desbloquee podrás obtener el número de Factura que corresponde a tu venta.

Abriendo la transacción:

Para abrir una transacción que te otorgue acceso exclusivo a una tabla para cambiar algún valor de esa tabla podrías escribir algo como:

SET TRANSACTION READ WRITE READ COMMITTED RESERVING CONFIGURACION FOR PROTECTED WRITE

Como puedes ver a continuación de la cláusula RESERVING se escribió el nombre de la tabla (CONFIGURACION, en este ejemplo) que se quiere reservar. Si se quiere reservar varias tablas entonces se escriben sus nombres separados por comas.

NOTA: También se puede acceder a una tabla en forma exclusiva si se abre la transacción como SNAPSHOT TABLE STABILITY pero esa sí que sería una pésima idea porque en ese caso se estaría bloqueando las tablas cabecera de ventas y detalles de ventas a todos los usuarios. Nadie podría no solamente insertar sino tampoco actualizar o borrar.

Conclusión:

En Firebird no se recomienda tener acceso exclusivo a una tabla. Si normalmente requieres tener acceso exclusivo entonces lo estás haciendo mal y no entiendes como funcionan las transacciones en Firebird. Sin embargo, hay algunos poquísimos casos en que sí es necesario el acceso exclusivo y es por ese motivo que tenemos la posibilidad de hacerlo. En este artículo hemos visto un ejemplo de uno de esos poquísimos casos.

NOTA: Aunque en este artículo no escribí sobre eso, quizás te hayas dado cuenta por tí mismo de la gran importancia que tiene que las transacciones sean cortas. O sea: que terminen lo antes posible. Y es que cuanto más demore una transacción en finalizar mayor es la probabilidad de que ocurran conflictos con otras transacciones. Una transacción que se demora 1 milisegundo muy raramente tendrá conflictos, pero una que se demora media hora …

Artículos relacionados:

Entendiendo a las transacciones

El índice del blog Firebird21

Más sobre Trace Manager

Deja un comentario

En este artículo del blog ya habíamos visto como se usa el programa FBTRACEMGR.EXE y cual es su utilidad:

Usando Trace Manager

ahora veremos otra característica muy interesante de ese programa: sesiones múltiples.

Cuando ejecutamos el programa podemos indicarle el nombre de la sesión y podemos tener varias sesiones ejecutándose simultáneamente y cada una de esas sesiones monitoreando distintas actividades.

¿Por qué haríamos algo así?

Porque si una sola sesión se encarga de monitorear todo entonces los resultados que obtendremos estarán todos mezclados. Estarán todos, sí, pero todos mezclados y por lo tanto realizar cualquier búsqueda en ellos nos puede hacer demorar mucho tiempo. Suele ser mejor separar las actividades.

Así, por ejemplo, podríamos tener una sesión que solamente se encargará de monitorear los INSERTs a las tablas, otra sesión se encargará de monitorear los UPDATEs, otra los DELETEs, otra los stored procedures, otra los triggers, etc. Esto nos permitirá tener todo más ordenado y las búsquedas serán rapidísimas.

Ejemplo 1: Queremos monitorear los INSERTs

Nuestro archivo de configuración podría ser algo como:

TRACE1

Captura 1. Si haces clic en la imagen la verás más grande

Y llamaríamos al programa FBTRACEMGR.EXE de esta manera:

TRACE2

Captura 2. Si haces clic en la imagen la verás más grande

Si ahora hacemos un INSERT en una tabla (por ejemplo en la tabla de BANCOS) escribiendo:

TRACE3

Captura 3. Si haces clic en la imagen la verás más grande

el FBTRACEMGR.EXE nos mostrará algo como esto:

TRACE4

Captura 4. Si haces clic en la imagen la verás más grande

¡¡¡BUENÍSIMO!!! No solamente vemos que ocurrió un INSERT en la tabla de Bancos sino también cual fue el comando que se escribió. Y mirando un poco más arriba también sabremos quien fue el usuario, la computadora, la fecha, la hora, el número de la transacción y los parámetros de la transacción. Un montón de información que nos será muy útil si estamos buscando errores.

Ejemplo 2: Queremos monitorear los UPDATEs

Ahora, nuestro archivo de configuración sería algo como:

TRACE5

Captura 5. Si haces clic en la imagen la verás más grande

Como puedes ver es muy parecido al anterior pero con el nombre de archivo y el filtro cambiados porque ahora lo que nos interesa es monitorear los UPDATE.

Lo interesante de esto es que si abrimos otra ventanita “Símbolo del sistema” y en esta nueva ventanita ejecutamos al programa FBTRACEMGR.EXE con este archivo de configuración entonces tendremos dos sesiones: una que se encarga de monitorear a los INSERTs y otra que se encarga de monitorear a los UPDATEs. Muy bueno.

TRACE6

Captura 6. Si haces clic en la imagen la verás más grande

Usamos la ventanita (1) para escribir comandos SQL, cuando uno de esos comandos es un INSERT la ventanita (2) nos mostrará sus datos y cuando el comando es un UPDATE entonces la ventanita (3) será la que mostrará los datos de la operación.

Conclusión:

Poder tener muchas sesiones abiertas al mismo tiempo es muy útil porque nos permite trabajar en forma más ordenada. De esta manera podemos tener una sesión encargada de monitorear los INSERTs, otra sesión encargada de monitorear los UPDATEs, y así sucesivamente.

Aunque en estos ejemplos la salida del programa FBTRACEMGR.EXE fue siempre hacia la pantalla lo normal y lo recomendable es que la salida sea hacia un archivo de texto, como vimos en el artículo anterior.

Artículos relacionados:

Usando Trace Manager

El índice del blog Firebird21

Older Entries Newer Entries