Mostrando entradas con la etiqueta Tutorial PL/SQL. Mostrar todas las entradas
Mostrando entradas con la etiqueta Tutorial PL/SQL. Mostrar todas las entradas

lunes 30 de junio de 2008

Integridad virtual con la base de datos Oracle 11g

Nuevas funcionalidades en Oracle 11g, las columnas virtualesSupongamos que nuestra base de datos Oracle dispone de una tabla en la que uno de los campos está formado realmente por muchos subcampos concatenados. El campo en cuestión no puede modificarse pero necesitamos validar y forzar la integridad de uno de los subcampos. Por ejemplo, queremos que los valores devueltos al ejecutar la función SUBSTR(column,7,3) sobre dicho campo estén incluidos en una tabla de referencia o lookup table.

Para versiones de la base de datos Oracle anteriores a la 11g no existía una forma sencilla de lidiar con este tipo de requerimientos ya que la validación de valores almacenados en una determinada columna, obligaba a que dichos valores estuvieran almacenados en una columna real que apuntase a la tabla de referencia o lookup table. En nuestro problema no existe una columna real sino que estamos tratando con una función aplicada sobre una columna. En la versión de la base de datos Oracle 10g y anteriores, lo más razonable sería crear una columna adicional en la tabla y, mediante el uso de un trigger PL/SQL, insertar o actualizar el valor retornado por la función SUBSTR en dicha nueva columna. A continuación podéis ver el código correspondiente a la implementación de esta solución:

SQL > CREATE TABLE tabla_referencia
2 ( validacion VARCHAR2(3) PRIMARY KEY,
3 descripcion VARCHAR2(20) );
Table created.

SQL > CREATE TABLE tabla_datos
2 ( concatenados VARCHAR2(20) PRIMARY KEY,
3 validacion VARCHAR2(3) NOT NULL
4 REFERENCES tabla_referencia );
Table created.

SQL > CREATE TRIGGER tabla_datos
2 BEFORE INSERT OR UPDATE
3 OF concatenados ON tabla_datos
4 FOR EACH ROW
5 BEGIN
6 :new.validacion :=
7 SUBSTR(:new.concatenados,7,3);
8 END;
9 /
Trigger created.

SQL > INSERT INTO tabla_referencia (validacion)
2 VALUES ('XYZ');
1 row created.

SQL > INSERT INTO tabla_datos (concatenados)
VALUES ('123456XYZ012345');
1 row created.

SQL > INSERT INTO tabla_datos (concatenados)
2 VALUES ('123456ABC012345');
INSERT INTO .....
*
ERROR at line 2:
ORA-02291: Integrity constraint violated -
parent key not found

La solución propuesta conlleva un par de problemas. El primero se trata de un obvio problema de almacenamiento ya que estamos almacenando el mismo dato dos veces, una vez en el campo concatenados y otra en el campo validacion. El segundo problema viene dado por la necesidad de utilizar un trigger PL/SQL y las implicaciones que este hecho puede conllevar sobre el rendimiento y los tiempos de ejecución de los comandos INSERT y UPDATE.

El caso es que con la versión de la base de datos Oracle 11g podemos forzar la integridad utilizando funciones aplicadas sobre una determinada columna; es decir, no estamos limitados a utilizar columnas reales, podemos utilizar una columna virtual. Así pues, con Oracle 11g podemos prescindir del trigger PL/SQL. Veamos a continuación como debemos crear nuestra tabla de datos para conseguir que esto ocurra:

SQL > CREATE TABLE tabla_datos
2 ( concatenados VARCHAR2(20) PRIMARY KEY,
3 validacion AS
4 (SUBSTR(concatenados,7,3))
5 REFERENCES tabla_referencia );
Table created.

En este caso hemos definido una columna virtual con el nombre concatenados, columna que es simplemente un metadato, que no se almacena de manera redundante, y que no requiere de un trigger PL/SQL para su mantenimiento, pero que si que se puede utilizar para forzar la integridad de nuestra base de datos cruzando dicho valor con los valores almacenados en nuestra tabla de referencia o lookup table.

Artículos relacionados: Oracle lanza la versión 11g de su base de datos.

martes 20 de mayo de 2008

Oracle 11g y la cláusula PIVOT: Como girar columnas en SQL y PL/SQL

Una tarea que puede resultarnos útil en determinadas circunstancias es conseguir girar (en inglés pivot) los resultados entregados por una consulta SQL o PLSQL. Por ejemplo, supongamos que escribimos la siguiente consulta para calcular el precio medio de los productos por almacen:

SQL> SELECT almacen, producto, AVG(precio) pmedio
2 FROM productos
3 GROUP BY almacen, producto
4 /

ALMACEN PRODUCTO PMEDIO
------- -------- ------
Ba ABC123 95
Ba DEF456 75
Ba XYZ987 160
Ma ABC123 100
Ma XYZ987 150
Va DEF456 80

6 rows selected.

Basándonos en esta consulta, lo que queremos conseguir es que los distintos productos (ABC123, DEF456 y XYZ987) lleguen a ser las cabeceras de la consulta. Esto se puede conseguir modificando la consulta SELECT de la siguiente manera:

SQL> SELECT almacen,
2 AVG(DECODE(producto,'ABC123',precio)) ABC123,
3 AVG(DECODE(producto,'DEF456',precio)) DEF456,
4 AVG(DECODE(producto,'XYZ987',precio)) XYZ987
5 FROM productos
6 GROUP BY almacen
7 ORDER BY almacen
8 /

ALMACEN ABC123 DEF456 XYZ987
------- ------ ------ ------
Ba 95 75 160
Ma 100 150
Va 80

No obstante, aunque la consulta tal y como está escrita funciona, no podemos decir que el llegar a ella sea fácil o intuitivo. Para poder escribir de una manera más sencilla y entendible este tipo de consultas, la versión 11g de las bases de datos Oracle incorpora la nueva cláusula PIVOT. Veamos como quedaría nuestra consulta utilizando la cláusula PIVOT:

SQL> SELECT *
2 FROM (SELECT almacen, producto, precio
3 FROM productos) prod
4 PIVOT (AVG(precio) FOR producto IN
5 ('ABC123','DEF456','XYZ987'))
6 ORDER BY almacen
7 /

ALMACEN 'ABC123' 'DEF456' 'XYZ987'
------- -------- -------- --------
Ba 95 75 160
Ma 100 150
Va 80

Otro artículo del tutorial PL/SQL: La nueva sentencia SQL MERGE.

lunes 14 de abril de 2008

Vistas materializadas y la funcionalidad "Query Rewrite"

PL/SQL - La funcionalidad de reescritura de consultas y las vistas materializadasYa he escrito anteriormente un par de artículos sobre vistas materializadas (materialized views): uno sobre los aspectos generales de las vistas materializadas en SQL y PLSQL y otro sobre el refresco de las vistas materializadas en SQL y PL/SQL. En este artículo voy a tratar una de las funcionalidades soportadas por las vistas materializadas, funcionalidad conocida como QUERY REWRITE.

Funcionalidad de reescritura de una consulta

