domingo, 22 de mayo de 2016

SQL AVANZADO (III). USO DE LA EXPRESION CASE EN SENTENCIAS UPDATE INFORMIX - Advanced Informix Sql (III). Using the CASE expression in a UPDATE sentence

En entradas anteriores hemos visto el uso de la función CASE en una SELECT, pero su uso puede ser muy útil también en una sentencia UPDATE.
Por ejemplo, imaginemos que queremos aumentar el precio de nuestros artículos en un porcentaje distinto según la sección a la que pertenezca el artículo.

Habitualmente haríamos un UPDATE por cada seccion asignando el precio correcto a sus artículos.
UPDATE articulos
SET precio = precio * 1.05
WHERE seccion = 1;

UPDATE articulos
SET precio = precio * 1.08
WHERE seccion = 2;

UPDATE articulos
SET precio = precio * 1.10
WHERE seccion = 3;

 Dependiendo del número de filas que tenga la tabla de artículos, podemos conseguir un importante ahorro de tiempo realizando todas las operaciones en una única sentencia:
UPDATE articulos
SET precio =
CASE
WHEN seccion = 1 THEN precio*1.05
WHEN seccion = 2 THEN precio*1.08
WHEN seccion = 3 THEN precio*1.10
END

 Tenemos que tener en cuenta que al menos debe haber una condicion WHEN y que ésta puede ser múltiple, es decir, podemos condicionar la actualización a varias condiciones.
Por ejemplo:
UPDATE articulos
SET precio =
CASE
WHEN seccion = 1 AND precio >= 100 THEN precio*1.05
WHEN seccion = 1 AND precio <100 THEN precio*1.08
END

 Vemos que actualizamos los artículos de la seccion 1 pero con distinto factor según su precio actual.
Espero que os sirva de utilidad.

Hasta la próxima.













viernes, 13 de mayo de 2016

DECLARACION DE CURSORES EN INFORMIX CON LA CLAUSULA WITH HOLD. Declaring Informix cursors using the WITH HOLD clause.

Si declaramos un cursor en Informix que va a tratar muchas filas, puede ser que nos interese declarar una transacción por cada una de ellas. El problema viene cuando al hacer el COMMIT o ROLLBACK de la transacción, observamos que nuestro cursor queda automáticamente cerrado.

Para evitarlo debemos declarar el cursor con la clausula WITH HOLD. Esta clausula puede utilizarse tanto en r4gl como en un Stored Procedure.

En STORED PROCEDURES:
CREATE PROCEDURE 'informix'.procedureName()

    FOREACH WITH HOLD
          SELECT column1, column2 INTO var1, var2 FROM table1WHERE condicion

         BEGIN WORK;
         ---
         ---
         UPDATE table2 SET  column1 = column1 - var1 WHERE condicion;
         UDPATE table3 SET column1 = column1 + (column1 * var2 / 100)
                WHERE condicion;
         ---
         ---
         COMMIT WORK;

    END FOREACH;

END PROCEDURE;


 En R4GL:

DECLARE cursorName CURSOR WITH HOLD FOR
          SELECT column1, column2 FROM table1 WHERE condicion

FOREACH cursorName INTO var1, var2

         BEGIN WORK;
         ---
         ---
         UPDATE table2 SET  column1 = column1 - var1 WHERE condicion;
         UDPATE table3 SET column1 = column1 + (column1 * var2 / 100)  
                WHERE condicion;
         ---
         ---
         COMMIT WORK;

END FOREACH



Espero que os sea de utilidad.

lunes, 2 de mayo de 2016

SQL AVANZADO (II). USO DE LA FUNCION WEEKDAY EN SENTENCIAS SQL INFORMIX - Advanced Informix Sql (II). Using the weekday function in Sql

Si periodicamente os toca realizar un informe y para obtenerlo os basais en alguna query de esas maravillosas que guardais como si fueran de oro, es conveniente que incluyais la fecha del dia en que lo lanzais para poder hacer un seguimiento......

