Anti Manual de SQL

De ClarionWiki
Ir a la navegación Ir a la búsqueda

Anti Manual De SQL

por Mauricio Nicastro

La idea de este anti manual de SQL es enumerar más que nada todos los errores o problemas que tuve cuando empecé a trabajar con SQL, con la esperanza que le pueda servir a alguien más. Muchas de las cosas que van a encontrar serán tontas y obvias, pero generalmente eso sucede después de haber estado un buen rato tratando de ver por qué eran tan tontas y obvias y no me daba cuenta.

1) Los browses lentos…

Sé que las claves de las tablas de SQL deben ser únicas. Para eso, defino generalmente campos autonumerados (dejo que el motor de encargue de eso) y esos campos los agrego a las claves. Tenía, cuando empecé, una tabla de artículos, con ese campo en los índices Código y Descripción.

Código + Autonumerado
Descripción + Autonumerado

El browse, con los dos tabs. Acá viene una aclaración: le vendí a mi cliente la idea de que con SQL todo iba a funcionar más rápido. Bien, cuando pasaba de un tab al otro, tenía que esperar como 20 segundos para que empiece a mostrar los datos. La tabla tenía 20000 registros, algo no excesivamente grande. Mi cliente, de más está aclarar, me miraba con ganas de hacer conmigo un mártir de la programación. Revisé todo, trabajé con el administrador de consultas de SQL que trae el MSSQL y ahí estaba todo bien, busqué embebido dentro del browse y nada. Hasta que en el manual del curso de SQL que tomé en Unisoft, releyéndolo por enésima vez, encontré la solución: LOS INDICES EN CLARION TIENEN QUE ESTAR DEFINIDOS CON EL CASE SENSITIVE CHEQUEADO. La razón es que si no es así, la sentencia SQL que le mandamos al motor hace un upper de los datos a consultar y ordena dinámicamente en memoria, y es ahí donde se pierde el tiempo.

2) El envío de comandos al motor…

Remplazar los precios en los artículos que cumplan con cierta condición puede hacerse de 2 formas distintas. Si trabajáramos con tablas TPS, tendríamos que hacer un loop (esperando que un índice se aproxime lo más posible a esa condición) y recorrer la tabla mientras vamos actualizando los datos. Con motores, se le puede enviar el comando a través de Prop:SQL y dejar que el motor haga la tarea. Yo tenía que actualizar una tabla de notas de pedido, descontando una cantidad de un campo para todas las notas de pedido que coincidieran con tal número de nota. Para eso:

NotasDePedido{ Prop:SQL } = ‘update notasdepedido `&|
  `set cantidad = cantidad – ‘ & Tmp:Cantidad &|
  ‘ where notasdepedido.nota = ‘ & Tmp:NotaDePedido

Esto, así como está, parece correcto y sin embargo no lo es. Por suerte la comunidad Clarion es bastante grande y encontré un amigo programador de U.S.A. que me resolvió el problema en el momento a través de ICQ. Qué sucedía? El campo notasdepedido.nota es un CSTRING y, por más que era igual al campo Tmp:NotaDePedido, los strings deben ser pasados entre comillas. Les aseguro que antes de que mi amigo me avivara de esto, debo haber probado 70 consultas distintas. El método de prueba y error puede ser útil a veces, pero otras es exasperante. Entonces, la consulta queda así:

NotasDePedido{Prop:SQL} = ‘update notasdepedido ` &|
   `set cantidad = cantidad – ‘ & Tmp:Cantidad &|
   ‘ where notasdepedido.nota = ‘ & ‘’’ &|
    Tmp:NotaDePedido & ‘’’

Tengan en cuenta que deben poner 3 comillas, una para abrir, la que realmente queremos poner, y la última para cerrar. Algunos usan <39> que es el ascii para las comillas. Es lo mismo, tal vez algo más prolijo.

3) Los campos fecha… Un campo Datetime en el motor se traduce a Clarion de una forma algo caprichosa. Tenemos un campo Fecha String 8, un grupo, y dentro del grupo 2 campos más, uno date y el otro time. Muchas veces debemos ordenar un browse por esa fecha, y poner un locator en el browse. Cómo se crea el índice en este caso? Qué tomar como parte de la clave? Otra vez más, el método de prueba y error. No fue tan complicado esta vez. En el índice ser tomarse el campo Fecha_DATE del grupo como parte de la clave. Si toman el grupo o el campo string, no funciona ni a palos. Suena obvio, vuelvo a insistir, pero siempre después de que uno lo hace andar.