Esta claro que acceder a una vista materializada puede ser significativamente más rápido que acceder a todas las tablas base utilizadas al crear dicha vista materializada. Es por esta causa por la que, si así lo hemos indicado al crear la vista materializada, el optimizador Oracle, si la consulta o query lo permite, puede reescribir el plan de ejecución de dicha consulta para acceder a la vista en lugar de a las tablas base. Obviamente, la reescritura de la consulta es transparente a las aplicaciones que la estén utilizando. Así pues, de alguna manera, el uso del QUERY REWRITE es similar al uso de un índice.

Los usuarios no necesitan tener privilegios especiales sobre la vista materializada para poder utilizar la reescritura de una consulta. Cualquier consulta ejecutada por un usuario que tenga permisos sobre las tablas base involucradas, podrá hacer uso de la reescritura de la consulta mediante el acceso a la vista materializada en lugar de acceder a las tablas base directamente. Esto sólo dependerá de lo que decida el optimizador.

Por otro lado, la funcionalidad de reescritura de una consulta puede ser habilitada o deshabilitada a la hora de crear o modificar una vista materializada:

CREATE MATERIALIZED VIEW nombre_vistam
.....
[{ENABLE|DISABLE} QUERY REWRITE]
.....
AS SELECT ... FROM ... WHERE ...

Cómo determinar si el optimizador usa el QUERY REWRITE

La mejor forma para saber si el optimizador está utilizando la funcionalidad de reescritura de una consulta es utilizar el comando EXPLAIN PLAN o la funcionalidad AUTOTRACE. Otro aspecto a considerar es que, si el optimizador utiliza una vista materializada, entonces se debe observar una mejora en el tiempo de respuesta de la consulta.

Control de la funcionalidad QUERY REWRITE

La funcionalidad de reescritura de una consulta sólo puede utilizarse cuando se utiliza el optimizador Oracle basado en costes.

Por otro lado, el parámetro dinámico QUERY_REWRITE_ENABLED se utiliza para configurar una sesión o instancia de una base de datos Oracle, indicando si permitimos al optimizador el uso o no de la funcionalidad de reescritura de una consulta. Si ponemos este parámetro a FALSE, entonces habremos desactivado la funcionalidad de QUERY REWRITE para dicha sesión o instancia.

QUERY_REWRITE_INTEGRITY es otro parámetro dinámico que nos permite configurar la funcionalidad de reescritura de una consulta para una sesión o instancia. Dicho parámetro acepta los siguientes valores:

ENFORCED: Se trata del valor por defecto. Si el parámetro toma este valor, entonces el optimizador reescribirá las consultas sólo si puede garantizar la consistencia de los datos entregados. Es decir, sólo las vistas materializadas que han sido actualizadas pueden ser utilizadas por la funcionalidad.

TRUSTED: El optimizador reescribirá las consultas basándose en relaciones declaradas previamente sin necesidad de que estas hayan sido impuestas (ENFORCED). Es decir, todas las vistas materializadas que estén debidamente actualizadas serán utilizadas por la funcionalidad de reescritura de consultas.

STALE_TOLERATED: Siempre que sea posible el optimizador utilizará las vistas materializadas aunque éstas no estén actualizadas. Obviamente esto puede causar que la consulta devuelva valores incorrectos.

También existen hints que permiten influir al optimizador en la decisión de utilizar o no la funcionalidad de QUERY REWRITE al elaborar el plan de ejcución de una consulta, estos hints son REWRITE y NOREWRITE.

martes 18 de diciembre de 2007

El refresco de las vistas materializadas en SQL y PL/SQL

Como quedó un teclado después del refresco (refresh) de vistas materializadas (Materialized views) en las bases de datos OracleYa he hablado en otro artículo acerca del funcionamiento básico de las vistas materializadas (materialized views), en éste voy a exponer los distintos tipos de refresco que se pueden utilizar para actualizar una vista materializada con los cambios provocados por las actualizaciones en las tablas base utilizadas en la misma. El tipo de refresco que debemos elegir dependerá de la frecuencia de actualización de las tablas base y de las necesidades que tengamos de disponer de datos exactos.

Tipos de refresco

COMPLETE: Este tipo de refresco implica el borrado de los datos existentes y la reinserción de todos los datos mediante la reejecución de la consulta SELECT que define la vista materializada.

FAST: El refresco aplica sólo a los cambios realizados sobre las tablas base desde el último refresco. Puede ser de dos tipos:

- Utilizando los logs de la vista materializada: En este caso todos los cambios sobre las tablas base se almacenan en dichos logs, aplicándose en el momento del refresco a la vista materializada. Estos logs deben ser creados sobre todas las tablas base de la vista utilizando el comando CREATE MATERIALIZED VIEW LOG, es decir, necesitaremos crear un log por cada tabla base que se utilice en la vista materializada.

- Utilizando rangos ROWID: Este tipo de refresco necesita de otro tipo de logs, los llamados logs de carga directa (direct loader logs).

Conviene puntualizar que no todas las vistas materializadas pueden soportar el refresco FAST. Por ejemplo, el uso de funciones SQL como SUM, AVG, MAX, MIN o COUNT no son admitidas por este tipo de refresco.

FORCE: Es la opción por defecto si no se selecciona ningún tipo de refresco a la hora de crear la vista materializada. Este tipo de refresco funciona de la siguiente manera, si es posible la vista se refrescará utilizando el mecanismo FAST, en caso contrario se empleará la opción COMPLETE.

NEVER: Esta opción suprime todos los refrescos de la vista materializada.

Formas de refresco

Refresco manual: Los refrescos manuales de las vistas materializadas se realizan utilizando el paquete PL/SQL estándar DBMS_MVIEW. Este paquete incluye un buen número de funciones y procedimientos PLSQL que permiten gestionar las vistas materializadas. Entre ellos cabe destacar:

DBMS_MVIEW.REFRESH ('nombre_vista_materializada') - Refresca una vista materializada específica.

DBMS_MVIEW.REFRESH_DEPENDENT ('nom_tab1, nom_tab2, ...') - Refresca todas las vistas materializadas que utilicen como tabla base alguna de las tablas o vistas materializadas indicadas en la lista (los nombres de las tablas o vistas materializadas deben separarse mediante comas).

DBMS_MVIEW.REFRESH_ALL_MVIEWS (n) - Refresca todas las vistas materializadas del sistema devolviendo un entero (n) que indica el número de registros que se han refrescado.

Todos estos procedimientos y funciones admiten parámetros adicionales entre los que cabe mencionar: el tipo de refresco (? - force, f - fast, c - complete), el segmento de rollback que se debe usar durante el refresco, si se continúa (true) o no (false) realizando el refresco tras detectar un error, si el refresco de todas las vistas materializadas se realiza en una sola transacción de manera que si falla el refresco de una vista falla el refresco de todas (true) o de si cada vista materializada se refresca en transacciones separadas (false).

Los refrescos manuales requieren que los parámetros del sistema JOB_QUEUE_PROCESSES y JOB_QUEUE_INTERVAL estén configurados para permitir la ejecución de trabajos encolados.

Refresco automático: Esta forma de refresco puede realizarse de dos formas:

ON COMMIT: La vista materializada se refresca cada vez que se ejecuta un COMMIT sobre alguna de las tablas base de la vista. Esto significa que la ejecución del COMMIT tomará más tiempo lo que puede afectar seriamente al rendimiento. Yo personalmente no recomiendo este tipo de refresco.

