Unidad IV. Lenguajes de Consulta Relacionales
Introducción a SQL
SQL es el lenguaje estándar para trabajar con bases de datos relacionales y es soportado prácticamente por todos los productos en el mercado. Originalmente, SQL fue desarrollado en IBM Research a principios de los años setenta; fue implementado por primera vez a gran escala en un prototipo de IBM llamado System R, y posteriormente en numerosos productos comerciales de IBM y de muchos otros fabricantes.
Todas nuestras explicaciones están basadas en la versión actual del estándar conocido informalmente como SQL/92, también como SQL-92 o simplemente como SQL2 [4.22-4.23]; el nombre oficial es Estándar Internacional del Lenguaje de Base de Datos SQL (1992).
En un principio, SQL fue diseñado para ser específicamente un «sublenguaje de datos». Sin embargo, con la incorporación en 1996 de la característica de PSM (módulos almacenados persistentes) al estándar, SQL se convirtió, en términos computacionales, en un lenguaje completo (ahora incluye instrucciones como CALL, RETURN, SET, CASE, IF, LOOP, LEAVE, WHILE y REPEAT, así como diversas características relacionadas como las variables y los manejadores de excepciones). Como consecuencia, ya no hay necesidad de combinar SQL con algún lenguaje «anfitrión» distinto para desarrollar una aplicación completa. Sin embargo, en este libro decidimos no abordar los PSM en detalle. No le sorprenderá saber que SQL utiliza el término tabla en vez de los dos términos relación y varrel. Por lo tanto, para ser consistentes con el estándar y los productos SQL, haremos algo parecido en este capítulo (y en el resto del libro, siempre que nos ocupemos en forma específica de SQL). Además, SQL no emplea los términos encabezado y cuerpo (de una tabla o relación).
Generalidades
SQL incluye operaciones tanto de definición como de manipulación de datos. Consideraremos primero las operaciones de definición. La figura 4.1 ofrece una definición de SQL para la base de datos de proveedores y partes
Como puede ver, la definición incluye una instrucción CREATE TABLE para cada una de las tres tablas base. Cada una de estas instrucciones CREATE TABLE especifica el nombre de la tabla base que va a ser creada, los nombres y tipos de datos de las columnas de la tabla y las claves primaria y externa de esa tabla. Surgen un par de cuestiones de sintaxis:
- Observe que a menudo usamos el carácter «#» (por ejemplo) en nombres de columnas, aunque de hecho ese carácter no es válido en SQL/92.
- Usamos el punto y coma «;» como terminador de una instrucción. El hecho que SQL/92 use en realidad dicho terminador depende del contexto.
Esta figura no incluye nada que corresponda a las definiciones de tipos (es decir, instrucciones TYPE) la razón es que SQL no permite que los usuarios definan sus propios tipos; de ahí que las columnas deban ser definidas solo en términos de tipos integrados (definidos por el sistema). SQL soporta los siguientes tipos integrados, que más o menos se explican por sí mismos:
CHARACTER [ VARYING ] (n) INTEGER DATE
BIT [ VARYING ] (n) SMALLINT TIME
NUMERIC (p,q) FLOAT (p) TIMESTAMP
DECIMAL (p,q) INTERVAL
También soporta diversos valores predeterminados, abreviaturas y escrituras alternas —por ejemplo, CHAR por CHARACTER—; aquí omitimos los detalles. También, los corchetes «[» y «]» en CHARACTER y BIT significan que el material que encierran es opcional (como es normal, por supuesto, en la BNF [forma Backus-Naur]). Por último, observe que SQL requiere que se especifiquen ciertas longitudes o precisiones para determinados tipos de datos (por ejemplo, CHAR. Aparentemente SQL considera esas longitudes y precisiones como parte del tipo (lo que implica que, por ejemplo, CHAR(3) y CHAR(4) sean tipos diferentes); pensamos que es mejor considerarlas como restricciones de integridad.
Una vez definida la base de datos, podemos ahora comenzar a operar en ella por medio de las operaciones SQL de manipulación SELECT, INSERT, UPDATE y DELETE. En particular, podemos realizar operaciones relacionales de restringir, proyectar y juntar sobre los datos. Empleando en cada caso la instrucción de manipulación de datos SELECT de SQL. La figura 4.2 muestra cómo se pueden formular ciertas operaciones de restricción, proyección y junta en SQL. Nota: El ejemplo de junta de esa figura ilustra la idea de que en ocasiones son necesarios los nombres calificados (por ejemplo, V.V#, VP.V#) para evitar la ambigüedad en las referencias a columnas. La regla general es que los nombres calificados siempre son aceptables, aunque los nombres no calificados también lo son en tanto no generen ambigüedad.
Subrayamos que SQL también soporta una forma abreviada de la cláusula SELECT, como lo ilustra el ejemplo siguiente:
SELECT *
FROM V
-- o "SELECT V.* ( es decir ,puede ser calificado)
El resultado es una copia de toda la tabla V; el asterisco es una abreviatura de una lista separada por comas con todos los nombres de columna de las tablas a las que se hace referencia en la cláusula FROM (de izquierda a derecha según el orden en el que están definidas dichas columnas en las tablas).
Las operaciones de actualización: Al igual que SELECT, las operaciones INSERT, UPDATE y DELETE se encuentran en general en el nivel de conjunto. Aquí tenemos algunos ejemplos de actualización en el nivel de conjunto para la base de datos de proveedores y partes:
INSERT
INTO TEMP ( P#,PESO )
SELECT P#,PESO
FROM P
WHERE COLOR = 'ROJO' ;
Este ejemplo da por hecho que ya creamos otra tabla TEMP con dos columnas, P# y PESO. La instrucción INSERT inserta en esa tabla los números de parte y los pesos correspondientes para todas las partes de color rojo.
UPDATE V
SET STATUS = STATUS * 2
WHERE CIUDAD = 'PARIS' ;
Esta instrucción UPDATE duplica el STATUS de todos los proveedores que se encuentran en París.
DELETE
FROM VP
WHERE P# = 'P2' ;
Esta instrucción DELETE elimina todos los envíos de la parte P2. SQL no incluye un equivalente directo de la operación de asignación relacional. Sin embargo, podemos simular esa operación eliminando primero todas las filas de la tabla de destino y realizando después en esa tabla un INSERT…SELECT…
Esquema de Información
El estándar de SQL incluye especificaciones para un catálogo estándar denominado esquema de información. De hecho, los términos convencionales «catálogo» y «esquema» son usados en SQL, aunque con un significado muy específico de ese lenguaje; hablando en términos generales, un catálogo de SQL consiste en los descriptores de una base de datos individual, mientras que un esquema de SQL consiste en los descriptores de esa parte de la base de datos que pertenece a un usuario individual. En otras palabras, puede haber cualquier cantidad de catálogos (uno por base de datos), cada uno dividido en cualquier número de esquemas. Sin embargo, es necesario que cada catálogo incluya exactamente un esquema denominado INFORMATION_SCHEMA y, desde la perspectiva del usuario, es ese esquema el que realiza la función normal de catálogo.
El esquema de información consiste entonces en un conjunto de tablas de SQL cuyo contenido en efecto refleja con precisión todas las definiciones de los demás esquemas del catálogo en cuestión. Para ser más exactos, el esquema de información está definido para contener un conjunto de vistas de un «esquema de definición» hipotético. No es necesario que la implementación soporte el esquema de definición como tal, pero sí requiere que (a) soporte algún tipo de «esquema de definición» y (b) soporte vistas de ese «esquema de definición» que luzcan como las del esquema de información.
Los puntos que destacan son:
- La razón para establecer los requerimientos en términos de dos partes independientes (a) y (b) como acabamos de describir, es como sigue. Primero, los productos existentes soportan en efecto algo parecido al «esquema de definición». Sin embargo, esos «esquemas de definición» varían ampliamente de un producto a otro (aun cuando los productos en cuestión sean del mismo fabricante). De ahí que tenga sentido la idea de requerir que la implementación soporte solamente ciertas vistas predefinidas de su «esquema de definición».
- En realidad hay «uno» (no «el») esquema de información ya que, como hemos visto, hay uno de estos esquemas en todo catálogo. Por lo tanto, la totalidad de los datos disponibles para un usuario dado generalmente no estarán descritos por un solo esquema de información. Sin embargo, para efectos de simplicidad, continuaremos hablando como si solo fuera uno.
Algunas de las vistas más importantes de este esquema, con la esperanza de que sus nombres puedan por sí mismos dar una idea de lo que esas vistas contienen. Sin embargo debemos decir que la vista TABLES contiene información de todas las tablas nombradas (tanto de vistas como de tablas base), mientras que la vista VIEWS contiene información solamente de vistas.
SCHEMATA REFERENTIAL_CONSTRAINTS
DOMAINS CHECK_CONSTRAINTS
TABLES KEY_COLUMN_USAGE
VIEWS ASSERTIONS
COLUMNS VIEW_TABLE_USAGE
TABLE_PRIVILEGES VIEW_COLUMN_USAGE
COLUMN_PRIVILEGES CONSTRAINT_TABLE_USAGE
USAGE_PRIVILEGES CONSTRAINT_COLUMN_USAGE
DOMAIN_CONSTRAINTS CONSTRAINT_DOMAIN_USAGE
Vistas
El siguiente es un ejemplo de una definición de vista de SQL:
CREATE VIEW BUEN_PROVEEDOR
AS SELECT V#,STATUS,CIUDAD
FROM V WHERE STATUS > 15 ;
Y este es un ejemplo de una consulta SQL frente a esta vista:
SELECT V#,STATUS FROM
BUEN_PROVEEDOR WHERE CIUDAD = 'LONDRES' ;
Sustituyendo la definición de la vista por la referencia al nombre de la vista, obtenemos una expresión como la siguiente:
SELECT BUEN_PROVEEDOR.V#,BUEN_PROVEEDOR.STATUS
FROM ( SELECT V#,STATUS,CIUDAD
FROM V
WHERE STATUS > 15 ) AS BUEN_PROVEEDOR
WHERE BUEN_PROVEEDOR.CIUDAD = 'LONDRES' ;
Y esta expresión puede simplificarse en algo como esto:
SELECT V#,STATUS
FROM V
WHERE STATUS > 15
AND CIUDAD = 'LONDRES' ;
De hecho, esta última es la consulta que se ejecuta. A manera de otro ejemplo, considere la siguiente operación DELETE:
DELETE
FROM BUENPROVEEDOR
WHERE CIUDAD = 'LONDRES'
El DELETE que en realidad se ejecuta luce similar al siguiente:
DELETE
FROM V
WHERE STATUS > 15
AND CIUDAD = 'LONDRES'
SQL Incrustado
La mayoría de los productos SQL permiten la ejecución de instrucciones SQL de manera directa (es decir, en forma interactiva desde una terminal en línea) y también como parte de un programa de aplicación (es decir, las instrucciones SQL pueden estar incrustadas, lo que significa que pueden estar entremezcladas con las instrucciones del lenguaje de programación de dicho programa).
En el caso de que las instrucciones estén incrustadas, el programa de aplicación puede estar escrito comúnmente en una variedad de lenguajes anfitrión (como COBOL, JAVA, PL/1, etcétera).
El principio fundamental subyacente al SQL incrustado, al cual nos referiremos como el principio de modo dual, es que toda instrucción SQL que puede ser usada en forma interactiva, también puede ser usada en un programa de aplicación. Por supuesto, hay varias diferencias de detalle entre una determinada instrucción SQL interactiva y su contraparte incrustada, en especial, las instrucciones de recuperación requieren de un tratamiento más amplio en un entorno de programa anfitrión, sin embargo, el principio es muy cierto.
Antes de poder explicar las instrucciones reales del SQL incrustado, es necesario cubrir algunos detalles preliminares. La mayoría de estos detalles se ilustran en el fragmento de programa de la figura 4.3.
Los puntos que destacan son:
- Las instrucciones del SQL incrustado están precedidas por EXEC SQL, para distinguirlas de las instrucciones del lenguaje anfitrión, y terminan con un símbolo terminador especial (un punto y coma en el caso de PL/I).
- Una instrucción SQL ejecutable puede aparecer en cualquier parte en donde aparezca una instrucción ejecutable del lenguaje anfitrión. Por cierto, observe ese «ejecutable». A diferencia del SQL interactivo, el SQL incrustado incluye algunas instrucciones que son puramente declarativas, no ejecutables. Por ejemplo, DECLARE CURSOR no es una instrucción ejecutable, tampoco son ejecutables BEGIN y END DECLARE SECTION, ni WHENEVER.
- Las instrucciones de SQL pueden incluir referencias a variables anfitrión; estas referencias deben incluir un prefijo de dos puntos para distinguirlas de los nombres de columnas de SQL. Las variables anfitrión pueden aparecer en SQL incrustado en cualquier lugar donde aparezca una literal en SQL interactivo. También pueden aparecer en una cláusula INTO de SELECT o de FETCH para designar destinos de operaciones de recuperación.
- Observe la cláusula INTO de la instrucción SELECT de la figura 4.3. La finalidad de esta cláusula es especificar las variables de destino en las que se recuperarán valores; la i-ésima variable de destino mencionada en la cláusula INTO corresponde al i-ésimo valor a recuperar, tal como se especifica en la cláusula SELECT.
- Todas las variables anfitrión a las que se hace referencia en instrucciones SQL deben estar declaradas (DCL en PL/I) dentro de una sección de declaración de SQL incrustado, la cual está delimitada por las instrucciones BEGIN y END DECLARE SECTION.
- Todo programa que contenga instrucciones de SQL incrustado debe incluir una variable anfitrión denominada SQLSTATE. Después de ejecutar cualquier instrucción de SQL, un código de estado es devuelto al programa en dicha variable; en particular, un código de estado de 00000 significa que la instrucción se ejecutó con éxito, y un valor de 02000 significa que la instrucción se ejecutó pero no se encontraron datos para satisfacer la petición. Por lo tanto, toda instrucción SQL del programa debe en principio estar seguida de
una comprobación de SQLSTATE y debe tomarse la acción adecuada si el valor no fue el esperado. Sin embargo, en la práctica dichas comprobaciones pueden estar implícitas.
- Las variables anfitrión deben tener un tipo de datos apropiado de acuerdo con los usos para los que son puestas. En particular, una variable anfitrión que vaya a ser usada como un destino (por ejemplo, en SELECT) debe tener un tipo de datos que sea compatible con el de la expresión que proporciona el valor a asignar para ese destino; en forma similar, una variable anfitrión que vaya a ser utilizada como un origen (por ejemplo, en INSERT) debe tener un tipo de datos que sea compatible con el de la columna de SQL a la que se van a asignar los valores del origen. Se aplican observaciones similares a una variable anfitrión que va a ser empleada en una comparación o en cualquier otro tipo de operación.
- Las variables anfitrión y las columnas de SQL pueden tener el mismo nombre.
- Como ya mencioné, toda instrucción de SQL debe en principio estar seguida de una comprobación del valor que SQLSTATE devuelve. Para simplificar este proceso se incluye la instrucción WHENEVER. Esta instrucción toma la forma EXEC SQL WHENEVER ; aquí, ón> puede ser SQLERROR o bien NOT FOUND y ón> puede ser CONTINUE o bien una instrucción GO TO. WHENEVER no es una instrucción ejecutable; más bien es una directiva para el compilador de SQL. «WHENEVER ón> GO TO « hace que el compilador inserte una instrucción de la forma «IF ón> GO TO END IF» después de encontrar cada instrucción SQL. ón> ón>
- Por último, observe que el SQL incrustado constituye un acoplamiento débil entre SQL y el lenguaje anfitrión.
Operaciones que no involucran cursores
Las instrucciones de manipulación de datos que no necesitan cursores son las siguientes:
- SELECT individual
- INSERT
- UPDATE (excepto la forma CURRENT)
- DELETE (de nuevo, con excepción de la forma CURRENT)
A continuación damos ejemplos de cada una de estas instrucciones:
SELECT individual: Obtener el STATUS y la CIUDAD de los proveedores cuyo número de proveedor está dado por la variable anfitrión V#DADO.
EXEC SQL SELECT STATUS,CIUDAD
INTO :CATEGORIA,:CIUDAD
FROM V
WHERE V# = :V#DADO ;
Empleamos el término SELECT individual para referirnos a una instrucción SELECT que produce una tabla que contiene una fila como máximo. En el ejemplo, si en la tabla V hay exactamente una fila que satisface la condición de la cláusula WHERE, entonces los valores de STATUS y CIUDAD de esa fila serán asignados a las variables CATEGORIA y CIUDAD de acuerdo con lo solicitado y SQLSTATE quedará establecido como 00000; si ninguna fila de V satisface la condición WHERE, SQLSTATE quedará establecido como 02000; y si más de una fila satisface la condición, el programa está mal y se asignará un código de error a SQLSTATE.
INSERT: Insertar en la tabla P una parte nueva (número, nombre y peso de parte dados por las variables anfitrión P#, PARTE y PESOP, respectivamente; el color y la ciudad se desconocen).
EXEC SQL INSERT
INTO P ( P#,PARTE,PESO )
VALUES ( :P#,:PARTE,:PESOP ) ;
A los valores COLOR y CIUDAD de la nueva parte se les asignarán los valores predeterminados aplicables.
UPDATE: Aumentar el STATUS de todos los proveedores de Londres en una cantidad dada por la variable anfitrión AUMENTO.
EXEC SQL UPDATE V
SET STATUS = STATUS + :AUMENTO
WHERE CIUDAD = 'LONDRES' ;
Si ninguna de las filas de proveedores satisface la condición WHERE, se asignará el valor 02000 a SQLSTATE.
DELETE: Eliminar todos los envíos de proveedores cuya ciudad esté dada por la variable anfitrión CIUDAD.
EXEC SQL DELETE
FROM VP
WHERE :CIUDAD =
( SELECT CIUDAD FROM V
WHERE V.V# = VP.V# ) ;
Una vez más, si ninguna fila de VP satisface la condición WHERE, se le asignará 02000 a SQLSTATE.