Dpto. de Organización y Estructura de la Información (EUI-UPM) Asignatura de Bases de Datos Pedro Pablo Alarcón Cavero Diciembre 1994 EL ESTANDAR ANSI SQL (86) INDICE 1. INTRODUCCION. 2. LENGUAJE DE DEFINICION DE DATOS 3. LENGUAJE DE MANIPULACION DE DATOS 3.1. OPERACIONES DE ACTUALIZACION 3.2. OPERACIONES DE CONSULTA O RECUPERACION 4. LENGUAJE DE CONTROL DE DATOS 5. SQL INMERSO. 6. EJEMPLOS. 1. INTRODUCCIÓN Desde que el Dr. Codd introdujera el concepto de Base de Datos Relacional, hacia 1970, hasta que se diseño un SGBDR (System R) pasaron varios años. El System R (diseñado por IBM), no era más que un prototipo, y el principal objetivo era que el sistema fuese operacionalmente completo, es decir, demostrar que era posible construir un Sistema Relacional, utilizable en un entorno real, para solucionar problemas verdaderos, con un desempeño al menos comparable al de los sistemas existentes. Este sistema incorporaba un sublenguaje de datos que permitía realizar cualquier acceso a la Base de Datos, que se llamó SEQUEL. Posteriormente otros sistemas adoptaron este sublenguaje, al que pasaron a llamar SQL, como sublenguaje de consulta de datos, realizando modificaciones sobre él, hasta hoy en día, en que su utilización dentro del ámbito comercial es realmente extensa. SQL es una abreviatura de "Structured Query Language", esto es, Lenguaje de Consulta Estructurado. Con este lenguaje se formulan operaciones relacionales, es decir, operaciones que permiten definir y manipular una base de datos relacional. En 1986, el Instituto Nacional Norteamericano de Normalización (ANSI) publicó las primeras normas que enunciaban la Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 1 sintaxis y la semántica de SQL. En 1989, ANSI definió el SQL2, basado en el anterior pero con una serie de mejoras (definición de claves primarias, integridad de los datos, etc.). En este tema, abordaremos el estudio del SQL 86, ya que es en éste en el que está basado la versión de Informix_SQL que se utilizará para desarrollar las prácticas de la asignatura. A lo largo de la exposición se hace referencia en los ejemplos a distintas tablas. Estas tablas se encuentran en el apartado 6 correspondiente a los ejemplos (página 18). 1.1. ARQUITECTURA En la figura siguiente se muestra como ven los usuarios el SGBDR, bajo el punto de vista de la arquitectura de bases de datos ANSI/X3/SPARC. SQL NIVEL EXTERNO NIVEL CONCEPTUAL NIVEL INTERNO Nivel Externo: VISTA 1 ... VISTA N TABLA BASE 1 TABLA BASE 2 ... TABLA BASE N FICHERO FÍSICO 1 FICHERO FÍSICO 2 ... FICHERO FÍSICO N lo componen las vistas y tablas base. Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 2 Nivel Conceptual: compuesto por tablas base. Nivel Interno: compuesto por ficheros físicos. Las tablas base están almacenadas en ficheros, donde cada fila corresponde a un registro del fichero. Incorpora la posibilidad de construir índices sobre cada una de las tablas. Por tanto, las tablas que los usuarios pueden manejar son de dos tipos: tablas base y vistas. • Una tabla base, es una tabla que tiene existencia por sí misma, es decir, sus filas (registros) se encuentran almacenados físicamente en uno o varios ficheros físicos. • Una vista es una tabla virtual, esto es, no tiene existencia por sí misma, se definen a partir de una o más tablas base. De ellas solamente existe su definición en el diccionario de datos, y cuando se opera con ellas, se opera en realidad con las tablas base sobre las que está definida. CATALOGO: Los SGBDR tienen una base de datos propia del sistema, que se llama catálogo o diccionario de datos y que contiene el esquema de las bases de datos de usuario, es decir, contiene información sobre las tablas base, las vistas, índices, los derechos de acceso, identificación de usuarios, etc. El catálogo no es propio del lenguaje SQL, sino de cada sistema en particular, pero puede consultarse utilizando la instrucción SELECT de SQL. 1.2. ENTORNO DE UTILIZACIÓN Una característica importante de SQL es que puede utilizarse a través de dos interfaces diferentes, un interface interactivo y un interface para programas de aplicación. a) Interactivo: SELECT LOCALIDAD FROM PROYECTO WHERE P# = 'P3’; El usuario teclea la proposición SELECT (consulta) en un terminal, y el sistema responde con la presentación del resultado en ese terminal. b) Embebido en un lenguaje de programación (Pascal, Cobol, C,...) EXEC SQL SELECT LOCALIDAD INTO :MLOCALIDAD FROM PROYECTO WHERE P# = 'P3’; Este ejemplo, muestra la misma proposición SELECT en esencia, embebida en un programa de aplicación (en el ejemplo Pascal). En este caso, la proposición se ejecutará cuando se ejecute el programa, y el resultado se devolverá no a un terminal, sino a la variable de programa MLOCALIDAD. También se incluyen en este grupo, los lenguajes de cuarta generación (4gl), que son lenguajes orientados a la gestión de bases de datos, y suelen ir incorporados con el SGBDR. Permiten programar aplicaciones para gestionar una base de datos utilizando sentencias SQL y las sentencias propias del lenguaje. Así pues, el SQL es al mismo tiempo un lenguaje de consulta interactivo y un lenguaje de programación de bases de datos, aunque difieren en ciertos detalles que se verán más adelante. En función de esta característica, existirán dos tipos de usuarios en el sistema, usuarios finales en terminales en línea y programadores de aplicaciones (el SGBDR deberá permitir el acceso concurrente a datos compartidos, por parte de múltiples usuarios (finales y programas de aplicación) de los dos tipos, realizando los controles necesarios para mantener la integridad y seguridad de los Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 3 datos). Por último, indicar que las sentencias SQL deben terminar en punto y coma (;), y que existe total libertad para escribir las palabras que componen una sentencia. 1.3. FUNCIONES DE SQL Aún cuando se describe a SQL como un lenguaje de consulta, en realidad es mucho más que eso, ya que dispone de otras funciones además de las de consultar una Base de Datos. Entre éstas se incluyen las relativas a: - definición de datos. - manipulación de los datos de la Base de Datos. - especificar restricciones de acceso y seguridad. Cada función tiene su conjunto propio de instrucciones, que se expresan respectivamente en: - Lenguaje de Definición de Datos (LDD) - Lenguaje de Manipulación de Datos (LMD) - Lenguaje de Control de Datos (LCD) 2. LENGUAJE DE DEFINICIÓN DE DATOS El lenguaje de definición de datos (LDD) tiene como principales funciones: - Crear, suprimir o modificar la definición de una tabla. - Definir y suprimir una vista de datos (tabla virtual). - Definir y suprimir índices de tablas. Estas funciones permiten definir y modificar el esquema de una base de datos y por tanto provocarán una modificación del catálogo del sistema, que contiene el esquema de la base de datos. Los tipos de datos que contempla SQL estándar son: • Cadenas de caracteres: CHARACTER (long) • Numérico exacto o entero • INTEGER o INT SMALLINT (entero corto) NUMERIC (precisión, escala) DECIMAL (precisión, escala) Numérico aproximado o de coma flotante: FLOAT (precisión) REAL DOUBLE PRECISION 2.1. SENTENCIAS SOBRE TABLAS Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 4 § Creación de tablas CREATE TABLE nombre_tabla (<definición_atributo_1> [UNIQUE] [NOT NULL], (<definición_atributo_2> [UNIQUE] [NOT NULL], ..................... (<definición_atributo_n> [UNIQUE] [NOT NULL]); donde: definición_atributo = nombre_atributo tipo_dato (tamaño) UNIQUE: no se permiten valores duplicados en la columna NOT NULL: no se permiten valores nulos en la columna • Modificación de tablas - Añadir un nuevo atributo ALTER TABLE <nombre_tabla> ADD <definición_atributo>; En el caso que la tabla contenga una serie de filas, no se podrá definir el atributo nuevo ni como UNIQUE ni como NOT NULL (ya que en las filas existentes no tendrá valor). - Modificar un atributo ya existente ALTER TABLE <nombre_tabla> MODIFY <definicion_atributo> Si la tabla ya contiene filas, se podrá poner el atributo como NOT NULL, si dicho atributo no tiene valor nulo en ninguna fila. Si se quiere definir como UNIQUE el atributo no puede tener valores duplicados en las filas existentes. • Eliminación de tablas DROP TABLE <nombre_tabla> Esta sentencia elimina tanto el contenido como la definición (esquema) de la tabla especificada. 2.2. SENTENCIAS SOBRE VISTAS • Creación de vistas CREATE VIEW <nombre_vista> (<lista_atributos>) AS ( <clausula SELECT> ) Permite definir una vista de usuario (tabla virtual) a partir de las tablas existentes en la base de datos. Las filas de la vista serán aquellas que resulten de ejecutar la consulta sobre la que está definida. La consulta se especifica mediante la cláusula SELECT que se verá posteriormente. • Eliminación de vistas DROP VIEW <nombre_vista>; Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 5 Esta sentencia permite eliminar la definición de una vista de usuario del catálogo de la base de datos. 2.3. SENTENCIAS SOBRE INDICES • Creación de índices CREATE [UNIQUE] INDEX <nombre_indice> ON <nombre_tabla> (<lista_atributos>); Esta sentencia crea un fichero índice para la tabla y atributos especificados. Este fichero tendrá las filas de la tabla ordenadas según los valores de los atributos (clave del índice), de manera que permita el acceso directo a las filas de la tabla. La opción UNIQUE, determina que no puede haber valores duplicados en la clave del índice. • Eliminación de índices DROP INDEX <nombre_indice> [ON <nombre_tabla>] Esta sentencia elimina el fichero índice especificado. La opción "ON nombre_tabla", cuando más de una tabla tenga índices con el mismo nombre. Es el sistema el encargado de utilizar los índices, para optimizar el acceso a los datos. El usuario sólo puede crear o eliminar índices, pero no indicar su utilización. 3. LENGUAJE DE MANIPULACION DE DATOS Se distinguen dos tipos de operaciones: • Operaciones de actualización (Actúan sobre una única tabla) INSERT : inserción de filas DELETE : eliminación de filas UPDATE : modificación de filas • Operaciones de consulta (Actúan sobre varias tablas) SELECT : consulta sobre la Base de Datos 3.1. OPERACIONES DE ACTUALIZACION 3.1.1. INSERCIÓN DE FILAS Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 6 • Inserción de una fila INSERT INTO <nombre_tabla> [(<lista_de _atributos>)] VALUES (<valor_1>, <valor_2>,...,<valor_n>); Los atributos que no aparezcan en lista_de_atributos quedarán con valor NULL (también puede aparecer en VALUES el valor NULL). Es obligatorio especificar un valor para los atributos que estén definidos como "NOT NULL". • Inserción de varias filas INSERT INTO <nombre_tabla> [(<lista_de_atributos>)] ( <clausula SELECT> ) Los atributos de ambas listas de atributos deben coincidir en el mismo dominio. La cláusula "SELECT" especifica una consulta cuyo resultado (filas) se insertará en la tabla especificada. 3.1.2. MODIFICACIÓN DE FILAS UPDATE <nombre_tabla> SET <atributo_1> = <valor_1>, <atributo_2> = <valor_2>, ........... <atributo_n> = <valor_n> [WHERE <condición>]; La modificación afectará a todas las filas que cumplan la condición, si se especifica ésta (la cláusula WHERE expresa la condición de búsqueda en una consulta y se verá posteriormente). Si no se especifica condición, la modificación afectará a todas las filas de la tabla. El valor que se asigne a un atributo puede ser una constante, o el resultado de una subconsulta (que deberá ir entre paréntesis). 3.1.3. ELIMINACION DE FILAS DELETE FROM <nombre_tabla> [WHERE <condición>]; No se pueden eliminar partes de una fila. Si no aparece la cláusula "WHERE" se eliminarán todas las filas de la tabla, no eliminándose la definición de ésta en el esquema. 3.2. OPERACIONES DE CONSULTA 3.2.1. SINTAXIS DE LA SENTENCIA SELECT [UNIQUE/DISTINCT] <expresión> Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 7 FROM <lista_de_tablas> [WHERE <condicion>] [GROUP BY <lista_de_atributos> [HAVING <condición_de_grupo> ]] [ORDER BY <lista_de_atributos> [ASC/DESC] ]; SELECT: Indica que la operación a realizar es una consulta, y además qué información se desea obtener. Se puede incluir la opción UNIQUE o DISTINCT para no obtener filas duplicadas en el resultado. FROM: Especifica las tablas en las que se encuentran los atributos implicados en la consulta. WHERE: Especifica la condición de búsqueda que se requiere para obtener la información deseada. GROUP BY: Permite agrupar el resultado en base a los atributos especificados. HAVING: Especifica una condición de grupo. ORDER BY: Permite ordenar el resultado en base a los atributos especificados 3.2.2. OPERADORES Los siguientes operadores se pueden utilizar para expresar condiciones de fila (cláusula WHERE) o de grupo (cláusula HAVING). - De comparación (<, <=, >, >=, <>, =) - Lógicos (AND, OR, NOT) - BETWEEN ... AND ... - LIKE - IN - IS NULL - Cuantificadores (ANY, SOME, ALL) - Existencial (EXISTS) 3.2.3. CONDICIONES DE SELECCIÓN 1. Recuperación simple Ø Obtener todos los datos de todos los proyectos. SELECT p#, descrip, localidad, cliente FROM proyecto ó: SELECT * FROM proyecto; *: equivale a todos los atributos de una tabla Ø Obtener los códigos de máquina (m#) para todas las máquinas utilizadas. SELECT DISTINCT m# FROM trabajos; Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 8 2. Recuperación calificada Ø Obtener los códigos de los conductores que son de Arganda. SELECT c# FROM conductores WHERE localidad = 'ARGANDA'; Ø Obtener los códigos de los conductores de Arganda que tengan categoría inferior a 20. SELECT c# FROM conductores WHERE localidad = 'ARGANDA' AND categ < 20; 3. Recuperación con más de una tabla. Ø Obtener para cada máquina utilizada, el código de máquina y el nombre. SELECT DISTINCT trabajos.m#, nombre FROM trabajos, maquinas WHERE trabajos.m# = maquinas.m#; En la condición de la consulta hemos especificado la unión natural entre las tablas trabajos y máquinas. Cuando un atributo tiene el mismo nombre en más de una tabla de las especificadas en la cláusula FROM es necesario expresar el atributo precediéndolo del nombre de la tabla y un punto. 4. Recuperación con el operador 'BETWEEN ... AND ...' Establece una comparación dentro de un intervalo. Ø Obtener el nombre de máquina para aquellas cuyo precio por hora esté comprendido entre 5000 y 15000 ptas. SELECT nombre FROM maquinas WHERE precio_hora BETWEEN 5000 AND 10000; También se puede utilizar NOT BETWEEN. 5. Recuperación con el operador 'LIKE'. Establece una comparación entre cadenas de caracteres, empleando los siguientes comodines: Ø '%' : sustituye a una cadena de caracteres cualquiera. '_' : sustituye a un carácter cualquiera. Obtener los nombres de aquellos trabajadores que comiencen por 'C'. SELECT nombre FROM conductores WHERE nombre LIKE "C%"; Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 9 También se puede utilizar NOT LIKE. 6. Recuperación con el operador "IN". Indica pertenencia. Comprueba la pertenencia de una valor a un conjunto dado (que debe ir entre paréntesis). Este conjunto puede especificarse por enumeración de sus elementos o por el resultado de la ejecución de una sentencia SELECT. Ø Obtener los nombres de aquellos conductores que residen en Arganda o en Rivas. SELECT nombre FROM conductores WHERE localidad IN ('ARGANDA','RIVAS'); Ø Obtener nombres de los trabajadores que han utilizado la máquina 'M2'. SELECT nombre FROM conductores WHERE c# IN (SELECT c# FROM trabajos WHERE m# = 'M2'); ó: SELECT nombre FROM conductores, trabajos WHERE conductores.c# = trabajos.c# AND m# = 'M2'; ó: SELECT nombre FROM conductores WHERE 'M2' IN (SELECT m# FROM trabajos WHERE conductores.c# = trabajos.c#); También se puede utilizar NOT IN. 7. Recuperación con el operador 'IS NULL'. Comprueba si un valor determinado es nulo (NULL). Ø Obtener los partes de trabajo que no figuren con el tiempo empleado. SELECT c#, m#, p#, fecha FROM trabajos WHERE tiempo IS NULL; También se puede utilizar IS NOT NULL. 8. Recuperación con cuantificadores (ALL, ANY). ALL: todos, ANY: alguno. Van acompañados de un operador de comparación: Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 10 Ø - ALL >= ALL < ALL, ... - ANY >= ANY < ANY, ... Obtener los conductores que no han participado en el proyecto 'P1'. SELECT nombre FROM conductor WHERE c# <> ALL (SELECT c# FROM trabajos WHERE p# = 'P1'); Ø Obtener los trabajadores con categoría inferior a la de algún trabajador de Arganda. SELECT nombre FROM conductores WHERE categ < ANY (SELECT categ FROM conductores WHERE localidad = "ARGANDA"); 9. Recuperación con el operador 'EXISTS'. Indica la existencia o no de un conjunto. Va asociado a una subconsulta. Ø Obtener nombres de las máquinas que se han utilizado en el proyecto P3. SELECT nombre FROM maquinas WHERE EXISTS (SELECT * FROM trabajos WHERE trabajos.m# = maquinas.m# AND p# = 'P3'); 3.2.4. OTROS OPERADORES 1. Recuperación con UNION Y DIFERENCIA Unión de conjuntos: operador UNION Diferencia de conjuntos: operador MINUS Ø Obtener los códigos de aquellos conductores que residan en Rivas o tengan categoría inferior a 15. Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 11 SELECT c# FROM conductores WHERE localidad = 'RIVAS' UNION SELECT c# FROM conductores WHERE categ < 25; Ø Obtener los códigos de aquellos trabajadores que tengan categoría inferior a 15 y que no hayan trabajado con la máquina M3. SELECT c# FROM conductores WHERE categ < 15 MINUS SELECT c# FROM trabajos WHERE m# = 'M3'; 2. Expresiones en la cláusula SELECT No sólo se pueden seleccionar atributos, sino expresiones en las que aparezcan atributos y/o constantes y operadores aritméticos (+, -, *, /). SELECT nombre, 'coste final por hora:', (precio_hora*1.15) FROM maquinas; 3.2.5. FUNCIONES AGREGADAS • COUNT (*): contador de tuplas (totalizador) • COUNT (DISTINCT): contador de tuplas (parcial), no tiene en cuenta valores nulos ni duplicados • AVG: media aritmética de un atributo o una expresión numérica • SUM: suma de atributos o expresiones numéricas • MAX: valor máximo de un atributo o expresión numérica • MIN: valor mínimo de un atributo o expresión numérica Devuelven un valor único, numérico, como resumen de la información relativa a atributos. No se puede combinar una función agregada, con columnas que devuelvan más de un valor, a menos que la consulta contenga una cláusula GROUP BY. Ø Obtener el número total de proyectos realizados SELECT COUNT(*) FROM proyectos; Ø Obtener el número total de máquinas que se han utilizado en 'P2'. Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 12 SELECT COUNT (DISTINCT m#) FROM trabajos WHERE p# = 'P2'; Ø Obtener el precio medio por hora de las máquinas utilizadas en el proyecto 'P1' SELECT AVG(precio_hora) FROM maquinas WHERE m# IN (SELECT m# FROM trabajos WHERE p#='P1'); Ø Obtener el precio total a pagar a los trabajadores del proyecto 'P3' SELECT SUM (tiempo*precio_hora) FROM trabajos, maquinas WHERE p# = 'P3' AND trabajos.m# = maquinas.m#; Ø Obtener el precio hora de máquina más elevado" SELECT MAX (precio_hora) FROM maquinas; Ø Obtener el nombre del conductor que ha trabajado menos tiempo con la máquina 'M2'. SELECT nombre FROM conductores WHERE c# IN (SELECT c# FROM trabajos WHERE tiempo IN (SELECT MIN(tiempo) FROM trabajos WHERE m# = 'M2')); 3.2.6. CLAÚSULA GROUP BY GROUP BY <lista_de_atributos> Agrupa el resultado de una consulta en base a uno o varios atributos, devolviendo una única fila por grupo. El agrupamiento no se realiza ordenado, el resultado de éste depende de como esté la tabla. Los atributos que aparezcan en la cláusula GROUP BY, deben aparecer en la cláusula SELECT. Ø Obtener por cada conductor que haya trabajado, el código de éste y la cantidad total de tiempo empleado. SELECT c#, SUM(tiempo) FROM trabajos GROUP BY c#; Resultado: Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 13 c# SUM(tiempo) C2 C3 C5 C4 C1 210 500 240 90 420 Aquí, si puede aparecer una función, puesto que GROUP BY devuelve un único valor por grupo. Para cada valor de P# distinto en la tabla trabajos, aparecerá su suma total. Ø Obtener para cada conductor en el proyecto 'P1', su código y el tiempo máximo trabajado. SELECT c#, MAX (tiempo) FROM trabajos WHERE p# = 'P1' GROUP BY c#; Resultado: c# MAX(tiempo) C2 100 3.2.7. CLAÚSULA HAVING HAVING <condicion_de_grupo> Siempre va acompañada de la cláusula GROUP BY. Especifica una condición de grupo, seleccionándose sólo aquellos grupos que cumplan la condición especificada. Conceptualmente es como el WHERE, salvo que éste actúa a nivel de tuplas. Ø Obtener para los conductores que hayan utilizado la misma máquina más de una vez entre el 12/09/94 y el 18/09/94, el código de conductor, el código de máquina y el tiempo total empleado. SELECT c#, m#, SUM (tiempo) FROM trabajos WHERE fecha BETWEEN 12/09/94 AND 18/09/94 GROUP BY c#, m# HAVING COUNT(*) > 1; Resultado: c# m# SUM(tiempo) C1 M2 C2 M3 240 110 3.2.8. CLAÚSULA ORDER BY Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 14 ORDER BY <lista_de_atributos> [ASC | DESC] El resultado de la consulta se ordena en base a los atributos que se indiquen en la lista. Los Atributos de ordenación deben ser atributos o expresiones que aparezcan en la cláusula SELECT. Cada atributo puede llevar un criterio de ordenación, ascendente (ASC) o descendente (DESC), por defecto ascendente. Ø Obtener los partes de trabajo correspondientes al proyecto 'P4' ordenados ascendentemente por conductor y máquina. SELECT * FROM trabajos WHERE p# = 'P4' ORDER BY c#, m#; Resultado: C# M# P# FECHA TIEMPO C1 C1 C3 C5 M2 M3 M1 M3 P4 P4 P4 P4 17/09/94 15/09/94 15/09/94 15/09/94 120 180 300 90 3.2.9. ALIAS DE TABLAS Y COLUMNAS Se utilizan en los siguientes casos: 1. Para hacer el nombre de una columna más significativo cuando se muestra. 2. Abreviar un nombre de una tabla o de una columna que se usa a menudo. 3. Hacer más clara una instrucción complicada de SQL. 4. Distinguir entre dos ocurrencias del mismo nombre de columna o tabla en cualquier instrucción SELECT. Ø Obtener los conductores que tengan la categoría más alta. SELECT X.nombre CONDUCTOR, X.categ CATEGORIA FROM conductores X, conductores Y WHERE X.categ > = Y.categ; 4. LENGUAJE DE CONTROL DE DATOS 4.1. SEGURIDAD DE LOS DATOS Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 15 • Concesión de privilegios La concesión de privilegios se utiliza para permitir a los usuarios el acceso completo o restringido a las tablas de la base de datos. GRANT <accesos> ON <lista_de_tablas> TO <lista_de_cesionarios>|PUBLIC [WITH GRANT OPTION]; donde: <accesos>: - ALL PRIVILEGES - SELECT - UPDATE - INSERT - DELETE PUBLIC: se conceden los privilegios especificados a todos los usuarios del sistema. WITH GRANT OPTION: se concede el privilegio de poder otorgar privilegios a otros usuarios. • Revocación de privilegios Se utiliza para anular privilegios ya concedidos a los usuarios. REVOKE <accesos> FROM <nombre_usuario> TO <lista_de_tablas> 4.2. INTEGRIDAD DE LOS DATOS Las instrucciones que permiten mantener la integridad de los datos son: • Commit work Los cambios que se puedan estar realizando sobre la base de datos se hacen fijos únicamente al completar la transacción. (Transacción: secuencia de operaciones tales que cada operación de la secuencia es necesaria para completar un resultado unitario. Todas las operaciones deben completarse para que la base de datos quede consistente) En la base de datos de ejemplo, si eliminamos un proyecto será necesario eliminar las filas de la tabla TRABAJOS que pertenezcan al proyecto eliminado. Para mantener la integridad de la base de datos o se ejecutan las dos operaciones de borrado o no se debe ejecutar ninguna. • Rollback work Elimina todos los cambios que se hayan podido producir en la base de datos desde la ejecución de la última instrucción COMMIT. Si se produce un error de programa o un fallo hardware el sistema realiza un ROLLBACK automáticamente. 5. SQL INMERSO Como se indicó en la introducción, las sentencias SQL pueden incluirse en un programa de aplicación escrito en un lenguaje de programación. En este contexto, llamaremos lenguaje anfitrión al lenguaje de programación utilizado, y lenguaje huésped al lenguaje SQL. Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 16 Para que puedan incluirse sentencias SQL en un programa, es necesario que el SGBDR disponga de un precompilador de SQL para el lenguaje de programación a utilizar. La función del precompilador es traducir las sentencias SQL que aparezcan en el programa fuente en llamadas a rutinas del SGBDR. Una vez precompilado el programa, el fichero resultante se compila con un compilador del lenguaje de programación utilizado. Como resultado final, se obtiene un programa ejecutable, que interactúa con el SGBDR. Se llama SQL inmerso, al conjunto de instrucciones SQL que permiten su utilización en programas escritos en lenguajes de programación. • Instrucciones Todas las instrucciones de SQL inmerso van precedidas de las palabras clave EXEC SQL. Dependiendo del lenguaje de programación acabarán en punto y coma o en otro indicador. • Variables principales Las variables principales del programa, pueden utilizarse para almacenar el resultado de una consulta que devuelva una sola fila o bien pueden utilizarse en la propia instrucción SQL. EXEC SQL SELECT nombre INTO :nom_cond FROM conductores WHERE p# = :variable_principal; Las variables que se usen en las instrucciones EXEC SQL, deben aparecer en una sección de declaración en el programa principal. Esta sección comenzará con EXEC SQL BEGIN DECLARE y finalizará con EXEC SQL END DECLARE. • Cursores El resultado de una consulta SQL que devuelva más de una fila no se puede almacenar en variables principales. Para estos casos se utilizarán los cursores, que son ficheros virtuales de registros, en los que se almacenan el resultado de una consulta (cada fila un registro). Por tanto un cursor debe asociarse con una consulta, y se declaran de la siguiente forma: EXEC SQL DECLARE <nombre_cursor> CURSOR FOR <consulta_SQL>; Para poder utilizar el fichero virtual que representa el cursor es necesario abrirlo primero: EXEC SQL OPEN <nombre_cursor>; En el programa que se utilice será necesario la lectura de los registros. La forma de leer un registro del cursor es: EXEC SQL FETCH <nombre_cursor> INTO <variables_principales>; Cuando ya no se necesite el cursor, es conveniente cerrarlo: EXEC SQL CLOSE <nombre_cursor>; 6. EJEMPLOS BASE DE DATOS DE EJEMPLO Un empresario que se dedica a la construcción, dispone de una base de datos para gestionar la contratación de maquinarias (camiones, excavadoras,...) en sus proyectos. En una fecha determinada la base de datos contiene la siguiente información: Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 17 • • • • TABLA PROYECTOS P# DESCRIP LOCALIDAD P1 P2 P3 P4 REFORMA VIVIENDA CONSTRUCCION CHALET REFORMA VIVIENDA CONSTRUCCION PISCINA ARGANDA RIVAS ARGANDA LOECHES CLIENTE FELIPE GARCIA PEDRO MARTINEZ ROSA ALVAREZ FERMIN BLANCO TABLA MAQUINAS M# NOMBRE PRECIO_HORA M1 M2 M3 EXCAVADORA HORMIGONERA VOLQUETE 15000 20000 10000 TABLA CONDUCTORES C# NOMBRE LOCALIDAD C1 C2 C3 C4 C5 JOSE SANCHEZ MANUEL DIAZ JUAN PEREZ LUIS ORTIZ JAVIER MARTIN ARGANDA ARGANDA RIVAS ARGANDA LOECHES CATEG 18 15 20 18 12 TABLA TRABAJOS C# M# P# FECHA C2 C3 C5 C4 C1 C2 C3 C2 C1 C5 C1 C2 M3 M1 M3 M3 M2 M3 M1 M3 M3 M3 M2 M3 P1 P2 P2 P2 P2 P3 P4 P2 P4 P4 P4 P1 10/09/94 10/09/94 10/09/94 10/09/94 12/09/94 13/09/94 15/09/94 15/09/09 15/09/94 15/09/94 17/09/94 18/09/94 TIEMPO 100 200 150 90 120 30 300 45 180 90 120 35 6.1. CREACIÓN DE LA BASE DE DATOS. • Creación de la tabla de proyectos: CREATE TABLE PROYECTOS ( P# CHAR(2) UNIQUE NOT NULL, Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 18 DESCRIP CHAR(20) NOT NULL, LOCALIDAD CHAR(15), CLIENTE CHAR(20) NOT NULL); El atributo P# se define como UNIQUE NOT NULL, puesto que es la clave de la tabla, y por tanto no debe tener valores duplicados (por la definición de clave) ni valores nulos (integridad de la entidad). Los atributos DESCRIP y CLIENTE se definen como NOT NULL, por considerar que es información que no debe faltar al insertar una fila (del mismo modo se podría considerar LOCALIDAD). Al ejecutar esta sentencia se creará la definición de la tabla PROYECTOS (la tabla estará vacía, sin filas), almacenándose dicha definición en el diccionario de datos (tablas propias del sistema que contienen el esquema de la base de datos). • Creación de la tabla partes de trabajo: CREATE TABLE TRABAJOS ( C# CHAR(2) NOT NULL, M# CHAR(2) NOT NULL, P# CHAR(2) NOT NULL, FECHA DATE NOT NULL, TIEMPO SMALLINT ); Los atributos que forman parte de la clave de esta tabla se han definido como NOT NULL (integridad de la entidad), pero en cualquier caso los atributos simples que son clave en otra tabla se deben definir como NOT NULL para mantener la integridad referencial. En este caso, puesto que la clave está formada por más de un atributo no podemos definir los atributos que la forman como UNIQUE, ya que lo que no debe repetirse es el valor de la clave, y no el valor de los atributos que la forman. Para establecer la unicidad de la clave, crearemos un índice. CREATE UNIQUE INDEX clave_trabajo ON TRABAJOS (C#, M#, P#, FECHA); De esta forma aseguramos la unicidad de la clave, al definir un índice con los atributos que la componen como único. Podríamos crear también índices para cada una de las demás tablas, sobre las claves de éstas, ya que estos atributos participarán en las uniones naturales cuando las consultas involucren más de una tabla. De esta forma, se tardará menos tiempo en ejecutar estas consultas. También se podrían crear índices para aquellos atributos que se consulten frecuentemente (por ejemplo nombre de conductor). Los índices deben crearse para asegurar la unicidad de una clave compuesta por más de un atributo, o para optimizar el acceso en las consultas. La creación de los índices debe estudiarse detenidamente, ya que al actualizar una tabla (insertar y borrar una fila, o modificar valores de los atributos que forman la clave de un índice) el sistema debe actualizar también los ficheros índices. Esta actualización consume tiempo, y si el número de índices es elevado, puede no ser rentable la optimización en consultas por el retraso en tiempo que sufren las actualizaciones. • Creación de una vista de usuario. Imaginemos ahora, que una consulta frecuente a la base de datos, es obtener los partes de trabajo, pero queremos que en el resultado no salgan códigos sino sus nombres asociados. Podríamos crear una vista a tal fin: CREATE VIEW partes_trabajo (proyecto, conductor, maquina, fecha, minutos) AS (SELECT descrip, conductores.nombre, maquinas.nombre, fecha, tiempo FROM proyectos, conductores, maquinas, partes Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 19 WHERE trabajos.p# = proyectos.p# AND trabajos.c# = conductores.c# AND trabajos.m# = maquinas.m#); Al ejecutar esta sentencia el sistema creará la definición de la vista partes_trabajo. Esta vista nunca contendrá filas (es una tabla virtual), sus filas corresponderán al resultado de la consulta sobre la que está definida. Por ejemplo, la consulta: SELECT conductor, maquina, fecha, minutos FROM partes_trabajo WHERE proyecto = 'CONSTRUCCION CHALET'; dará como resultado: CONDUCTOR MAQUINA FECHA JUAN PEREZ JAVIER MARTIN LUIS ORTIZ JOSE SANCHEZ MANUEL DIAZ EXCAVADORA 10/09/94 VOLQUETE 10/09/94 VOLQUETE 10/09/94 HORMIGONERA 12/09/94 VOLQUETE 15/09/94 MINUTOS 200 150 90 120 45 6.2. ACTUALIZACIÓN DE LA BASE DE DATOS • Inserción de filas Ø Insertar un nuevo proyecto INSERT INTO proyectos VALUES ('P5', 'ALICATAR SUELO', 'ARGANDA','MARIA MARTIN'); • Actualización de filas Ø Subir el precio_hora de todas las máquinas en un 10% UPDATE maquinas SET precio_hora = precio_hora * 1.1; Ø Incrementar la categoría de los conductores de Arganda con la tercera parte de la categoría más baja. UPDATE conductores SET categ = categ + (SELECT categ/6 FROM conductores WHERE categ = (SELECT MIN(categ) FROM conductores) ) WHERE localidad = 'ARGANDA'; Esta solución no es válida en Informix_sql, ya que no permite que se realice una consulta sobre la tabla que se está Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 20 modificando en la asignación del nuevo valor para el atributo. La actualización puede realizarse con éxito utilizando una tabla temporal como se muestra a continuación: SELECT (categ/6) tercio FROM conductores WHERE categ IN (SELECT MIN (categ) FROM conductores) INTO TEMP minima; UPDATE conductores SET categ = categ + (SELECT tercio FROM minima) WHERE localidad = 'ARGANDA'; DROP TABLE minima; De esta forma, utilizamos una tabla temporal en la que se almacena la tercera parte de la categoría más baja en el atributo tercio, y posteriormente se actualiza el valor de categoría consultando dicha tabla temporal. Por último eliminamos la tabla temporal, aunque no sería del todo necesario, porque el sistema elimina las tablas temporales al abandonar el entorno sql. • Borrado de filas Ø Eliminar de la base de datos al conductor Javier Martín. DELETE FROM conductores WHERE nombre = 'JAVIER MARTIN'; Con esta sentencia eliminamos al conductor, pero la base de datos puede quedar inconsistente, ya que en la tabla TRABAJOS puede figurar el código de éste (no debe consentirse nunca la aparición de inconsistencias en la base de datos, ya que dan lugar a resultados incorrectos). Para evitar que esta eliminación provoque una inconsistencia, habrá que eliminar de la base de datos cualquier referencia al conductor eliminado. En nuestro caso, C# está definido como NOT NULL en la tabla TRABAJOS, por tanto habrá que eliminar todas las filas de esta tabla en las que aparezca el conductor eliminado para dejar la base de datos consistente. Si admitiera valores nulos en lugar de eliminar la fila entera podríamos modificar el valor del código de conductor con NULL. DELETE FROM trabajos WHERE c# NOT IN (SELECT c# FROM conductores); También podíamos haber realizado la siguiente secuencia: DELETE FROM trabajos WHERE c# IN (SELECT c# FROM conductores WHERE nombre = 'JAVIER MARTIN'); DELETE FROM conductores WHERE nombre = 'JAVIER MARTIN'); Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 21 Las dos operaciones de eliminación de datos (eliminación en trabajos y en conductores), deben ejecutarse como una transacción única, de tal forma que se ejecuten las dos operaciones o no se ejecute ninguna. 6.3. RECUPERACIONES O CONSULTAS A LA BASE DE DATOS. Ø Obtener el nombre de los trabajadores que han participado en proyectos realizados en la localidad de Arganda. SELECT nombre FROM conductores WHERE c# IN (SELECT c# FROM trabajos WHERE p# IN (SELECT p# FROM proyectos WHERE localidad = 'ARGANDA')); Resultado: NOMBRE MANUEL DIAZ ó: SELECT nombre FROM conductores, trabajos, proyectos WHERE proyecto.localidad = 'ARGANDA' AND proyectos.p# = trabajos.p# AND trabajos.c# = conductores.c#; Resultado: NOMBRE MANUEL DIAZ MANUEL DIAZ MANUEL DIAZ En esta segunda solución es necesario indicar la tabla del atributo LOCALIDAD, ya que podría haber ambigüedad con el atributo LOCALIDAD de la tabla PROYECTOS. Por otra parte, si no queremos que aparezcan tuplas duplicadas en el resultado podemos incluir la opción UNIQUE: SELECT UNIQUE nombre Ø Obtener el importe total de los trabajos realizados al cliente Felipe García, más el 20% de beneficio de la empresa. SELECT SUM(tiempo*precio_hora/60)*1.20 'IMPORTE_TOTAL' FROM trabajos, maquinas, proyectos WHERE cliente = 'FELIPE GARCIA' AND proyectos.p# = trabajos.p# AND trabajos.m# = maquinas.m#; Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 22 Resultado: IMPORTE_TOTAL 1620000 En este ejemplo, se calcula la suma de las cantidades resultantes de multiplicar el tiempo (expresado en minutos) empleado por cada máquina en los diferentes proyectos que se hayan realizado al cliente Felipe García, por el coste de la máquina por minuto (coste de una hora dividido entre 60 minutos). El resultado de dicha suma se multiplica por 1.20 para obtener, el coste total más el 20%. Se ha especificado un literal ('IMPORTE_TOTAL') para que este sea la cabecera de la tabla resultado. Ø Obtener nombre de los conductores que no tengan la categoría más alta. SELECT nombre FROM conductores WHERE categ NOT IN (SELECT MAX(categ) FROM conductores); Resultado: NOMBRE MANUEL DIAZ JAVIER MARTIN ó: SELECT x.nombre FROM conductores x, conductores y WHERE x.categ < y.categ; Resultado: X.NOMBRE MANUEL DIAZ MANUEL DIAZ MANUEL DIAZ JAVIER MARTIN JAVIER MARTIN JAVIER MARTIN JAVIER MARTIN En esta segunda solución se utilizan mediante el empleo de alias de tabla, dos copias de la misma tabla (conductores), que a efectos de la consulta son tratadas como dos tablas diferentes. Si no queremos que aparezcan filas duplicadas en la tabla resultado: SELECT UNIQUE x.nombre Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 23 Ø Obtener el tiempo total empleado en cada proyecto. SELECT p#, SUM(tiempo) FROM trabajos GROUP BY p#; Resultado: P# SUM(tiempo) P1 P2 P3 P4 135 605 30 690 En esta consulta, al agrupar por el atributo p#, se obtiene por cada valor diferente de p# en la tabla TRABAJOS el valor de la función especificada. Es decir, por todas las filas que tienen el mismo valor de p# (grupo) se obtiene la suma correspondiente al valor del atributo TIEMPO. Ø Conductores que han participado en todos los proyectos de Arganda. SELECT nombre FROM conductores, trabajos, proyectos WHERE proyecto.localidad = 'ARGANDA' AND conductores.c# = trabajos.c# AND trabajos.p# = proyectos.p# GROUP BY nombre HAVING COUNT (DISTINCT p#) = (SELECT COUNT (p#) FROM proyectos WHERE localidad = 'ARGANDA'); Resultado: NOMBRE MANUEL DIAZ En la cláusula WHERE especificamos las condiciones de fila para obtener los datos relativos a los conductores que han participado en proyectos realizados en Arganda. Después se agrupa por nombre y especificamos que por cada grupo (nombre de conductor) el número de proyectos distintos en los que ha participado debe ser igual al número de proyectos realizados en Arganda. Ø Obtener el código del proyecto en el que más tiempo se ha empleado. SELECT p# FROM trabajos GROUP BY p# HAVING SUM(tiempo) IN (SELECT MAX(SUM(tiempo)) FROM trabajos Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 24 GROUP BY p#); Esta solución que es válida en SQL estándar, no lo es en la versión de Informix_sql que utilizaremos en las prácticas, ya que no permite anidar funciones. Para solucionar este problema, podemos crear una tabla temporal para obtener los valores correspondientes a la primera función. Veamos una posible solución: SELECT SUM(tiempo) suma FROM trabajos GROUP BY p# INTO TEMP sumas; SELECT p# FROM trabajos GROUP BY p# HAVING SUM(tiempo) IN (SELECT MAX(suma) FROM sumas); Resultado: P# P4 La primera consulta crea la tabla temporal SUMAS, en la que se almacena en el atributo SUMA, la suma de los tiempos empleados para cada uno de los proyectos. En la segunda consulta para cada proyecto, se comprueba si la suma del tiempo empleado coincide con el proyecto con mayor tiempo empleado. Una vez realizada la consulta es conveniente eliminar la tabla temporal, ya que si antes de abandonar la sesión se intenta ejecutar de nuevo esta consulta aparecerá un error al intentar crear una tabla temporal ya existente. En cualquier caso, al abandonar la sesión desaparecen todas las tablas temporales que se hayan podido crear durante ésta. Pedro Pablo Alarcón Cavero SQL_Estándar. Pág. 25