Refresco programado: El refresco se programa para que ocurra a una hora o tiempo determinado. Por ejemplo, una vista se podría programar para que se refresque todos los días a una determinada hora mediante el uso de las cláusulas START WITH (seguido de la hora en formato datetime del primer refresco automático) y NEXT (seguido de una expresión en formato datetime que se utilizará para calcular el intervalo entre refrescos automáticos). En este caso también es necesario configurar correctamente el parámetro del sistema JOB_QUEUE_PROCESSES.

Ejemplo:

CREATE MATERIALIZED VIEW nombre_vm
...
REFRESH START WITH ROUND(SYSDATE + 1) + 9/24
NEXT NEXT_DAY(TRUNC(SYSDATE), 'TUESDAY') + 16/24
AS SELECT ...;

En caso del ejemplo, la base de datos Oracle refrescará automáticamente la vista materializada mañana a la 9:00 AM y posteriormente todos los martes a la 4:00 PM.

lunes 26 de noviembre de 2007

SQL y PL/SQL - La nueva sentencia MERGE

Dios y su ordenador de SQL y PLSQLLa sentencia MERGE, a la que muchos denominan UPSERT debido a su funcionalidad, está disponible desde la aparición de la versión 9i de la base de datos Oracle. Se trata de una de las funcionalidades del kernel de Oracle más utiles a la hora de permitir el uso de la tecnología ETL (Extract, Transform and Load - Extraer, Transformar y Cargar) con las bases de datos Oracle. Este tipo de tecnología puede utilizarse y está especialmente enfocada para ser usada en aplicaciones de data warehousing (almacen de datos). Básicamente, lo que permite la sentencia SQL MERGE es, dependiendo de una condición lógica, actualizar registros (UPDATE) cuando la condición se cumple, o insertar registros (INSERT) cuando dicha condición no se cumple, de ahí surge la denominación de UPSERT.

Anteriormente a la versión 9i de Oracle, la alternativa en SQL era ejecutar dos sentencias DML, un UPDATE y un INSERT, cada una utilizando condiciones lógicas opuestas. En cuanto a las alternativas en PL/SQL eran: bien intentar insertar un registro y si la sentencia INSERT fallaba debido a una excepción PLSQL del tipo DUP_VAL_ON_INDEX, entonces realizar un UPDATE del registro en cuestión; bien intentar actualizar un registro y si la sentencia UPDATE devolvía SQL%NOTFOUND, entonces ejecutar la correspondiente sentencia INSERT.

Las ventajas de la sentencia SQL MERGE son claras:

- Permite con una misma sentencia realizar un UPDATE si el registro existe, o un INSERT si se trata de un nuevo registro.

- La sentencia puede paralelizarse de forma transparente.

- Se evita la necesidad de realizar actualizaciones múltiples.

- Es especialmente útil para realizar operaciones en masa y, como ya he mencionado, en aplicaciones de data warehousing.

- El rendimiento de la base de datos mejora ya que, al necesitarse menos sentencias SQL para realizar las mismas operaciones, también se necesitan menos accesos a las tablas fuente.

Ejemplo de sentencia MERGE

En el presente ejemplo voy a partir del hecho de que tenemos dos tablas en un data warehouse, una de ellas con muchos registros denominada clientes y otra más pequeña denominada datos_cli cuyos registros deben insertarse en la tabla clientes. En pocas palabras, estoy suponiendo que la tabla datos_cli contiene los cambios a realizar sobre la tabla clientes. La sentencia SQL MERGE que debemos utilizar para actualizar la tabla cliente podría ser la siguiente:

MERGE INTO clientes cli USING datos_cli dac
ON (cli.cliente_id = dac.cliente_id)
WHEN MATCHED THEN
UPDATE SET
cli.nombre = dac.nombre,
cli.direccion = dac.direccion
WHEN NOT MATCHED THEN
INSERT (cliente_id, nombre, direccion)
VALUES (dac.cliente_id, dac.nombre, dac.direccion);

Así pues, la sentencia MERGE del ejemplo realizará las siguientes operaciones:

- Si existe un registro en datos_cli con el mismo ID de otro registro en la tabla clientes, entoces actualizará el valor de los campos nombre y dirección de la tabla clientes con los valores correspondientes de la tabla datos_cli.

- Si no existe un registro en clientes con el ID del registro de datos_cli que está siendo procesado, entonces se insertará dicho registro en la tabla clientes.

En resumen, la sintaxis de la sentencia MERGE debe incorporar:

Una cláusula INTO, que especifica la tabla destino donde los registros serán actualizados o insertados.

Una cláusula USING, especificando el origen de los datos que van a ser insertados o que van a servir para actualizar la tabla destino. El origen de los datos puede tratarse de una tabla, una vista, o del resultado de la ejecución de una consulta SELECT.

Una cláusula ON, que especifica la condición bajo la cual se realizará, bien la operación UPDATE (si la condición se cumple), bien la operación INSERT (si la condición no se cumple).

Las cláusulas WHEN MATCHED | NOT MATCHED, que son las que indican a la base de datos Oracle que acción realizar si se cumple o no se cumple la condición del ON. Estas dos cláusulas se pueden poner en cualquier orden.

Artículos relacionados:
La sentencia INSERT multitabla de la versión 9i de Oracle.
La cláusula WITH de la versión 9i de la base de datos Oracle.

martes 6 de noviembre de 2007

SQL y PL/SQL - La sentencia INSERT multitabla de Oracle 9i

La vaca de SQL y PLSQL y la sentencia INSERT multitablaLa versión 9i de las bases de datos Oracle ha introducido la posibilidad de utilizar sentencias INSERT multitabla. Así pues, la sentencia SQL o PLSQL INSERT... SELECT ha cambiado ligeramente su sintaxis, de manera que ahora permite la inserción de datos en más de una tabla de la base de datos de forma paralela. Existen dos formas de utilizar el comando INSERT multitabla: no condicional y condicional. En la forma no condicional, una cláusula compuesta INTO se ejecuta cada vez que la consulta SELECT devuelve un registro. En la forma condicional, las cláusulas compuestas INTO figuran dentro de cláusulas WHEN a partir de las que se determina si la correspondiente cláusula compuesta INTO se ejecuta o no.

Una claúsula compuesta INTO consiste de una o más cláusulas INTO. Una cláusula INTO debe especificar la tabla de la base de datos sobre la que se van a insertar los datos. Esta cláusula no admite alias. La cláusula INTO tambien proporciona el valor del los campos a ser insertados mediante la cláusula VALUES. La expresiones usadas en la cláusula VALUE pueden tratarse de cualquier expresión permitida, pero siempre debe hacer referencia a columnas devueltas por la consulta SELECT incluida en el INSERT.

Veamos un ejemplo de la forma no condicional:

INSERT ALL
INTO productos
VALUES (producto_id, producto, cantidad)
INTO ventas
VALUES (cliente_id, producto_id, cantidad, total)
SELECT cliente_id,
producto_id,
producto,
SUM(cantidad) cantidad,
SUM(precio) total
FROM pedidos, lineas_pedidos
WHERE pedidos.pedido_id = lineas_pedidos.pedido_id
GROUP BY cliente_id, producto_id, producto;

