3.3 Programación de la base de datos Un sistema manejador de base de datos no es sólo un repositorio estático de datos,es también un software que nos permite desarrollar programas que resuelvan necesidades de información y que resguarden la consistencia de la misma. En esta unidad, abordaremos temas de programación de bases de datos por lo que necesitaremos un sistema manejador de bases de datos (Database Management System, DBMS). Te proponemos trabajar con PostgreSQL ya que es software libre y existen versiones para cualquier sistema operativo 1. El código con el que ejemplificaremos esta unidad estará basado en este DBMS. Hay dos actividades preponderantes en el manejo de sistemas de bases de datos. Por una parte, la recuperación de información, actividad que consiste en solucionar una necesidad de información del usuario, por ejemplo: ¿cuántas ventas ha realizado el vendedor Juan Gutiérrez? ¿Cuál es el monto total de venta por cada cliente? Esta recuperación se hace mediante consultas. En esta unidad, conocerás las sentencia SELECT, que permite obtener información de manera versátil. Por otro lado, el procesamiento de la información, que es una actividad primordial ya que nos permite manipular los datos de la base con diversos fines. Por ejemplo, verificar que la información sea almacenada de forma correcta, monitorear quién está almacenando la información y revisar que se cumplan las políticas de la empresa antes de aceptar un cambio en los datos. Vas a conocer varias sentencias para manipular información de una base de datos, todas programadas con SQL mediante el dialecto de PostgreSQL. 3.3.1. Fundamentos de consultas de base de datos La instrucción SQL que nos permite consultar información de una o varias tablas es la instrucción SELECT. Recuerda que el resultado de una consulta siempre 1 Para obtener una versión de PostgreSQL y la documentación necesaria para manejarlo, visita el sitio http://www.postgresql.org. serán columnas y renglones. La sintaxis general de uso de esta instrucción es la siguiente: Syntax: SELECT [ ALL | DISTINCT [ ON ( expression [, ...] ) ] ] * | expression [ AS output_name ] [, ...] [ FROM from_item [, ...] ] [ WHERE condition ] [ GROUP BY expression [, ...] ] [ HAVING condition [, ...] ] [ { UNION | INTERSECT | EXCEPT } [ ALL ] select ] [ ORDER BY expression [ ASC | DESC | USING operator ] [, ...] ] [ LIMIT { count | ALL } ] [ OFFSET start ] [ FOR UPDATE [ OF tablename [, ...] ] ] Si queremos seleccionar todas las columnas y todos los renglones de una tabla usamos el siguiente comando. Observa el resultado que arroja esta consulta. TEST=# SELECT * FROM tlibro; libro_id | título | autor_id | tema_id | sinopsis ----------+---------------------------+----------+---------+----------------------------1 | Cuentos crueles | 10 | 2 | Todos los fuegos el fuego | 7 | 5 | Serie de cuentos de ficción 5 | Cuentos del siglo XIX 3 | Primero sueño | 6 | 5 | Poesía colonial 4 | Ficciones | 11 | 5 | Mejores cuentos de autor 5 | Artificios | 11 | 5 | Cuentos de J. L. Borges 6 | El llano en llamas | | 7 | El túnel | | 8 | Los bandidos de Río Frío | 3 | 5 | Novela naturalista 9 | Clemencia | 4 | 5 | Inicio del modernismo | | (9 rows) Para seleccionar algunas columnas usamos la siguiente instrucción. TEST=# SELECT libro_id, titulo, sinopsis FROM tlibro; libro_id | título | sinopsis ----------+---------------------------+----------------------------1 | Cuentos crueles | Cuentos del siglo XIX 2 | Todos los fuegos el fuego | Serie de cuentos de ficción 3 | Primero sueño | Poesía colonial 4 | Ficciones | Mejores cuentos de autor 5 | Artificios | Cuentos de J. L. Borges 6 | El llano en llamas | 7 | El túnel | 8 | Los bandidos de Río Frío | Novela naturalista 9 | Clemencia (9 rows) | Inicio del modernismo Con la instrucción SELECT también es posible seleccionar constantes y expresiones. Fíjate cómo las columnas del resultado de la consulta pueden tener un sobrenombre o alias (“ejemplo de suma”). TEST=# SELECT 2 + 2 AS "ejemplo de suma", TEST-# pi() AS "valor de pi", TEST-# 'Este es un letrero' AS "Letrero"; ejemplo de suma | valor de pi | Letrero -----------------+------------------+-------------------4 | 3.14159265358979 | Este es un letrero (1 row) La cláusula WHERE nos permite determinar qué renglones son parte de la salida y cuáles no. Si un renglón es parte de la salida, éste debe satisfacer una condición. El WHERE establece condiciones que son evaluadas como true o false. Por ejemplo: Supongamos que deseamos obtener el titulo del libro 7. TEST=# SELECT titulo FROM tlibro WHERE libro_id = 7; título ---------El túnel (1 row) Ahora deseamos el título de los libros del autor 11. TEST=# SELECT titulo FROM tlibro WHERE autor_id = 11; tíitulo -----------Ficciones Artificios (2 rows) Obtengamos el nombre del autor del libro 5. TEST=# SELECT au.nombre TEST-# FROM tlibro AS li, tautor AS au TEST-# WHERE li.autor_id = au.autor_id TEST-# AND li.libro_id = 5; nombre -----------Jorge Luis (1 row) Este último ejemplo asigna alias a las tablas mediante la partícula AS. De esta manera puedo usar el alias en lugar del nombre de la tabla. 3.3.2. Consulta de varias tablas Rescatar registros de una sola tabla es muy inusual. En la realidad, siempre obtenemos datos de diferentes tablas, a veces de muchas. Es importante entonces conocer la manera de ‘juntarlas’ para poder obtener valores almacenados en columnas de unas y de otras tablas. Esto lo realizamos con una operación relacional llamada Junta o Join. Existen en general tres tipos de join. TIPOS DE JOIN Cross join El resultado es un producto cartesiano, es decir, una combinación de todos los valores de una tabla contra todos los valores de otra tabla. No resulta ser muy útil en la práctica. Inner join El resultado es un conjunto de registros que resultan de la combinación de dos o más tablas siempre y cuando existan columnas en común y los valores de dichas columnas coincidan. Outer join El resultado es un inner join que además incluye aquellos valores donde no hay coincidencia en el origen, ya sea, del lado izquierdo (LEFT OUTER JOIN) o del lado derecho (RIGHT OUTER JOIN) o aquellos que no coinciden en ningún lado (FULL OUTER JOIN). Figura 3.4. Tipos de join A continuación unos ejemplos prácticos. Si quisiéramos obtener los autores de cada libro tendríamos que usar un join porque los datos están en dos tablas. El título está en la tabla tlibro y el nombre del autor en la tabla tautor. TEST=# SELECT li.titulo, au.nombre, au.apellidos TEST-# FROM tlibro AS li INNER JOIN tautor AS au TEST-# ON (li.autor_id = au.autor_id); titulo | nombre | apellidos ---------------------------+----------------+-----------Los bandidos de Río Frío | Manuel Clemencia | Ignacio Manuel | Altamirano | Payno Primero sueño | Sor Juana Inés | de la Cruz Todos los fuegos el fuego | Julio | Cortázar Cuentos crueles | No | Recuerdo Artificios | Jorge Luis | Borges Ficciones | Jorge Luis | Borges (7 rows) Otra manera de hacer lo mismo sería mediante la cláusula USING, que se encarga de igualar las columnas que tienen el mismo nombre en las dos tablas. Utilizamos USING en lugar de ON. TEST=# SELECT li.titulo, au.nombre, au.apellidos TEST-# FROM tlibro AS li INNER JOIN tautor AS au TEST-# USING (autor_id); titulo | nombre | apellidos ---------------------------+----------------+-----------Los bandidos de Río Frío | Manuel | Payno Clemencia | Ignacio Manuel | Altamirano Primero sueño | Sor Juana Inés | de la Cruz Todos los fuegos el fuego | Julio | Cortázar Cuentos crueles | No | Recuerdo Artificios | Jorge Luis | Borges Ficciones | Jorge Luis | Borges (7 rows) Como puedes observar, el resultado de estos join no incluye los libros que no tienen autor. Esto sucede porque el join rescata sólo aquellos registros que cumplen la condición de igualdad entre columnas en común. Si quisiéramos rescatar los libros que tiene y los que no tienen autor debemos utilizar un OUTER JOIN. TEST=# SELECT li.titulo, au.nombre, au.apellidos TEST-# FROM tlibro AS li LEFT OUTER JOIN tautor AS au TEST-# ON (li.autor_id = au.autor_id); titulo | nombre | apellidos ---------------------------+----------------+-----------Los bandidos de Río Frío | Manuel Clemencia | Ignacio Manuel | Altamirano | Payno Primero sueño | Sor Juana Inés | de la Cruz Todos los fuegos el fuego | Julio | Cortázar Cuentos crueles | No | Recuerdo Artificios | Jorge Luis | Borges Ficciones | Jorge Luis | Borges El llano en llamas | | El túnel | | (9 rows) Ahora, para obtener los libros con autor más los libros sin autor debemos usar la siguiente instrucción. TEST=# SELECT li.titulo, au.nombre, au.apellidos TEST-# FROM tlibro AS li RIGHT OUTER JOIN tautor AS au TEST-# ON (li.autor_id = au.autor_id); titulo | nombre | apellidos ---------------------------+----------------+-----------| Anderson | Imbert Los bandidos de Río Frío | Manuel | Payno Clemencia | Ignacio Manuel | Altamirano Primero sueño | Sor Juana Inés | de la Cruz Todos los fuegos el fuego | Julio | Cortázar Cuentos crueles | No | Recuerdo Artificios | Jorge Luis | Borges Ficciones | Jorge Luis | Borges (8 rows) Combinemos ahora varios tipos de join y obtengamos todos los libros, con autor o sin autor, indicando el tema al que pertenecen. El número de operaciones join siempre es igual al número de tablas menos uno. TEST=# SELECT li.titulo, au.nombre, au.apellidos, te.nombre TEST-# FROM (tlibro AS li LEFT OUTER JOIN tautor AS au TEST(# ON (li.autor_id = au.autor_id) TEST(# LEFT OUTER JOIN ttema AS te TEST(# ON (li.tema_id = te.tema_id)); titulo | nombre | apellidos | nombre ---------------------------+----------------+------------+----------Los bandidos de Río Frío | Manuel Clemencia | Ignacio Manuel | Altamirano | Literatura | Payno | Literatura Primero sueño | Sor Juana Inés | de la Cruz | Literatura Todos los fuegos el fuego | Julio | Cortázar | Litera tura Cuentos crueles | No | Recuerdo | Literatura Artificios | Jorge Luis | Borges | Literatura Ficciones | Jorge Luis | Borges | Literatura El llano en llamas | | | El túnel | | | (9 rows) Ahora obtengamos los autores con y sin libro más el tema asociado. TEST=# SELECT li.titulo, au.nombre, au.apellidos, te.nombre TEST-# FROM (tlibro AS li RIGHT OUTER JOIN tautor AS au TEST(# ON (li.autor_id = au.autor_id) TEST(# LEFT OUTER JOIN ttema AS te TEST(# ON (li.tema_id = te.tema_id)); titulo | nombre | apellidos | nombre ---------------------------+----------------+------------+----------Ficciones | Jorge Luis | Borges | Literatura Los bandidos de Río Frío | Manuel | Payno | Literatura Clemencia | Ignacio Manuel | Altamirano | Literatura Primero sueño | Sor Juana Inés | de la Cruz | Literatura Todos los fuegos el fuego | Julio | Cortázar | Literatura Cuentos crueles | No | Recuerdo | Literatura Artificios | Jorge Luis | Borges | Literatura | Anderson | Imbert | (8 rows) Finalmente, si lo que quisiéramos obtener fuera: todos los libros con y sin autor, todos los autores, con y sin libro, y los temas asociados, tendríamos que hacer uso del FULL OUTER JOIN. TEST=# SELECT li.titulo, au.nombre, au.apellidos, te.nombre TEST-# FROM (tlibro AS li FULL OUTER JOIN tautor AS au TEST(# ON (li.autor_id = au.autor_id) TEST(# FULL OUTER JOIN ttema AS te TEST(# ON (li.tema_id = te.tema_id)); titulo | nombre | apellidos | nombre ---------------------------+----------------+------------+----------Primero sueño | Sor Juana Inés | de la Cruz | Literatura Los bandidos de Río Frío | Manuel | Payno | Literatura Clemencia | Ignacio Manuel | Altamirano | Literatura Ficciones | Jorge Luis | Borges | Literatura Todos los fuegos el fuego | Julio | Cortázar | Literatura Cuentos crueles | Recuerdo | Literatura | No Artificios | Jorge Luis | Borges | Literatura El túnel | | | El llano en llamas | | | | Anderson | Imbert | (10 rows) 3.3.3. Instrucción CASE SQL tiene la posibilidad de aplicar expresiones CASE en la salida de las consultas. Estas expresiones son similares a las de cualquier lenguaje de programación, la sintaxis es la siguiente: CASE WHEN condition1 THEN result1 WHEN condition2 THEN result2 [ … ] [ ELSE default_result ] END [ AS alias ] Vamos a suponer que deseamos obtener los libros vendidos con una leyenda que indique si el precio de venta fue menor, igual o mayor a 100 pesos. La consulta se resolvería de la siguiente manera. TEST=# SELECT li.titulo, TEST-# CASE WHEN ve.precio_venta < 100 THEN 'Vendido menor a 100' TEST-# WHEN ve.precio_venta = 100 THEN 'Vendido a 100' TEST-# ELSE 'Vendido mayor a 100' TEST-# END AS "Rango de venta", ve.precio_venta TEST-# FROM tlibro AS li INNER JOIN tventa AS ve TEST-# ON (li.libro_id = ve.libro_id); titulo | Rango de venta | precio_venta ---------------------------+---------------------+-------------Todos los fuegos el fuego | Vendido menor a 100 | 90 Todos los fuegos el fuego | Vendido menor a 100 | 90 Primero sueño | Vendido a 100 | 100 Primero sueño | Vendido mayor a 100 | 110 Primero sueño | Vendido mayor a 100 | 110 Ficciones | Vendido mayor a 100 | 120 (6 rows) 3.3.4. Subconsultas También es posible utilizar una consulta (SELECT) como base u origen de otra. A tal situación se le llamará subconsulta. Por ejemplo, puedes obtener los nombres de los autores a partir de un SELECT * FROM tautor, en lugar de hacerlo de la tabla en sí. TEST=# SELECT nombre FROM (SELECT * FROM tautor) AS autores; nombre ---------------Ignacio Manuel Manuel Jorge Luis Sor Juana Ines Julio No (6 rows) Obtengamos los libros con autores cuyo apellido comience con la letra I o L a partir de una subconsulta. TEST=# SELECT titulo FROM tlibro TEST-# WHERE autor_id IN TEST-# (SELECT autor_id FROM tautor TEST(# WHERE nombre ~ '^[I-J]'); tíitulo --------------------------Todos los fuegos el fuego Ficciones Artificios Clemencia (4 rows) 3.3.5. Consultas de agrupamiento Es posible obtener con SQL varios registros agrupados en uno sólo. Adicionalmente podemos contar o sumar los registros del grupo mediante funciones de agregado. La idea del agrupamiento es muy importante para generar reportes y consultas útiles al usuario. Éste no espera que la máquina muestre una lista interminable de registros sobre los cuales hay que contar y sumar. Nuestros usuarios esperan datos agrupados por categorías con totales de esas categorías. Imagina que deseamos saber cuántas ediciones (ISBN) existen por libro. Podríamos obtener la lista de libros con sus ISBN y ponernos a contar cuántas veces se repiten estos últimos. TEST=# SELECT li.titulo, ed.isbn TEST-# FROM tlibro AS li INNER JOIN tedicion AS ed TEST-# ON (li.libro_id = ed.libro_id); titulo | isbn ---------------------------+-----Cuentos crueles | 1001 Cuentos crueles | 1002 Todos los fuegos el fuego | 2001 Primero sueño | 3001 Primero sueño | 3002 Ficciones | 4001 (6 rows) En lugar de la consulta anterior, construyamos una instrucción con la cláusula GROUP BY. Esta cláusula permite agrupar los renglones en ciertas categorías y contar o sumar valores de esas categorías. TEST=# SELECT li.titulo, COUNT(ed.isbn) AS "Total de ediciones" TEST-# FROM tlibro AS li INNER JOIN tedicion AS ed TEST-# ON (li.libro_id = ed.libro_id) TEST-# GROUP BY li.titulo; titulo | Total de ediciones ---------------------------+-------------------Cuentos crueles | 2 Ficciones | 1 Primero sueño | 2 Todos los fuegos el fuego | 1 (4 rows) La función COUNT (tabla.columna) es un ejemplo de función de agregado. Permite obtener el conteo de registros del grupo. Si deseamos obtener la suma total por grupo de alguna columna, debemos usar la función SUM (tabla.columna). Veamos un ejemplo. TEST=# select * from tventa; libro_id | isbn | precio_venta | fecha_venta ----------+------+--------------+------------2 | 2001 | 90 | 2004-05-12 2 | 2001 | 90 | 2004-05-12 3 | 3001 | 100 | 2004-05-12 3 | 3002 | 110 | 2004-05-12 3 | 3002 | 110 | 2004-05-12 4 | 4001 | (6 rows) 120 | 2004-05-12 TEST=# SELECT li.titulo, SUM(ve.precio_venta) AS "Total_venta" TEST-# FROM tlibro AS li INNER JOIN tventa AS ve TEST-# ON (li.libro_id = ve.libro_id) TEST-# GROUP BY li.titulo; titulo | Total_venta ---------------------------+------------Ficciones | 120 Primero sueño | 320 Todos los fuegos el fuego | 180 (3 rows) Si quisiéramos obtener sólo aquellos grupos con determinada condición utilizamos la cláusula HAVING. Esta cláusula es el equivalente al WHERE pero actúa a nivel de grupos y sus criterios usan las funciones de agregado. Determinemos los libros que tienen más de una edición. TEST=# SELECT li.titulo, COUNT(ed.isbn) AS "Total de ediciones" TEST-# FROM tlibro AS li INNER JOIN tedicion AS ed TEST-# ON (li.libro_id = ed.libro_id) TEST-# GROUP BY li.titulo TEST-# HAVING COUNT(ed.isbn) > 1; titulo | Total de ediciones -----------------+-------------------Cuentos crueles | 2 Primero sueño 2 (2 rows) | 3.3.6. Operadores avanzados Los operadores avanzados de SQL permiten combinar tablas con la posibilidad de escribir subconsultas para realizar operaciones de UnionUnión, Intersección y Diferencia, a continuación se explican estas operaciones: Unión Permite obtener el conjunto completo de renglones de dos consultas. No duplica renglones en la salida. Por ejemplo, el siguiente SELECT obtiene la unión de los titulo de libros y de autores. Las columnas que se unen en la salida deben ser del mismo tipo. TEST=# SELECT titulo FROM tlibro TEST-# UNION TEST-# SELECT nombre FROM tautor; titulo --------------------------Anderson Artificios Clemencia Cuentos crueles El llano en llamas El túnel Ficciones Ignacio Manuel Jorge Luis Julio Los bandidos d e Río Frío Manuel No Primero sueño Sor Juana Inés Todos los fuegos el fuego (16 rows) Intersección Permite obtener los renglones que se encuentran en las dos consultas, los que aparecen en ambos. Por ejemplo, el siguiente SELECT determina los libros vendidos a partir de las tablas ‘tlibro’ y ‘tventa’. TEST=# SELECT libro_id FROM tlibro TEST-# INTERSECT TEST-# SELECT libro_id FROM tventa; libro_id ---------2 3 4 (3 rows) Diferencia Obtiene, a partir de dos consultas, aquellos renglones que están del lado izquierdo, pero no en el derecho. Por ejemplo, intentemos obtener aquellos libros que no se han vendido. TEST=# SELECT libro_id FROM tlibro TEST-# EXCEPT TEST-# SELECT libro_id FROM tventa; libro_id ---------1 5 6 7 8 9 (6 rows) 3.3.7. Plan de ejecución de consultas Todos los sistemas manejadores de bases de datos nos permiten analizar cómo fue realizada nuestra consulta. Los datos que podemos obtener de la ejecución de una consulta son: las tablas que fueron utilizadas, el método de acceso para obtener los datos de esas tablas (secuencial, indexado, etc.) y la manera como se realizó el join entre las tablas. A todos estos datos los llamamos “plan de ejecución”. Para obtener el plan de ejecución de una consulta utilizamos el comando EXPLAIN. Veamos un ejemplo. EXPLAIN ANALYZE SELECT titulo FROM tlibro WHERE autor_id = 11; 3.3.8. Creación de vistas Una vista es un objeto de la base de datos que almacena una consulta. Funciona como una tabla, pero la vista no existe físicamente en la base de datos, se genera de forma dinámica. Una vista nos permite encapsular consultas que utilizamos de forma recurrente, nos evita escribirlas de nuevo. También nos ayuda a manipular consultas muy complejas de una forma más sencilla. Por ejemplo, construyamos una vista con los libros vendidos de mayor a menor venta. CREATE VIEW vlibrosvendidos AS SELECT li.titulo, COUNT(*) AS totventas FROM tlibro AS li INNER JOIN tventa AS ve ON (li.libro_id = ve.libro_id) GROUP BY li.titulo ORDER BY COUNT(*) DESC; Obtengamos ahora el libro más vendido TEST=# SELECT * FROM vlibrosvendidos TEST-# LIMIT 1; titulo | totventas ---------------+----------Primero sueño | 3 (1 row) Utiliza las vistas como un mecanismo para optimizar la programación de tus interfaces y para mostrar sólo aquellos datos que son necesarios de acuerdo al tipo de usuario que los va a consultar. 3.3.9. Consultas especializadas Podemos utilizar la cláusula ORDER BY para pedir a SQL que ordene los registros de salida. Obtengamos la lista de autores ordenados por apellido. TEST=# SELECT apellidoS, nombre TEST-# FROM tautor TEST-# ORDER BY apellidoS ASC; apellidos | nombre ------------+---------------- Altamirano | Ignacio Manuel Borges | Jorge Luis Cortázar | Julio Imbert | Anderson Payno | Manuel Recuerdo | No de la Cruz | Sor Juana Inés (7 rows) Ordenemos las ventas por libro, alfabéticamente, y por fecha más reciente. Observa cómo utilizamos constantes numéricas para referirnos a las columnas de salida que deseamos ordenar. TEST=# SELECT li.titulo, ve.fecha_venta TEST-# FROM tlibro li INNER JOIN tventa ve TEST-# ON (li.libro_id = ve.libro_id) TEST-# ORDER BY 1 ASC, 2 DESC; título | fecha_venta ---------------------------+------------Ficciones | 2004-05-12 Primero sueño | 2004-05-12 Primero sueño | 2004-05-12 Primero sueño | 2004-05-12 Todos los fuegos el fuego | 2004-05-12 Todos los fuegos el fuego | 2004-05-12 (6 rows) Cuando ejecutamos un comando SELECT, obtenemos todos los registros que cumplen nuestras condiciones. También es posible determinar cuántos registros queremos en la salida de una consulta. Para ello, utilizamos las cláusulas LIMIT y OFFSET. LIMIT permite indicar cuántos registros queremos en la salida. Por ejemplo, obtenga los cinco primeros libros ordenados por título. TEST=# SELECT titulo TEST-# FROM tlibro TEST-# ORDER BY titulo TEST-# LIMIT 5; título -------------------Artificios Clemencia Cuentos crueles El llano en llamas El túnel (5 rows) Con la cláusula OFFSET podemos indicar cuántos registros debe saltar SQL antes de darnos la salida. TEST=# SELECT titulo TEST-# FROM tlibro TEST-# ORDER BY titulo TEST-# LIMIT 5 TEST-# OFFSET 2; titulo -------------------------Cuentos crueles El llano en llamas El túnel Ficciones Los bandidos de Río Frío (5 rows) 3.3.10. Lenguajes de programación de bases de datos Los sistemas de bases de datos, por definición teórica, incluyen dos lenguajes. Por una parte, un lenguaje de definición de datos (DDL, Data Definition Language), y por otra, un lenguaje de manipulación de datos (DML, Data Manipulation Language). Con el DDL, podemos crear todos los objetos de almacenamiento de datos, las restricciones impuestas a los mismos y los permisos de acceso que éstos tendrán. Con el DML, somos capaces de insertar, modificar o eliminar datos2. En la práctica, ambos forman un solo lenguaje de programación conocido como SQL. Los lenguajes de manipulación de datos pueden ser procedurales (es necesario especificar qué datos se necesitan y cómo obtenerlos) o declarativos (sólo es necesario indicar qué datos se necesitan y no cómo obtenerlos). El lenguaje SQL 2 Algunos autores incluyen en el DML las instrucciones para consultar datos, pero otros prefieren hablar de un lenguaje de consulta de datos (DQL, Data Query Language). pertenece al segundo grupo, esto que lo hace sencillo. Pero, a pesar de que el SQL resulta un lenguaje poderoso de manipulación de datos, su carácter no procedural impiden que lo utilicemos para programar toda la lógica de negocio de una organización. Es entonces necesario echar mano de otros lenguajes que sí son procedurales. A partir de la versión 1999 del SQL, podemos programar, dentro de la base de datos, procedimientos y funciones. Así, todo DBMS cuenta con un lenguaje de programación, que resulta de la unión entre un lenguaje procedural y el SQL. Esta unión es necesaria ya que el SQL no incluye estructuras de control como IF-THEN-ELSE, ni estructuras iterativas como FOR o WHILE. Por ejemplo, Oracle tiene el lenguaje pl/sql, PostgreSQL cuenta con pl/pgsql, y SQL Server incluye su Transact-SQL. 3.3.11. Procedimientos almacenados de bases de datos La construcción de programas en una base de datos cambia de manejador en manejador. Pero también es posible generalizar a partir de conocer la manera de programar en un manejador. Los programas que creamos en una base de datos se llaman genéricamente procedimientos almacenados (stored procedures). Podemos decir que son programas que combinan instrucciones de un lenguaje procedural con instrucciones de SQL. Generalmente, utilizamos los procedimientos almacenados para agrupar varias operaciones realizadas sobre la base de datos. Una vez realizado el procedimiento almacenado, ya no ejecutamos las operaciones una por una, sino que ejecutamos el procedimiento en un solo momento. También se recomiendan cuando tenemos tareas que deseamos automatizar. La mayoría de los manejadores de bases de datos permiten programar la ejecución de un procedimiento en un momento determinado. Así, podemos ejecutar tareas de administración y mantenimiento de la base de datos de forma automática. Finalmente, utilizamos procedimientos almacenados para encapsular las modificaciones a los datos de la base, con el fin de reducir la complejidad de programación de aplicaciones en la capa de interfaces y mejorar la seguridad de nuestra aplicación escondiendo la lógica de actualización de datos. En nuestro caso, revisaremos la manera de construir funciones con pl/pgsql en PostgreSQL. Una función en pl/pgsql tiene la siguiente estructura: CREATE FUNTION identifier (arguments) RETURNS type AS ‘ DECLARE declaration; […] BEGIN statement; […] END; ‘ LANGUAGE ‘plpgsql’; Por ejemplo, pensemos en programar una función que sume dos números dentro de la base de datos. El procedimiento o, en este caso, función queda almacenado en la base de datos como un objeto. Para ejecutar la función utilizamos la instrucción SELECT. CREATE FUNCTION suma() RETURNS integer AS ‘ DECLARE suma integer; BEGIN suma := 10 + 10; return suma; END; ‘ LANGUAGE ‘plpgsql’; TEST=# SELECT suma() AS "Suma"; Suma -----20 (1 row) Algunos manejadores como SQL Server, permiten que la salida de un procedimiento sea una consulta de datos. PostgreSQL, por ejemplo, no lo permite; para hacerlo es necesario usar cursores. Lo que sí podemos hacer es utilizar la instrucción SELECT INTO para guardar valores de una o varias columnas en variables. SELECT INTO targaet_variable [, ,,,] target_column [, ...] select_clauses; Veamos un ejemplo. CREATE FUNCTION get_autor(integer) RETURNS text AS ‘ DECLARE vautor_id ALIAS FOR $1; vnombre text; vapellido text; BEGIN SELECT INTO vnombre, vapellido nombre, apellidos FROM tautor WHERE autor_id = vautor_id; IF NOT FOUND THEN RETURN ‘’Autor no existe’’; ELSE RETURN vnombre || ‘’ ’’ || vapellido; END IF; END; ‘ LANGUAGE ‘plpgsql’; TEST=# SELECT get_autor(11); get_autor ------------------Jorge Luis Borges (1 row) Los argumentos de la función son asociados a identificadores precedidos del signo ‘$’ en el mismo orden en el que se pasan a la función. La cláusula ALIAS la utilizamos para distinguir de forma más clara cada argumento asignándole un sobrenombre. El comando FOUND toma valor de true si el SELECT INTO inmediato anterior fue exitoso. Debe ser usado en una sentencia IF/THEN. La estructura de la condicional IF es la siguiente. IF condition THEN statement; […] ELSE statement; […] END IF; Hay tres ciclos en PL/pgSQL. El primero es un loop básico. LOOP statement; [...] EXIT WHEN condition; END LOOP Un ejemplo sería el siguiente. CREATE FUNCTION cuadrado (integer) RETURNS integer AS ‘ DECLARE num1 ALIAS FOR $1; result integer; BEGIN result := num1; LOOP result := result * result; EXIT WHEN result >= 10000; END LOOP; RETURN result; END; ‘ LANGUAGE ‘plpgsql’; TEST=# SELECT cuadrado(3); cuadrado ---------43046721 (1 row) El segundo es un ciclo WHILE. WHILE condition LOOP statement; [ ... ] END LOOP; El siguiente es un ejemplo. CREATE FUNCTION ciclo_suma(integer, integer) RETURNS integer AS ‘ DECLARE menor ALIAS FOR $1; mayor ALIAS FOR $2; result integer = 0; BEGIN WHILE result != mayor LOOP result := result + 1; END LOOP; RETURN result; END; ‘ LANGUAGE ‘plpgsql’; TEST=# SELECT ciclo_suma(3, 30); ciclo_suma -----------30 (1 row) El tercero es un ciclo FOR. Podemos usarlo en un intervalo de valores o para todos los renglones de un SELECT. FOR identifier IN [ REVERSE ] expression1 .. expresion2 LOOP statement; [ … ] END LOOP; FOR record_variable IN select_statement LOOP statements; [ … ] END LOOP; A continuación hay un ejemplo. CREATE FUNCTION total_ventas() RETURNS integer AS ‘ DECLARE result integer = 0; vrenglon tventa%ROWTYPE; BEGIN FOR vrenglon IN SELECT * FROM tventas LOOP result := result + vrenglon.precio_venta; END LOOP; RETURN result; END; ‘ LANGUAGE ‘plpgsql’; TEST=# SELECT total_ventas(); total_ventas -------------620 (1 row) 3.3.12. Manejo de transacciones Una transacción es un conjunto de instrucciones DML que se realizan todas o no se realiza ninguna. Estas actualizaciones están sincronizadas con la base de datos, la cual implementa mecanismos para evitar problemas de actualización por concurrencia. Una transacción puede terminar en una actualización de los datos de la base (commit) o puede terminar sin actualizar la base, regresándola al estado consistente con el cuál empezó (rollback). Una transacción comienza con la palabra BEGIN, todas las instrucciones siguientes a ella forman parte de la transacción. Para que los cambios sean permanentes debe terminar con el comando COMMIT, en caso de querer deshacer los cambios utilizamos ROLLBACK. El siguiente es un ejemplo. TEST=# BEGIN; BEGIN TEST=# INSERT INTO ttema VALUES(1, 'Filosofia'); INSERT 49932 1 TEST=# SELECT * FROM ttema; tema_id | nombre ---------+-----------5 | Literatura 1 | Filosofia (2 rows) TEST=# ROLLBACK; ROLLBACK TEST=# SELECT * FROM ttema; tema_id | nombre ---------+-----------5 | Literatura (1 row) 3.3.13. Cursores Un cursor es un apuntador a un conjunto de registro producidos por un comando SELECT. Es utilizado generalmente por aplicaciones conectadas permanentemente a la base de datos y que necesitan recuperar renglones constantemente. La ventaja de usar un cursor es que no se necesita reejecutar el query para pedir los valores más recientes en la base. Adicionalmente, los resultados del query no se almacenan en la memoria de la aplicación cliente. Usando cursores Syntax: DECLARE cursorname [ BINARY ] [ INSENSITIVE ] [ SCROLL ] CURSOR FOR query [ FOR { READ ONLY | UPDATE [ OF column [, ...] ] ] DECLARE sirve para declarar el cursor y ejecutar la instrucción SELECT. Sólo podemos declarar cursores dentro de una transacción en PostgreSQL. Syntax: FETCH [ FORWARD | BACKWARD | RELATIVE ] [ # | ALL | NEXT | PRIOR ] { IN | FROM } cursor FETCH recupera los renglones que produjo la instrucción SELECT. Vamos a ver un ejemplo. TEST=# BEGIN; BEGIN TEST=# DECLARE cur_autores CURSOR TEST-# FOR SELECT * FROM tautor; DECLARE CURSOR TEST=# FETCH 4 FROM cur_autores; autor_id | nombre | apellidos ----------+----------------+-----------4 | Ignacio Manuel | Altamirano 3 | Manuel | Payno 11 | Jorge Luis | Borges 6 | Sor Juana Inés | de la Cruz (4 rows) TEST=# FETCH NEXT FROM cur_autores; autor_id | nombre | apellidos ----------+--------+----------7 | Julio | Cortázar (1 row) TEST=# FETCH PRIOR FROM cur_autores; autor_id | nombre | apellidos ----------+----------------+-----------6 | Sor Juana Inés | de la Cruz (1 row) TEST=# FETCH BACKWARD 3 FROM cur_autores; autor_id | nombre | apellidos ----------+----------------+-----------11 | Jorge Luis 3 | Manuel | Borges | Payno 4 | Ignacio Manuel | Altamirano (3 rows) TEST=# FETCH 3 FROM cur_autores; autor_id | nombre | apellidos ----------+----------------+-----------3 | Manuel 11 | Jorge Luis | Payno | Borges 6 | Sor Juana Inés | de la Cruz (3 rows) TEST=# COMMIT; COMMIT También podemos mover la posición del cursor hacia un registro en particular con la instrucción MOVE. Syntax: MOVE [ direction ] [ count ] { IN | FROM } cursor Un ejemplo sería el siguiente. TEST=# BEGIN; BEGIN TEST=# DECLARE cur_autores CURSOR TEST-# FOR SELECT * FROM tautor; DECLARE CURSOR TEST=# FETCH FROM cur_autores; autor_id | nombre | apellidos ----------+----------------+-----------4 | Ignacio Manuel | Altamirano (1 row) TEST=# MOVE FORWARD 4 TEST-# IN cur_autores; MOVE 4 TEST=# FETCH FROM cur_autores; autor_id | nombre | apellidos ----------+----------+----------1 | Anderson | Imbert (1 row) TEST=# FETCH FROM cur_autores; autor_id | nombre | apellidos ----------+----------+----------2 | Villiers | de L'isle (1 row) TEST=# COMMIT; COMMIT Cerrando cursores Para cerrar un cursor podemos hacerlo con la instrucción CLOSE. Syntax: CLOSE cursor Adicionalmente, si cerramos el bloque de la transacción con COMMIT O ROLLBACK el cursor se cierra automáticamente. 3.3.14. Características orientadas a objetos El estándar SQL de 1999, conocido como SQL3, incorporó a este lenguaje algunas características de la orientación a objetos. Una de las más importantes es la herencia de tablas para lo cuál cada tabla debe estar identificada con un identificador de objeto. Los mismo sucede con los renglones, éstos están identificados por un ID. Columnas de sistema Todas las tablas en PostgreSQL incluyen, además de las columnas definidas por el usuario, algunas columnas con información que el propio sistema actualiza. Dos de ellas son la columna oid (object identifier) y tableoid (table object identifier). La primera es un número único que identifica a cada renglón en la tabla. La segunda es un identificador único en el servidor para cada tabla. A continuación puedes ver cómo obtener estas columnas. TEST=# select oid from pruebafechas; oid ------41481 41482 41483 41484 (4 rows) TEST=# select tableoid from pruebafechas; tableoid ---------41479 41479 41479 41479 (4 rows) Herencia de tablas Es un mecanismo objeto-relacional que permite a una tabla heredar atributos de una o más tablas. Esta situación crea una relación de padres e hijas entre las tablas. La tabla hija tendría sus propias columnas y adicionalmente las de su tabla padre (sólo se permite un padre). Una consulta sobre la tabla padre puede realizarse sólo para sus columnas o incluyendo las columnas de sus descendientes. La tabla hija nunca regresa las columnas de sus padres. Veamos un ejemplo del uso de la herencia de tablas. Creemos una tabla padre y agreguemos un registro. CREATE TABLE trabajador ( nombre varchar(25) NOT NULL, apellidop varchar(25) NOT NULL, trabajador_id integer PRIMARY KEY ); TEST=# INSERT INTO trabajador VALUES('Carlos', 'Mendez', 1); INSERT 41545 1 TEST=# SELECT * FROM trabajador; nombre | apellidop | trabajador_id --------+-----------+--------------Carlos | Méndez | 1 Vamos a crear ahora una tabla hija. Observa el uso de INHERITS para referenciar la tabla padre. CREATE TABLE trabhon ( retiva numeric, retisr numeric ) INHERITS (trabajador); Ahora, insertemos un registro en la tabla hija. TEST=# INSERT INTO trabhon VALUES('Arturo', 'Garcia', 2, 10,10); INSERT 41551 1 Las siguientes consultas muestran la información de las tablas. TEST=# SELECT * FROM trabhon; nombre | apellidop | trabajador_id | retiva | retisr --------+-----------+---------------+--------+-------Arturo | Garcia | 2 | 10 | (1 row) TEST=# SELECT * FROM trabajador; nombre | apellidop | trabajador_id --------+-----------+--------------Carlos | Mendez | 1 Arturo | Garcia | 2 (2 rows) TEST=# SELECT * FROM ONLY trabajador; nombre | apellidop | trabajador_id --------+-----------+--------------Carlos | Méndez (1 row) | 1 10