La fecha del dia puede incluirse con un simple TODAY, pero si queremos darle un formato mas 'amigable' podemos utilizar la funcion WEEKDAY.

Esta función nos devuelve el dia de la semana en que nos encontramos con un número cuyo significado va desde el 0 (Domingo) hasta el 6 (Sábado)

Si lo combinamos con la expresión CASE que vimos en otra entrada de este mismo blog, podremos obtener resultados interesantes:
SELECT CASE WEEKDAY(today)
    WHEN 0 THEN 'Domingo'
    WHEN 1 THEN 'Lunes'
    WHEN 2 THEN 'Martes'
    WHEN 3 THEN 'Miercoles'
    WHEN 4 THEN 'Jueves'
    WHEN 5 THEN 'Viernes'
    WHEN 6 THEN 'Sabado'
END || ', '|| day(today)|| ' de '||
CASE MONTH(today)
    WHEN 1 THEN 'Enero'
    WHEN 2 THEN 'Febrero'
    WHEN 3 THEN 'Marzo'
    WHEN 4 THEN 'Abril'
    WHEN 5 THEN 'Mayo'
    WHEN 6 THEN 'Junio'
    WHEN 7 THEN 'Julio'
    WHEN 8 THEN 'Agosto'
    WHEN 9 THEN 'Septiembre'
    WHEN 10 THEN 'Octubre'
    WHEN 11 THEN 'Noviembre'
    WHEN 12 THEN 'Diciembre'
END || ' de '|| YEAR(today) FECHA,
COUNT(*) PEDIDOS,
SUM(importe) IMPORTE
FROM pedidos


Obtendremos una salida como esta:


Estoy de acuerdo en que el ejemplo es un poco simple pero quiero que os quedeis con la idea de que con esta función combinada con la expresión CASE podeis obtener una salida interesante para una fecha.
Ni que decir tiene que igual que la he utilizado con TODAY podeis utilizarla con cualquier fecha incluyendo columnas de tipo DATE de vuestra base de datos.

Espero que os sirva de utilidad.
Hasta la próxima.

miércoles, 27 de abril de 2016

USO DE CURSORES EN PROCEDIMIENTOS ALMACENADOS (SPL) - Using Cursors in Informix Stored Procedures (SPL)

Dentro de un procedimiento almacenado se pueden usar cursores de forma parecida a como hemos visto en 4gl.

* Con FOREACH:

FOREACH
    SELECT id, nombre
        INTO idCliente, nombreCliente
       FROM clientes
     ORDER BY id

     RETURN idCliente, nombreCliente WITH RESUME;

END FOREACH;   
                     
En este caso la query asociada al CURSOR debe ser especificada inmediatamente despues de la palabra FOREACH.

Es importante recalcar que debemos utilizar la clausula 'WITH RESUME' si queremos que el bucle continue y obtener los resultados de todas las filas proporcionadas por la query.

* De forma DINAMICA:

Pongo el mismo ejemplo que en la entrada anterior del blog para que podais ver que hay muy pocas diferencias.

LET myQuery = "SELECT pedidos.id_pedido, pedidos.fecha FROM pedidos";
--
--Incluyo la tabla de paises si se ha definido un filtro
--
IF filtro IS NOT NULL THEN
    LET myQuery = TRIM(myQuery) || ", paises";
END IF;
LET myQuery = TRIM(myQuery) ||
    " WHERE pedidos.fecha BETWEEN '01/01/2015' AND '31/12/2015';
--
-- Incluyo las condiciones de Join con la tabla de paises
--
IF filtro IS NOT NULL THEN
    LET myQuery = TRIM(myQuery) ||
         "  AND paises.nombre = " || TRIM(filtro) ||
         "  AND pedidos.id_pais = paises.id_pais";
END IF;

Una vez montada la query en la variable de texto, declaramos el CURSOR:

--
-- Preparo la variable que contiene la query antes de declarar el cursor
-- 
PREPARE mySql FROM myQuery;
DECLARE myCursor CURSOR FOR  mySql;
OPEN myCursor;
FETCH myCursor INTO  idPedido, fechaPedido;
IF (SQLCODE = 0) THEN
        WHILE 1=1

                RETURN id_pedido, fechaPedido WITH RESUME;

               FETCH myCursor INTO idPedido, fechaPedido;
               IF (SQLCODE = 100) THEN
                     EXIT WHILE;
               END IF;  

        END WHILE; 
END IF; CLOSE myCursor;
FREE myCursor;
FREE mySql;

Si teneis cualquier duda me poneis un comentario y procuraré responderos lo antes posible.

Hasta pronto.

USO DE CURSORES DINAMICOS EN INFORMIX 4GL - Using dynamic Cursors in Informix 4gl

Si la query asociada a la declaración de un CURSOR puede variar en funcion de determinadas condiciones, no nos queda mas remedio que definirlo de manera dinámica a partir de una variable:

Por ejemplo, imaginemos que queremos seleccionar los pedidos del ultimo año, pero el usuario puede optar por filtrar su pais de origen o no.
Dependiendo de si existe un filtro o no deberemos incluir un JOIN en nuestra QUERY:

Montamos la query en una variable de texto:

DEFINE myQuery CHAR(300) ,
            datosPedido LIKE pedidos.*,
            filtro CHAR(30)
---
---
---
LET myQuery = "SELECT pedidos.* FROM pedidos"
##
## Incluyo la tabla de paises si se ha definido un filtro
##
IF filtro IS NOT NULL THEN
    LET myQuery = myQuery CLIPPED, ", paises"
END IF
LET myQuery = myQuery CLIPPED,
    " WHERE pedidos.fecha BETWEEN '01/01/2015' AND '31/12/2015'
##
## Incluyo las condiciones de Join con la tabla de paises
##
IF filtro IS NOT NULL THEN
    LET myQuery = myQuery CLIPPED,
         "  AND paises.nombre = ", filtro,
         "  AND pedidos.id_pais = paises.id_pais"
END IF

Una vez montada la query en la variable de texto, declaramos el CURSOR:

##
## Preparo la variable que contiene la query antes de declarar el cursor
##
PREPARE mySql FROM myQuery
DECLARE myCursor CURSOR FOR  mySql
FOREACH myCursor INTO datosPedido
    ----
    ----
    ----
END FOREACH
FREE myCursor
FREE mySql

Hay que destacar que tenemos que hacer un FREE tanto del cursor como de la instruccion preparada para liberar recursos.


Hasta la próxima.

martes, 26 de abril de 2016

SQL AVANZADO (I). USO DE LA EXPRESION CASE EN SENTENCIAS SQL INFORMIX - Advanced Informix Sql (I). Using the CASE expression in Sql

Supongo que algún jefe impaciente os habrá pedido alguna vez un informe de forma urgente (a mi me pasó hace tiempo...).
Por SQL es posible obtener información muy valiosa pero muchas veces el formato en el que se almacena la información no es del todo inteligible para la mayoría de los mortales y os habrá tocado 'maquillarlo' antes de entregarlo con el trabajo que eso conlleva.

Bueno, pues el uso de la expresión CASE en una sentencia SQL me ha servido para librarme de algunas de esas labores tan tediosas.

Imaginemos la típica tabla de pedidos en la que tenemos una columna que indica la situación del mismo con estos valores:
  • 10-Recibido
  • 20-En fabricación
  • 30-Terminado
  • 40-Enviado
  • 50-Entregado
En un informe normal obtenido por SQL unicamente podriamos obtener el código de la situación y tendríamos que explicar su significado.

Sin embargo si utilizamos la expresión CASE podemos obtener algo como esto:

SELECT 
id_pedido PEDIDO,
CASE situacion
  WHEN 10 THEN "RECIBIDO"
  WHEN 20 THEN "EN FABRICACION"
  WHEN 30 THEN "TERMINADO"
  WHEN 40 THEN "ENVIADO"
  WHEN 50 THEN "ENTREGADO"
  ELSE "DESCONOCIDO"
END SITUACION,
fecha_pedido FECHA,
cliente CLIENTE
FROM PEDIDOS

