En el ejemplo vemos como la sentencia INSERT se utiliza para insertar los valores producto_id, producto y cantidad en la tabla productos y los valores cliente_id, producto_id, cantidad y total en la tabla ventas, todo ello utilizando una sola sentencia SQL o PL/SQL. Resulta obvio comentar que la sentencia que he utilizado como ejemplo, podría escribirse de forma alternativa mediante el empleo de dos sentencias INSERT.

La forma condicional de las sentencias SQL o PLSQL INSERT multitabla dispone, a su vez, de dos opciones:

1) Forma condicional FIRST, sólo se insertan los valores especificados en la primera condicion verdadera.

2) Forma condicional ALL, se insertan los valores especificados en todas las condiciones verdaderas.

La sintaxis sería:

INSERT [ALL/FIRST]
WHEN THEN
INTO (col, col, ...) VALUES (val, val, ...)
.....
INTO (col, col, ...) VALUES (val, val, ...)
WHEN THEN
INTO (col, col, ...) VALUES (val, val, ...)
.....
INTO (col, col, ...) VALUES (val, val, ...)
ELSE
INTO (col, col, ...) VALUES (val, val, ...)
.....
INTO (col, col, ...) VALUES (val, val, ...)
SELECT ...;

Ventajas de las sentencias INSERT multitabla en SQL y PL/SQL

1) Eliminan la necesidad de utilizar múltiples sentencias INSERT ... SELECT para añadir registros en varias tablas de la base de datos.

2) Elimina la necesidad de utilizar un procedimiento PL/SQL para realizar diferentes inserciones de registros dependiendo de diferentes condiciones lógicas. Esto ya se puede realizar con una única sentencia SQL del tipo INSERT ... SELECT, mediante el uso de la cláusula WHEN.

3) Mejora el rendimiento significativamente ya que la consulta SELECT correspondiente, al existir un sólo comando INSERT, sólo tienen que ejecutarse una vez, en lugar de tener que repetirse su ejecución en cada INSERT.

Artículos relacionados: La nueva cláusula WITH de la versión 9i de la base de datos Oracle.

martes 25 de septiembre de 2007

La claúsula WITH en SQL y PL/SQL

Esposa reclama la atención del programador PL/SQL con la claúsula WITH del SQLLa versión 9i de las bases de datos Oracle permite el uso de la claúsula WITH en SQL y PLSQL. Este comando permite reusar una consulta SELECT cuando esta hay que utilizarla más de una vez en una sentencia o consulta SQL compleja. Los resultados de la consulta definida en la claúsula WITH son almacenados en una tabla temporal pudiendo de esta forma mejorar el rendimiento de la sentencia principal.

Aunque no siempre conseguiremos mejorar el rendimiento utilizando la claúsula WITH, lo que sin duda facilitaremos es la lectura y el mantenimiento del código PL/SQL o SQL. Dentro de la claúsula WITH daremos un nombre a las consultas SELECT a reutilizar (WITH admite la definición de múltiples consultas con sólo separarlas por comas), dicho nombre será visible para todas las consultas definidas posteriormente dentro del mismo WITH. Obviamente, también será visible para la sentencia o consulta principal.

Uso de la claúsula WITH

En el siguiente ejemplo encontraremos todos las divisiones de una empresa cuyos empleados tienen un salario medio un 10 por ciento por encima del salario medio de la empresa:

WITH salario_division AS (
  SELECT division,
         AVG(salario) salario_medio
  FROM   empleados
  GROUP BY division )
SELECT division,
       salario_medio
FROM   salario_division
WHERE  salario_medio > (
       SELECT AVG(salario_medio) * 1.1
       FROM   salario_division )
ORDER BY salario_medio;

Como se puede observar en la consulta existen dos bloques, una consulta principal y una subconsulta. Ambas necesitan realizar operaciones agrupando datos. Reescribamos la sentencia sin utilizar la claúsula WITH:

SELECT division,
       AVG(salario) salario_medio
FROM   empleados
GROUP BY division
HAVING AVG(salario) > (
       SELECT AVG(salario) * 1.1
       FROM   empleados )
ORDER BY AVG(salario);

Comparando ambas sentencias podemos concluir que al utilizar la claúsula SQL WITH y almacenar en una tabla temporal la consulta que hace el GROUP BY, evitamos que se tengan que agrupar los datos más de una vez. Este hecho debe hacernos pensar que el rendimiento de la sentencia que utiliza la claúsula WITH debe ser mejor que el de la sentencia que no lo utiliza.

Características de la claúsula WITH

a) Sólo se puede usar en sentencias SELECT.

b) Cuando se define una consulta con el mismo nombre de una tabla existente en la base de datos Oracle, puesto que el analizador sintáctico o parser (ver fases en la ejecución de una sentencia SQL) de las sentencias SQL o PLSQL busca de dentro a fuera, el nombre dentro de la claúsula WITH tendrá prioridad frente al nombre de la tabla.

c) Puede contener más de una consulta. Cada consulta se separa mediante comas. Las consultas definidas después de otras consultas pueden utilizar las definiciones previas.

Artículos relacionados:
Vistas materializadas o materializaed views.
Cursores PL/SQL.
Tablas externas en el SQL de Oracle.

miércoles 6 de junio de 2007

Vistas materializadas (materialized views) en SQL y PL/SQL (1)

Materialized views en programación PLSQL - Chiste virus gallegoEl SQL de las bases de datos Oracle permite crear vistas materializadas o materialized views. Estas vistas materializadas, a parte de almacenar la definición de la vista propiamente dicha, también almacenan los registros que resultan de la ejecución de la sentencia SELECT que define la vista. Como las vistas normales, la sentencia SELECT es la base de la vista, pero la sentencia SQL se ejecuta cuando se crea la vista y los resultados se almacenan físicamente constituyendo una tabla real que ocupa sitio en el disco duro. Esta tabla puede definirse utilizando los mismos parámetros de almacenamiento que se pueden utilizar para una tabla normal (tablespace, etcétera). Las vistas materializadas también admiten índices, esta funcionalidad resulta muy útil a la hora de mejorar el rendimiento de las sentencias PLSQL o SQL que utilicen vistas materializadas.

Cuando una sentencia SQL o PL/SQL accede a una vista materializada el servidor de la base de datos Oracle, transforma la sentencia dirigiéndose directamente a los datos de la vista que están ya almacenados, en lugar de utilizar los datos de las diferentes tablas utilizadas en la definición de dicha vista.

Evidentemente, si una vista (view) utiliza muchas tablas base enlazadas de forma compleja, y dicha vista va a ser utilizada frecuentemente, será muy conveniente definirla como una vista materializada o materialized view. Esto contribuirá enormemente a mejorar el rendimiento de la base de datos, ya que la sentencia SQL base de la vista sólo se ejecutará una vez.

Por otro lado, está el inconveniente de que si la vista materializada o materialized view va a tener que reutilizarse en el futuro, entonces necesitaremos un mecanismo para actualizar o refrescar dicha vista materializada, ya que las tablas base de la vista pueden haber sufrido modificaciones desde la creación de la misma.

Por todo esto, a la hora de determinar si una vista debe definirse como vista o es mejor definirla como vista materializada, debemos valorar los costes de tener que ejecutar la sentencia SQL base de una vista normal siempre que se acceda a dicha vista, frente a los costes de almacenamiento y actualización de una vista materializada.

