< Anterior | Siguiente >

Lección 3: Gestionar un cursor con open y forEach

Un cursor es una construcción SQL que se comporta como un marcador, señalando hacia una fila y permitiendo recorrer una serie de resultados de búsqueda (es decir, filas de tabla) de uno en uno. EGL puede gestionar cursores automáticamente, pero también proporciona las sentencias open y forEach para poder utilizar los cursores.
Por qué y cuándo se efectúa esta tarea

En la lección anterior, puede que haya observado que la sentencia delete incluía la palabra clave noCursor. Aunque la sentencia get selecciona una o varias filas de toda una tabla, la sentencia delete opera normalmente sobre una fila indicada por un cursor creado por una sentencia anterior. La palabra clave noCursor indica que no existe ningún cursor de ese tipo y que la sentencia debe utilizar una cláusula WHERE para identificar la fila del mismo modo que la sentencia get predeterminada.

La sentencia EGL open crea un cursor e indica un conjunto de resultados, o un conjunto de resultados de una o varias filas que el cursor puede recorrer. En lugar de acceder al cursor directamente, se utilizan otras sentencias EGL para recuperar la próxima fila del conjunto de resultados según lo indicado por el cursor, o para realizar operaciones en todo el conjunto de resultados utilizando el cursor para recorrer las filas una por una.

Para crear un cursor y un conjunto de resultados en EGL, utilice la sentencia open para identificar una o varias filas y dar al conjunto de resultados nuevo un nombre y una variable temporal que contenga la fila indicada por el cursor:
tempItem Item;
open allExpensiveItems for tempItem;
En este ejemplo, tempItem es una única variable de registro del componente SQLRecord que representa la tabla ITEMS de la base de datos. El código for tempItem especifica que EGL utilizará el cursor para mover filas a este registro una por una. El código open allExpensiveItems da un nombre al conjunto de resultados; en términos estrictos, allExpensiveItems es un identificador, no una variable.
Al igual que una sentencia get, EGL crea una sentencia SQL SELECT a partir de la sentencia open, que puede explicitar y editar. La sentencia SELECT predeterminada para el ejemplo de sentencia open anterior es como la siguiente:
tempItem Item;

open expensiveItems for tempItem with
  #sql{
    select
      EGL.ITEM.ITEM_ID, EGL.ITEM.NAME, EGL.ITEM.IMAGE, 
      EGL.ITEM.PRICE, EGL.ITEM.DESCRIPTION
    from EGL.ITEM
    where
      EGL.ITEM.ITEM_ID >= :tempItem.ItemId
    order by
      EGL.ITEM.ITEM_ID asc
  };
Esta cláusula WHERE predeterminada especifica sólo que cada artículo debe tener un valor ITEM_ID superior al anterior. (EGL gestiona las sentencias SQL DECLARE CURSOR y OPEN CURSOR y no las muestra en el SQL explícito).
Para seleccionar filas determinadas para añadirlas al conjunto de resultados, debe editar la cláusula WHERE igual que hizo con la sentencia get. Por ejemplo, puede seleccionar sólo los artículos cuyo coste sea superior al de una variable determinada:
tempItem Item;
minCost Price = 500;

open expensiveItems for tempItem with
  #sql{
    select
      EGL.ITEM.ITEM_ID, EGL.ITEM.NAME, EGL.ITEM.IMAGE, 
      EGL.ITEM.PRICE, EGL.ITEM.DESCRIPTION
    from EGL.ITEM
    where
      EGL.ITEM.PRICE >= :minCost
    order by
      EGL.ITEM.ITEM_ID asc
  };
Ahora, el conjunto de resultados expensiveItems contiene todas las filas de la base de datos cuya columna PRICE es igual o superior a la variable minCost, que está establecida en 500.
Para acceder a estas filas, utilice la sentencia forEach, que ejecuta un bloque de código en un bucle, aplicando dicho código a cada artículo del conjunto de resultados. Durante cada iteración del bucle, EGL mueve la fila indicada por el cursor a la variable temporal y hace avanzar el cursor a la fila siguiente del conjunto de resultados. Por ejemplo, el código que sigue muestra los valores de cada uno de los artículos cuyo precio es superior a 500:
tempItem Item;
minCost Price = 500;

open expensiveItems for tempItem with
  #sql{
    select
      EGL.ITEM.ITEM_ID, EGL.ITEM.NAME, EGL.ITEM.IMAGE, 
      EGL.ITEM.PRICE, EGL.ITEM.DESCRIPTION
    from EGL.ITEM
    where
      EGL.ITEM.PRICE >= :minCost
    order by
      EGL.ITEM.ITEM_ID asc
  };
  
foreach (tempItem)
  SysLib.writeStdout(tempItem.Name :: " " ::
    tempItem.Description :: " $" :: tempItem.Price);