4) Los locators filtered…

Este tipo de locator es muy útil, ya que va filtrando a medida que uno va tipeando los datos. Además tiene la ventaja que puede ir filtrando por el principio de la cadena o, si está chequeado el campo Find Anywhere, buscar los datos en cualquier lugar dentro de la cadena. Acá, debo admitirlo, no sé si fue un error mío, un problema de Clarion o qué, pero no lo pude hacer andar. En mi browse, el campo que debía filtrar no estaba como columna del listbox sino como hot field fuera de este. A medida que tipeaba en el locator, lo mismo aparecía en el campo y no hubo forma de que esto funcionara. Probé con variables en el locator, con el campo, con lo que sea y no anduvo. Hasta que decidí dejar que el motor lo haga. Creé una variable donde ingreso el texto a buscar y en el accepted del entry, va algo así:

IF Loc:Descripcion <> 
   BRW1::View:Browse{prop:sqlfilter} = |
   'Descripcion LIKE %' & clip(Loc:Descripcion)&|
   '%
ELSE
   BRW1::View:Browse{Prop:SqlFilter} = 
END !IF
BRW1.ResetQueue(Reset:Queue)
BRW1.ResetFromFile()

Funciona y bien. Pueden agregar otra variable más que indicarle si quieren que busque en toda la cadena o solo al inicio de la misma.

5) El refresco de pantalla…

Un browse de facturas, donde vemos el encabezado y los items. Agregamos una nueva factura, vamos al form, regresamos y… los datos no aparecen. Listo, un ThisWindow.Reset(1) por ahí es suficiente pensamos. Si encuentran donde ponerlo, me avisan, porque no lo encontré. Después de preguntar (ya no recuerdo si en el foro, a mi amigo de U.S.A., a mi amigo de Rusia, o dónde), descubrí (no yo, vuelvo a aclarar), que el código a poner es:

BRW1.ResetQueue(Reset:Queue)
BRW1.ResetFromFile()

Volvemos a refrescar la Queue y si hace falta, también traemos los datos del archivo.

6) Los campos en los process…

Por ahí debo tener algún process dando vuelta. En SQL, a diferencia con los TPS, hay que indicarle al motor que registro traer pero también, qué campo hay que traer. Si tienen un process, deben definir todos los campos que les interesan como Hot Fields. Si no lo hacen, trae cualquier cosa y entonces nada anda bien.

7) Las relaciones padres – hijos …

Vuelvo al caso de las facturas. Si uso un campo autonumerado, quién debe autonumerar? Clarion o el motor? Leí por ahí que es mejor que lo haga el motor. Perfecto, tengo el campo autonumerado en el encabezado de la factura y ese es la clave externa en los hijos. Uso el SuperInvoice… pero no anda. El motor numera en el momento de grabar los datos, demasiado tarde para el SuperInvoice, quien me graba los hijos con 0 en la clave externa. Hay que poner demasiado código para evitar esto, así que lo considero demasiado complejo (tengan en cuenta que mis conocimientos de Clarion no son muy grandes todavía). Leo en Clarion Magazine una nota al respecto, donde recomiendan un campo CSTRING (18) donde guardamos día y hora. Pruebo y anda a la perfección. Resumiendo: definir en la tabla un CSTRING (18) y en prime fields hacemos:

RecordId = today() & clock() 

RecordId es el campo del cual les hablo. Inicializamos al padre así, lo transferimos al hijo y todo funciona bien. Algunos dirán que corremos el riesgo que varias terminales accedan al mismo tiempo y entonces inicialice de la misma manera, dando entonces claves duplicadas. Puede ser. Mi experiencia, con más de 20 terminales funcionando en forma simultánea, indica que eso nunca ha ocurrido. Estamos hablando de milésimas de segundos, obviamente puede suceder, pero es bastante improbable.

Hasta acá, todo lo que recuerdo que puede llegar a ser útil. La experiencia me va a hacer tropezar nuevamente con otras piedras, pero una vez que las saca, muchas veces con la ayuda de Uds., las volcaré acá para que no le pase a alguien más.

Feliz año nuevo para todos.

Mauricio