Ya sé que el ejemplo es algo sencillo pero estoy seguro de que le vereis la forma de sacarle partido.
Lo que no estoy seguro es de si estas expresiones son estandares de SQL y se pueden utilizar con otros gestores de Bases de Datos.

Eso ya me lo comentais vosotros...


viernes, 8 de abril de 2016

CONTROL DE TRANSACCIONES DESDE PROCEDIMIENTOS INFORMIX - Controling transactions from Informix Stored procedures

Muchas veces se nos da la situación de que un procedimiento debe hacer muchas operaciones que conviene englobar dentro de una transaccion.
Esto lo solucionamos incluyendo todas las instrucciones entre las etiquetas BEGIN WORK y COMMIT WORK.
El problema viene cuando este procedimiento puede ser llamado desde diferentes sitios, es decir, puedo intentar ejecutarlo desde una aplicación Java, PHP o incluso desde el propio dbaccess y no sabemos si cuando se hace la llamada la transacción ya se encuentra abierta o no.

Para solucionar este problema lo que suelo hacer es capturar el error -535 (Already in transaction), de forma que cuando intento abrir la transacción en el procedimiento salta esta excepcion asumo que quien lo está ejecutando ya ha abierto la transaccion previamente.
Si he detectado que existe una transaccion abierta previa, evito cerrar la transaccion dentro del procedimiento para que siga su curso.

Por ejemplo:

CREATE PROCEDURE sp_myProcedure ()

DEFINE transactionOpen SMALLINT;

ON EXCEPTION IN (-535)
   LET transactionOpen = 1; --      Activo la variable de control
END EXCEPTION WITH RESUME;

LET transactionOpen = 0; -- Incia el procedimiento poniendo valor 0 a la variable de control
BEGIN WORK; -- En este momento saltaria la excepcion -535 y se activaria a 1 la variable de control
---
---
---
--- Solo cierro la transaccion si no se ha activado la variable transactionOpen
---
IF transactionOpen = 0 THEN
    COMMIT WORK;
END IF;
END PROCEDURE



Espero que os sirva de utilidad.

lunes, 14 de marzo de 2016

CAPTURA DE EXCEPCIONES DESDE INFORMIX 4GL - Capture of exceptions from Informix 4gl

Si usamos habitualmente procedimientos almacenados, es probable que bajo determinadas circunstancias deseemos provocar una excepción voluntariamente con el código -746.
Si el procedimiento es llamado desde 4gl tenemos que encontrar la forma de capturar esa excepción y mostrar un mensaje en pantalla.

Pongamos un ejemplo de un procedimiento que recibe un parametro con un pedido de un determinado articulo. Lo primero que hace es comprobar que hay suficiente stock para poder atenderlo y, en caso contrario, provocar una excepcion.

CREATE PROCEDURE 'user'.sp_controlStock (
        articulo LIKE tablaArticulos.id_articulo,
        cantidad INTEGER)

    DEFINE stockArticulo LIKE tablaAlmacen.stockDisponible;

    SELECT stockDisponible INTO stockArticulo
         FROM tablaAlmacen
       WHERE id_articulo = articulo;

    IF stockArticulo < cantidad THEN
        RAISE EXCEPCION -746,0,'No hay stock disponible!!';
    END IF;

END PROCEDURE;


Para controlar la excepcion -746 desde 4gl tendriamos que deshabilitar el control de las excepcion con WHENEVER ERROR CONTINUE, tratar el error que deseamos controlar y por ultimo volver a habilitar el control de las excepciones.

WHENEVER ERROR CONTINUE
EXECUTE sp_controlStock USING articulo, cantidad
IF STATUS = 0 THEN
    pedidoOk =TRUE
ELSE
    IF STATUS = -746 THEN
       MESSAGE "No hay stock disponible para atender el pedido!!"
       pedidoOk = FALSE
    ELSE
       CALL controlErrores()
    END IF 
END IF 
WHENEVER ERROR CALL controlErrores



Hasta la próxima...

martes, 8 de marzo de 2016