end
También puede utilizar get next para recorrer los resultados manualmente. En este caso, debe detectar si existen más resultados en el conjunto de resultados; por ejemplo, puede comprobar el valor de sysVar.sqlData.sqlcode, que está establecido en cero siempre y cuando no haya más resultados disponibles:
tempItem Item;
minCost Price = 500;

open expensiveItems for tempItem with
  #sql{
    select
      EGL.ITEM.ITEM_ID, EGL.ITEM.NAME, EGL.ITEM.IMAGE, 
      EGL.ITEM.PRICE, EGL.ITEM.DESCRIPTION
    from EGL.ITEM
    where
      EGL.ITEM.PRICE >= :minCost
    order by
      EGL.ITEM.ITEM_ID asc
  };

// Recuperar la primera fila.
get next tempItem;

// Siempre que haya más filas,
// mover la fila siguiente a la variable temporal
// y mostrar sus valores.
while (sysvar.sqlData.sqlcode == 0) 
  SysLib.writeStdout(tempItem.Name :: " " ::
    tempItem.Description :: " $" :: tempItem.Price);
  get next tempItem;
end
La sentencia get realiza otras diversas operaciones que puede utilizar con un cursor abierto y un conjunto de resultados:
  • Si utiliza get next, puede especificar el conjunto de resultados en lugar de la variable temporal como en el ejemplo anterior. Por ejemplo, get next from expensiveItems mueve la fila siguiente del conjunto de resultados a la variable temporal; este código es equivalente a get next tempItem. De forma parecida, puede utilizar el nombre del conjunto de resultados en la sentencia forEach; forEach (from expensiveItems) es equivalente a forEach (tempItem).
  • get current recupera la fila indicada actualmente por el cursor sin mover el cursor a la fila siguiente.
  • get first y get last recuperan la primera y la última fila de los resultados, respectivamente.
  • get previous recupera la fila anterior al cursor, a la inversa de get next
  • get absolute(posición) recupera la fila de una posición determinada del conjunto de resultados. Por ejemplo, get absolute(5) tempItem recupera la quinta fila del conjunto de resultados, y get absolute(-2) tempItem recupera la antepenúltima fila.
  • get relative(posición) recupera la fila de una posición determinada relativa a la posición actual del cursor. Por ejemplo, get relative(3) tempItem recupera la tercera fila a continuación de la posición actual del cursor, y get relative(-2) tempItem recupera la segunda fila antes de la posición actual del cursor. get relative(0) tempItem es equivalente a get current tempItem.
Para utilizar estas sentencias get posicionales a parte de get next, debe indicar que el conjunto de resultados es scrollable (desplazable), o que tiene previsto cambiar la posición del cursor manualmente en lugar de recorrer las filas una por una del principio al final. Añada la palabra clave scroll a la sentencia open, como en este ejemplo:
open expensiveItems scroll for tempItem;
EGL cierra automáticamente el cursor cuando encuentra alguna de varias condiciones, que incluyen una sentencia get next que no devuelve resultados porque el cursor se encuentra al final del final del conjunto de resultados, otra sentencia open en el mismo conjunto de resultados o el final del programa. Si ha terminado con el cursor y el conjunto de resultados, puede cerrarlos con la sentencia EGL close, como en el ejemplo siguiente:
close expensiveItems;
Una vez cerrados el cursor y el conjunto de resultados, get next y otras sentencias que dependen del cursor dejan de funcionar.

Recorrer un conjunto de resultados