Sintaxis del comando SQL utilizado para crear vistas materializadas

CREATE MATERIALIZED VIEW nombre_vistam
 [TABLESPACE nombre_ts]
 [PARALELL (DEGREE n)]
 [BUILD {INMEDIATE|DEFERRED}]
 [REFRESH {FAST|COMPLETE|FORCE|NEVER|ON COMMIT}]
 [{ENABLE|DISABLE} QUERY REWRITE]
AS SELECT ... FROM ... WHERE ...

Los valores por defecto de las distintas opciones están subrayados.

Si se elige la opción BUILD INMEDIATE, entonces la tabla asociada con la vista materializada se puebla con datos en el momento de la ejecución del comando SQL CREATE. Por el contrario, si se utiliza BUILD DEFERRED, el comando CREATE creará sólo la estructura de la vista, pero la tabla física asociada no se poblará con datos hasta que se realice el primer refresco o actualización de la vista materializada.

La opción REFRESH permite indicar el mecanismo que la base de datos utilizará para refrescar o actualizar la vista materializada. Los diferentes mecanismos y la forma en que una vista materializada o materialized view puede refrescarse, serán objeto de otro artículo en este blog. Como anticipo diré que un refresco completo o COMPLETE, significa que la tabla asociada con la vista materializada se borra completamente, volviéndose a insertar todos los registros devueltos por la ejecución de la sentencia SQL base de la vista, y que un refresco rápido o FAST, significa que la vista materializada se actualiza sólo según hayan sido los cambios realizados sobre las tablas base de la vista desde el último refresco. Para poder utilizar el refresco rápido o FAST, hay que crear previamente los logs de la vista materializada utilizando el comando CREATE MATERIALIZED VIEW LOG.

La opción ENABLE/DISABLE QUERY REWRITE determina si el optimizador Oracle puede o no reescribir las sentencias SQL de manera que, de ser posible, en la fase de ejecución se utilice la vista materializada en lugar de las tablas base de la vista incluidas en la sentencia SQL original. Este es un tema ciertamente complejo y que será objeto de un artículo completo en este blog. Como anticipo indicaré que la reescritura de sentencias SQL sólo está disponible cuando se utiliza el optimizador Oracle basado en costes.

Artículos relacionados: El refresco de las vistas materializadas o materialized views en PLSQL.

jueves 8 de marzo de 2007

Procedimientos y Funciones en PLSQL

Ratón de ordenador en procedimientos y funciones PL/SQLLos procedimientos PL/SQL son subprogramas compuestos por un conjunto de sentencias SQL. Funciones y procedimientos PL/SQL no son muy diferentes. Un procedimiento o función está constituido por un conjunto de sentencias SQL y PL/SQL lógicamente agrupados para realizar una tarea específica. Los procedimientos y funciones almacenados constituyen un bloque de código PLSQL que ha sido compilado y almacenado en las tablas del sistema de la base de datos Oracle.

Los procedimientos o funciones PLSQL son dinámicos ya que admiten parámetros que les pueden ser pasados antes de su ejecución. Por lo tanto, un procedimiento o función puede realizar diferentes tareas dependiendo de los parámetros que le hayan pasado.

Los procedimientos y funciones Oracle están compuestos por una parte en la que se definen de variables y cursores, otra parte ejecutable compuesta por sentencias SQL y PLSQL, y otra parte opcional enfocada a manejar las excepciones y errores ocurridos durante la ejecución.

Los pasos que sigue Oracle para ejecutar un procedimiento o función son los siguientes:
- Verificar si el usuario tiene permiso de ejecución.
- Verificar la validez del procedimiento o función.
- Y finalmente ejecutarlo.

Las ventajas de usar procedimientos y funciones en lugar de scripts SQL son:
- Facilidad para gestionar la seguridad.
- Mejor rendimiento al estar compilados y almacenados en la base de datos.
- Mejor gestión de la memoria.
- Mayor productividad e integridad.

La diferencia más importante entre los procedimientos y las funciones es que una función debe devolver un valor al bloque PL/SQL que la llamó (sólo un único valor). Sin embargo, en los procedimientos podemos definir múltiples parámetros de salida que se devolverían al bloque PL/SQL desde el que se ejecutó el procedimiento.

Sintaxis de un procedimiento PLSQL:

   CREATE OR REPLACE PROCEDURE [esquema].nombre-procedim
      (nombre-parámetro {IN, OUT, IN OUT} tipo de dato, ..) {IS, AS}
         Declaración de variables;
         Declaración de constantes;
         Declaración de cursores;
         BEGIN
            Cuerpo del subprograma PL/SQL;
            EXCEPTION
            Bloque de excepciones PL/SQL;
         END;


Sintaxis de una función PLSQL:

   CREATE OR REPLACE FUNCTION [esquema].nombre-funcion
      (nombre-parámetro IN tipo-de-dato, ..)
      RETURN tipo-de-dato {IS, AS}
         Declaración de variables;
         Declaración de constantes;
         Declaración de cursores;
         BEGIN
            Cuerpo del subprograma PL/SQL;
            EXCEPTION
            Bloque de excepciones PL/SQL;
         END;


Aclaraciones sobre la sintaxis:

Nombre-parámetro: es el nombre que nosotros queramos dar al parámetro. Podemos utilizar múltiples parámetros. En caso de no necesitarlos podemos omitir los paréntesis.

IN: especifica que el parámetro es de entrada y que por tanto dicho parámetro tiene que tener un valor en el momento de llamar a la función o procedimiento. Si no se especifica nada, los parámetros son por defecto de tipo entrada.

OUT: especifica que se trata de un parámetro de salida. Son parámetros cuyo valor es devuelto después de la ejecución el procedimiento al bloque PL/SQL que lo llamó. Las funciones PLSQL no admiten parámetros de salida.

IN OUT: Son parámetros de entrada y salida a la vez.

Tipo-de-dato: Indica el tipo de dato PLSQL que corresponde al parámetro (NUMBER, VARCHAR2, etc).

Ejemplos prácticos

Ejemplo de creación de un procedimiento PL/SQL:

   CREATE OR REPLACE PROCEDURE
      procedimiento1 (a IN NUMBER, b IN OUT NUMBER) IS
         vmax NUMBER;
         BEGIN
            SELECT salario, maximo
            INTO b, vmax
            FROM empleados
            WHERE empleado_id=a;
            IF b < vmax THEN
               b:=b+100;
            END IF;
            EXCEPTION
            WHEN NO_DATA_FOUND THEN
               b:=-1;
               RETURN;
            WHEN OTHERS THEN
               RAISE;
         END;


Ejemplo de SQL script que llama a un procedimiento PLSQL:

   DECLARE
      vsalario NUMBER;
   BEGIN
      procedimiento1 (3213, vsalario)
      dbms_output.put_line
         ('El salario del empleado 3213 es ', vsalario);
   END;


Ejemplo de ejecución desde SQL de un procedimiento PL/SQL que sólo utiliza parámetros de entrada:

   sql> exec proc_solo_parametros_entrada (2000, 2, 'Pepe');

viernes 23 de febrero de 2007

Triggers en PL/SQL

