Página de inicio - Tecla de acceso: 1
Notas técnicas

MultiBase. Notas a la versión 2.0, release 05

3. Notas

En modo transaccional la instrucción LOAD irá bloqueando incrementalmente cada uno de los registros que inserta en la tabla. Por tanto, si el número de filas a insertar es muy elevado, se puede saturar la tabla de bloqueos del sistema operativo. Para evitar esta situación y además acelerar el proceso es recomendable bloquear la tabla durante el proceso de LOAD.

Ejemplo:

...
begin work
lock table mitabla in share mode
load from "mitabla.unl" insert into mitabla
unlock table mitabla
commit work
...

Al incluir un carácter de 8 bits en una consulta de un FORM o en cualquier instrucción SQL se puede producir el siguiente error: «Carácter ilegal encontrado en la sentencia». Este error se debe que el carácter indicado no está definido como carácter alfabético en la tabla de caracteres de MultiBase.

Esta tabla se puede modificar incluyendo una entrada para ese carácter en la sección «UPPER/LOWER» del fichero «TMAPPING», que se encuentra en el subdirectorio «etc» de MultiBase. Por ejemplo:

select * from provincias
    where descripcion > "ë"

Para que esta instrucción funcione correctamente debe incluirse la siguiente línea en el fichero «TMAPPING» para el terminal en concreto:

:AT386
.UPPER/LOWER
\211 \211 e con diéresis

Este ejemplo es válido para un terminal que utilice el juego de caracteres «GCS#2» de IBM.

La forma más rápida de actualizar una fila devuelta por un CURSOR se realiza por medio de la instrucción «UPDATE... WHERE CURRENT OF...».

Para ello, deberá definirse el CURSOR como «CURSOR de actualización» («FOR UPDATE»). Dado que un CURSOR definido «FOR UPDATE» debe estar asociado a sentencias SELECT simples, es decir, no pueden incluir ni más de una tabla, ni agregados, ni «ORDER BY», ni «GROUP BY», etc., para actualizar a través de un CURSOR con algún tipo de ordenación no puede utilizarse, por tanto, la sentencia «UPDATE... WHERE CURRENT OF...».

En estos casos se puede emplear la siguiente estrategia: Añadir a la sentencia SELECT de la definición del CURSOR la columna ROWID y utilizar en la sentencia UPDATE como única condición «WHERE ROWID=...».

Ejemplo:

database almacen
define
    variable numero integer
    variable pr_vent like articulos.pr_vent
    variable articulo like articulos.articulo
end define

main begin
    declare cursor actualiza for select rowid,
    articulo, pr_vent
    into numero, articulo, pr_vent
    from articulos
    order by articulo
    foreach actualiza begin
    update articulos set pr_vent = pr_vent * 1,05
        where rowid = $numero
    end
end main

La ejecución del programa anterior será más rápida que si se incluyesen las condiciones de búsqueda de la fila a actualizar en la instrucción UPDATE.

La definición de una variable no puede realizarse con la cláusula «AT». El compilador no produce ningún error, pero en ejecución dará un «Error no recuperable en ejecución».

La definición de una variable multilínea en un FRAME (cláusula «n LINES») deja sin efecto el resto de atributos de la variable.

Para cambiar de tabla en un FORM con relación cabeceras-líneas (sección JOINS) hay que utilizar las instrucciones HEAD y LINES en lugar de NEXT TABLE. Con esta última puede haber problemas de «s roll» de las filas que intervienen en las listas en curso de las respectivas tablas.

En la impresión de una variable con «PUT STREAM» cuyo valor sea «NULL» se escriben tantos espacios en blanco como longitud tenga la variable. Si no se desea este comportamiento, se puede optar por hacer concatenaciones dependiendo del resultado que se desee.

Ejemplo:

variable var1 char (10)

INSTRUCCIÓN RESULTADO
var="1234"
RESULTADO
(var=NULL)
put stream standard "[", var1, "]" [1234    ] [    ]
put stream standard var1 & "" [1234] [  ]
put stream standard var1 && null [1234  ] [  ]

