martes, febrero 19, 2008

¿ Quien es "alias" ?

Tengo 20 años programando en Clipper, y mas o menos 15 dando clases de programación y análisis y diseño de sistemas, de los cuales 5 estuve dando clases en la Universidad La Salle de México, como catedrático de la Escuela de Ingeniería en la carrera de Ingeniería en Cibernética y Ciencias de la Computación.

He dirigido algunos trabajos de tesis sobre bases de datos, 2 de ellos específicamente sobre lenguajes Xbase y hasta una tesis sobre programación orientada al Objeto con FiveWin, sí sí, aunque ustedes no lo crean FiveWin fue en su momento (hace como 8 años) el tema de un trabajo de titulación profesional de 2 de mis alumnos.

El caso es que en todos estos años pegándole a la tecla con Clipper y ahora con (x)Harbour, me he encontrado con que casi nadie saber manejar los archivos DBF en condiciones.

Actualmente la idea es ir mas por el tema del SQL, pero hay muchas personas que todavía manejan archivos DBF y que conociendo un poco mas sobre ellos se pueden hacer cosas muy interesante.

Pero vamos a ver..... ¿ como que nadie sabe usar los DBFs ?, si cientos de programadores del mundo los han usado durante años..... de acuerdo, pero de todos esos cientos de programadores, que levanten la mano aquellos que consideren que saben manejar los DBF BIEN.

Si levantaste la mano, continúa leyendo, espero poder enseñarte algo nuevo, si no la levantaste, también continúa leyendo, seguro vas a aprender algo nuevo.

El mito de las 256 "áreas" de trabajo.

Los que venimos del dBase III recordamos que en los libros ponían, dBase puede abrir hasta 256 "areas" de trabajo, entendiendo por "areas de trabjo" un espacio de memoria en donde se "abría" la tabla DBF (está mal llamarla "base de datos"), lo que en el SQL moderno de hoy se llama "cursor".

Estas "areas" estaban numeradas, de tal forma que cuando queríamos abrir una tabla, todos hacíamos (y algunos siguen haciendo) la forma erronea de abrir la tabla:

SELECT 1 // para seleccionar el "area" donde vamos a abrir la tabla
USE archivo.dbf

Esto quería decir que la tabla ARCHIVO.DBF estaría abierta en el "área" 1, así pues siempre que quisiéramos hacer referencia a los campos o a alguna operación sobre ARCHIVO.DBF, antes que nada tendríamos que hacer primeramente un salto al "área" donde está abierta dicha tabla, algo como esto:

SELECT 1
GO TOP
DO WHILE ! EOF()
? Recno()
SKIP
ENDDO

La cosa comienza a ponerse interesante cuando requieres usar mas tablas simultáneamente, digamos unas 5 tablas, entonces erroneamente haces:

SELECT 1
USE archivo1.dbf
SELECT 2
USE archivo2.dbf
......
SELECT 5
USE archivo5.dbf

Mal, mal, mal, esto te lleva a hacer lo que yo he llamado "código ping-pong", es decir selección de área, operación, selección de otra área, otra operación, selección de otra area mas, otra operación mas, y luego vamos de vuelta al área original, lo cual nos lleva a un código lleno de SELECTs por todos sitios, que es complicado de mantener.

Si a esto aunamos que a 16 bits el número de áreas de trabajo estaba limitado por la memoria (las 256 áreas de trabajo que prometía dBase nunca fueron ciertas), entonces nuestro código se volvía un maremagnum de saltos entre áreas, de tener que cerrar tablas para abrir otras en las mismas "áreas" de tener que acordarte en que número de área quedaba tal o cual tabla DBF de tal o cual proceso, vamos, un rollo.

Y todo por no utilizar el "alias" de los archivos DBF.

¿ Y quien o qué es ALIAS ?

En todos los lenguajes de programación no orientados a datos, como Pascal, "C" o el mismo Basic, cuando se manejan archivos de datos, siempre se les asignan 2 nombres, el nombre "físico" que es el nombre del archivo tal cual se almacena en el disco, y el nombre "lógico", que es el nombre con el cual haremos referencia dentro del código de nuestro programa.

Clipper/(x)Harbour no es una excepción a esta regla, porque en realidad, SIEMPRE estas manejando 2 nombres cuando abres una tabla DBF para trabajar sobre ella, es decir, cuando haces algo como esto :

USE archivo.dbf

En realidad estas generando un "ALIAS", es decir, un "apodo" para hacer referencia al archivo abierto durante la ejecución de tu programa.

Si revisas la sintaxis del comando USE, te encontrarás varias cosas interesantes:

USE [ ;
[INDEX ] ;
[ALIAS ] ;
[EXCLUSIVE | SHARED] ;
[NEW] ;
[READONLY] ;
[VIA ]

El comando USE tiene una clausula ALIAS, que si no fuera importante, creeme, no la pondrían como parte de la sintaxis.

Esta clausula ALIAS se utiliza para darle un "nombre lógico" a tu tabla para que puedas manejarla en memoria, si tu omites este nombre, se asigna automáticamente como "alias" el mismo nombre de la tabla, sin extensión.

El ALIAS de una tabla es una pieza muy importante de la programación de datos porque si sabes como utilizarlo, te ahorrarás lineas y líneas de SELECTS inútiles.

Existe un "operador ALIAS", representado por un "->" signo de menos + signo de mayor que, este operador nos servirá para hacer referencia al área de trabajo que deseamos usar sin necesidad de tener que hacer un SELECT

Por ejemplo:

USE archivo1.dbf
archivo1->(dbgotop())
DO WHILE ! archivo1->(EOF())
? archivo1->(RECNO())
archivo1->(DBSKIP())
ENDDO

En este ejemplo asumimos que no hemos usado la cáusula ALIAS, luego entonces el ALIAS asignado por omisión será "archivo1" (sin extensión), luego usaremos el operador "alias" (->) para indicar que operaciones deben realizarse sobre él.

Nota por favor que todas las operaciones sobre la tabla son funciones NO COMANDOS y que todas las operaciones van encerradas entre paréntesis.

Otra cláusula importante del comando USE es la claúsula NEW, esta clausula te ahorrará miles de lineas de SELECTS ya que esta clausula abre la tabla DBF en la primer área que se encuentre disponible, es decir, no tienes que hacer un SELECT previo a abrir un DBF, simplemente le pones NEW al momento de abrir, y dejas que Clipper/(x)Harbour se haga cargo del resto.

USE archivo1.dbf NEW
USE archivo2.dbf NEW

No tienes que hacer ningun SELECT para abrir las tablas, ahora, para hacer referencia a los campos de cada archivos, o bien realizar operaciones sobre las tablas, tampoco hay necesidad de hacer SELECT, el ALIAS nos soluciona la vida:

USE archivo1.dbf NEW
USE archivo2.dbf NEW

archivo1->(DBGOTOP())
archivo2->(DBGOBOTTOM())

DO WHILE ! archivo2->(BOF())
? archivo2->(fieldget(1))
? archivo1->(fieldget(5))
IF ! archivo1->(EOF())
archivo1->(DBSKIP())
ENDIF
archivo2->(DBSKIP(-1))
ENDDO

El manejo de los campos es muy simple, funciona igual, basta con que pongas el nombre del campo despues del operador alias SIN PARENTESIS:

? archivo1->nombre
? archivo2->iva

Los campos también son asignables usando el operador alias, tanto para leer sus valores, como para actualizar o agregar valores, sustituyendo con esto al comando REPLACE:

cNombre := archivo1->nombre
archivo2->(DBAPPEND()) // el comando APPEND BLANK se sustituye pro su función
archivo2->nombre := cNombre // no hay REPLACE, el valor se asinga directamente

Esto de los alias puede ser tan complejo como quieras, un ejemplo típico es buscar en una segunda tabla el valor de un campo contenido en la primera:

CON SELECTS:

SELECT 1
cNombre := field->nombre
SELECT 2
SEEK cNombre

CON ALIAS:

archivo2->(DBSEEK(archivo1->nombre))

Efectivamente, con la línea anterior, basta con que hagas referencia a los alias y en vez de usar 4 líneas de código en una sola solucionas el problema y haces exactamente lo mismo.

Ahora esto del alias es mas versátil porque tu puedes dar TU PROPIO ALIAS

USE archivo1.dbf ALIAS MiAlias NEW
MiAlias->(DBGOTOP())
? MiAlias->nombre

Usando tu propio ALIAS puedes darle un nombre mas identificativo al manejo de tus tablas DBFs.

Veamos otro ejemplo mas complejo pero sin duda mucho mas útil:

USE archivo.dbf ALIAS ArchPant SHARED NEW
USE archivo.dbf ALIAS ArchRep SHARED NEW
USE archivo.dbf ALIAS ArchMovs SHARED NEW

NO, No hay ningún error, estoy abriendo LA MISMA TABLA (archivo.dbf) 3 veces AL MISMO TIEMPO..... ¿ que no causa esto un error del tipo ALIAS already in use ?.... pues va a ser que no.... y esto es algo de lo mas alucinante y útil de los archivos DBFs.

Una de las cosas que casi nadie usa es esta poderosa característica de los DBFs, poder abrir la misma tabla 2, 3, 4, o "n" veces al mismo tiempo, esto se logra CAMBIANDO EL ALIAS y abriendo la tabla como compartida (SHARED) como si fuesemos a programar para red.

Lo interesante de esta forma de usar los DBFs es que cada "alias" mantiene su puntero independiente, es decir puedes hacer algo tan loco como esto:

ArchPant->(DBGOTOP())
ArchMovs->(DBGOTO(5))
ArchRep->(DBGOBOTTOM())

El puntero de cada "alias" se moverá a distintas posiciones, sin importar que estemos hablando del mismo archivo DBF (recuerda, una cosa es el nombre físico y otra es el nombre lógico) Y si tu preguntas los valores de los campos :

ArchPant->nombre
ArchMovs->nombre
ArchRep->nombre

Seguramente obtendrás valores distintos en cada oportunidad, esto porque sin importar que sea el mismo archivo, para efectos prácticos en la memoria de la computadora esta "abierto" 3 veces bajo 3 alias distintos y cada alias guarda y mantiene su propio estado. Solo como comentario, Xailer a través de los DataSets facilita aun mas el manejo de los alias.

Pero volviendo al tema, para los que no tienen Xailer o tienen la versión personal, aun así podemos seguir haciendo cosas interesantes a base de usar solo los alias:

Si yo hago:

ArchMovs->(DbAppend())
ArchMovs->nombre := "Juan Perez"
ArchMovs->direccion := "Da igual donde viva, Viva El "
ArchMovs->(DBCommit())
ArchMovs->(DBUnlock())

El registro añadido será visible EN TODOS LOS ALIAS QUE APUNTEN AL MISMO ARCHIVO
FISICO, esto es maravilloso, porque es como dar de alta al mismo tiempo en 3 tablas distintas.

La pregunta que puede surgir en este momento es .... ¿ y esto como para que me serviría en la vida real ?.... pues para facilitarte la existencia:

Supongamos que tu quieres mostrar en pantalla un browse del archivo de clientes, donde tienes además 2 índices, uno por clave y otro por nombre, y que además quieres hacer operaciones de mantenimiento como altas bajas y cambios y para rematar quieres hacer reportes que pueden ir ordenados por clave o por nombre, entonces haríamos algo como esto:

USE clientes ALIAS CliBrow SHARED NEW
CliBrow->(OrdSetFocus("nombre")) // arrancamos ordenado por nombre del cliente
CliBrow->(DBGOTOP())

USE clientes ALIAS CliMovs SHARED NEW
CliMovs->(OrdSetFocus("clave")) // esta segunda instacia del archivo va ordenado por clave

USE clientes ALIAS CliReps SHARED NEW
CliRep->(OrdSetFocus("clave") // idem anterior.

Si yo quiero meter la primer instancia del archivo de clientes a un browse, basta con hacer:

CliBrow->(Browse())

¡¡¡Así de simple !!!!! El usuario podrá moverse sobre el browse y podrá cambiar los criterios de ordenación sin afectar a los otros 2 alias que apuntan a la misma tabla.

Ahora viene lo bueno, si yo quiero hacer un reporte o hacer un ABM sobre la tabla clientes NO LO HAGO SOBRE EL ALIAS DEL BROWSE, lo hago ya sea sobre el alias CliMovs o sobre el alias CliReps para no molestar al alias del browse.

Cuando trabajas con un solo alias, y quieres dar un mantenimiento a una tabla mostrada en un browse, entonces tienes que "guardar" el estado del área de trabajo que muestras en el browse, es decir, en que registro estás, sobre que indice estas trabajando, etc. y luego hacer las operaciones de mantenimiento sobre ese mismo alias que pueden vincular, mover el puntero sobre la tabla, agregar un registro nuevo, cambiar el orden del índice actual, realizar búsquedas, etc, y luego de la operación, debes regresar todo a como estaba antes. Esta técnica te evita eso precisamente.

Por ejemplo, si tu quieres agregar un registro el código sería algo como esto:

CliMovs->(RLOCK())
CliMovs->(DBGOBOTTOM())
nSiguienteClave := CliMovs->clave+1
CliMovs->(DbAppend())
CliMovs->clave := nSiguienteClave
CliMovs->nombre:= cVariableConElNuevoNombre
CliMovs->(DbCommit())
CliMovs->(DBUnlock())

Nota que toda la operación de mantenimiento la hacemos sobre el Alias CliMovs, no sobre el Alias CliBrow, de esta manera no tenemos que guardar posiciones ni estados, porque CliBrow no se va a mover su puntero y su índice se quedan como están, en cambio CliMovs hará toda la labor de mantenimiento y el cambio se verá reflejado automáticamente en CliBrow y en CliRep en cuanto haya terminado la actualización CliMovs, el Browse ni si quiera se va a mover, a menos claro, que la operación de mantenimiento afecte al registro en el cual estaba el puntero del alias CliBrow.

Lo mismo pasa para los reportes, si yo quiero un reporte, usualmente ese reporte abarca un rango de datos, para no molestar al Browse ni a su alias, pues el reporte lo emitimos sobre el alias CliRep:

CliRep->(OrdSetFocus("nombre"))
CliRep->(OrdScope(0,"A")) // reporte de todos los clientes cuyo nombre empiece con "A"
CliRep->(OrdScope(1,"A"))
CliRep->(DBGOTOP())
DO WHILE ! CliRep->(EOF())
... aqui val el codigo del reporte...
CliRep->(DBSKIP())
ENDDO

Y el CliBrow ni se va a enterar que hay un alias llamado CliRep con un Scope sobre la tabla moviendo el puntero para generar un informe.

Esta técnica de programación es ideal para Windows, sobre todo si estás trabajando en entornos MDI porque puedes asignar a cada ventana hija los alias que vaya a necesitar para trabajar, en los entornos MDI una de las cosas mas complicadas de manejar es precisamente esa, evitar que el puntero en la ventana "A" se mueva cuando en la ventana "B" se está realizando otra operación sobre la misma tabla DBF.

Puedes incluso abrir todas las tablas al momento de arrancar tu programa, porque en (x)Harbour no existe la limitación de las 256 "areas" de dBase, en (x)Harbour prácticamente no hay limite en la cantidad de archivos que puedas abrir simultáneamente.

En Xailer Pro y Enterprise no tienes los problemas de Alias cuando trabajas en entornos MDI porque Xailer te provee de los DataSets que usualmente ya van "atados" a cada formulario y como expliqué antes, te ahorran el tema de tener que lidiar con alias, en FiveWin o en la versión Personal de Xailer, el uso de esta técnica es indispensable para trabajar con entornos MDI, en (x)Harbour modo consola, esta ténica te puede ayudar a eliminar muchos vicios de programación y a ahorrar muchas líneas de código.

Cabe señalar que esto mismo es aplicable a Advantage DataBase Server, de hecho el manual de ADS recomienda que todas las tablas estén abiertas desde que arranca la aplicación, esto es aplicable tanto a archivos DBF con NTX o CDX o bien a tablas ADT con índices ADI.

En un próximo artículo discutiremos otra técnica relacionada con esta llamada "alias dinámicos" que hará que no tengas que preocuparte prácticamente por nada que tenga que ver con el acomodo de las tablas en memoria.

10 comentarios:

Paimar dijo...

Buenas.

Lo que has comentado sobre abrir todas las areas a la vez una vez iniciada la aplicacion.. no es prejudicial a la hora de programar en red?

Gracias por tus post los espero siempre con ansia desde Guipuzcoa , España.

Un saludo de un programador xbase aficionado desde DbaseIII (ahora minigui extended)

Luis Ponce dijo...

Rene,

Excelente la exposicion del manejo de nuestras queridas dbf's, siempre se aprende algo mas, en FivwWin existe una Clase la Tdbf crees tu que sea posible utilizar esta tecnica con esta clase ?, lo intentare.

Saludos desde Peru
Luis Ponce

Rene Flores dijo...

Paimar:

No, no te afecta en absoluto por que en realidad fisicamente el archivo solo esta abierto una vez, los alias son simples apuntadores a memoria que guardan el estado de la tabla, pero la tabla no está abierta 3 veces simplemente tiene 3 alias.

Luis:

La clase TDBF de FW de momento no puede usar esta tecnica, porque no abre la tabla, primero tienes que abrir la tabla con un USE y luego crear el objeto TDBF.

Con algunas modificaciones se podría adaptar la clase para usarla.

Paimar dijo...

Gracias Rene, espero ansioso mas posts en tu blog !

Un saludo

Paimar (España)

carloskds dijo...

Un punto que no comentas rene, es que se pueden ubicar mas de una instruciccion dentro del parentesis.

por ejemplo, es posible:
ALIAS->(ordsetfocus("CODI"), DBGotop())

y de esta forma se ahora aun mas codigo.

salu2
carlos vargas

Rene Flores dijo...

Carlos:

Gracias por la aclaración, efectivamente, las operaciones sobre alias son en realidad bloques de código, luego entonces, puedes agregar mas instrucciones (funciones solamente) separadas por comas.

joseluis.sanchez dijo...

René,
yo uso la técnica de alias desde hace mucho y siempre la he considerado muy bena. Sin embargo, según algunos reconocidos expertos xbase que no voy a nombrar, el uso de alias penaliza el rendimiento de la aplicación. Me voy a explicar. Si tu haces

select CL
do while ! eof()
...
skip
enddo
estás evaluando una sola vez el select, y haces un cambio de area de trabajo.
Sin embargo si haces
do while ! CL->(eof())
...
CL->(DbSkip())
enddo
estás evaluando el select del alias en cada instrucción en que lo usas, y estás penalizando el rendimiento.

¿ Me puedes aclarar si esto es cierto ?

Felicidades por tu post, y un fuerte abrazo desde España,
José Luis Sánchez

Jose G. dijo...

Hola Rene, tu explicación sobre el ALIAS me parecio muy interesante espero publiques mas informacion sobre el tema (Alias) y bueno tambien me gustaria saber si es cierto que al usar el alias como indicaste estás evaluando el select del alias en cada instrucción en que lo usas, y estás penalizando el rendimiento del sistema.

jHernandez dijo...

Hola Sr. Rene Flores, reciba saludos cordiales de un programador mexicano que lo admira mucho. No abandone su blog de objeto persistente. Comentando sobre esta interesante entrada sobre el asunto de los ALIAS, yo comence a utilizar su recomendacion hace algunos a#os, y pues todo muy bien. Hasta hace poco que experimente problema con el ERROR DBCMD/2001 Area de trabajo no usada: ORDSETFOCUS

Resulta que tengo varias tablas DBF abiertas desde el principo de mi aplicacion como ud. lo recomienda. Pense que talvez se saturo pero entiendo que no es asi y movi de lugar las asignaciones de apertura y el error se repite con otra tabla dbf. es decir, cambio lugares, abro unas y despues las restantes y siempre falla en alguna (supongo que al azar?). Hasta el momento no lo he resuelto, seguire intentando.

Mi codigo al abrir mis tablas DBF es el siguiente:



// Archivo de datos de la empresa (encriptados)
use e_cajav2 ALIAS e_cajav2 SHARED NEW

// Archivo de sistema
use e_SYSTEM ALIAS e_SYSTEM SHARED NEW

// File Maestro formas de pago
use E_MTOPAG index E_MTOPA1 ALIAS E_MTOPAG SHARED NEW

// File medios de publicidad
use g_medios index g_codmed ALIAS g_medios SHARED NEW

// File Control de lockers
use g_ctrloc index g_codloc ALIAS g_ctrloc SHARED NEW

// File de usuarios del sistema
use e_MTOUSR index e_MTOUS3,e_MTOUS2,e_MTOUS1 ALIAS e_MTOUSR

// File de control de apertura y cierre de caja-e
use e_CAJCTR ALIAS e_CAJCTR SHARED NEW

// File Conceptos de pago
use g_concep index g_conce1, g_conce2 ALIAS g_concep SHARED NEW

// File Detalle de recibos o tickets
use e_DetRec index e_detre1 ALIAS e_detrec SHARED NEW

// File de paso para detalle de recibos o tickets
use e_DetPas ALIAS e_detpas Exclusive NEW

// File actividades
use g_activi index g_codact ALIAS g_activi SHARED NEW

// Directorio de clientes/Socios
use g_DirSoc index CODIGO, NOMBRE, ACTIVIDA, VENCIDO, CORTESIA, ESTATUS, MEDIO, SEXO, COLONIA, CIUDAD, ESTADO, PAIS, PROSPECT, GRUPO ALIAS Brw_Ctes SHARED NEW

Manuel L. dijo...

Hola René, me puedes ayudar con lo siguiente por favor?...

Tengo una tabla que visualizo en un browse, estoy ocupando el alias, cuando muevo el puntero con GOTOP() o GOTO(aArr[5]) la barra cursor del browse no se mueve ni al primer registro ni al número interno indicado del browse aunque si lo hace en la tabla. Este es el código:

aArr[5]:=val(inputbox(aArr[2],aArr[1],''))
fRegCasos.Brw_RegCasos.SetFocus()
CAS->(dbgotop())
IF !dbSeek(eval(aArr[5]))
msgbox("Registro no encontrado.")
else
CAS->(dbgoto(aArr[5]))
msgbox(cas->numinte)
fRegCasos.Brw_RegCasos.refresh()
ENDIF
RETURN( NIL )

¿Que falta para que en el browse tambien se mueva el puntero?

Espero tu respuesta y de antemano, gracias.