Un Trigger en PLSQL es un tipo especial de procedimiento almacenado asociado con una tabla de la base de datos Oracle y que se "dispara" (is triggered) cada vez que se ejecuta sobre dicha tabla una sentencia INSERT, una sentencia UPDATE o una sentencia DELETE, es decir, es un procedimiento que permite realizar distintas operaciones cuando un registro de una tabla Oracle es actualizado o borrado, o cuando se añade un registro nuevo a dicha tabla.

Móvil con cámara en programación PLSQL
Hay muchas circunstancias en las que el uso de un trigger PL/SQL puede ser extremadamente útil, por ejemplo:

- Cuando los datos de una tabla son generados desde otro tipo de procedimientos y se necesita controlar los valores que toman algunos campos determinados de la tabla en cuestión.

- Para duplicar los contenidos de una tabla automáticamente y en tiempo real.

- Para implementar complejas restricciones sobre los valores que pueden tomar los campos de una tabla Oracle, es decir, cuando los CONSTRAINTS que se pueden definir sobre una tabla son insuficientes.

- Para controlar las modificaciones de los valores de los campos de una tabla (auditorías).

- Para incrementar automáticamente los valores de un campo.

- Para realizar actualizaciones de una tabla en cascada.

- Para modificar campos o registros de una tabla que un usuario no puede modificar directamente.

Los triggers PL/SQL constituyen una potente herramienta para mantener la integridad de la base de datos, ya que pueden llevar a cabo cualquier acción que sea necesaria para el mantenimiento de dicha integridad.

Los triggers PLSQL pueden llamar a otros procedimientos y disparar otros triggers, pero no admiten parámetros y no pueden ser invocados desde otros procedimientos PLSQL.

Los triggers están almacenados en la tabla catálogo del sistema como parte de las propiedades de una tabla.

Tipos de triggers PLSQL

Los triggers PLSQL pueden actuar antes o después de que se realice una inserción, un borrado o una actualización de un registro de la tabla Oracle involucrada.

Se pueden definir triggers PL/SQL diferentes para cada tipo de evento (INSERT, UPDATE, DELETE) pero lo más lógico es crear un único trigger para todos los eventos y usar una sentencia IF para distinguir que acción realizar dependiendo del evento.

Por otro lado los triggers pueden ser a nivel de registro (row) o a nivel de sentencia (statement).

- A nivel de registro o fila el trigger PL/SQL se ejecuta cada vez que un registro es actualizado, insertado o borrado.

- A nivel de sentencia, el trigger se ejecuta una vez que la sentencia PL/SQL INSERT, UPDATE o INSERT se completa. Obviamente en este caso el trigger sólo puede ser ejecutado después (AFTER) de que se ejecute dicha sentencia.

Ventajas de los triggers PL/SQL

Estandarización: Un solo procedimiento controla las acciones que pueden realizar el resto de procedimientos sobre una tabla Oracle específica.

Eficiencia: Cuando un trigger es analizado por el optimizador Oracle, el plan de ejecución se almacena en la memoria virtual del servidor, con lo cual, si se vuelve a disparar el trigger PLSQL, este se ejecuta instantáneamente ya que existe una copia del plan de ejecución en la memoria.

Seguridad: Aumentan la seguridad porque permiten restringir el acceso de los usuarios a los datos de las tablas, al no poder éstos modificar o acceder a los mismos directamente. Las acciones que realiza un trigger normalmente se limitan en base a los permisos que tiene el usuario que creo la tabla involucrada y no en base a los permisos que tiene el usuario que disparó el trigger.

Sintaxis de los triggers en PL/SQL

  CREATE OR REPLACE TRIGGER nombre-del-trigger
    [FOLLOWS nombre-otro-trigger]
    [BEFORE/AFTER]
    [INSERT/DELETE/UPDATE/UPDATE OF lista-columnas]
    ON nombre-tabla
    [REFERENCING [OLD AS nombre-antiguo][NEW AS nombre-nuevo]]
    [FOR EACH ROW/FOR EACH STATEMENT]
    [WHEN {condiciones}]
      {Bloque estándar de sentencias PL/SQL... BEGIN, EXCEPTION}


Aclaraciones:

La diferencia entre UPDATE y UPDATE OF lista-columnas es que, en el segundo caso, el trigger PL/SQL se ejecuta sólo cuando se modifica alguna de las columnas de la lista y, en el primer caso, el trigger se ejecuta en caso de que cualquiera de las columnas de la tabla sea modificada.

La cláusula FOLLOWS se utiliza para determinar el orden en que se deben ejecutar diferentes triggers del mismo tipo (INSERT, DELETE o UPDATE) que se disparan al mismo tiempo sobre una misma tabla. Así el trigger que está siendo creado se ejecutará después del trigger indicado en la cláusula FOLLOWS (sólo disponible para la versión 11g de la base de datos Oracle).

La cláusula REFERENCING permite referirnos con el nombre que nosotros indiquemos (por defecto los nombres son old y new) al valor antiguo (old) de un registro que ha sido borrado o actualizado o al valor nuevo (new) de un registro que ha sido actualizado o insertado. Yo, sinceramente, no le veo una utilidad evidente a esta cláusula debido a que los valores por defecto son suficientemente explicativos.

La cláusula WHEN determina que el trigger PLSQL se disparé sólo para los registros que cumplen la condición de la cláusula. Esta cláusula sólo se puede usar en triggers a nivel de registro.

Ejemplo:

  CREATE TRIGGER tr1_empleados
    BEFORE INSERT OR UPDATE OF salario
    ON empleados
    FOR EACH ROW
    WHEN (:new.salario > 5000);
      BEGIN
        UPDATE empleados
        SET salario = 5000
        WHERE empleado_id = :new.empleado_id;
      END;


En este ejemplo, si insertamos o actualizamos el registro de un empleado de manera que su salario sea superior a 5000 euros, el trigger PL/SQL actualizará dicho salario al valor de 5000 euros independientemente del salario que nosotros hayamos insertado o introducido al modificar el registro.

sábado 10 de febrero de 2007

Manejo de excepciones en PL/SQL (excepciones definidas por el usuario)

Como continuación del anterior artículo dedicado al manejo de las excepciones PLSQL predefinidas, ahora voy a hablar de las excepciones definidas por el usuario.

Serpientes en PL/SQL
PLSQL permite al usuario definir sus propias excepciones. Estas excepciones deben ser declaradas y lanzadas explícitamente utilizando la sentencia RAISE. Este tipo de excepciones deben ser declaradas en el segmento DECLARE. Se declara una excepción como cualquier otra variable, asignandole el tipo EXCEPTION. Como las variables, una excepción declarada en un bloque es local a ese bloque y global a todos los sub-bloques que comprende.

También existe un procedimiento estándar de PL/SQL que permite lanzar errores y definir y enviar un mensaje de error. Su formato es el siguiente:

   RAISE_APPLICATION_ERROR (número de error, mensaje de error);

Cuando desde un programa se realiza una llamada a este procedimiento, se lanza una excepción y se deshacen todos los cambios realizados por el programa sobre la base de datos Oracle.