En instalaciones con arquitectura cliente-servidor, desde una máquina «cliente» (MS-DOS, Windows o UNIX) no se pueden «chequear» las tablas de la base de datos residente en el «servidor». El error que se produce es: «Imposible abrir el fichero «......»».

La instrucción WINDOW convierte todos los datos que muestra en CHAR. Por lo tanto, cuando se muestra un dato numérico formateado (atributo FORMAT), para seleccionar algún valor de esta ventana la variable receptora deberá ser de tipo CHAR. Si la variable receptora es numérica, CTL intentará cambiar las comas decimales por punto decimal, con lo cual una columna de la WINDOW que tenga un formato con puntos de miles no se podrá convertir a numérico sin necesidad de escribir una función «ad hoc».

El formato interno de almacenamiento de decimales negativos es diferente entre las versiones 1.0 y 2.0 . La variable de entorno DECNEG puede tomar como valor 1 ó 2 para forzar uno u otro formato. Esta variable apareció por la necesidad de mantener bases de datos construidas con un SQL de versiones 1.0. Así, un SQL de una versión 2.0 puede mantener columnas con valores decimales negativos de versiones 1.0 definiendo simplemente «DECNEG=1». El valor por defecto de esta variable en todas las versiones 2.0 es «DECNEG=2» para UNIX y «DECNEG=1» para MS-DOS y Windows. A partir de la release 5 esta variable se comporta igual en todos los sistemas operativos, es decir, por defecto «DECNEG=2».

Por lo tanto, si se va a instalar una versión 2.0.05 sobre bases de datos MS-DOS o Windows de versiones anteriores que contengan alguna columna de tipo DECIMAL con valores negativos, deberá, o bien poner la variable «DECNEG=1», o bien descargar la tabla con el valor «DECNEG=1», borrarla, poner «DECNEG=2», crearla y cargarla de nuevo.

La variable de compatibilidad general «MBCOMPAT=1» asume también «DECNEG=1».

4. El optimizador de SQL

En esta release se ha modificado el optimizador de «queries» del CTSQL. A continuación se proporciona información sobre la forma en que actúa.

El optimizador de «queries» de CTSQL decide una estrategia de búsqueda sobre las tablas incluidas en las sentencias en base a los siguientes factores:

Para tomar una decisión el optimizador realiza tres pasos:

Más adelante se explica en detalle la forma en la que actúa el optimizador bajo diferentes condiciones.

El optimizador sólo se decantará por una estrategia determinada -índice seleccionado o secuencia de tablas- si ésta es mejor que el resto. Por tanto, si no existe ningún motivo que lo justifique utilizará la primera estrategia encontrada. Esto se refiere tanto al índice seleccionado para cada tabla como a la secuencia de acceso a las tablas. De este modo se permite que el programador pueda forzar al SQL a utilizar una estrategia determinada, como se verá más adelante.

4.1. Decisión del índice a emplear