CONOCER LAS CONSTRAINTS FOREIGN KEY DE UNA TABLA - Knowing the foreign keys of a Informix table by Sql

Si queremos conocer las constraints de tipo Foreign Key que apuntan a una determinada tabla por SQL, podemos utilizar esta query en la que sólo debeis sustituir "my_table_name" por el nombre de la tabla que querais inspeccionar.


SELECT d.tabname primaria, i.colname columna,
       a.tabname secundaria, f.colname columna,
       b.constrname constraint, b.owner propietario, b.constrtype tipo,
       CASE c.delrule
          WHEN 'C' then 'SI'
          WHEN 'R' then 'NO'
       END cascade_delete
  FROM systables a,
       sysconstraints b,
       sysreferences c,
       systables d,
       sysindexes e,
       syscolumns f,
       sysconstraints g,
       sysindexes h,
       syscolumns i
 WHERE d.tabname = "my_table_name"
   AND c.ptabid = d.tabid
   AND b.constrid = c.constrid
   AND g.constrid = c.primary
   AND b.constrtype = 'R'
   AND a.tabid = b.tabid
   AND e.idxname = b.idxname -- Indice tabla secundaria
   AND h.idxname = g.idxname -- Indice tabla primaria
   AND i.tabid = g.tabid -- Columnas tabla primaria
   AND f.tabid = b.tabid -- Columnas tabla secundaria
   AND ((f.colno = e.part1 AND i.colno = h.part1) or
        (f.colno = e.part2 AND i.colno = h.part2) or
        (f.colno = e.part3 AND i.colno = h.part3) or
        (f.colno = e.part4 AND i.colno = h.part4) or
        (f.colno = e.part5 AND i.colno = h.part5) or
        (f.colno = e.part6 AND i.colno = h.part6) or
        (f.colno = e.part7 AND i.colno = h.part7) or
        (f.colno = e.part8 AND i.colno = h.part8) or
        (f.colno = e.part9 AND i.colno = h.part9) or
        (f.colno = e.part10 AND i.colno = h.part10) or
        (f.colno = e.part11 AND i.colno = h.part11) or
        (f.colno = e.part12 AND i.colno = h.part12) or
        (f.colno = e.part13 AND i.colno = h.part13) or
        (f.colno = e.part14 AND i.colno = h.part14) or
        (f.colno = e.part15 AND i.colno = h.part15) or
        (f.colno = e.part16 AND i.colno = h.part16))

Los tipos de constrains reflejados en la columna constrtype de la tabla sysconstraints pueden ser:

  • C Constraint de tipo Check
  • P Constraint Primary Key
  • R Constraint Reference (Foreign Key)
  • U Constraint Unique
  • N Constraint NOT NULL 
Espero que os sea de utilidad.

martes, 1 de marzo de 2016

CONOCER LAS TABLAS QUE MAS ESPACIO OCUPAN EN UN DBSPACE DESDE SQL - Knowing the size of a Informix table in a Dbspace by Sql

TABLAS DE UN DBSPACE

Si queremos ir equilibrando la ocupacion de los dbspaces que componen nuestra base de datos, es interesante tener una herramienta que nos permita conocer qué tablas son las que mas espacio ocupan en un determinado dbspace para ver si nos conviene pasarlas a otro.

Desde sql ejecutamos la instrucción:

DATABASE sysmaster;
SELECT 
   t2.name dbspace,
   t1.tabname table,
   t1.owner owner,
   TRUNC(t3.nrows,0) rows,
   TRUNC(t3.rowsize,0) row_size,
   TRUNC(t3.nrows*t3.rowsize/1024/1024,2) MB
FROM systabnames t1,sysdbspaces t2, segeua:systables t3
WHERE t2.dbsnum=trunc(t1.partnum/1048576)
  AND t2.name='dbspace_01'
  AND t1.tabname = t3.tabname
ORDER BY 6 DESC


Obtendremos una lista como esta:














DBSPACE DE UNA TABLA

Con una pequeña modificación de la query anterior podemos conocer el dbspace en el que se encuentra una determinada tabla:

DATABASE sysmaster;
SELECT
    t2.name dbspace,
    t1.tabname table,
    t1.owner owner,
    TRUNC(t3.nrows,0) rows,
    TRUNC(t3.rowsize,0) rowsize,
    TRUNC(t3.nrows*t3.rowsize/1024/1024,2) MB
FROM systabnames t1,sysdbspaces t2, segeua:systables t3
WHERE t2.dbsnum=trunc(t1.partnum/1048576)
  AND t1.tabname='tabla_01'
  AND t1.tabname = t3.tabname


A mi me han sido de utilidad y continuan siendolo.
Hasta la próxima.

jueves, 25 de febrero de 2016

CONOCER LA OCUPACION DE DBSPACES EN INFORMIX POR SQL - Knowing the free space of a Informix Dbspace by SQL

Aunque actualmente existen herramientas como OAT, que nos permiten visualizar graficamente la ocupación de los Dbspaces definidos en nuestra base de datos Informix, es conveniente poder obtener esta información por SQL por si queremos incluir está información en un registro para hacer un seguimiento detallado.

Para obtener información de nuestros Dbspaces, asi como de los Chunks que los forman debemos recurrir a dos tablas de la Base de datos sysmaster:
  • sysdbspaces
  • syschunks
La forma de calcular el espacio que ocupan, asi como el espacio libre varía en función de si es un dbspace  normal, o es un blobspace o es un sbspace.

Lo he resuelto haciendo tres querys unidas por la clausula UNION:

DATABASE sysmaster;
--
-- Dbspace 'normal'
--
SELECT
d.name[1,10] Name,
d.nchunks Chunks,
SUM(k.chksize*2/1024) Total,
SUM(k.nfree*2/1024) Free,
100-(SUM(k.nfree*2/1024)*100)/(SUM(k.chksize*2/1024)) Perc
FROM sysdbspaces d, syschunks k
WHERE d.dbsnum=k.dbsnum
AND d.is_blobspace=0
AND d.is_sbspace=0
GROUP BY 1,2

UNION
--
-- Blobspaces
--
SELECT
d.NAME[1,10] Name,
d.nchunks Chunks,
SUM(k.chksize*2/1024) Total,
SUM(k.nfree*8/1024)Free,
100-(SUM(k.nfree*8/1024)*100)/(SUM(k.chksize*2/1024)) Perc
FROM sysdbspaces d, syschunks k
WHERE d.dbsnum=k.dbsnum
AND d.is_blobspace=1
GROUP BY 1,2

UNION
--
-- Sbspaces
--
SELECT
d.NAME[1,10] Name,
d.nchunks Chunks,
SUM(k.chksize*2/1024) Total,
SUM(k.udfree*2/1024) Free,
100-(SUM(k.udfree*2/1024)*100)/(SUM(k.chksize*2/1024)) Perc
FROM sysdbspaces d, syschunks k
WHERE d.dbsnum=k.dbsnum
AND d.is_sbspace=1
GROUP BY 1,2
ORDER BY 1


El resultado es una tabla como esta:













Hasta la próxima.

lunes, 22 de febrero de 2016

DECLARACION DE CURSORES BASADOS EN UN PROCEDIMIENTO - Declaring Cursors based in a Informix Stored Procedure (SPL)

Como hemos visto en entradas anteriores, la declaración de un cursor, ya sea en 4GL o en SPL, esta intimamente ligada a una query SQL que nos proporciona los resultados deseados.
Pues bien, tambien podemos ligar la declaración de un cursor a los resultados proporcionados por un procedimientotanto en r4GL como en SPL.

Imaginemos un procedimiento que nos proporcione una lista de los clientes de un determinado pais. Si deseamos obtener información detallada de todos los paises haremos lo siguiente:

* En R4GL:

##
## Lo primero que debemos hacer  es preparar el procedimiento que vamos a
## utilizar.
## En la definición del procedimiento incluimos un interrogante por cada
## parámetro de entrada, en nuestro ejemplo recibirá el id del país
## y nos proporcionará el id y nombre de cada uno de los clientes.
##
PREPARE procClientes FROM "EXECUTE PROCEDURE procClientes(?)"
##
## A continuación declaramos un cursor de la tabla de paises tal como hemos visto en entradas anteriores:
##
DECLARE cursorPais CURSOR FOR
SELECT id_pais, nombre_pais FROM paises
ORDER BY nombre_pais

FOREACH cursorPais INTO idPais, nombrePais
       ##  
       ## Le paso el idPais al procedimiento y nos devuelve una lisa de clientes
       ##
       FOREACH procClientes USING idPais INTO idCliente, nombreCliente
             ----         
             ----                        
       END FOREACH;

END FOREACH


* En SPL:

FOREACH
     SELECT id_pais, nombre_pais INTO idPais, nombrePais
         FROM paises
       ORDER BY nombre_pais

       --
       -- En Spl no hace falta preparar el procedimiento.
       -- Lo utilizamos directamente pasandole los parámetros
       -- de entrada entre parentesis
       -- 
       FOREACH
             EXECUTE PROCEDURE procCLientes (idPais)
                     INTO idCliente, nombreCliente;

             RETURN nombrePais, nombreCliente WITH RESUME;

       END FOREACH;

END FOREACH;


Espero que os sea de utilidad.
Hasta la próxima.


lunes, 15 de febrero de 2016

DECLARACION Y USO DE CURSORES EN INFORMIX 4GL - Declaring Cursors in Informix 4gl

Una de las herramientas mas potentes en Informix 4gl es el uso de cursores para obtener información de la base de datos. Voy a comentaros cómo se declaran y las formas que conozco de explotarlos.
La forma de declararlos es muy sencilla:

DECLARE myCursor CURSOR FROM
SELECT * FROM tabla
WHERE condiciones

Es decir, definimos un CURSOR con el nombre que queramos (myCursor en el ejemplo) asociado a una query que nos aportará los datos que necesitamos.
Existen dos formas distintas de abrir el cursor y empezar a explotar la informacion aportada:

* Uso de FOREACH:

DEFINE datosCliente RECORD LIKE clientes.*

DECLARE myCursor CURSOR FOR
SELECT * FROM clientes
WHERE condiciones

FOREACH myCursor INTO datosCliente
   ---
   ---
   ---
END FOREACH
FREE myCursor

* Uso de OPEN - CLOSE

DEFINE datosCliente RECORD LIKE clientes.*

DECLARE myCursor CURSOR FOR
SELECT * FROM clientes
WHERE condiciones

OPEN myCursor
FETCH myCursor INTO datosCliente
IF STATUS = 0 THEN
    WHILE TRUE
        ---
        ---
        ---
        FETCH myCursor INTO datosCliente        
        IF STATUS = NOTFOUND THEN
           EXIT WHILE
        END IF
    END WHILE
END IF
CLOSE myCursor
FREE myCursor

No hay diferencias en cuanto al uso de uno u otro método, pero yo recomiendo usar OPEN - CLOSE para realizar REPORTS por una razón muy sencilla, al realizar el primer FETCH podemos averiguar si el cursor nos va a devolver alguna información. De esta forma podemos hacer el START REPORT seguros de que al menos va a tener una linea.

DECLARE myCursor CURSOR FOR
SELECT * FROM clientes
WHERE condiciones

OPEN myCursor
FETCH myCursor INTO datosCliente
IF STATUS = 0 THEN
    ##
    ## Inicio el Report cuando sé que va a tener contenido
    ##
    START REPORT myReport TO printer
    WHILE TRUE
        OUTPUT TO REPORT miReport (datosCliente)
        FETCH miCursor INTO datosCliente
        IF STATUS = NOTFOUND THEN
            EXIT WHILE
        END IF
    END WHILE
    ##
    ## Finalizo el Report cuando salgo del while
    ##
    FINISH REPORT myReport
END IF
CLOSE myCursor
FREE myCursor

Espero que os sea de utilidad... hasta la próxima.