En esta lección, recorrerá un conjunto de resultados con la sentencia forEach para recuperar artículos de la base de datos por encima de un precio determinado.
  1. Cree un programa en el paquete programs llamado selectItems.
  2. Elimine el código predeterminado del programa de modo que sea como el siguiente:
    package programs;
    
    program selectItems type BasicProgram {}
      
      function main()
      end
      
    end
  3. Añada una función para manejar los errores que EGL pueda encontrar al acceder a la base de datos:
    function handleDBException(exception SQLException)
      SysLib.writeStdout("Error de acceso a la base de datos."::
        "Consulte la sección Resolución de problemas");
      SysLib.writeStdout(exception.message);
    end
    Aprenderá más cosas acerca del manejo de los errores encontrados al acceder a la base de datos más adelante en esta guía de aprendizaje.
  4. Añada una función para recuperar filas de la base de datos en función de un parámetro Price (precio) correspondiente al coste máximo:
    function printExpensiveItems(maxPrice Price in)
      
    end
    El componente Price dataItem se basa en el tipo primitivo DECIMAL. Está definido en el archivo EGLDerbyDB/primitivetypes/data.
  5. Si no ha utilizado la asistencia de contenido para añadir la variable Price a la declaración de la función, añada la sentencia import siguiente al principio del archivo, inmediatamente debajo de la sentencia package:
    import EGLDerbyDB.primitivetypes.data.*;

    Recuerde que puede utilizar la asistencia de contenido para completar los nombres de palabras clave y tipos tecleando las primeras letras y pulsando CONTROL+Barra espaciadora. Al insertar un tipo con asistencia de contenido, como por ejemplo el componente Price dataItem, la asistencia de contenido también añade la sentencia import adecuada al archivo.

  6. Dentro de la función nueva, añada una sentencia open para crear un conjunto de resultados y un cursor. También necesitará una variable temporal Item para que contenga la fila indicada por el cursor:
      tempItem Item;
      open expensiveItems for tempItem;
  7. De nuevo, si no ha utilizado la asistencia de contenido para insertar el tipo de registro Item, añada una sentencia import para llevar al ámbito el componente Record:
    import EGLDerbyDB.data.Item;
  8. Explicite el código SQL situando el cursor en la sentencia open, pulsándola con el botón derecho del ratón y pulsando Sentencia SQL > Añadir. La sentencia open tiene este aspecto:
      open expensiveItems for tempItem with
        #sql{
          select
            EGL.ITEM.ITEM_ID, EGL.ITEM.NAME, EGL.ITEM.IMAGE, 
            EGL.ITEM.PRICE, EGL.ITEM.DESCRIPTION
          from EGL.ITEM
          where
            EGL.ITEM.ITEM_ID >= :tempItem.ItemId
          order by
            EGL.ITEM.ITEM_ID asc
        };
  9. Cambie la cláusula WHERE del código SQL explícito para seleccionar sólo las filas cuyo valor de la columna EGL.ITEM.PRICE sea igual o superior al de la variable maxPrice pasada a la función:
      open expensiveItems for tempItem with
        #sql{
          select
            EGL.ITEM.ITEM_ID, EGL.ITEM.NAME, EGL.ITEM.IMAGE, 
            EGL.ITEM.PRICE, EGL.ITEM.DESCRIPTION
          from EGL.ITEM
          where
            EGL.ITEM.PRICE >= :maxPrice
          order by
            EGL.ITEM.ITEM_ID asc
        };
    Recuerde incluir el signo de dos puntos antes de la variable maxPrice para indicar que se trata de una variable EGL, no de una palabra clave o valor SQL.
  10. Después de la sentencia open, añada un bucle forEach que muestre la información relativa a los artículos en la consola:
      forEach (tempItem)
        SysLib.writeStdout(tempItem.Name :: " " ::
          tempItem.Description :: " $" :: tempItem.Price);
      end
    La función completa tiene este aspecto:
    function printExpensiveItems(maxPrice Price in)
      
    tempItem Item;
    open expensiveItems for tempItem with
      #sql{
        select
          EGL.ITEM.ITEM_ID, EGL.ITEM.NAME, EGL.ITEM.IMAGE, 
          EGL.ITEM.PRICE, EGL.ITEM.DESCRIPTION
        from EGL.ITEM
        where
          EGL.ITEM.PRICE >= :maxPrice
        order by
          EGL.ITEM.ITEM_ID asc
      };
      
    forEach (tempItem)
      SysLib.writeStdout(tempItem.Name :: " " ::
        tempItem.Description :: " $" :: tempItem.Price);
    end
  11. Llame a la función desde la función main y pase un valor para maxPrice:
    function main()
      try
        printExpensiveItems(200);
      onException(exception SQLException)
        handleDBException(exception);
      end
    end
  12. Genere y ejecute el programa.
Resultados
El programa visualiza los nombres de los artículos en venta de la base de datos cuyo precio es igual o superior al valor pasado a la función. Por ejemplo, si se pasa 200 a la función, se generan los resultados siguientes:
Laptop PC Great little machine.  Take it anywhere with you. $1111.11
Combination Safe Keep your valuables safe and secure.  Put 'em in here. $222.22
Desktop PC Lots'a power, at the right price.  Includes monitor. $888.55

Este es el código completo del archivo selectItems.egl. Si ve errores marcados con símbolos X rojos en el archivo, asegúrese de que el código coincide con el código de este archivo: Archivo selectItems.egl completado después de la lección 3.

Punto de comprobación de lección

En esta lección, a aprendido a trabajar con las filas de un conjunto de resultados una por una con las sentencias open y forEach.
Trabajar en las filas de un conjunto de resultados una por una puede utilizando un cursor ser más conveniente y eficaz que utilizar get para recuperar toda la lista de resultados en una matriz de registros.
Para obtener más información, consulte los temas de ayuda relativos a las sentencias open y forEach.
< Anterior | Siguiente >

Comentarios