Los elementos que intervienen en la decisión de si un índice es más adecuado que otro son los siguientes:

  1. Número de partes del índice referenciadas en la «WHERE» mediante condiciones de «=», «>=», «>», o bien las condiciones «MATCHES», «LIKE», «IN», «BETWEEN» que provoquen condiciones equivalentes a las anteriores.

  2. En un índice compuesto la referencia a una de sus partes sólo se tendrá en cuenta si además se referencian todas las componentes anteriores a ésta en otras condiciones de la «WHERE».

  3. Un índice referenciado en todas sus partes por las condiciones anteriormente citadas es mejor que un índice del que no se referencien todas las componentes.

  4. Un índice referenciado en todas sus partes por condiciones de igualdad es mejor que otro referenciado en todas sus partes por cualquier otra condición.

  5. Un índice referenciado en todas sus partes por condiciones de igualdad es mejor que otro en su mismo caso si el primero es índice único y el segundo no.

  6. A igualdad de condiciones de elegibilidad entre dos índices, el optimizador seleccionará en primer lugar el primero al que se haga referencia (que en la cláusula «WHERE» aparezca uno de sus componentes con anterioridad); en el caso de igualdad (la primera referencia se hace mediante una columna que está en ambos índices) se elegirá el que sea clave primaria (si lo fuese) o el que se haya creado antes.

  7. Una condición es optimizable si es única o está unida al resto de condiciones de esta tabla con un nexo «AND». Por ejemplo, la sentencia «SELECT * FROM clientes WHERE cliente > 20 OR provincia > 10» no es optimizable, y por tanto será necesario acceder secuencialmente a toda la tabla para obtener el resultado. Por el contrario, la sentencia «SELECT * FROM clientes WHERE cliente > 20 AND provincia > 10» sí es optimizable, y se accederá por «cliente» si hay un índice en la columna «cliente», o bien si hay un índice compuesto cuya primera componente sea la columna «cliente»; y se accederá por «provincia» si ocurre esto mismo para «provincia». Si están definidos los dos índices se accederá por el primero referenciado en la «WHERE» (en este ejemplo «cliente»).

    Una forma de forzar al acceso por «provincia» mediante su índice, si sabemos que también existe un índice por «cliente», puede ser haciendo que la primera condición no sea optimizable, por ejemplo:

    select * from clientes where (cliente > 20 or 1=0)
         and provincia > 10


    o bien cambiando el orden en la «WHERE»:

    select * from clientes where provincia > 10
         and cliente > 20

  8. Una condición optimizable por ROWID es siempre mejor que un acceso por índice. Por ejemplo:

    select * from clientes where cliente > 15
         and rowid in (13, 23, 1, 45)


    no utilizará el índice «cliente», sino que accederá directamente a las filas indicadas por el ROWID.

  9. En general, una cláusula «ORDER BY» hace que sobre el resultado obtenido de la SELECT se realice una ordenación posterior, salvo si esta ordenación puede ser optimizada. Esto ocurre si se dan la siguientes condiciones (todas):

Si se dan todas estas condiciones, CTSQL no necesitará realizar una ordenación posterior a la selección.

4.2. Decisión de la secuencia de tablas

Siempre es mejor acceder a una tabla antes que a otra si el número de filas válidas de la primera es menor que el de la segunda. De este modo, el número de accesos a disco, en principio, es inferior. Por ejemplo, supongamos dos tablas, «a» y «b», con las filas que se muestran a continuación:

Tabla a Tabla b
a1 b1
a2 b2
a3  
a4  
a5  

La sentencia: «SELECT * FROM a, b», necesitará el siguiente número de lecturas de disco dependiendo de la tabla por la que se empiece:

  1. Tabla «a», Tabla «b»: Las lecturas necesarias serán a1, b1, b2, a2, b1, b2, _a5, b1, b2. En total 15 lecturas.

  2. Tabla «b», Tabla «a»: Las lecturas serán, b1, a1, a2, a3, a4, a5, b2, a1, a2, a3, a4, a5. En total 12 lecturas.

Esta diferencia crece proporcionalmente a la diferencia de filas entre las tablas y exponencialmente al número de tablas.

Para establecer qué tabla es la que devuelve menor número de filas, CTSQL utilizará las condiciones más restrictivas. Es decir, una tabla que pueda ser accedida mediante un índice o ROWID es mejor q e otra que no pueda serlo. Si existen dos tablas que no pueden ser accedidas por índice, se elegirá la que tenga menor número de filas. CTSQL obtendrá este número del campo «nrows» de la tabla «syst bles» del catálogo del sistema. Este campo solamente se actualiza por medio de la instrucción UPDATE STATISTICS del SQL. Por tanto, conviene tener en cuenta que para algunas sentencias donde intervenga un gran número de tablas con grandes diferencias en el número de filas será conveniente tener este valor razonablemente actualizado.

Si dos tablas no van a ser accedidas por índices y tienen el mismo número de filas en la columna «nrows», CTSQL elegirá el orden de acceso definido en la cláusula «FROM».

En el Apéndice 3 de estas notas se incluyen ejemplos de cómo actúa el optimizador con diferentes sentencias SELECT.

« »