Ejemplo:

   DECLARE
     vprecio inventario.precio%TYPE;
     barato EXCEPTION;
   BEGIN
     [Otras sentencias]
     BEGIN
       SELECT precio FROM inventario
       WHERE articulo = 'tvplasma'
       INTO vprecio;
       IF vprecio < 100 THEN
         RAISE barato;
       END IF;
       EXCEPTION
       WHEN NO_DATA_FOUND THEN
         RAISE_APPLICATION_ERROR (-20001, 'Sin TV de plasma');
       WHEN TOO_MANY_ROWS THEN
         RAISE_APPLICATION_ERROR (-20002, 'Muchas TV de plasma');
       WHEN barato THEN
         DBMS_OUTPUT.PUT_LINE("El articulo es muy barato.");
         UPDATE inventario
         SET precio = precio + 100
         WHERE articulo = 'tvplasma';
         DBMS_OUTPUT.PUT_LINE("Precio subido 100 euros");
       WHEN OTHERS THEN
         DBMS_OUTPUT.PUT_LINE("Error, abortando ejecución.");
         RAISE;
     END;
     [Otras sentencias]
   END;

En este ejemplo si la sentencia SELECT INTO fallase por no devolver ningún registro (NO_DATA_FOUND) o por devolver más de uno (TOO_MANY_ROWS), se lanzaría un error con los mensajes especificados en el procedimiento RAISE_APPLICATION_ERROR deteniéndose la ejecución del programa. Si el precio del artículo buscado (tvplasma) fuera menor de 100 euros, entonces se actualizaría el precio del artículo sumándo 100 euros al precio original y la ejecución del programa continuaría. Para cualquier otro error, se mandaría un mensaje de error a la pantalla y la ejecución del programa se abortaría.

lunes 15 de enero de 2007

Manejo de excepciones en PL/SQL (excepciones predefinidas)

Programación PLSQL - No, no te bajamos de la red, nacistesLos errores que se producen durante la ejecución de un bloque de código PL/SQL pueden ser manejados a gusto del programador, es decir, si durante la ejecución de una sentencia PLSQL se produce un error, podemos hacer que el programa realice unas acciones u otras dependiendo del tipo de error que se haya generado, esto es algo parecido a lo que se puede hacer cuando programamos en C++ o Java. Para conseguir esto debemos añadir dentro del bloque de código PL/SQL una sección para tratamiento de las excepciones.

Existen dos tipos de excepciones:
- Excepciones predefinidas
- Excepciones definidas por el usuario.

En este artículo voy a hablar sólo de las excepciones predefinidas.

PL/SQL proporciona un gran número de excepciones predefinidas que permiten controlar las condiciones de error más habituales. Las excepciones predefinidas no necesitan ser declaradas y son las siguientes:

DUP_VAL_ON_INDEX - Se produce cuando se intenta almacenar un valor ya existente en una columna que tiene restricción de índice único.

TIMEOUT_ON_RESOURCE - Se excedió el tiempo máximo de espera por un recurso en Oracle.

NOT_LOGGED_ON - El programa efectuó una llamada a Oracle sin estar conectado.

LOGIN_DENIED - El login o la contraseña utilizados para entrar en Oracle son inválidos.

NO_DATA_FOUND - Una sentencia SELECT INTO no devolvió ningún registro.

TOO_MANY_ROWS - Una sentencia SELECT INTO devolvió más de un registro.

ZERO_DIVIDE - Se ha ejecutado una división donde el divisor valía cero.

STORAGE_ERROR - Si no se dispone de más memoria o la memoria esta dañada.

PROGRAM_ERROR - Ocurrió un problema interno al ejecutar el código PL/SQL.

INVALID_NUMBER - Cuando falla la conversión de una cadena de caracteres hacia un número porque la cadena no representa un número válido.

VALUE_ERROR - Ocurrió un error aritmético, de conversión o truncamiento. Por ejemplo, esto sucede cuando se intenta dar un valor muy grande a una variable que no soporta dicho tamaño.

ROWTYPE_MISMATCH - Los elementos de una asignación (el valor a asignar y la variable que lo contendrá) son de tipos incompatibles. También se presenta este error cuando un parámetro pasado a un subprograma no es del tipo esperado.

SYS_INVALID_ROWID - Falla la conversión de una cadena de caracteres hacia un tipo rowid porque la cadena no representa un número.

INVALID_CURSOR - Se efectuó una operación no válida sobre un cursor. Suele ocurrir cuando un cursor no está abierto y se ejecuta una sentencia para cerrar dicho cursor.

CURSOR_ALREADY_OPEN - Cuando se intenta abrir un cursor que ya estaba abierto. Hay que recordar que un cursor de tipo FOR se abre automáticamente por lo que no se debe ejecutar la sentencia OPEN.

ACCESS_INTO_NULL - Se intentó asignar un valor a los atributos de un objeto no inicializado.

COLLECTION_IS_NULL - Se intentó asignar un valor a una tabla anidada aún no inicializada.

SELF_IS_NULL - El parámetro SELF (el primero que es pasado a un método MEMBER) es nulo.

OTHERS - Cualquier otro tipo de error que pueda producirse. Cuando se utiliza la excepción OTHERS, cualquier excepción que no se haya tratado anteriormente se procesará según la secuencia de instrucciones incluida dentro de la sección OTHERS. OTHERS debe ser la última excepción tratada dentro de la sección dedicada al tratamiento de excepciones.

La sintaxis de una sección para tratamiento de excepciones es como sigue:

   BEGIN
     [Secuencia de sentencias]
     EXCEPTION
     WHEN [nombre de la excepción 1] THEN
       [Sentencias para tratar la excepción 1]
     .................................................
     WHEN [nombre de la excepción n] THEN
       [Sentencias para tratar la excepción n]
     WHEN OTHERS THEN
       [Sentencias para tratar el resto de las excepciónes]
     END;

En el siguiente ejemplo se utilizan las excepciones predefinidas NO_DATA_FOUND, TOO_MANY_ROWS y OTHERS:

   DECLARE
     vprecio inventario.precio%TYPE;
   BEGIN
     [Otras sentencias]
     BEGIN
       SELECT precio FROM inventario
       WHERE cantidad = 100
       INTO vprecio;
       EXCEPTION
       WHEN NO_DATA_FOUND THEN
         DBMS_OUTPUT.PUT_LINE("No hay ningún artículo.");
       WHEN TOO_MANY_ROWS THEN
         DBMS_OUTPUT.PUT_LINE("Hay más de un artículo.");
       WHEN OTHERS THEN
         DBMS_OUTPUT.PUT_LINE("Error, abortando ejecución.");
         RAISE;
     END;
     [Otras sentencias]
   END;

En este ejemplo si la sentencia SELECT INTO fallase por no devolver ningún registro o por devolver más de uno, se mandaría un mensaje de error a la pantalla pero la ejecución del programa continuaría; por contra, para cualquier otro error, aunque también se mandaría un mensaje de error a la pantalla, la ejecución del programa se abortaría (comando RAISE).

martes 5 de diciembre de 2006

Cursores en PL/SQL

Programación PL/SQL en el WCLos cursores se utilizan en PL/SQL para manejar las sentencias SELECT. Un cursor esta formado por un conjunto de registros devueltos por una instrucción SQL del tipo SELECT. Desde un punto de visto interno a la base de datos Oracle, los cursores son segmentos de memoria utilizados para realizar operaciones con los registros devueltos tras ejecutar una sentencia SELECT.

Se pueden distinguir dos tipos de cursores:

- Cursores implícitos: Se utilizan cuando la sentencia SELECT devuelve un solo registro y su formato es como sigue:

     DECLARE
       lsalario empleados.salario%TYPE;
       ldni empleados.dni%TYPE;
     BEGIN
       SELECT salario, dni
       INTO lsalario, ldni
       FROM empleados
       WHERE nombre = 'Juan'
       AND apellidos = 'Rodrigo Comas';
       /* Resto de sentencias del bloque */
     END;

Nota: Mucha gente considera que las sentencias UPDATE, dentro de un bloque PLSQL, son también cursores implícitos, no obstante, yo prefiero no incluirlas dentro de este concepto.

- Cursores explícitos: Se utilizan cuando la sentencia SELECT puede devolver varios registros. También se pueden utilizar en consultas que devuelvan un solo registro por razones de eficiencia con respecto a los cursores implícitos, eficiencia que mejorará especialmente si el cursor explícito se tiene que ejecutar varias veces dentro del bloque de código PL/SQL.

Un cursor explícito tiene que ser definido previamente como cualquier otra variable PLSQL y debe serle asignado un nombre. Veamos un ejemplo que muestra el DNI y el salario de los trabajadores incluidos en la tabla empleados:

     DECLARE
       CURSOR cemp IS
       SELECT salario, dni
       FROM empleados;
       cepm_rec cemp%ROWTYPE;
     BEGIN
       FOR cemp_rec IN cemp
       LOOP
         DBMS_OUTPUT.PUT_LINE
           (cemp_rec.dni || ' ' || cemp_rec.salario);
       END LOOP;
     END;

Los cursores explícitos admiten el uso de parámetros. Los parámetros deben declararse junto con el cursor. Por ejemplo:

     DECLARE
       CURSOR cemp(pnombre IN VARCHAR2) IS
       SELECT salario, dni
       FROM empleados
       WHERE nombre = pnombre;
       cepm_rec cemp%ROWTYPE;
       vnombre VARCHAR2(20);
     BEGIN
       vnombre := 'Juan';
       DBMS_OUTPUT.PUT_LINE
         ('Sueldo de los empleados con nombre ' || vnombre);
       FOR cemp_rec IN cemp(vnombre)
       LOOP
         DBMS_OUTPUT.PUT_LINE
           (cemp_rec.dni || ' ' || cemp_rec.salario);
       END LOOP;
     END;

En los ejemplos anteriores los cursores se han controlado con la sentencia FOR pero también pueden controlarse mediante el uso de las sentencias OPEN, FETCH y CLOSE. La sentencia OPEN identifica el cursor que se tiene que utilizar. La sentencia FETCH pone, registro a registro, los valores devueltos por el cursor en las variables correspondientes, variables que pueden estar constituidas por una lista de variables o un registro PLSQL (este es el caso de los ejemplos que incluyo en este artículo). Por último, la sentencia CLOSE cierra el cursor y libera la memoria reservada. Veamos como quedaría nuestro ejemplo utilizando este tipo de sentencias en lugar de utilizar la sentencia FOR:

     DECLARE
       CURSOR cemp(pnombre IN VARCHAR2) IS
       SELECT salario, dni
       FROM empleados
       WHERE nombre = pnombre;
       cepm_rec cemp%ROWTYPE;
       vnombre VARCHAR2(20);
     BEGIN
       vnombre := 'Juan';
       DBMS_OUTPUT.PUT_LINE
         ('Sueldo de los empleados con nombre ' || vnombre);
       OPEN cemp(vnombre);
       LOOP
         FETCH cemp INTO cemp_rec;
         DBMS_OUTPUT.PUT_LINE
           (cemp_rec.dni || ' ' || cemp_rec.salario);
         EXIT WHEN cemp%NOTFOUND; -- Último registro.
       END LOOP;
       DBMS_OUTPUT.PUT_LINE
         ('Número de empleados procesados ' || cemp%ROWCOUNT);
       CLOSE cemp;
     END;

Sólo me queda señalar que existe una tercera opción para manejar cursores que a mí, particularmente, no me gusta utilizar pero que no quiero omitir:

     DECLARE
       TYPE ecursor IS REF CURSOR RETURN empleados%ROWTYPE;
       cemp ecursor;
       cepm_rec empleados%ROWTYPE;
     BEGIN
       OPEN cemp FOR SELECT * FROM empleados;
       FOR cemp_rec IN cemp
       LOOP
         DBMS_OUTPUT.PUT_LINE
           (cemp_rec.dni || ' ' || cemp_rec.salario);
       END LOOP;
     END;

Finalmente sólo mencionar que existen cuatro tipos de atributos que nos permiten controlar la ejecución de un cursor:

- %ISOPEN: Devuelve "true" si el cursor está abierto.
- %FOUND: Devuelve "true" si el registro fue satisfactoriamente procesado.
- %NOTFOUND: Devuelve "true" si el registro no pudo ser procesado. Normalmente esto ocurre cuando ya se han procesado todos los registros devueltos por el cursor.
- %ROWCOUNT: Devuelve el número de registros que han sido procesados hasta ese momento.

martes 21 de noviembre de 2006

Sentencias de control en PL/SQL

Programación PLSQL con los SimpsonsEn este artículo voy a mencionar las opciones que ofrece PLSQL para:

1. Controlar las acciones a realizar dependiendo de una o varias condiciones.
2. Controlar una secuencia de operaciones iterativa.
3. Controlar la secuencia de ejecución del programa.

Control condicional

Comando IF

Permite ejecutar unas sentencias u otras dependiendo de que se cumplan o no las condiciones indicadas en la sentencia.

    IF -- Condición THEN -- Sentencias
    ELSEIF -- Condición THEN -- Sentencias
    ELSE -- Sentencias
    END IF;

Control de iteraciones

Comando LOOP

El bucle se ejecuta continuamente hasta que dentro del bucle se ejecuta la sentencia EXIT. Parece lógico pensar que dentro del bucle se debe incluir una sentencia IF para determinar cuando se debe salir del bucle.

    LOOP
      -- Sentencias
    END LOOP;

Comando WHILE

El bucle se ejecuta de forma continua mientra que la condición del WHILE sea verdadera. También se puede salir del bucle utilizando la sentencia EXIT.

    WHILE -- Condición
    LOOP
      -- Sentencias
    END LOOP;

Comando FOR

En el siguiente ejemplo el bucle se ejecutará 10 veces y la variable i se incrementará en una unidad en cada ejecución del bucle. También se puede salir del bucle utilizando la sentencia EXIT.

    FOR i IN 1..10
    LOOP
      -- Sentencias
    END LOOP;

Control secuencial

Comando GOTO

Este comando está en desuso y la mayoría de los programadores expertos recomienda que no se utilice. En el ejemplo que propongo, el primer conjunto de sentencias no se ejecutarían ya que el GOTO está diciendo que se reanude la ejecución desde la etiqueta X.

    GOTO X;
    -- Primer conjunto de sentencias
    << X >>
    -- Segundo comjunto de sentencias

Como os habréis dado cuenta el ejemplo propuesto carece de lógica, ¿para qué incluir un grupo de sentencias que nunca se va a ejecutar?, por lo que el comando GOTO siempre debe estar incluido dentro de un comando IF o de algún otro tipo de control condicional.