Curso de SQL Server
Curso de SQL Server
Curso de SQL Server
1
Tipos de datos Definición de Datos
Definir constantes según el tipo de
dato 8.1. Introducción
2.7. Las expresiones 8.2. Definir una base de datos
2.8. Funciones CREATE DATABASE
2.9. Las variables Intercalaciones COLLATE
2.10. Otros elementos del lenguaje 8.3. Eliminar una base de datos
DROP DATABASE
Unidad 3. Consultas simples 8.4. Modificar las propiedades de
una BD ALTER DATABASE
3.1. Introducción 8.5. Crear una tabla CREATE
3.2. Origen de datos FROM TABLE
3.3. La lista de selección Tipos de datos: precisión, escala,
3.4. Columnas del origen de datos longitud y prioridad
3.5. Alias de columna 8.6. Eliminar una tabla DROP
3.6. Funciones TABLE
Funciones en Transact-SQL 8.7. Modificar la definición de una
3.7. Columnas calculadas tabla ALTER TABLE
3.8. Utilización del asterisco * 8.8. Crear una vista CREATE VIEW
3.9. Las palabras clave $IDENTITY 8.9. Eliminar una vista DROP VIEW
y $ROWGUID 8.10. Definición de índice
3.10. Ordenación de las filas del 8.11. Tipos de índices
resultado ORDER BY 8.12. Ventajas e inconvenientes de
3.11. Eliminar filas duplicadas los índices
DISTINCT/ALL 8.13. Definir un índice CREATE
3.12. La cláusula TOP INDEX
3.13. Selección de filas WHERE 8.14. Eliminar un índice DROP
3.14. Predicados INDEX
Los predicados CONTAINS y
FREETEXT Unidad 9. Programación en
3.15. Condiciones de búsqueda TRANSACT SQL
compuestas
9.1. Introducción
Unidad 4. Consultas multitabla 9.2. Procedimientos almacenados
STORE PROCEDURE
4.1. Introducción 9.3. Eliminar procedimientos
4.2. La unión de tablas UNION almacenados
4.3. La diferencia EXCEPT 9.4. Crear y ejecutar un
4.4. La intersección INTERSECT procedimiento
4.5. La composición de tablas 9.5. Instrucciones de control de flujo
4.6. El producto cartesiano CROSS 9.6. IF… ELSE
JOIN 9.7. WHILE – BREAK- CONTINUE
4.7. La composición interna INNER 9.8. WAITFOR
JOIN 9.9. GOTO
4.8. La Composición externa LEFT, 9.10. TRY... CATCH
RIGHT y FULL OUTER JOIN 9.11. Desencadenadores o
4.9. Combinar varias operaciones TRIGGERS
9.12. CREATE TRIGGER
9.13. DISABLE TRIGGER
9.14. ENABLE TRIGGER
9.15. DROP TRIGGER
2
Unidad 1. El entorno gráfico SSMS (I)
1.1. Introducción
SQL Server 2005 es un sistema gestor de bases de datos relacionales de Microsoft
Corporation orientado a sistemas medianos y grandes aunque también puede rodar en
ordenadores personales. SQL Server Management Studio (SSMS) es la herramienta de
SQL Server que permite definir y gestionar todas las bases de datos almacenadas en el
servidor SQL Server 2005.
En este tema veremos cómo utilizar el SQL Server Management Studio para manejar
las bases de datos del servidor y organizaremos el texto en los siguientes puntos:
Existen diferentes versiones (ediciones) del producto, por lo que es un producto muy
versátil, que puede cumplir con las exigencias de cualquier empresa, puede ser utilizado
para gestionar bases de datos en un PC en modo local a gestionar todo el sistema de
información de grandes empresas pasando por sistemas que requieran menos potencia y
por sistemas móviles.
Para realizar este curso te recomendamos instalar la versión gratuita: Express. Puedes
descargarla desde la página web de Microsoft, desde el enlace para iniciar descarga. Si
quieres ver las diferentes ediciones y sus características principales visita el siguiente
avanzado
Tan sólo deberemos seguir el asistente. Los puntos más importantes a tener en cuenta
son:
3
Indicar que se trata de una Instancia predeterminada.
Lo ideal es que en este punto instales el programa, para ir probando lo que vayas
aprendiendo de aquí en adelante. Puedes realizar el siguiente Ejercicio Instalación de
SQL Server 2005. El videotutorial práctico de instalación también te ayudará.
4
En la parte izquierda tenemos abierto el panel Explorador de Objetos en el que
aparece debajo del nombre del servidor con el que estamos conectados una serie de
carpetas y objetos que forman parte del servidor.
Las bases de datos de SQL Server 2005 utilizan tres tipos de archivos:
5
Archivos de datos secundarios
Los archivos de datos secundarios son todos los archivos de datos menos el archivo
de datos principal. Puede que algunas bases de datos no tengan archivos de datos
secundarios, mientras que otras pueden tener varios archivos de datos secundarios.
La extensión de nombre de archivo recomendada para los archivos de datos
secundarios es .ndf.
Además los archivos de datos se pueden agrupar en grupos de archivos. Para cada
base de datos pueden especificarse hasta 32.767 archivos y 32.767 grupos de
archivos.
Archivos de registro
Los archivos de registro (archivos de log) almacenan toda la información de registro
que se utiliza para recuperar la base de datos, el también denominado registro de
transacciones. Como mínimo, tiene que haber un archivo de registro por cada base
de datos, aunque puede haber varios. La extensión recomendada para los nombres
de archivos de registro es .ldf.
SQL Server 2005 no exige las extensiones de nombre de archivo .mdf, .ndf y .ldf, pero
estas extensiones ayudan a identificar las distintas clases de archivos y su uso.
Cada base de datos tiene al menos 2 archivos (un archivo de datos principal y un
archivo de registro) y opcionalmente un grupo de archivos.
6
Las bases de datos de los usuarios se deben crear preferentemente fuera de la carpeta
Bases de datos del sistema.
7
Lo mínimo a introducir será el campo Nombre de la base de datos, éste es el nombre
de la base de datos lógica, la base de datos a la que nos referiremos dentro del SSMS, a
nivel conceptual (en la imagen Mibase).
Esta base de datos está asociada a dos archivos físicos, en la parte inferior aparecen
esos archivos. Para facilitarnos la tarea, al teclear el nombre de la bd lógica, se rellenan
automáticamente los nombres de los archivos físicos, el de datos con el mismo nombre y
el del archivo de registro con el mismo nombre seguido de _log. Estos nombres son los
nombres que se asumen por defecto pero los podemos cambiar, posicionando el cursor
en el nombre y cambiándolo.
Para cada archivo físico podemos definir una serie de parámetros como el tipo de
archivo (si es de datos o de transacciones Registro) y su ocupación inicial (Tamaño
inicial).
Si no indicamos ninguna ubicación podemos ver que los guarda en la carpeta del SQL
Server/MSSQL.n/MSSQL/DATA.
n representa un número que puede variar de una instalación a otra.
8
Estos son los archivos mínimos en los que se almacenará la base de datos, pero como
ya vimos anteriormente se puede almacenar en más archivos, los tenemos que definir
todos en esta ventana a continuación de los dos obligatorios.
Al pulsar el botón Agregar se crea una nueva fila en la tabla de archivos físicos donde
deberemos escribir el nombre del archivo, su tipo (desplegando la lista podemos elegir
entre de datos o de registro) y demás parámetros.
9
No podemos eliminar ni el de datos primario, ni el de registro inicial.
10
Aparecerá dentro de la carpeta Bases de datos. Si no se ve pulsa en el icono
Actualizar .
11
Pulsando en Agregar indicamos el archivo de datos primario en su ubicación y
automáticamente se adjuntará la base de datos lógica asociada a este archivo.
12
La opción Adjuntar sólo se utiliza la primera vez, cuando todavía no tenemos la base
de datos en el disco.
Esto es así porque SQL Server sigue en marcha, a pesar de que se cierre el gestor.
Ten en cuenta que el servidor de base de datos normalmente se crea para que sirva
información a diferentes programas, por eso sería absurdo que dejara de funcionar
cuando cerramos el programa gestor, que sólo se utiliza para realizar modificaciones
sobre la base.
Para poder realizar acciones sobre la base de datos, ésta debe estar desconectada.
Para ello, desde el SSMS, desplegamos el menú contextual de la base de datos que nos
interese manipular y seleccionaremos la opción Poner fuera de conexión:
Para volver a conectar la base de datos y seguir trabajando con ella, accederemos al
mismo menú contextual pero elegiremos la opción Poner en conexión:
13
El caso más inmediato en el que puedes necesitar conectar y desconectar la base de
datos es copiar a un pendrive los archivos de las bases que utilizarás en los ejercicios
para poder trabajar en diferentes ordenadores con ellos. Para aprender cómo hacerlo,
visita el siguiente Ejercicio Trasladar una base de datos a otro equipo.
En caso de que tu versión de SQL Server no tenga las opciones Poner en conexión y
Poner fuera de conexión, deberás utilizar la opción Separar... y luego volver a
adjuntarla.
Para crear una nueva tabla primero nos tenemos que posicionar en la base de datos
donde queremos que se almacene la tabla, desplegar el menú contextual y seleccionar la
opción Nueva tabla.
14
A cada columna se le asigna un nombre, un tipo de datos, y opcionalmente una serie de
propiedades, en este tema veremos las básicas y las demás las veremos con más detalle
cuando veamos la instrucción SQL CREATE TABLE.
Algunos tipos no necesitan más, como por ejemplo el tipo entero (int), y otros se
pueden completar con una longitud, como los tipos alfanuméricos:
En este ejemplo hemos definido una columna (Codigo) de tipo Entero corto (Smallint),
y una columna (Nombre) que almacenará hasta 20 caracteres alfanuméricos (nchar(20)),
en este caso la longitud la indicamos en la pestaña Propiedades de columna en la
propiedad Longitud.
15
Las propiedades de la columna pueden variar dependiendo del tipo de datos de la
columna seleccionada, por ejemplo los campos enteros no tienen la propiedad longitud,
ya que el propio tipo define la longitud del campo, en cambio los campos de tipo
numeric o decimal no tiene la propiedad longitud pero sí las propiedades escala y
precisión, los valores que permiten definir el tamaño del campo.
Las columnas de este tipo se utilizan normalmente para numerar las filas de la tabla,
como no habrán dos filas con el mismo valor (el sistema se encarga de incrementar el
valor cada vez que se crea una nueva fila), estos campos se suelen utilizar como claves
primarias.
En SQL Server 2005 no existe el tipo de datos Contador pero se consigue el mismo
funcionamiento asignando a la columna un tipo de datos numérico y definiendo la columna
como columna de identidad.
16
(Inicialización de identidad) y en cuánto incrementará cada vez que se cree un nuevo
registro (Incremento de identidad).
Aunque este tipo de columnas se utiliza frecuentemente como clave primaria, SQL
Server no le asigna automáticamente esta función, la tenemos que definir nosotros
mismos, pero sí fuerza a que sea una columna sin valores nulos. No se puede definir más
de una columna de identidad por tabla.
Aparecerá una llave a la izquierda del nombre, símbolo de las claves principales:
Para definir una clave primaria compuesta por varias columnas, seleccionamos las
columnas manteniendo pulsada la tecla Ctrl y luego seleccionamos la opción.
Para quitar una clave principal, hacemos lo mismo pero en esta ocasión seleccionamos
la opción Quitar clave principal.
17
La nueva columna se colocará delante:
Del mismo modo si queremos eliminar la definición de una columna, nos posicionamos
en la columna a eliminar y seleccionamos la opción Eliminar columna:
18
Para entrar a la ventana de definición de la tabla utilizamos la opción Modificar de su
menú contextual (También es posible que se llame Diseño):
La primera columna sirve para indicarnos el estado de una fila, por ejemplo el * nos
indica que es una nueva fila, esta fila realmente no está en la tabla, nos sirve de
contenedor para los nuevos datos que queremos insertar.
Para insertar una nueva fila de datos sólo tenemos que rellenar los campos que
aparecen en esa fila (la del *), al cambiar de fila los datos se guardarán automáticamente
en la tabla a no ser que alguno infrinja alguna regla de integridad, en ese caso SQL Server
nos devuelve un mensaje de error para que corrijamos el dato erróneo, si no lo podemos
corregir entonces sólo podemos deshacer los cambios.
19
1.17. Eliminar filas
Para eliminar una fila completa, la seleccionamos y pulsamos la tecla Supr o bien
desplegamos su menú contextual y seleccionamos la opción Eliminar.
Como ya hemos visto, en una base de datos relacional, las relaciones entre las tablas
se implementan mediante la definición de claves ajenas, que son campos que contienen
valores que señalan a un registro en otra tabla, en esta relación así creada, la tabla
referenciada se considera principal y la que contiene la clave ajena es la subordinada.
Desde el entorno gráfico del SSMS podemos definir claves ajenas entrando en el
diseño de la tabla y desplegando el menú contextual del campo que va a ser clave ajena:
20
Al pulsar el botón que se encuentra en la fila Especificación de tablas y columnas se
abre el diálogo donde definiremos la relación:
De esta forma hemos definido una relación entre las tablas Facturas y Clientes. Para
ver las relaciones existentes entre las diferentes tablas tenemos los diagramas.
21
Primero debemos definir el diagrama, para ello seleccionamos la opción
correspondiente:
22
La llave indica la tabla principal (padre) y el símbolo infinito señala la tabla que contiene
la clave ajena.
Hemos aprendido hasta ahora lo básico para poder crear una base de datos y rellenarla
con tablas relacionadas entre sí y con datos, ahora veamos cómo recuperar esos datos.
Para ello debemos abrir la zona de trabajo de tipo Query, abriendo una nueva consulta,
seleccionando previamente el servidor y pulsando el botón de la barra
de botones o si queremos realizar la consulta sobre un servidor con el cual todavía no
hemos establecido conexión, seleccionando de la barra de menús la opción Nuevo >
Consulta de motor de base de datos:
23
.
En este último caso nos aparecerá el cuadro de diálogo para establecer la conexión (el
mismo que vimos al principio del tema).
A continuación se abrirá una nueva pestaña donde podremos teclear las sentencias
SQL:
Además aparece una nueva barra de botones que nos permitirá ejecutar los comandos
más útiles del modo query.
Sólo tenemos que teclear la sentencia a ejecutar, por ejemplo empezaremos por crear
la base de datos.
24
Si ahora desplegamos la carpeta Bases de Datos del Explorador de Objetos,
observaremos la base de datos que hemos creado:
Podemos incluir en una misma consulta varias sentencias SQL, cuando pulsamos
Ejecutar se ejecutarán todas una detrás de otra. Si tenemos varias consultas y sólo
queremos ejecutar una, la seleccionaremos antes de ejecutarla.
25
Fijándonos en la pestaña de la consulta, en el nombre aparece el nombre del servidor
seguido de un punto y el nombre de la base de datos sobre la que se va a actuar y luego
un guión y el nombre de la consulta.
Cuando creamos una nueva consulta, ésta actuará sobre la base de datos activa en
ese momento. Por defecto la base de datos activa es la predeterminada (master). Si
queremos que la base de datos activa sea por ejemplo la base de datos ventas, hacemos
clic sobre su nombre en el Explorador de objetos, y ésta pasará a ser la base de datos
activa. Si ahora creamos una nueva consulta, ésta actuará sobre la base de datos ventas.
Si queremos crear una consulta que siempre actúe sobre una determinada base de
datos y no nos queremos preocupar de qué base de datos tenemos activa podemos
añadir al principio de la consulta la instrucción USE nombreBaseDatos; esto hará que
todas las instrucciones que aparezcan después, se ejecuten sobre la base de datos
indicada.
Por ejemplo:
USE ventas;
SELECT * FROM pedidos;
Obtiene todos los datos de la tabla pedidos que se encuentra en la base de datos
ventas.
Si no utilizamos USE y almacenamos la consulta, al abrirla otra vez, cogerá como base de
datos la predeterminada (no la activa) y se volverá a ejecutar sobre la base de datos
master.
26
En la pestaña Propiedades de conexión, en el cuadro Conectar con base de datos:
Seleccionamos <Examinar servidor > para elegir la base de datos.
27
Para facilitarnos la redacción y corrección de las sentencias, el editor de SQL presenta
las palabras de distintos colores según su categoría y podemos utilizar el panel
Explorador de Objetos para arrastrar desde él los objetos sobre la zona de trabajo y así
asegurarnos de que los nombres de los objetos (por ejemplo nombre de tabla, de
columna, etc.) sean los correctos.
Como hemos dicho el texto que se escribe en este editor de código se colorea por
categoría. Los colores son los mismos que se utilizan en todo el entorno SQL Server. En
esta tabla aparecen los colores más comunes.
Color Categoría
28
En la lista Mostrar valores para, seleccionamos el entorno que se verá afectado.
Ahora sólo nos queda aprender a redactar sentencias SQL, cosa que se verá en otro
momento, mientras tanto podemos utilizar el Generador de Consulta que incluye SSMS y
que veremos a continuación en el apartado sobre vistas.
29
Para definir una vista en el Explorador de Objetos desplegamos la base de datos
donde la guardaremos y elegimos la opción Nueva vista del menú contextual de la
carpeta Vistas, se pondrá en funcionamiento el generador de consultas pidiéndonos las
tablas en las que se basará la vista. Pulsamos sobre la tabla a añadir al diseño de la vista
y pulsamos el botón Agregar, podemos añadir así cuántas tablas queramos.
30
La aparición de estos paneles es configurable, en la barra de herramientas Diseñador
de vistas los iconos remarcados en azul son los correspondientes a cada panel:
31
Unidad 1. El entorno gráfico SSMS (XIV)
Nosotros, a lo largo del curso, crearemos las vistas desde el panel SQL que veremos
más adelante.
Conforme vamos marcando casillas de las tablas del panel diagrama, los cambios se
ven reflejados en los demás paneles excepto en el panel de resultados que se actualiza
ejecutando la consulta.
En cada fila de la rejilla se define una columna del resultado o una columna que se
utiliza para obtener el resultado.
En Columna tenemos el nombre de la columna de la se obtienen los datos o la
expresión cuando se trata de una columna calculada.
En Alias escribimos el nombre que tendrá la columna en la vista, también
corresponde con el encabezado de la columna en la rejilla de resultado. Si se deja el
campo en blanco, por defecto se asume el mismo nombre que hay en Columna.
En Tabla tenemos el nombre de la tabla del origen de la consulta a la que pertenece
la Columna, por ejemplo la primera columna del resultado se saca de la columna
32
Codigo de la tabla LIBROS y se llamará CodLibro. La cuarta columna de la vista
cogerá sus datos de la columna Usuario de la tabla Prestamos y se llamará Usuario
(Alias se ha dejado en blanco por lo que asume el nombre que hay en Columna.
En la columna Resultados indicamos si queremos que la columna se visualice o no,
las columnas con la casilla marcada se visualizan.
Las columnas Criterio de ordenación y Tipo de orden permiten ordenar las filas del
resultado según una o más columnas. Se ordena por las columnas que tienen algo
en Tipo de orden y cuando se ordena por varias columnas Criterio de ordenación
indica que primero se ordena por la columna que lleva el nº 1 y después por la
columna que lleva el nº 2 y así sucesivamente. En el ejemplo las filas del resultado
se ordenarán primero por código de libro y después por código de préstamo, todas
las filas dentro del mismo libro se ordenarán por código de préstamo.
En cada celda indicamos una condición que debe cumplir la columna correspondiente y
se puede combinar varias condiciones mediante O (OR) e Y (AND) según coloquemos las
condiciones en la misma columna o en columnas diferentes. En el ejemplo anterior
tenemos la condición compuesta: ((usuario=1) AND (Dias>5)) OR (Usuario=2).
También podemos Elimnar filas de la rejilla para eliminar columnas del resultado, lo
conseguimos seleccionando la fila haciendo clic sobre su extremo izquierda y cuando
aparece toda la fila remarcada pulsamos Supr o desde el menú contextual de la fila.
Podemos definir consultas más complejas como por ejemplo consultas de resumen,
pulsando sobre el botón Agrupar por de la barra de herramientas, se añade a la rejilla
una nueva columna Agrupar por con las siguientes opciones:
33
Unidad 1. El entorno gráfico SSMS (XV)
Por defecto el generador añade a la consulta una cláusula TOP (100) PERCENT que
indica que se visualizarán el 100% de las filas. Esta cláusula no la hemos definido
nosotros sino que la añade automáticamente el generador.
Una vez tenemos la vista definida la guardamos y podremos hacer con ella casi todo lo
que podemos hacer con una tabla. De hecho si nos fijamos en el Explorador de objetos,
en la carpeta Vistas:
Vemos que la estructura es muy similar a la estrutura de una tabla. Y que podemos
modificar su definición y ejecutarla, igual que con las tablas:
34
Unidad 2. Introducción al SQL. Transact-SQL (I)
Como ejemplos de sistemas gestores de bases de datos que utilizan SQL podemos
citar DB2, SQL Server, Oracle, MySql, Sybase, PostgreSQL o Access.
Ejercer un control sobre los datos tal como la asignación de privilegios de acceso a
los datos (GRANT/REVOKE).
La gestión de transacciones (COMMIT/ROLLBACK).
Una transacción se puede definir como un conjunto de acciones que se tienen que
realizar todas o ninguna para preservar la integridad de la base de datos.
Por ejemplo supongamos que tenemos una base de datos para las reservas de avión.
Cuando un usuario pide reservar una plaza en un determinado vuelo, el sistema tiene que
comprobar que queden plazas libres, si quedan plazas reservará la que quiera el usuario
generando un nuevo billete y marcando la plaza como ocupada. Aquí tenemos un proceso
que consta de dos operaciones de actualización de la base de datos (crear una nueva fila
en la tabla de billetes y actualizar la plaza reservada en el vuelo, poniéndola como
ocupada) estas dos operaciones se tienen que ejecutar o todas o ninguna, si después de
crear el billete no se actualiza la plaza porque se cae el sistema, por ejemplo, la base de
35
datos quedaría en un estado inconsistente ya que la plaza constaría como libre cuando
realmente habría un billete emitido para esta plaza. En este caso el sistema tiene el
mecanismo de transacciones para evitar este error. Las operaciones se incluyen las dos
en una misma transacción y así el sistema sabe que las tiene que ejecutar las dos, si por
lo que sea no se pueden ejecutar las dos, se encarga de deshacer los cambios que se
hubiesen producido para no ejecutar ninguna.
Las instrucciones que gestionan las autorizaciones serán utilizadas normalmente por el
administrador mientras que las otras, referentes a proceso de transacciones serán
utilizadas también por los programadores.
El DML se compone de las instrucciones para el manejo de los datos, para insertar
nuevos datos, modificar datos existentes, para eliminar datos y la más utilizada, para
recuperar datos de la base de datos. Veremos que una sola instrucción de recuperación
de datos es tan potente que permite recuperar datos de varias tablas a la vez, realizar
cálculos sobre estos datos y obtener resúmenes.
El DML interactúa con el nivel externo de la base de datos por lo que sus instrucciones
son muy parecidas, por no decir casi idénticas, de un sistema a otro, el usuario sólo indica
lo que quiere recuperar no cómo se tiene que recuperar, no influye el cómo están
almacenados los datos.
A lo largo del curso se explicarán cada una de las formas de explotación de la base de
datos. Dependiendo de tu perfil profesional (programador o administrador) o de tu interés
personal te resultará más útil un bloque u otro.
TRANSACT-SQL es un lenguaje muy potente que nos permite definir casi cualquier
tarea que queramos efectuar sobre la base de datos. En este tema veremos que
TRANSACT-SQL va más allá de un lenguaje SQL cualquiera ya que incluye
características propias de cualquier lenguaje de programación, características que nos
permiten definir la lógica necesaria para el tratamiento de la información:
Tipos de datos.
Definición de variables.
Estructuras de control de flujo.
Gestión de excepciones.
Funciones predefinidas.
36
Puede ser utilizado como cualquier SQL como lenguaje embebido en aplicaciones
desarrolladas en otros lenguajes de programación como Visual Basic, C, Java, etc. Y por
supuesto los lenguajes incluidos en la plataforma .NET.
Por ejemplo:
Esta instrucción nos permite SELECCIONAR el código y nombre DE los Clientes CUYA
localidad sea Valencia.
Si sabemos algo de inglés nos será más fácil interpretar a la primera lo que quiere decir
la instrucción, y de lo contrario, como el número de palabras que se emplean es muy
reducido, enseguida nos las aprenderemos.
CREATE (Crear)
DROP (Eliminar)
ALTER (Modificar)
Permite crear una base de datos llamada mibase, a continuación escribiremos las
demás cláusulas que completarán la acción, en este caso dónde se almacenará la base
de datos, cuánto ocupará, etc...
37
Permite crear una nueva tabla llamada mitabla, entre paréntesis completaremos la
acción indicando la definición de las columnas de la tabla.
Por ejemplo:
En esta sentencia nos aparecen dos cláusulas, la cláusula FROM que nos permite
indicar de dónde hay que coger los datos y la cláusula WHERE que permite indicar una
condición de selección.
Otra característica de una sentencia SQL es que acaba con un punto y coma (;)
originalmente éste era obligatorio y servía para indicar el fin de la instrucción, pero ahora
se puede omitir, aunque se recomienda su uso.
Las palabras reservadas no se pueden utilizar para otro propósito, por ejemplo una
tabla no se puede llamar FROM, y los nombres (los identificadores) siguen las reglas
detalladas en el punto siguiente.
38
Nombres cualificados. En ocasiones deberemos utilizar nombres cualificados, por
ejemplo cuando se escribe un nombre de tabla, SQL presupone que se está refiriendo a
una de las tablas de la base de datos activa, si queremos hacer referencia a una tabla de
otra base de datos utilizamos su nombre cualificado
nombrebasedatos.nombredeesquema.nombretabla, utilizamos el punto para separar el
nombre del objeto y el nombre de su contenedor.
O por ejemplo si en una consulta cuyo origen son dos tablas, queremos hacer
referencia a un campo y ese nombre de campo es un nombre de campo en las dos tablas,
pues utilizaremos su nombre cualificado nombretabla.nombrecampo.
El valor NULL.
Puesto que una base de datos es un modelo de una situación del mundo real, ciertos
datos pueden inevitablemente faltar, ser desconocidos o no ser aplicables, esto se debe
de indicar de alguna manera especial para no confundirlo con un valor conocido pero que
sea cero por ejemplo, SQL tiene para tal efecto el valor NULL que indica precisamente la
ausencia de valor.
Por ejemplo: no es lo mismo que el alumno no tenga nota a que tenga la nota cero, esto
afectaría también a todos los cálculos que se pueden realizar sobre la columna nota.
39
No se permiten los caracteres especiales o los espacios incrustados.
Si queremos utilizar un nombre que no siga estas reglas, normalmente para poder
incluir espacios en blanco, lo tenemos que escribir encerrado entre corchetes [ ] (también
se pueden utilizar las comillas pero recomendamos utilizar los corchetes).
SQL Server proporciona un conjunto de tipos de datos del sistema que define todos los
tipos de datos que pueden utilizarse. También podemos definir nuestros propios tipos de
datos en Transact-SQL o Microsoft .NET Framework.
Si quieres conocer todos los tipos de datos disponibles en SQLServer 2005, visita el
siguiente avanzado .
Para indicar valores negativos y positivos añadimos el prefijo + o - según sea el valor
positivo o negativo. Sin prefijo se entiende que el valor es positivo.
Si quieres ver cómo definir constantes para otros tipos de datos, visita el siguiente
avanzado (arriba).
40
Una expresión es una combinación de símbolos y operadores que el motor de base de
datos de SQL Server evalúa para obtener un único valor. Una expresión simple puede ser
una sola constante, variable, columna o función escalar. Los operadores se pueden usar
para combinar dos o más expresiones simples y formar una expresión compleja.
Tipos de operadores:
- Operadores numéricos:
suma +
resta -
multiplicación *
división /
módulo
%
(resto de una división)
AND &
OR |
OR exclusivo ^
- Operadores de comparación:
Igual a =
41
Menor o igual que <=
Distinto de <>
No es igual a !=
- Operadores lógicos:
Aquí sólo los nombraremos ya que en el tema de consultas simples los veremos en
detalle.
ALL IN
AND LIKE
ANY NOT
BETWEEN OR
EXISTS SOME
- Operadores de cadenas:
Concatenación +
Resultados de la expresión
2.8. Funciones
42
SQL Server 2005 proporciona numerosas funciones integradas y permite crear
funciones definidas por el usuario.
Las variables
En Transact-SQL podemos definir variables, que serán de un tipo de datos
determinado, como tipos de datos podemos utilizar los propios de la base de datos SQL-
SERVER, pero también podemos utilizar tipos propios del lenguaje que no pueden ser
utilizados en DDL. El tipo Cursor y el tipo Table son dos de estos tipos.
El nombre de la variable debe empezar por el símbolo @, este símbolo hace que SQL
interprete el nombre como un nombre de variable y no un nombre de objeto de la base de
datos.
Para asignar un valor a una variable, la asignación se realiza con la palabra SELECT y
el signo igual con el formato:
El valor puede ser cualquier valor constante, otro nombre de variable, una expresión
válida o algo más potente, parte de una sentencia SELECT de SQL.
Por ejemplo:
SELECT @empleados = 0;
43
El valor almacenado en la variable se visualizará en la pestaña de resultados. También
se puede usar para escribir mensajes:
/* Esto es un comentario
/* */ Varias líneas
en varias líneas */
USE nbBaseDeDatos
Hace que la base de datos activa pase a ser la base de datos indicada en la instrucción,
las consultas que se ejecuten a continuación se harán sobre tablas de esa base de datos
si no se indica lo contrario. Es una instrucción útil para asegurarnos de que la consulta se
ejecuta sobre la base de datos correcta.
GO
BEGIN...END
3.1. Introducción
Vamos a empezar por la instrucción que más se utiliza en SQL, la sentencia SELECT.
La sentencia SELECT es, con diferencia, la más compleja y potente de las sentencias
SQL, con ella podemos recuperar datos de una o más tablas, seleccionar ciertos registros
e incluso obtener resúmenes de los datos almacenados en la base de datos. Es tan
compleja que la estudiaremos a lo largo de varias unidades didácticas incorporando poco
a poco nuevas funcionalidades.
44
El resultado de una SELECT es una tabla lógica que alberga las filas resultantes de la
ejecución de la sentencia.
Empezaremos por ver las consultas más simples, basadas en una sola tabla y nos
limitaremos a la siguiente sintaxis:
SELECT [ALL|DISTINCT]
45
[TOP expresion [PERCENT] [WITH TIES]]
<lista_seleccion>
FROM <origen>
[WHERE <condicion_busqueda> ]
[ORDER BY {expression_columna|posicion_columna [ASC|DESC]} [
,...n ]]
<origen>::=
nb_tabla | nb_vista [[ AS ] alias_tabla ]
Tanto para las tablas como para las vistas, podemos hacer referencia a tablas que
están en otras bases de datos (siempre que tengamos los permisos adecuados), en este
caso tenemos que cualificar el nombre de la tabla, indicando delante el nombre de la base
de datos (Lógica) y el nombre del esquema al que pertenece la tabla dentro de la base de
datos.
Cuando no se definen esquemas, SQL-Server crea uno por defecto en cada base de
datos denominado dbo.
Se utilizan los nombres de alias para simplificar los nombres de tablas a veces largos y
también cuando queremos combinar una tabla consigo misma; ya volveremos sobre los
alias de tabla cuando veamos consultas multitabla.
Podemos escribir:
SELECT ...
FROM tabla1 Sacamos los datos de la tabla tabla1
46
SELECT ...
FROM tabla1 t1 Sacamos los datos de la tabla tabla1 y le
asignamos un alias de tabla: t1
SELECT ...
FROM tabla1 AS t1 Es equivalente a la sentencia anterior.
Si la tabla o la vista están en otra base de datos del mismo equipo que está ejecutando
la instancia de SQL Server, se utiliza el nombre cualificado con el formato
nbBaseDatos.nbEsquema.nbTabla.
Si la tabla o la vista están fuera del servidor local en un servidor vinculado, se utiliza un
nombre de cuatro partes con el formato nbservidor.catalogo.nbEsquema.nbTabla.
Volveremos más adelante sobre las conexiones remotas.
<lista_seleccion> ::=
{ *
| {nombre_tabla|nombre_vista|alias_tabla}.*
| { [{nombre_tabla|nombre_vista|alias_tabla}.]
{nb_columna|$IDENTITY|$ROWGUID}
|<expresion>
}[[AS] alias_columna]
| alias_columna = <expresion>
} [ ,...n ]
Separamos la definición de cada columna por una coma y las columnas del resultado
aparecerán en el mismo orden que en la lista de selección.
Para cada columna del resultado su tipo de datos, tamaño, precisión y escala son los
mismos que los de la expresión que da origen a esa columna.
47
Una subconsulta escalar, que es otra instrucción SELECT que devuelve un único
valor y se evalúa para cada fila del origen de datos (esto no lo veremos de
momento).
Una expresión compleja generada al usar operadores en una o más expresiones
simples.
La palabra clave *.
La asignación de variables con el formato @variable_local = expresión.
La palabra clave $IDENTITY.
La palabra clave $ROWGUID.
El resultado sería:
48
Juan Victor NULL 1990-01-13
El resultado sería:
El alias de columna se indica mediante la cláusula AS. Se escribe el nuevo texto tal cual
sin comillas siguiendo las reglas de los identificadores.
Ejemplo:
49
El resultado será :
Numclie nombrecliente
en vez de:
Numclie nombre
La palabra AS es opcional.
3.6. Funciones
Existen funciones que podemos utilizar en la lista de selección, e incluso en otras
cláusulas que veremos más adelante, como el WHERE. Las principales funciones son las
siguientes:
Funciones de fecha:
Ver
Función Descripción
+
50
GETUTCDATE Devuelve la hora UTC.
Devuelve un valor datetime nuevo que resulta de sumar un intervalo de tiempo a una
DATEADD
fecha especificada.>
SET
Establece el primer día de la semana en un número del 1 al 7.
DATEFIRST
Ver
Función Descripción
+
Devuelve el valor de código ASCII del carácter situado más a la izquierda de una expresión
ASCII
de caracteres.
LEN Devuelve el total de caracteres de una cadena, excluidos los espacios en blanco finales.
LTRIM Devuelve una cadena tras quitarle los espacios en blanco iniciales.
RTRIM Devuelve una cadena tras quitarle los espacios en blanco finales.
51
LEFT Devuelve los N últimos caracteres de una cadena.
Devuelve una cadena Unicode con los delimitadores agregados para convertirla en un
QUOTENAME
identificador delimitado válido de Microsoft SQL Server 2005.
Otras funciones:
Ver
Función Descripción
+
52
Unidad 3. Consultas simples (V)
La expresión puede contener cualquier operador válido (+, -, *, /, &…), cualquier función
válida, nombres de columnas del origen de datos, nombres de parámetros o constantes y
para combinar varias operaciones se pueden utilizar los paréntesis.
El resultado será:
SELECT idfab,idproducto,descripcion,(existencias*precio) AS
valoracion
FROM productos;
53
El resultado sería:
El resultado será:
Listar las ventas en cada oficina con el formato: 22 tiene ventas de 186,042.00 €
54
El resultado sería:
oficina ventas
El incluir una constante como columna en la lista de selección puede parecer inútil (se
repetirá el mismo valor en todas las filas) pero veremos más adelante que tiene utilidad en
ciertos casos.
alias_columna = <expresion>
Ejemplo:
SELECT *
55
FROM oficinas;
SELECT oficinas.*
FROM oficinas;
Esta forma se utiliza normalmente cuando el origen está basado en varias tablas y
queremos indicar todas las columnas no del origen completo sino de una tabla concreta.
Es equivalente a:
56
Podemos indicar una columna o varias separadas por una coma, la columna de
ordenación se especifica mediante el nombre de columna en el origen de datos o su
posición dentro de la lista de selección. Si utilizamos el nombre de columna, no hace falta
que la columna aparezca en la lista de selección. Si utilizamos la posición es la posición
de la columna dentro de la lista de selección empezando en 1.
Ejemplos:
Mostrar las ventas de cada oficina, ordenadas por orden alfabético de región y dentro
de cada región por ciudad.
Da como resultado:
Listar las oficinas de manera que las oficinas de mayores ventas aparezcan en primer
lugar.
57
FROM oficinas
ORDER BY ventas DESC;
Resultado:
58
este Castellón 1800,00
En este caso hemos utilizado el alias de columna para hacer referencia a la columna
calculada y también se puede observar que las filas aparecen ordenadas por región
ascendente (no hemos incluido nada después del nombre de la columna) y dentro de cada
región por superávit y descendente.
SELECT dir
FROM oficinas;
dir
106
104
105
108
108
108
108
59
NULL
NULL
Si un mismo empleado dirige varias oficinas (por ejemplo el 108), su código aparece
repetido en el resultado. Para evitarlo modificamos la consulta:
dir
NULL
104
105
106
108
Los que se eliminan son valores duplicados de filas del resultado, por ejemplo:
dir region
NULL este
NULL norte
104 este
105 este
106 este
108 centro
108 oeste
Ahora el 108 aparece dos veces porque las dos filas donde aparece no son iguales
(porque tienen distinta región).
60
NOTA: La cláusula DISTINCT hace que la consulta tarde algo más en ejecutarse
debido al proceso adicional de buscar y eliminar las repeticiones, por lo que se aconseja
utilizarla únicamente cuando sea imprescindible.
La cláusula TOP indica que en el resultado no deben aparecer todas las filas
resultantes sino un cierto número de registros, las n primeras. Si la consulta incluye la
cláusula ORDER BY, se realiza la ordenación antes de extraer los n primeros registros.
La expresión representa ese número n y debe devolver un número entero sin signo.
61
SELECT TOP 3 * FROM productos
ORDER BY ventas
Si existen más registros con las mismas ventas que el último valor de la lista, éstos no
saldrán en el resultado de la consulta.
En el ejemplo el registro con cod = 2 no sale en el resultado y tiene las mismas ventas
que cod = 3.
Si queremos que salgan añadimos la cláusula WITH TIES. La cláusula WITH TIES sólo
se puede emplear si la SELECT incluye un ORDER BY, de lo contrario dará error.
Obtenemos:
Se incluyen en el resultado todos los registros que tienen ventas iguales al último
registro.
Otro ejemplo:
Devuelve las 10 peores oficinas en cuanto a ventas: ordenamos las oficinas por ventas
de menor a mayor y sacamos las 10 primeras.
62
SELECT TOP 50 PERCENT * FROM productos ORDER BY ventas
Devuelve:
WHERE <condicion_búsqueda>
<condicion_búsqueda> ::=
{ [NOT]<predicado>
|(<condicion_búsqueda>)
}
[{AND|OR} [NOT] {<predicado>|(<condicion_búsqueda>)}]
[ ...n ]
En el resultado de la consulta sólo aparecerán las filas que cumplan que la condición de
búsqueda sea TRUE, los valores NULL no se incluyen, por lo tanto, en las filas del
resultado. La condición de búsqueda puede ser una condición simple o una condición
compuesta por varias condiciones (predicados) unidas por operadores AND y OR, no hay
límite en cuanto al número de predicados que se pueden incluir. En las condiciones
compuestas se pueden utilizar paréntesis para delimitar predicados y se aconseja su uso
cuando se incluyen operadores AND y OR en la misma condición de búsqueda.
3.14. Predicados
En SQL tenemos 7 tipos de predicados, condiciones básicas de búsqueda:
Comparación estándar
Pertenencia a un intervalo (BETWEEN)
Pertenencia a un conjunto (IN)
Test de valor nulo (IS NULL).
Coincidencia con patrón (LIKE)
Si contiene (CONTAINS)
63
FREETEXT
Comparación estándar.
Compara el valor de una expresión con el valor de otra. Para la comparación se pueden
emplear = , <> , !=, < , <= , !<, > , >= ,!>
Sintaxis:
Un nombre de columna,
una constante,
una función (inclusive la función CASE),
una variable,
una subconsulta escalar o
cualquier combinación de nombres de columna, constantes y funciones conectados
mediante uno o varios operadores o una subconsulta.
Ejemplo:
Las columnas que aparecen en el WHERE no tienen por qué aparecer en la lista de
selección, esta instrucción es igual de válida:
64
SELECT numemp, nombre FROM empleados
WHERE ventas > cuota;
Hallar oficinas cuyas ventas estén por debajo del 80% de su objetivo:
SELECT oficina
FROM oficinas
WHERE ventas < (.8 * objetivo);
SELECT oficina
FROM oficinas
WHERE dir = 108;
65
Unidad 3. Consultas simples (XII)
Pertenencia a un intervalo. BETWEEN
Examina si el valor de la expresión de test está en el rango delimitado por los valores
resultantes de expresion1 y expresion2, estos valores no tienen por qué estar ordenados
en ANSI/ISO; expresion1 debe ser menor o igual a expresion2.
66
Examina si el valor de la expresion es uno de los valores incluidos en la lista de valores
indicados entre paréntesis. Se pueden expresar los valores mediante cualquier expresión,
la única condición es que todas las exp_valor devuelvan el mismo tipo de datos.
Ejemplo:
Para practicar puedes realizar este Ejercicio Pertenencia a un conjunto con IN.
Una condición de búsqueda puede ser TRUE, FALSE o NULL/UNKNOW, este último
caso se produce cuando algún campo que interviene en la condición tiene valor NULL.
A veces es útil comprobar explícitamente los valores NULL en una condición de búsqueda
ya que estas filas puede que queramos darles un tratamiento especial, para ello tenemos
el predicado IS NULL.
Este test produce un valor TRUE o FALSE, por lo que se podrá combinar con otras
condiciones. El valor NULL no es en sí un valor por eso no lo podemos utilizar en una
igualdad.
Esta instrucción no da error pero no obtiene lo que en principio parece que quiere
obtener. No obtenemos los empleados cuya oficina sea un valor nulo (es decir los
empleados que no tienen oficina), no obtenemos nada, en cambio los obtendremos
utilizando el test de valor nulo:
Resultado:
67
numemp nombre oficina
Se utiliza cuando queremos comparar el valor de una columna con un patrón en el que
se utilice caracteres comodines.
68
SELECT numemp,nombre
FROM empleados
WHERE nombre LIKE ‘An%’;
numemp nombre
SELECT numemp,nombre
FROM empleados
WHERE nombre LIKE ‘%z’;
numemp nombre
SELECT numemp,nombre
FROM empleados
WHERE nombre LIKE ‘%on%’;
numemp nombre
SELECT numemp,nombre
FROM empleados
WHERE nombre LIKE '__a%';
69
numemp nombre
Obtiene los nombres cuya tercera letra sea una a (en el patrón tenemos dos caracteres
subrayado).
SELECT numemp,nombre
FROM empleados
WHERE nombre LIKE '[a-d]%';
Es equivalente a escribir:
SELECT numemp,nombre
FROM empleados
WHERE nombre LIKE '[abcd]%';
SELECT numemp,nombre
FROM empleados
WHERE nombre LIKE '[^abcd]%';
SELECT numemp,nombre
FROM empleados
WHERE nombre LIKE '[^a-d]%';
70
[ESCAPE 'car_escape']
Por ejemplo queremos buscar los nombres compuestos que incluyen un subrayado. En
este caso tenemos que poner el carácter _ como un carácter normal no como un comodín,
así que lo escribiremos así:
SELECT numemp,nombre
FROM empleados
WHERE nombre LIKE '%[_]%';
O bien,
SELECT numemp,nombre
FROM empleados
WHERE nombre LIKE '%!_%' ESCAPE '!';
Como siempre, se pueden utilizar paréntesis para alterar esta prioridad en una
condición de búsqueda.
Los operadores lógicos pueden devolver tres valores distintos: TRUE, FALSE, NULL
(UNKNOWN).
AND Combina dos condiciones y se evalúa como TRUE cuando ambas condiciones
son TRUE.
71
TRUE TRUE FALSE NULL
Hallar los vendedores que están por debajo de su cuota y tienen ventas inferiores a
30.000.
SELECT nombre
FROM empleados
WHERE ventas < cuota AND ventas < 30000;
Hallar los vendedores que están debajo de su cuota, pero cuyas ventas no sean
inferiores a 150.000.
SELECT nombre
FROM empleados
WHERE ventas < cuota AND ventas < 150000;
SELECT oficina
FROM oficinas
WHERE NOT dir = 108;
72
SELECT oficina
FROM oficinas
WHERE dir <> 108;
Devuelven:
oficina
11
12
13
Las oficinas sin director no aparecen, para que aparezcan deben añadir otro predicado:
oficina dir
11 106
12 104
13 105
26 NULL
28 NULL
4.1. Introducción
Hasta ahora hemos visto consultas que obtienen los datos de una sola tabla, en este
tema veremos cómo obtener datos de diferentes tablas.
Para obtener datos de varias tablas tenemos que combinar estas tablas mediante
alguna operación basada en el álgebra relacional.
73
El álgebra relacional define una serie de operaciones cuyos operandos son tablas y
cuyo resultado es también una tabla.
La unión UNION
La diferencia EXCEPT
La intersección INTERSECT
El producto cartesiano CROSS JOIN
La composición interna INNER JOIN
La composición externa LEFT JOIN, RIGHT JOIN Y FULL JOIN
En todo el tema cuando hablemos de tablas nos referiremos tanto a las tablas que
físicamente están almacenadas en la base de datos como a las tablas temporales y a las
resultantes de una consulta o vista.
Para poder realizar la operación, las dos tablas tienen que tener el mismo esquema
(mismo número de columnas y tipos compatibles) y la tabla resultante hereda los
encabezados de la primera tabla.
La sintaxis es la siguiente:
< consulta > representa la especificación de la consulta que nos devolverá la tabla a
combinar.
Puede ser cualquier especificación de consulta con la limitación de que no admite la
cláusula ORDER BY, los alias de campo se pueden definir pero sólo tienen efecto cuando
se indican en la primera consulta ya que el resultado toma los nombres de columna de
esta.
Ejemplo: Suponemos que tenemos una tabla Valencia con las nuevas oficinas de
Valencia y otra tabla Madrid con las nuevas oficinas de Madrid y queremos obtener una
tabla con las nuevas oficinas de las dos ciudades:
74
El resultado sería:
OFI ciudad
11 Valencia
28 Valencia
23 Madrid
OFI ciudad
11 Valencia
23 Madrid
28 Valencia
Ahora las filas aparecen ordenadas por el número de oficina y hemos utilizado el
nombre de columna de la primera consulta.
Cuando aparezcan en el resultado varias filas iguales, el sistema por defecto elimina las
repeticiones.
Si se especifica ALL, el sistema devuelve todas las filas resultante de la unión incluidas las
repetidas
El empleo de ALL también hace que la consulta se ejecute más rápidamente ya que el
sistema no tiene que eliminar las repeticiones.
Se pueden combinar varias tablas con el operador UNION. Por ejemplo supongamos
que tenemos otra tabla Pamplona con las oficinas nuevas de Pamplona:
75
Combinamos las tres tablas.
Otro ejemplo:
Obtener todos los productos cuyo precio exceda de 20 € o que se haya vendido más de
300 euros del producto en algún pedido.
{<consulta>|(<consulta>)}
EXCEPT
{<consulta>|(<consulta>)}
[{EXCEPT {<consulta>|(<consulta>)}}[ ...n ] ]
[ORDER BY {expression_columna|posicion_columna [ASC|DESC]}
[ ,...n ]]
T1 T2
Cod
Codigo
1
2
2
3
4
4
5
5
6
76
SELECT cod FROM T1
EXCEPT
SELECT codigo FROM T2;
Devuelve:
Cod
Ejemplo:
Listar los productos que no aparezcan en ningún pedido.
{ <consulta>|(<consulta>)}
INTERSECT
{<especificacion_consulta>|(<especificacion_consulta>)}
[{INTERSECT {<consulta>|(<consulta>)}} [ ...n ] ]
[ORDER BY {expression_columna|posicion_columna [ASC|DESC]}
[ ,...n ]]
Devuelve:
77
Cod
Ejemplo: Obtener todos los productos que valen más de 20 euros y que además se
haya vendido en un pedido más de 300 euros de ese producto.
La sentencia SELECT permite realizar esta composición, incluyendo dos o más tablas
en la cláusula FROM.
Empezaremos por estudiar la operación a partir de la cual están definidas las demás
operaciones de composición de tabla, el producto cartesiano.
78
|<tabla_origen> CROSS JOIN <tabla_origen>
Tabla_origen puede ser un nombre de tabla o de vista o una tabla derivada (resultado
de una SELECT), en este último caso la SELECT tiene que aparecer entre paréntesis y la
tabla derivada debe llevar asociado obligatoriamente un alias de tabla. También puede ser
una composición de tablas.
Se pueden utilizar hasta 256 orígenes de tabla en una instrucción, aunque el límite varía
en función de la memoria disponible y de la complejidad del resto de las expresiones de la
consulta. También se puede especificar una variable table como un origen de tabla.
Ejemplo:
SELECT *
FROM empleados, oficinas;
Si ejecutamos esta consulta veremos que las filas del resultado están formadas por las
columnas de empleados y las columnas de oficinas. En las filas aparece cada empleado
combinado con la primera oficina, luego los mismos empleados combinados con la
segunda oficina y así hasta combinar todos los empleados con todas las oficinas.
Si ejecutamos:
SELECT *
FROM empleados CROSS JOIN oficinas;
Obtenemos lo mismo.
Este tipo de operación no es la que se utiliza más a menudo, lo más frecuente sería
combinar cada empleado con los datos de SU oficina. Lo podríamos obtener añadiendo a
la consulta un WHERE para filtrar los registros correctos:
SELECT *
FROM empleados, oficinas
WHERE empleados.oficina=oficinas.oficina;
Aquí nos ha aparecido la necesidad de cualificar los campos ya que el nombre oficina
es un campo de empleados y de oficinas por lo que si no lo cualificamos, el sistema nos
da error.
Hemos utilizado en la lista de selección *, esto nos recupera todas las columnas de las
dos tablas.
Recupera todas las columnas de empleados y las columnas ciudad y región de oficinas.
También podemos combinar una tabla consigo misma, pero en este caso hay que
definir un alias de tabla, en al menos una, sino el sistema da error ya que no puede
nombrar los campos.
79
SELECT *
FROM oficinas, oficinas as ofi2;
FROM
<tabla_origen> INNER JOIN <tabla_origen> ON <condicion_combi>
SELECT *
FROM empleados INNER JOIN oficinas
ON empleados.oficina=oficinas.oficina;
SELECT *
FROM pedidos INNER JOIN productos
ON producto = idproducto AND fab = idfab;
Es fácil ver la utilidad de esta instrucción y de hecho se utilizará muy a menudo, pero
hay algún caso que no resuelve. En las consultas anteriores, no aparecen las filas que no
tienen fila correspondiente en la otra tabla.
80
FROM empleados INNER JOIN oficinas
ON empleados.oficina=oficinas.oficina;
No aparecen los empleados que no tienen oficina, ni las oficinas que no tienen
empleados, porque para que salga la fila, debe de existir una fila de la otra tabla que
cumpla la condición.
Para resolver este problema debemos utilizar otro tipo de composición, la composición
externa.
Para practicar puedes realizar este Ejercicio La composición interna INNER JOIN.
Sintaxis
FROM
<tabla_origen> {LEFT|RIGHT|FULL} [OUTER] JOIN <tabla_origen>
ON <condicion_combi>
81
La palabra OUTER es opcional y no añade ninguna función.
Las palabras LEFT, RIGHT y FULL indican la tabla de la cual se van a añadir las filas sin
correspondencia.
Obtiene los empleados con su oficina y los empleados (tabla a la izquierda LEFT del
JOIN) que no tienen oficina aparecerán también en el resultado con los campos de la tabla
oficinas rellenados a NULL.
82
103 Juan Rovira 12 Alicante 12
Obtiene los empleados con su oficina y las oficinas (tabla a la derecha RIGHT del JOIN)
que no tienen empleados aparecerán también en el resultado con los campos de la tabla
empleados rellenados a NULL.
83
109 María Sunta 11 Valencia 11
Aparecen tanto los empleados sin oficina como las oficinas sin empleados.
NOTA: Cuando necesitamos obtener filas con datos de dos tablas con una condición de
combinación utilizaremos un JOIN, os aconsejo empezar por escribir el JOIN con la
condición que sea necesaria para combinar las filas, y luego plantearos si la composición
debe de ser interna o externa. Para este segundo paso ésta sería la norma a seguir:
Si pueden haber filas de la primera tabla que no estén relacionadas con filas de la
segunda tabla y nos interesa que salgan en el resultado, entonces cambiamos a
LEFT JOIN.
Si pueden haber filas de la segunda tabla que no estén relacionadas con filas de la
primera tabla y nos interesa que salgan en el resultado, entonces cambiamos a
RIGHT JOIN.
Si necesitamos LEFT y RIGHT entonces utilizamos FULL JOIN.
¿Pueden haber empleados que no tengan oficina y nos interesan?, si es que sí,
necesitamos un LEFT JOIN.
Seguiríamos preguntando:
¿Pueden haber oficinas que no tengan empleados y nos interesan?, si es que sí,
necesitamos un RIGHT JOIN.
84
Unidad 4. Consultas multitabla (VI)
Por ejemplo:
O bien:
5.1. Introducción
Una de las funcionalidades de la sentencia SELECT es el permitir obtener resúmenes
de los datos contenidos en las columnas de las tablas.
Para poder llevarlo a cabo la sentencia SELECT consta de una serie de cláusulas
específicas (GROUP BY, HAVING), y Transact-SQL tiene definidas unas funciones para
poder realizar estos cálculos, las funciones de agregado (también llamadas funciones de
columna).
La diferencia entre una consulta de resumen y una consulta de las que hemos visto hasta
ahora es que en las consultas normales las filas del resultado se obtienen directamente de
las filas del origen de datos y cada dato que aparece en el resultado tiene su dato
correspondiente en el origen de la consulta mientras que las filas generadas por las
consultas de resumen no representan datos del origen sino un total calculado sobre estos
datos. Esta diferencia hará que las consultas de resumen tengan algunas limitaciones que
veremos a lo largo del tema.
Un ejemplo sería:
85
A la izquierda tenemos una consulta simple que nos saca las oficinas con sus ventas
ordenadas por región, y a la derecha una consulta de resumen que obtiene la suma de las
ventas de las oficinas de cada región
La palabra ALL indica que se tiene que tomar en cuenta todos los valores de la
columna. Es el valor por defecto.
La palabra DISTINCT hace que se consideren todas las repeticiones del mismo valor
como uno sólo (considera valores distintos).
Todas las funciones de agregado se aplican a las filas del origen de datos una vez
ejecutada la cláusula WHERE (si la hubiera).
86
Una función de agregado puede aparecer en la lista de selección en cualquier lugar en
el que puede aparecer un nombre de columna. Puede, por ejemplo, formar parte de una
expresión pero no se pueden anidar funciones de agregado.
Tampoco se pueden mezclar funciones de columna con nombres de columna ordinarios.
Hay excepciones a esta regla pero cuando definimos agrupaciones y subconsultas que
veremos más adelante.
Expresion puede ser de cualquier tipo excepto text, image o ntext. No se permite
utilizar funciones de agregado ni subconsultas. El tipo de dato devuelto es int.
Si el número de valores devueltos por expresion es superior a 231-1, COUNT genera un
error, en ese caso hay que utilizar la función COUNT_BIG.
La función cuenta los valores distintos de NULL que hay en la columna. La palabra ALL
indica que se tienen que tomar todos los valores de la columna, mientras que DISTINCT
hace que se consideren todas las repeticiones del mismo valor como uno solo. Estos
parámetros son opcionales, por defecto se considera ALL.
Por ejemplo:
Devuelve 4 porque tenemos cuatro valores distintos, no nulos, en la columna región, los
valores repetidos los considera sólo una vez. Ahora sí nos devuelve cuántas regiones
tenemos en oficinas.
Si utilizamos * en vez de expresión, devuelve el número de filas del origen que nos
quedan después de ejecutar la cláusula WHERE.
87
Si tenemos un COUNT(columna) y columna no contiene valores nulos, se obtiene el
mismo resultado que COUNT(*) pero el COUNT(*) es más rápido por lo que en este caso
hay que utilizarlo en vez de COUNT(columna).
Por ejemplo:
Es mejor que:
Las dos nos devuelven el número de empleados que tienen una oficina asignada pero
la primera es mejor porque se calcula más rápidamente.
88
Devuelve el valor mínimo de la expresión sin considerar los nulos.
MIN se puede usar con columnas numéricas, de caracteres y de datetime, pero no con
columnas de bit. No se permiten funciones de agregado ni subconsultas.
Utilizar DISTINCT no tiene ningún sentido con MIN (el valor mínimo será el mismo si
consideramos las repeticiones o no) y sólo se incluye para la compatibilidad con SQL-92.
El resultado será del mismo tipo aunque puede tener una precisión mayor.
Devuelve la suma de las ventas de todas las oficinas y de los objetivos de todas las
oficinas, el de mayor importe.
89
Devuelve la varianza estadística de todos los valores de la expresión especificada.
VAR sólo se puede utilizar con columnas numéricas. Los valores NULL se pasan por alto.
GROUPING (nb_columna)
Es una función de agregado que genera como salida una columna adicional con el valor
1 si la fila se agrega mediante el operador CUBE o ROLLUP, o el valor 0 cuando la fila no
es el resultado de CUBE o ROLLUP.
Nb_columna tiene que ser una de las columnas de agrupación y la cláusula GROUP BY
debe contener el operador CUBE o ROLLUP.
En el siguiente punto, cuando veamos las cláusulas CUBE y ROLLUP quedará más
claro.
Muchas veces cuando calculamos resúmenes nos interesan totales parciales, por
ejemplo saber de cada empleado cuánto ha vendido, y cuál ha sido su pedido máximo, de
cada cliente cuándo fue la última vez que nos compró, etc.
90
En todos estos casos en vez de obtener una fila única de resultados necesitamos una
fila por cada empleado, cliente, etc.
Una consulta con una cláusula GROUP BY agrupa los datos de la tabla origen y
produce una única fila resultado por cada grupo formado. Las columnas indicadas en el
GROUP BY se llaman columnas de agrupación o agrupamiento .
Cuando queremos realizar una agrupación múltiple, por varias columnas, éstas se
indican en la cláusula GROUP BY en el orden de mayor a menor agrupación igual que con
la cláusula ORDER BY.
expresion_agrupacion puede ser una columna o una expresión no agregada que haga
referencia a una columna devuelta por la cláusula FROM. Un alias de columna que esté
definido en la lista de selección no puede utilizarse para especificar una columna de
agrupamiento.
No se pueden utilizar columnas de tipo text, ntext e image en expresion_agrupacion.
Ejemplo:
Resultado:
NULL 2
11 2
12 3
13 1
21 2
91
22 1
Hay empleados sin oficinas (con oficina a nulo), estos forman un grupo con el valor
NULL en oficina, en este caso hay dos empleados así.
Ejemplo:
Resultado:
Número Importe
rep clie
de pedidos máximo
92
Unidad 5. Consultas de resumen (V)
Hemos dicho que los resúmenes se calculan sobre todas las filas del origen después de
haber ejecutado el WHERE, pues ALL permite obtener un resumen de las filas que no
cumplen el WHERE.
ALL Incluye todos los grupos y conjuntos de resultados, incluso aquellos en los que no
hay filas que cumplan la condición de búsqueda especificada en la cláusula WHERE.
Cuando se especifica ALL, se devuelven valores NULL para las columnas de resumen de
los grupos que no cumplen la condición de búsqueda. No se puede especificar ALL con
los operadores CUBE y ROLLUP.
Resultado:
Número Importe
rep clie
de pedidos máximo
93
107 2109 1 313,50
Cuál ha sido el efecto de añadir ALL? Se han añadido filas para las filas del origen que
no cumplen la condición del WHERE pero sin que intervengan en el cálculo de las
funciones de agregado.
Por ejemplo el representante 101 tiene pedidos con el cliente 2102 pero estos pedidos
no son del año 1997, por eso aparece la primera fila (no estaba en el resultado de la otra
consulta) pero con 0 y NULL como resultados de las funciones de agregado.
ROLLUP especifica que, además de las filas que normalmente proporciona GROUP
BY, se incluyen filas de resumen en el conjunto de resultados. Los grupos se resumen en
un orden jerárquico, desde el nivel inferior del grupo hasta el superior. La jerarquía del
grupo se determina por el orden en que se especifican las columnas de agrupamiento.
Cambiar el orden de las columnas de agrupamiento puede afectar al número de filas
generadas en el conjunto de resultados.
Por ejemplo:
Resultado:
Número Importe
rep clie
de pedidos máximo
94
102 NULL 3 37,50
Por ejemplo:
95
FROM pedidos
WHERE YEAR(fechapedido) = 1997
GROUP BY rep, clie WITH CUBE;
Resultado:
Número Importe
rep clie
de pedidos máximo
96
NULL NULL 23 450,00
Efecto: Obtenemos además de los resultados obtenidos con ROLLUP (los totales por
cada representante), los totales por el otro criterio (los totales por cada cliente).
El número de filas de resumen del conjunto de resultados se determina mediante el
número de columnas que contiene la cláusula GROUP BY. Cada operando (columna) de
la cláusula GROUP BY se enlaza según el agrupamiento NULL y se aplica el
agrupamiento al resto de los operandos (columnas). CUBE devuelve todas las
combinaciones posibles de grupo y subgrupo.
Tanto si utilizamos CUBE como ROLLUP, nos será útil la función de agregado
GROUPING.
Si cogemos por ejemplo la primera fila remarcada (101 NULL …) el valor NULL, no
sabemos si se refiere a una fila de subtotal o a que el representante 101 ha realizado un
pedido sin número de cliente. Para poder salvar este problema se utiliza la función de
agregado GROUPING.
97
Número Importe Fila
rep clie
de pedidos máximo Resumen
Las filas que corresponden a subtotales aparecen con un 1 y las normales con un cero.
Ahora que estamos más familiarizados con las columnas de agrupamiento debemos
comentar una regla a no olvidar:
HAVING funciona igual que la cláusula WHERE pero en vez de actuar sobre las filas
del origen de datos, actúa sobre las filas del resultado, selecciona grupos de filas por lo
que la condición de búsqueda sufrirá alguna limitación, la misma que para la lista de
selección:
Ejemplo:
Resultado:
98
oficina Número de empleados
13 1
22 1
Esta SELECT es la misma que la del primer ejemplo del apartado sobre la cláusula
GROUP BY, la diferencia es que le hemos añadido la cláusula HAVING, que hace que del
resultado sólo se visualicen los grupos que cumplan la condición. Es decir sólo aparecen
las oficinas que tienen menos de 2 empleados.
Siempre que en una condición de selección haya una función de columna, la condición
deberá incluirse en la cláusula HAVING, además, como HAVING filtra filas del resultado,
sólo puede contener expresiones (nombres de columnas, expresiones, funciones…) que
también pueden aparecer en la lista de selección, por lo que también se aplica la misma
regla a no olvidar:
HAVING SUM(ventas)=10000
6.1. Introducción
Una subconsulta es una consulta que aparece dentro de otra consulta o subconsultas,
en la lista de selección o en la cláusula WHERE o HAVING, originalmente no se podían
incluir en la lista de selección.
Aparece siempre encerrada entre paréntesis y tiene la misma sintaxis que una
sentencia SELECT normal con alguna limitación:
No puede incluir una cláusula COMPUTE o FOR BROWSE y sólo puede incluir una
cláusula ORDER BY cuando se especifica también una cláusula TOP.
99
Ejemplo de subconsulta: Listar los empleados cuya cuota no supere el importe vendido
por el empleado.
SELECT nombre
FROM empleados
WHERE cuota <= (SELECT SUM(importe)
FROM pedidos
WHERE rep = numemp);
Por cada fila de la tabla de empleados (de la consulta externa) se calcula la subconsulta
y se evalúa la condición, por lo que utilizar una subconsulta puede en algunos casos
„ralentizar‟ la consulta, en contrapartida se necesita menos memoria que una composición
de tablas.
Hay que tener en cuenta de cómo se ejecuta la consulta; por cada fila de la consulta
externa se calcula el resultado de la subconsulta y se evalúa la comparación.
En el ejemplo, se coge el primer empleado (numemp= 101, por ejemplo) y se calcula la
subconsulta sustituyendo numemp por el valor 101, se calcula la suma de los pedidos del
100
rep = 101, y el resultado se compara con la cuota de ese empleado, y así se repite el
proceso con todas las filas de empleados.
Por ejemplo:
La columna oficina se encuentra en los dos orígenes (oficinas y empleados) pero esta
consulta no dará error (no se nos pedirá cualificar los nombres como pasaría en una
composición de tablas), dentro de la subconsulta se considera oficina el campo de la tabla
empleados. Con lo que compararía la oficina del empleado con la misma oficina del
empleado y eso no es lo que queremos, queremos comparar la oficina del empleado con
la oficina de oficinas, lo escribiremos pues así para forzar a que busque la columna en la
tabla oficinas.
Los operadores de comparación sin modificar son los operadores de comparación que
vimos con la cláusula WHERE.
Sintaxis:
En este caso la segunda expresión será una subconsulta, con una sola columna en la
lista de selección y deberá devolver una única fila como mucho.
Ese valor único será el que se compare con el resultado de la primera expresión.
Si la subconsulta no devuelve ninguna fila, la comparación opera como si la segunda
expresión fuese nula.
101
Si la subconsulta devuelve más de una fila o más de una columna, da error.
Ejemplo:
SELECT nombre
FROM empleados
WHERE cuota <= (SELECT SUM(importe)
FROM pedidos
WHERE rep = numemp);
La subconsulta devuelve una sola columna y como mucho una fila ya que es una
consulta de resumen sin cláusula GROUP BY.
<expresion> IN subconsulta
Ejemplo:
SELECT *
FROM empleados
WHERE oficina IN (SELECT oficina
FROM oficinas
WHERE region = 'Este');
Por cada empleado se calcula la lista de las oficinas del Este (nº de oficina) y se evalúa
si la oficina del empleado está en esta lista. Obtenemos pues los empleados de oficinas
del Este.
102
Juan representant 1987- 27500,0 28600,0
103 29 23 104
Rovira e 03-01 0 0
José
1987- 20000,0 14300,0
104 Gonzále 33 23 dir ventas 106
05-19 0 0
z
SELECT *
FROM empleados
WHERE oficina IN (SELECT oficina
FROM oficinas
WHERE region = 'Otro');
La lista generada está vacía por lo que la condición IN devuelve FALSE y en este caso
no sale ningún empleado.
SELECT empleados.*
FROM Empleados INNER JOIN oficinas ON empleados.oficina =
oficinas.oficina
WHERE region = 'Este';
SELECT *
103
FROM empleados
WHERE oficina NOT IN (SELECT oficina
FROM oficinas
WHERE region = 'Este');
Devuelve los empleados cuya oficina no esté en la lista generada por la subconsulta, es
decir empleados que trabajan en oficinas que no son del Este.
Hay que tener especial cuidado con los valores nulos cuando utilizamos el operador
NOT IN porque el resultado obtenido no siempre será el deseado por ejemplo:
* En la consulta anterior no salen los empleados que no tienen oficina ya que para esos
empleados la columna oficina contiene NULL por lo que no se cumple el NOT IN.
* Si la subconsulta no devuelve ninguna fila, la condición se cumplirá para todas las filas
de la consulta externa, en este caso todos los empleados.
* Si la subconsulta devuelve algún valor NULL, la condición NOT IN es NULL lo que nos
puede ocasionar algún problema.
Por ejemplo, queremos obtener las oficinas que no están asignadas a ningún
empleado.
SELECT *
FROM Oficinas
WHERE oficina NOT IN (SELECT oficina
FROM empleados);
Esta consulta no devuelve ninguna fila cuando sí debería ya que hay oficinas que nos
están asignadas a ningún empleado. El problema está en que la columna oficina de la
tabla empleados admite nulos por lo que la subconsulta devuelve valores nulos en todos
los empleados que no están asignados a ninguna oficina. Estos valores nulos hacen que
no se cumpla el NOT IN. La solución pasa por eliminar estos valores molestos:
SELECT *
FROM Oficinas
WHERE oficina NOT IN (SELECT oficina
FROM empleados
WHERE oficina IS NOT NULL);
104
FROM empleados
WHERE numemp IN (SELECT rep
FROM pedidos
WHERE fab = 'ACI');
En este caso, como un empleado puede tener varios pedidos hay que añadir DISTINCT
para eliminar las repeticiones de empleados (si un empleado tiene varios pedidos de ACI
aparecería varias veces).
Sin embargo esta sentencia con NOT IN, queremos los empleados que no tienen
pedidos de ACI:
Esta consulta devuelve los empleados que tienen pedidos que no son de ACI, pero un
empleado puede tener pedidos de ACI y otros de otros fabricantes y por estos otros
saldría en el resultado cuando sí tiene pedidos de ACI y no debería salir.
Hay que tener mucho cuidado con este tipo de preguntas.
105
Se utiliza este tipo de comparación cuando queremos comparar el resultado de la
expresión con una lista de valores y actuar en función del modificador empleado.
El test ANY
ANY significa que, para que una fila de la consulta externa satisfaga la condición
especificada, la comparación se debe cumplir para al menos un valor de los devueltos por
la subconsulta.
Por cada fila de la consulta externa se evalúa la comparación con cada uno de los
valores devueltos por la subconsulta y si la comparación es True para alguno de los
valores ANY es verdadero, si la comparación no se cumple con ninguno de los valores de
la consulta, ANY da False a no ser que todos los valores devueltos por la subconsulta
sean nulos en tal caso ANY dará NULL.
Ejemplo:
SELECT *
FROM empleados
WHERE cuota > ANY (SELECT cuota
FROM empleados empleados2
WHERE empleados.oficina = empleados2.oficina);
Obtenemos los empleados que tienen una cuota superior a la cuota de alguno de sus
compañeros de oficina, es decir los empleados que no tengan la menor cuota de su
oficina.
El test ALL
SELECT *
FROM empleados
WHERE cuota > ALL (SELECT cuota
FROM empleados empleados2
WHERE empleados.oficina = empleados2.oficina);
106
En el ejemplo anterior obtenemos los empleados que tengan una cuota superior a todas
las cuotas de la oficina del empleado. Podríamos pensar que obtenemos el empleado de
mayor cuota de su oficina pero no lo es, aquí tenemos un problema, la cuota del empleado
aparece en el resultado de subconsulta por lo tanto > no se cumplirá para todos los
valores y sólo saldrán los empleados que no tengan oficina (para los que la subconsulta
no devuelve filas).
Para salvar el problema tendríamos que quitar del resultado de la subconsulta la cuota
del empleado modificando el WHERE:
De esta forma saldrían los empleados que tienen una cuota mayor que cualquier otro
empleado de su misma oficina.
O bien
Para no considerar los empleados que tengan la misma cuota que el empleado. En este
caso saldrían los empleados con la mayor cuota de sus oficina, pero si dos empleados
tienen la misma cuota superior, saldrían, hecho que no sucedería con la otra versión.
Ejemplo:
SELECT *
FROM empleados
WHERE EXISTS (SELECT *
FROM pedidos
107
WHERE numemp = rep and fab ='ACI');
Obtenemos los empleados que tengan un pedido del fabricante ACI. Por cada
empleado, se calcula la subconsulta (obteniendo los pedidos de ese empleado y con
fabricante ACI), si existe alguna fila, el empleado sale en el resultado, si no, no sale.
Cuando se utiliza el operador EXISTS es muy importante añadir una referencia externa,
no es obligatorio pero en la mayoría de los casos será necesario. Veámoslo con ese
mismo ejemplo, si quitamos la referencia externa:
SELECT *
FROM empleados
WHERE EXISTS (SELECT *
FROM pedidos
WHERE fab ='ACI');
Sea el empleado que sea, la subconsulta siempre devolverá filas (si existe algún
pedido cuyo fabricante sea ACI) o nunca, indistintamente del empleado que sea, por lo
que se obtendrán todos los empleados o ninguno para que el resultado varíe según las
filas de la consulta externa habrá que incluir una referencia externa.
Otra cosa a tener en cuenta es que la lista de selección de una subconsulta que se
especifica con EXISTS casi siempre consta de un asterisco (*). No hay razón para
enumerar los nombres de las columnas porque no se van a utilizar y supone un trabajo
extra para el sistema.
SELECT *
FROM empleados
WHERE NOT EXISTS (SELECT *
FROM pedidos
WHERE fab ='ACI' AND rep=numemp);
7.1. Introducción
Hasta ahora hemos trabajado con tablas que tenían datos introducidos y cuando nos ha
hecho falta hemos añadido nuevos datos en las mismas y hemos modificado algún dato
directamente desde el entorno de SSMS, en este tema veremos cómo hacerlo con
instrucciones de Transact-SQL.
Seguimos en el DML porque las instrucciones que veremos actúan sobre los datos de
la base de datos no sobre su definición y tenemos tres tipos de operaciones posibles:
108
Insertar nuevas filas en una tabla.
Modificar datos ya almacenados.
Eliminar filas de una tabla.
SELECT ...
INTO nb_NuevaTabla
FROM ...
En la nueva tabla las columnas tendrán el mismo tipo y tamaño que las columnas del
resultado de la SELECT, se llamarán con el nombre de alias de la columna o en su
defecto con el nombre de la columna, pero no se transfiere ninguna otra propiedad del
campo o de la tabla como por ejemplo las claves e índices.
Para practicar puedes realizar este Ejercicio Insertar datos creando una nueva tabla.
109
De la sentencia INSERT completa, nosotros estudiaremos la sintaxis más utilizada y
estándar:
<destino> ::=
{
[nbBaseDatos.nbEsquema. | nbEsquema.]nbTablaVista
}
Con esta instrucción podemos insertar una fila de valores determinados o un conjunto
de filas derivadas de otra consulta.
Si utilizamos una vista, y ésta tiene un origen basado en varias tablas, en su lista de
selección deberán aparecer columnas de una sola tabla (no podemos insertar datos en
varias tablas a la vez).
Con la cláusula VALUES indicamos entre paréntesis los valores a insertar, separados
por comas.
Cada valor se puede indicar mediante:
110
una expresión que normalmente será una constante,
mediante la palabra reservada DEFAULT que indica „valor por defecto‟ en este caso
la columna se rellenará con el valor predeterminado de la columna, si la columna no
tiene DEFAULT se sustituirá por el valor nulo NULL.
Mediante la palabra reservada NULL valor nulo.
Delante de VALUES, de forma opcional podemos indicar una lista de columnas entre
paréntesis. Las columnas son columnas del destino.
Cuando indicamos nombres de columnas, esas columnas serán las que reciban los
valores a insertar, la asignación de valores se realiza por posición, la primera columna
recibe el primer valor, la segunda columna el segundo, y así sucesivamente.
En la lista, las columnas pueden estar en cualquier orden y también se pueden omitir
algunas columnas.
Cuando no se indica una lista de columnas el sistema asume por defecto todas las
columnas de la tabla y en el mismo orden que aparecen en la definición de la tabla, en
este caso, los valores se tienen que especificar en el mismo orden que las columnas en la
definición de la tabla, y se tiene que especificar un valor por cada columna ya que los
valores se rellenan por posición, la primera columna recibe el primer valor, la segunda
columna el segundo, y así sucesivamente.
Cuando se insertan nuevas filas en una tabla, el sistema comprobará que la nueva fila
no infrinja ninguna regla de integridad, por ejemplo no podremos asignar a una columna
PRIMARY KEY un valor nulo o que ya exista en la tabla, a una columna UNIQUE un valor
que ya exista en la tabla, a una columna NOT NULL un valor NULL, a una clave ajena
(FOREIGN KEY) un valor que no exista en la tabla de referencia.
De producirse alguna de las situaciones anterior, la instrucción genera un mensaje de
error y la fila no se inserta.
Ejemplos.
111
En este caso hemos indicado sólo dos columnas y dos valores, las demás columnas se
rellenan con el valor por defecto si lo tiene (DEFAULT) o con NULL. Si alguna columna no
nombrada no admite nulos ni tiene cláusula DEFAULT definida, la instrucción dará error.
Aquí no hemos indicado una lista de columnas luego los valores se tienen que indicar
en el mismo orden que las columnas dentro de la tabla, si nos equivocamos de orden, el
valor se guardará en una columna errónea (si los tipos son compatibles) o generará un
mensaje de error y la fila no se insertará (si los tipos no son compatibles).
Tabla_derivada es cualquier instrucción SELECT válida que devuelva filas con los datos
que se van a cargar en el destino.
Cada fila devuelta por la SELECT es una lista de valores que se intentará insertar como
con la cláusula VALUES, por lo que las columnas devueltas por la SELECT deberán
cumplir las mismas reglas que los valores de la lista de valores anteriores.
Ejemplo:
En este caso no hemos incluido una lista de columnas, por lo que en la SELECT
tenemos que generar los valores en el mismo orden que en trabajo.
Si hubiesemos escrito:
112
INSERT INTO trabajo SELECT ciudad, oficina, ventas
FROM oficinas
WHERE region = 'Centro';
Hubiese dado error porque la columna col1 es INT y el valor a asignar es texto (el
nombre de la ciudad de la oficina).
En este caso hemos incluido una lista de columnas, la SELECT debe generar los
valores correspondientes, y col3 que no se rellena explícitamente se rellenará con NULL
porque la columna col3 no está definida como columna calculada, ni con DEFAULT, ni
IDENTITY y además admite nulos.
Hace que la nueva fila contenga los valores predeterminados definidos para cada
columna.
Hay que tener en cuenta una serie de aspectos al utilizar esta instrucción:
Puede generar filas duplicadas en la tabla si los valores que se generan son siempre los
mismos.
Si la tabla tiene una clave principal, esta tendrá que estar basada en una columna con la
propiedad IDENTITY para que se generen valores diferentes automáticamente.
Si una columna está definida como NOT NULL tendrá que incluir un DEFAULT o ser una
columna calculada con una expresión compatible.
Para modificar los datos de una tabla es necesario disponer del privilegio UPDATE
sobre dicha tabla.
UPDATE
113
[ TOP ( expression ) [ PERCENT ] ]
<destino>
SET { nbcolumna = { expresion | DEFAULT | NULL }
} [ ,...n ]
[ FROM{ <origen> }]
[ WHERE <condicion> ]
[;]
<destino> ::=
{
[nbBaseDatos.[nbEsquema.]| nbEsquema.]nbTablaVista
}
Expresión en cada asignación debe generar un valor del tipo de dato apropiado para la
columna indicada. La expresión debe ser calculable basada en los valores de la fila
actualmente en actualización. Si para el cálculo se utiliza una columna que también se
modifica, el valor que se utilizará es el de antes de la modificación, lo mismo para la
condición del WHERE.
Expresión también puede ser una subconsulta siempre y cuanto devuelva un único
valor y cumpla las condiciones anteriormente expuestas.
Por ejemplo:
Actualiza todas las filas de la tabla oficinas dejando el campo ventas con el valor cero.
Si el campo ventas está definido con un valor predeterminado 0, la sentencia anterior
equivale a:
En una misma sentencia podemos actualizar varias columnas, sólo tenemos que indicar
las distintas asignaciones separadas por comas:
114
Los nombres de columna pueden especificarse en cualquier orden.
Si no queremos actualizar todas las filas de la tabla sino unas cuantas, utilizaremos la
cláusula TOP, o unas determinadas, utilizaremos la cláusula WHERE.
Por ejemplo:
[ WHERE <condicion> ]
Utilizamos la cláusula WHERE para filtrar las filas a actualizar. Se actualizarán todas las
filas que cumplan la condición. Por ejemplo si queremos actualizar sólo las oficinas del
Este:
UPDATE oficinas
SET ventas = 0
WHERE region = 'Este';
Cuando el campo de la otra tabla se utiliza para la cláusula SET, entonces debemos
utilizar la cláusula FROM.
La cláusula FROM permite definir un origen de datos basado en varias tablas, y ese
origen será el utilizado para realizar la actualización.
Por ejemplo queremos actualizar el importe de los pedidos con el precio de la tabla
productos.
115
FROM pedidos INNER JOIN productos
ON fab = idfab AND producto = idproducto;
Además del permiso de UPDATE, se requieren permisos SELECT para la tabla que se
actualiza si la instrucción UPDATE contiene una cláusula WHERE o en el caso de que el
argumento expression de la cláusula SET utilice una columna de la tabla, y permisos
SELECT para la tabla del origen si utilizamos una cláusula FROM o un WHERE con
subconsulta.
DELETE
[ TOP ( expression ) [ PERCENT ] ] [ FROM ] <destino>
[ FROM <origen>]
[ WHERE < condicion>]
[; ]
<destino> ::=
{
[nbBaseDatos. nbEsquema. | nbEsquema.]nbTablaVista
}
Con esta instrucción podemos eliminar una o varias filas de una tabla.
<destino> es el nombre de la tabla de donde queremos eliminar las filas, puede ser un
nombre de tabla o un nombre de vista (de momento basada en una sólo tabla).
La segunda cláusula FROM sirve para indicar un origen que permita una condición de
WHERE sobre una tabla diferente de destino.
116
La instrucción básica sería pues:
DELETE oficinas;
Equivalente a:
La cláusula WHERE permite eliminar determinadas filas, indica una condición que
deben cumplir las filas que se eliminan.
Por ejemplo:
DELETE oficinas
WHERE region = ’Este’;
Por ejemplo:
Originalmente sólo se podía indicar una tabla en la cláusula FROM, pero ahora
podemos indicar un origen basado en varias tablas.
117
WHERE region = 'Este';
En el origen tenemos las dos tablas y en la primera FROM indicamos de qué tabla
queremos borrar.
Esto se podía haber resuelto, como toda la vida, mediante una subconsulta:
Para finalizar no debemos olvidar que para poder ejecutar un DELETE se requieren
permisos DELETE en la tabla de donde vamos a eliminar, y también se requieren los
permisos para utilizar SELECT si la instrucción contiene una cláusula WHERE.
En el ejemplo anterior, si un empleado asignado a una oficina del Este tiene pedidos, no
se podrá eliminar y entonces no se eliminará ningún empleado.
TRUNCATE TABLE
[nbBaseDatos.[nbEsquema.]| nbEsquema.]nbTabla [; ]
Esta sentencia quita todas las filas de una tabla sin registrar las eliminaciones
individuales de filas. Desde un punto de vista funcional, TRUNCATE TABLE es
equivalente a la instrucción DELETE sin una cláusula WHERE; no obstante, TRUNCATE
TABLE es más rápida y utiliza menos recursos de registros de transacciones y de sistema.
118
Por regla general, se utilizan menos bloqueos.
Si se ejecuta la instrucción DELETE con un bloqueo de fila, se bloquea cada fila de
la tabla para su eliminación. TRUNCATE TABLE siempre bloquea la tabla y la
página, pero no cada fila.
Pero no todo son ventajas, no se puede utilizar TRUNCATE TABLE en las siguientes
tablas:
Tablas a las que se hace referencia mediante una restricción FOREIGN KEY (las
tablas que entran como principales en una relación).
Tablas que participan en una vista indizada.
8.1. Introducción
El DDL (Data Definition Language, o Data Description Language según autores), es la
parte del SQL dedicada a la definición de la base de datos, consta de sentencias para
definir la estructura de la base de datos, permite definir gran parte del nivel interno de la
base de datos. Por este motivo estas sentencias serán utilizadas normalmente por el
administrador de la base de datos.
Bases de datos
Tablas
Vistas
Índices
119
Como ya hemos comentado, las sentencias DDL están más orientadas al administrador
de la base de datos, es el que más las va a utilizar, el programador tiene que conocer
cuestiones relativas a la estructura interna de una base de datos, pero no tiene que ser
experto en ello por lo que el estudio del tema se centrará en las sentencias y sobre todo
en las cláusulas que pensamos pueden ser útiles a un programador y no entraremos en
mucho detalle en cuanto a la estructura física de la base de datos y en la administración
de la misma.
Como ya vimos en el primer tema, las bases de datos de SQL Server 2005 utilizan tres
tipos de archivos:
CREATE DATABASE
Con la cláusula ON especificamos los ficheros utilizados para almacenar los archivos
de datos.
[ ON
[ PRIMARY ] [ <esp_fichero> [ ,...n ]
120
[ , <grupo> [ ,...n ] ]
<esp_fichero> ::=
(
NAME = nbfichero_logico ,
FILENAME = 'nbfichero_fisico'
[ , SIZE = tamaño [ KB | MB | GB | TB ] ]
[ , MAXSIZE = { max_size [ KB | MB | GB | TB ] |
UNLIMITED } ]
[ , FILEGROWTH = incremento_crecimiento [ KB | MB | GB |
TB | % ] ]
)
Cada archivo también puede tener un tamaño máximo especificado con MAXSIZE. Si
no se especifica un tamaño máximo, el archivo puede crecer hasta utilizar todo el espacio
disponible en el disco. Esta característica es especialmente útil cuando SQL Server se
utiliza como una base de datos incrustada en una aplicación para la que el usuario no
dispone fácilmente de acceso a un administrador del sistema. El usuario puede dejar que
los archivos crezcan automáticamente cuando sea necesario y evitar así las tareas
administrativas de supervisar la cantidad de espacio libre en la base de datos y asignar
más espacio manualmente.
Detrás de la lista de archivos del grupo de archivos principal, con <grupo> se puede
colocar una lista opcional de elementos separados por comas que definan los grupos de
archivos de usuario y sus archivos.
121
<grupo> ::=
{
FILEGROUP nbgrupo [ DEFAULT ]
<esp_fichero> [ ,...n ]
}
Nbgrupo es el nombre del grupo y a continuación indicamos los archivos de datos que
pertenecen a ese grupo, los archivos pertenecientes al grupo se indican con los del grupo
principal.
DEFAULT
Cambia el grupo de archivos predeterminado de la base de datos a Nbgrupo. Sólo un
grupo de archivos de la base de datos puede ser el grupo de archivos predeterminado.
Con la cláusula LOG ON definiremos los archivos utilizados para almacenar el registro
de la base de datos (los archivos de registro).
La sintaxis es la siguiente:
COLLATE <nbintercalacion>
< nbintercalacion >:: =
nbinterWindows_ CaseSensitivity_AccentSensitivity
122
instantánea de base de datos, ni tampoco con las cláusulas FOR ATTACH o FOR.
Existen otras cláusulas a nivel de administración de la base de datos que no detallaremos
aquí como son:
[ WITH <external_access_option> ]
<external_access_option> ::=
{
DB_CHAINING { ON | OFF }
| TRUSTWORTHY { ON | OFF }
}
Para crear una instantánea de base de datos (copia de sólo lectura de una base de
datos).
123
[ENCRYPTION {ON|OFF}]
]
[COLLATE collation_name comparison_style]
database password ::= identifier
Para crear una base de datos protegida mediante contraseña, opción disponible para
SQL Server Mobile (Microsoft SQL Server 2005 Mobile Edition (SQL Server Mobile), antes
denominado Microsoft SQL Server 2000 Windows CE 2.0 (SQL Server CE), es una base
de datos compacta y con una gran variedad de funciones diseñada para admitir una lista
ampliada de dispositivos inteligentes y Tablet PC. Entre los dispositivos inteligentes están
todos los dispositivos que ejecuten Microsoft Windows CE 5.0, Microsoft Mobile
Pocket PC 2003, Microsoft Mobile Version 5.0 Pocket PC o Microsoft Mobile Version 5.0
Smart Phone. Esta compatibilidad adicional con dispositivos permite a los programadores
usar la misma funcionalidad de base de datos en un gran número de dispositivos.)
La base de datos puede ser una base de datos normal o una instantánea de base de
datos.
Para poder ejecutar la sentencia el usuario debe tener permiso de CONTROL y se debe
de ejecutar en un contexto diferente del de la base de datos a eliminar, por ejemplo:
use b1
DROP DATABASE b1
Como se ve en la sintaxis podemos eliminar varias bases de datos con una sóla
sentencia DROP DATABASE.
Por ejemplo:
124
Para poder ejecutar esta sentencia se debe de tener el permiso ALTER en la base de
datos. Esta sentencia se debe ejecutar en el modo de confirmación automática (modo de
administración de transacciones predeterminado) y no se permite en una transacción
explícita o implícita.
Sintaxis:
Con esta sentencia resumida vemos que nos permite cambiar la definición de la base
de datos, nos va a permitir cambiar la definición de los ficheros que conforman la base de
datos, también nos permite cambiar la definición de los grupos, la definición de varias
opciones, el tipo de intercalación e incluso cambiar el nombre de la base de datos (con la
cláusula MODIFY NAME).
Por ejemplo:
Como con la instrucción CREATE DATABASE veremos aquí un resumen de lo que más
le puede interesar a un programador, sin entrar en demasiados detalles de administración.
Como muchas de las palabras ya las hemos explicado con la sentencia CREATE
DATABASE, sólo insistiremos en lo nuevo.
<cambiar_ficheros>::=
{
ADD FILE < esp_fichero > [ ,...n ]
[ TO FILEGROUP { nbgrupo | DEFAULT } ]
| ADD LOG FILE < esp_fichero > [ ,...n ]
| REMOVE FILE nbfichero
| MODIFY FILE < esp_fichero >
125
}
Con este grupo de opciones podemos cambiar la definición de los archivos de datos de
la base de datos.
ADD FILE permite añadir un nuevo archivo de datos (o varios) si no se añade nada (o
TO FILEGROUP DEFAULT), el archivo se añadirá al grupo principal, si añadimos TO
FILEGROUP nbgrupo, se añadirá el archivo al grupo indicado.
<esp_fichero>::=
(
NAME = nbarchivo
[ , NEWNAME = nuevo_nbarchivo ]
[ , FILENAME = 'nbarchivo_fisico' ]
[ , SIZE = tamaño [ KB | MB | GB | TB ] ]
[ , MAXSIZE = { tamaño_máximo [ KB | MB | GB | TB ] |
UNLIMITED } ]
[ , FILEGROWTH = incremento [ KB | MB | GB | TB| % ] ]
[ , OFFLINE ]
)
126
Con FILENAME podemos cambiar el nombre del fichero físico, esto nos permite
también cambiar la ubicación del archivo físico.
Por ejemplo:
La cláusula OFFLINE establece el archivo sin conexión e impide el acceso a todos los
objetos del grupo de archivos. ¡Muy importante!, esta opción sólo se debe de utilizar si el
archivo está dañado y si se puede restaurar. Un archivo establecido en OFFLINE sólo se
puede restablecer con conexión mediante la restauración del archivo a partir de una copia
de seguridad. Para obtener más información acerca de cómo restaurar un solo archivo,
consultar en la ayuda la sentencia RESTORE (Transact-SQL).
UNLIMITED especifica que el tamaño del archivo aumenta hasta que el disco esté
lleno. En SQL Server 2005, un archivo de registro especificado con un aumento ilimitado
tiene un tamaño máximo de 2 TB y un archivo de datos tiene un tamaño máximo de 16
TB.
<cambiar_grupos>::=
{
| ADD FILEGROUP nbgrupo
| REMOVE FILEGROUP nbgrupo
| MODIFY FILEGROUP nbgrupo
{
{ READONLY | READWRITE }
| { READ_ONLY | READ_WRITE }
}
| DEFAULT
| NAME = nuevo_ nbgrupo
}
}
127
READ_ONLY | READONLY Especifica que el grupo de archivos es de sólo lectura. En
este caso no se permitirá la actualización de los objetos del mismo. Una base de datos de
sólo lectura no permite realizar modificaciones en los datos por lo que:
Para cambiar este estado, se debe tener acceso exclusivo a la base de datos.
<opciones>::=
SET
{
{ <optionspec> [ ,...n ] [ WITH <termination> ] }
| ALLOW_SNAPSHOT_ISOLATION {ON | OFF }
| READ_COMMITTED_SNAPSHOT {ON | OFF } [ WITH
<termination> ]
}
<optionspec>::=
{
<db_state_option>
| <db_user_access_option>
| <db_update_option> | <external_access_option>
| <cursor_option>
128
| <auto_option>
| <sql_option>
| <recovery_option>
| <database_mirroring_option>
| <supplemental_logging_option>
| <service_broker_option>
| <date_correlation_optimization_option>
| <parameterization_option>
}
<db_state_option> ::=
{ ONLINE | OFFLINE | EMERGENCY }
<db_user_access_option> ::=
{ SINGLE_USER | RESTRICTED_USER | MULTI_USER }
<db_update_option> ::=
{ READ_ONLY | READ_WRITE }
<external_access_option> ::=
DB_CHAINING { ON | OFF }
| TRUSTWORTHY { ON | OFF }
}
<cursor_option> ::=
{ CURSOR_CLOSE_ON_COMMIT { ON | OFF }
| CURSOR_DEFAULT { LOCAL | GLOBAL }
}
<auto_option> ::=
{
AUTO_CLOSE { ON | OFF }
| AUTO_CREATE_STATISTICS { ON | OFF }
| AUTO_SHRINK { ON | OFF }
| AUTO_UPDATE_STATISTICS { ON | OFF }
| AUTO_UPDATE_STATISTICS_ASYNC { ON | OFF }
}
<sql_option> ::=
{
ANSI_NULL_DEFAULT { ON | OFF }
| ANSI_NULLS { ON | OFF }
| ANSI_PADDING { ON | OFF }
| ANSI_WARNINGS { ON | OFF }
129
| ARITHABORT { ON | OFF }
| CONCAT_NULL_YIELDS_NULL { ON | OFF }
| NUMERIC_ROUNDABORT { ON | OFF }
| QUOTED_IDENTIFIER { ON | OFF }
| RECURSIVE_TRIGGERS { ON | OFF }
}
<recovery_option> ::=
{
RECOVERY { FULL | BULK_LOGGED | SIMPLE }
| TORN_PAGE_DETECTION { ON | OFF }
| PAGE_VERIFY { CHECKSUM | TORN_PAGE_DETECTION | NONE }
}
< database_mirroring_option> ::=
{ <partner_option> | <witness_option> }
<partner_option> ::=
PARTNER { = 'partner_server'
| FAILOVER
| FORCE_SERVICE_ALLOW_DATA_LOSS
| OFF
| RESUME
| SAFETY { FULL | OFF }
| SUSPEND
| REDO_QUEUE ( integer { KB | MB | GB } |
UNLIMITED )
| TIMEOUT integer
}
<witness_option> ::=
WITNESS { = 'witness_server'
| OFF
}
<supplemental_logging_option> ::=
SUPPLEMENTAL_LOGGING { ON | OFF }
<service_broker_option> ::=
{
ENABLE_BROKER
| DISABLE_BROKER
| NEW_BROKER
130
| ERROR_BROKER_CONVERSATIONS
}
<date_correlation_optimization_option> ::=
{
DATE_CORRELATION_OPTIMIZATION { ON | OFF }
}
<parameterization_option> ::=
{
PARAMETERIZATION { SIMPLE | FORCED }
}
<termination> ::=
{
ROLLBACK AFTER integer [ SECONDS ]
| ROLLBACK IMMEDIATE
| NO_WAIT
}
<opciones>::=
SET
{
{ <optionspec> [ ,...n ] [ WITH <termination> ] }
| ALLOW_SNAPSHOT_ISOLATION {ON | OFF }
| READ_COMMITTED_SNAPSHOT {ON | OFF } [ WITH
<termination> ]
}
<optionspec>::=
{
<db_state_option>
| <db_user_access_option>
| <db_update_option> | <external_access_option>
| <cursor_option>
| <auto_option>
131
| <sql_option>
| <recovery_option>
| <database_mirroring_option>
| <supplemental_logging_option>
| <service_broker_option>
| <date_correlation_optimization_option>
| <parameterization_option>
}
<db_state_option> ::=
{ ONLINE | OFFLINE | EMERGENCY }
<db_user_access_option> ::=
{ SINGLE_USER | RESTRICTED_USER | MULTI_USER }
<db_update_option> ::=
{ READ_ONLY | READ_WRITE }
<external_access_option> ::=
DB_CHAINING { ON | OFF }
| TRUSTWORTHY { ON | OFF }
}
<cursor_option> ::=
{ CURSOR_CLOSE_ON_COMMIT { ON | OFF }
| CURSOR_DEFAULT { LOCAL | GLOBAL }
}
<auto_option> ::=
{
AUTO_CLOSE { ON | OFF }
| AUTO_CREATE_STATISTICS { ON | OFF }
| AUTO_SHRINK { ON | OFF }
| AUTO_UPDATE_STATISTICS { ON | OFF }
| AUTO_UPDATE_STATISTICS_ASYNC { ON | OFF }
}
<sql_option> ::=
{
ANSI_NULL_DEFAULT { ON | OFF }
| ANSI_NULLS { ON | OFF }
| ANSI_PADDING { ON | OFF }
| ANSI_WARNINGS { ON | OFF }
| ARITHABORT { ON | OFF }
132
| CONCAT_NULL_YIELDS_NULL { ON | OFF }
| NUMERIC_ROUNDABORT { ON | OFF }
| QUOTED_IDENTIFIER { ON | OFF }
| RECURSIVE_TRIGGERS { ON | OFF }
}
<recovery_option> ::=
{
RECOVERY { FULL | BULK_LOGGED | SIMPLE }
| TORN_PAGE_DETECTION { ON | OFF }
| PAGE_VERIFY { CHECKSUM | TORN_PAGE_DETECTION | NONE }
}
< database_mirroring_option> ::=
{ <partner_option> | <witness_option> }
<partner_option> ::=
PARTNER { = 'partner_server'
| FAILOVER
| FORCE_SERVICE_ALLOW_DATA_LOSS
| OFF
| RESUME
| SAFETY { FULL | OFF }
| SUSPEND
| REDO_QUEUE ( integer { KB | MB | GB } |
UNLIMITED )
| TIMEOUT integer
}
<witness_option> ::=
WITNESS { = 'witness_server'
| OFF
}
<supplemental_logging_option> ::=
SUPPLEMENTAL_LOGGING { ON | OFF }
<service_broker_option> ::=
{
ENABLE_BROKER
| DISABLE_BROKER
| NEW_BROKER
| ERROR_BROKER_CONVERSATIONS
133
}
<date_correlation_optimization_option> ::=
{
DATE_CORRELATION_OPTIMIZATION { ON | OFF }
}
<parameterization_option> ::=
{
PARAMETERIZATION { SIMPLE | FORCED }
}
<termination> ::=
{
ROLLBACK AFTER integer [ SECONDS ]
| ROLLBACK IMMEDIATE
| NO_WAIT
}
Se pueden ver más detalles de estas opciones en la ayuda de SQL SERVER buscando
ALTER DABATABASE.
Si las columnas de la instrucción CREATE TABLE se definen como un tipo definido por
el usuario CLR, se necesita la propiedad del tipo o el permiso REFERENCES.
Si las columnas de la instrucción CREATE TABLE tienen una colección de esquemas
XML asociada, se necesita la propiedad de la colección de esquemas XML o el permiso
REFERENCES.
CREATE TABLE
[ nbBaseDatos.[nbEsquema].| nbEsquema.]nbTabla
( { <definicion_columna> | < definicion_colCalc > } [
,...n ]
[ <restriccion_tabla> ] [ ,...n ] )
[ ; ]
134
manera predeterminada la base de datos actual. El inicio de sesión de la conexión actual
debe estar asociado a un Id. de usuario existente en la base de datos especificada por
nbBaseDatos, y ese Id. de usuario debe tener permisos CREATE TABLE.
nbEsquema Es el nombre del esquema al que pertenece la nueva tabla.
nbTabla Es el nombre de la nueva tabla. Los nombres de tablas deben seguir las reglas
de los identificadores. nbTabla puede contener un máximo de 128 caracteres excepto
para los nombres de tablas temporales locales (nombres precedidos por un único signo de
número (#)) que no pueden superar los 116 caracteres.
Los nombres de columnas deben seguir las reglas de los identificadores y deben ser
únicos en la tabla. nbCol puede contener de 1 a 128 caracteres. nbCol se puede omitir en
las columnas creadas con un tipo de datos timestamp, en tal caso, si no se especifica
nbCol, el nombre de la columna timestamp será de manera predeterminada timestamp.
En cuanto al tipo de dato, esta es la sintaxis:
<tipo_dato> ::=
[nbEsquema_tipo. ] nbtipo
[ ( precision [ , escala ] | max |
[ { CONTENT | DOCUMENT } ] xml_schema_collection )
]
[ nbEsquema_tipo. ] nbtipo
nbtipo Especifica el tipo de datos de la columna y nbEsquema_tipo el esquema al que
pertenece el tipo. El tipo de datos puede ser uno de los siguientes:
Un tipo de datos del sistema de SQL Server 2005 como los que ya conocemos.
Un tipo de alias basado en un tipo de datos del sistema de SQL Server. Los tipos de
datos de alias se crean con la instrucción CREATE TYPE para poder utilizarlos en
una definición de tabla. La asignación NULL o NOT NULL de un tipo de datos de
135
alias puede anularse durante la instrucción CREATE TABLE. No obstante, la
especificación de longitud no se puede cambiar; la longitud del tipo de datos de alias
no se puede especificar en una instrucción CREATE TABLE.
Un tipo definido por el usuario CLR. Los tipos definidos por el usuario CLR se crean
con la instrucción CREATE TYPE para poder utilizarlos en una definición de tabla.
Max sólo se aplica a los tipos de datos varchar, nvarchar y varbinary para almacenar
231 bytes de datos de caracteres y binarios, y 230 bytes de datos Unicode.
Para obtener más información acerca de los valores de precisión y de escala válidos,
visita el siguiente avanzado.
[ COLLATE nbIntercalacion ]
Con la cláusula COLLATE podemos definir el tipo de intercalación que se utilizará para
la columna (Ver CREATE TABLE).
Con la cláusula DEFAULT podemos especificar un valor por defecto, es decir el valor
que tomará el campo cuando no se haya especificado explícitamente un valor durante la
inserción. Las definiciones DEFAULT se pueden aplicar a cualquier columna excepto a las
definidas como timestamp o a aquellas que tengan la propiedad IDENTITY. Si se
especifica un valor por defecto a una columna de un tipo definido por el usuario, dicho tipo
debe ser compatible con la conversión implícita de exp_constante en el tipo definido por el
usuario. exp_constante sólo puede ser NULL o un valor constante (por ejemplo, una
136
cadena de caracteres, una función escalar o una función del sistema, definida por el
usuario o CLR).
Para mantener la compatibilidad con las versiones anteriores de SQL Server, se puede
asignar un nombre de restricción a DEFAULT con [ CONSTRAINT nbRestriccion ]. Los
nombres de restricción deben ser únicos en el esquema al que pertenece la tabla.
La propiedad IDENTITY se puede asignar a las columnas tinyint, smallint, int, bigint,
decimal(p,0) o numeric(p,0). Sólo se puede crear una columna de identidad para cada
tabla. Las restricciones DEFAULT y los valores predeterminados enlazados no se pueden
utilizar en las columnas de identidad. En este caso, deben especificarse el valor de
inicialización y el incremento, o ninguno de esto valores. Si no se especifica ninguno, el
valor predeterminado es (1,1).
[ ROWGUIDCOL ]
ROWGUIDCOL indica que la nueva columna es una columna de GUID de filas. Sólo se
puede designar una columna uniqueidentifier por tabla como columna ROWGUIDCOL.
La propiedad ROWGUIDCOL se puede asignar únicamente a una columna
uniqueidentifier.
Las columnas de tipos de datos definidos por el usuario no se pueden designar con
ROWGUIDCOL.
La propiedad ROWGUIDCOL no impone la unicidad de los valores almacenados en la
columna. ROWGUIDCOL tampoco genera automáticamente valores para nuevas filas
insertadas en la tabla, por lo que se debe de utilizar la función NEWID en las instrucciones
INSERT o utilizar la función NEWID como el valor predeterminado de la columna para
generar valores únicos en cada fila.
Por último nos quedan las restricciones de clave que aparecen en la sintaxis como:
[ <restriccion_columna> [ ...n ] ]
< restriccion_columna > ::=
[ CONSTRAINT nbRestriccion]
{ { PRIMARY KEY | UNIQUE }[ CLUSTERED | NONCLUSTERED ]
[ WITH FILLFACTOR = factorRelleno
| WITH ( < opcion_indice > [ , ...n ] )
137
]
[ ON { partition_scheme_name ( partition_column_name
)
| filegroup | "default" } ]
| [ FOREIGN KEY ]
REFERENCES [ nbEsquema.] nbTablaPadre [ ( col_padre
) ]
[ ON DELETE { NO ACTION | CASCADE | SET NULL | SET
DEFAULT } ]
}
[ ON UPDATE { NO ACTION | CASCADE | SET NULL | SET
DEFAULT } ]
[ NOT FOR REPLICATION ]
| CHECK [ NOT FOR REPLICATION ] (expresion_validacion)
nbRestriccion es el nombre de la restricción, como hemos visto antes, debe ser único
en el esquema al que pertenece la tabla. Las restricciones se implementan internamente
con índices por lo que a veces podemos utilizar el término índice o restricción
indistintamente.
PRIMARY KEY indica que la columna es la clave principal de la tabla. Sólo se puede
crear una restricción PRIMARY KEY para cada tabla. Si la clave primaria (principal) está
compuesta por varias columnas entonces no podemos utilizar esta restricción, tendremos
que utilizar una restricción de tabla que veremos más adelante.
CLUSTERED indica que el índice que se va a crear es un índice agrupado. Como sólo
puede haber un índice agrupado por tabla, si todavía no hay ninguno definido, por defecto
se creará con la clave primaria, si ya existe un índice agrupado, la clave principal se
creará sin índice agrupado.
Una clave primaria no puede contener valores nulos, por lo que todas las columnas
definidas en una restricción PRIMARY KEY se deben definir como NOT NULL. Si cuando
definimos la columna, no se indica nada, la columna se establecerá a NOT NULL.
Si la clave principal se define en una columna de tipo definido por el usuario CLR, la
implementación del tipo debe admitir el orden binario.
UNIQUE indica que la columna no admite valores duplicados, por lo que se crea un
índice único. Una tabla puede tener varios índices únicos.
138
Si se define una restricción UNIQUE en una columna de tipo definido por el usuario
CLR, la implementación del tipo debe admitir el orden binario o basado en el operador.
Las demás opciones de índices:
Son cláusulas que nos permiten definir con más detalle el índice pero que no veremos
aquí por entrar demasiado en cuestiones internas.
La cláusula CHECK.
Con CHECK indicamos una regla de validación que deberán cumplir todas las filas de la
tabla, es una restricción que exige la integridad del dominio al limitar los valores posibles
que se pueden escribir en la columna.
Las restricciones CHECK no se pueden definir en las columnas text, ntext o image.
Por ejemplo queremos que la columna Precio de la tabla que estamos definiendo no
pueda contener valores negativos:
…
Precio CURRENCY CONSTRAINT precio_pos CHECK (Precio > = 0)
139
…
Por último a nivel de columna podemos definir una restricción de clave ajena:
[ FOREIGN KEY ]
REFERENCES [ nbEsquema.] nbTablaPadre [ ( col_padre )
]
[ ON DELETE { NO ACTION | CASCADE | SET NULL | SET
DEFAULT } ]
[ ON UPDATE { NO ACTION | CASCADE | SET NULL | SET
DEFAULT } ]
[ NOT FOR REPLICATION ]
Con esta cláusula definimos una regla de integridad referencial, los valores contenidos
en la columna deberán apuntar a un registro en la tabla de referencia (la tabla padre).
La palabra FOREIGN KEY no es obligatoria cuando estamos a nivel de columna, el
utilizarla o no tiene el mismo efecto.
La col_padre debe tener en la tabla padre una restricción de PRIMARY KEY o UNIQUE,
si no se indica una columna padre (columna de referencia) se utiliza la clave primaria de la
tabla padre.
Las restricciones FOREIGN KEY sólo pueden hacer referencia a las tablas de la misma
base de datos en el mismo servidor. La integridad referencial entre bases de datos debe
implementarse a través de desencadenadores.
140
padres y madres y el dni para referencial el tutor, esto último lo podemos hacer porque el
campo dni tiene una restricción UNIQUE.
Por último nos queda completar las reglas de integridad referencial en cuanto a qué
hacer cuando se eliminan o modifican valores que intervienen en una relación referencial.
Indica qué ocurre cuando se intenta eliminar un registro padre en la relación que
estamos definiendo. El valor predeterminado es NO ACTION.
SET NULL Si se borra una fila de la tabla primaria, todas las filas correspondientes de
la tabla que estamos definiendo tomarán el valor NULL en el campo clave ajena. En otras
palabras, si se elimina un padre, sus hijos se quedan sin padre.
Para ejecutar esta restricción, la columna clave ajena debe admitir valores NULL.
SET DEFAULT Es como la anterior pero en vez del valor NULL toman el valor que tienen
predeterminado. Si no hay ningún valor predeterminado establecido de forma explícita,
tomarán el valor NULL. Hay que tener en cuenta que el valor predeterminado debe de
existir en la tabla primaria.
….,
Proveedor INTEGER REFERENCES Proveedores ON DELETE NO
ACTION,
…)
141
Estas dos sentencias son equivalentes e indican que si se intenta borrar de la tabla
Proveedores un proveedor asignado a un artículo, el sistema da un error y no deja
eliminar el proveedor.
….,
Proveedor INTEGER REFERENCES Proveedores ON DELETE CASCADE,
…)
….,
Proveedor INTEGER REFERENCES Proveedores ON DELETE SET NULL,
…)
Indica qué ocurre cuando se intenta cambiar un valor del campo relacionado de la tabla
padre en la relación que estamos definiendo. El valor predeterminado es NO ACTION.
SET NULL Si se modifica un valor de la columna padre en la tabla primaria, todas las
filas correspondientes de la tabla que estamos definiendo tomarán el valor NULL en el
campo clave ajena. En otras palabras, si se modifica el identificativo de un padre, sus hijos
se quedan sin padre.
Para ejecutar esta restricción, la columna clave ajena debe admitir valores NULL.
SET DEFAULT Es como la anterior pero en vez del valor NULL toman el valor que tienen
predeterminado. Si no hay ningún valor predeterminado establecido de forma explícita,
tomarán el valor NULL. Hay que tener en cuenta que el valor predeterminado debe de
existir en la tabla primaria.
….,
Proveedor INTEGER REFERENCES Proveedores ON UPDATE NO
ACTION,
…)
142
Si cambiamos el código del proveedor 3 a 3000 y hay artículos asignados al proveedor
3, el sistema da un error y no deja modificar el proveedor.
….,
Proveedor INTEGER REFERENCES Proveedores ON UPDATE CASCADE,
…)
….,
Proveedor INTEGER REFERENCES Proveedores ON UPDATE SET
NULL,
…)
Hasta el momento hemos aprendido a definir restricciones sobre una columna, mientras
la estamos definiendo añadimos a su definición la restricción o restricciones que
queramos.
También existen restricciones de tabla, son restricciones que se definen después de
definir todas las columnas de la tabla y que pueden afectar a una o varias columnas de la
tabla. Como veremos la sintaxis para definir una restricción de tabla es muy parecida a la
sintaxis de la misma restricción de columna, lo que varía es que ahora tenemos que
indicar las columnas afectadas por la restricción.
143
[(col_padre)[,...n] )]
[ ON DELETE { NO ACTION | CASCADE | SET NULL | SET
DEFAULT } ]
[ ON UPDATE { NO ACTION | CASCADE | SET NULL | SET
DEFAULT } ]
[ NOT FOR REPLICATION ]
| CHECK [NOT FOR REPLICATION] (expresion_validacion )
}
Como las cláusulas son las mismas que para las restricciones de columna, no
repetiremos la explicación de cada cláusula, lo que veremos es un ejemplo y la
explicación de ese ejemplo.
Utilizando la restricciones de tabla parece que la definición queda más clara, por un
lado tenemos la definición de cada columna, y luego las restricciones.
144
…
El Motor de la base de datos entendería que queremos definir dos claves primarias y
eso es imposible. En este caso habría que utilizar una restricción de tabla:
Ocurre lo mismo con las demás restricciones. Imaginemos ahora una tabla de líneas de
pedido en la que tenemos en una línea el producto pedido, y la cantidad pedida.
La combinación (codprod,codprov) forma una clave ajena que hace referencia a la tabla
Productos, en este caso como la clave principal de la tabla Productos está compuesta por
los dos campos, la clave ajena tiene que tener el mismo número de campo y del mismo
tipo.
Una columna calculada es una columna cuyo valor no se introduce, sino que se obtiene
como resultado de un cálculo.
145
< definicion_columna > ::=
nbCol AS expresion
[ PERSISTED
[NOT NULL]
[ <restriccion_columna> [...n ] ]
]
expresion es la expresión que define el valor de una columna calculada y está basada
en otras columnas de la tabla.
Una columna calculada es, por defecto, una columna virtual no almacenada físicamente
en la tabla. PERSISTED indica que la columna calculada se almacena en la tabla y
automáticamente se actualizan los valores almacenados en ella cuando se actualizan las
columnas de las que depende.
Para que la columna pueda ser definida con PERSISTED la expresión que la calcula
debe ser determinista. Una expresión es determinista a menos que utilice una función no
determinista.
Una tabla temporal es una tabla creada por un determinado proceso y desaparece
cuando termina éste.
Se pueden crear tablas temporales locales y globales. Las tablas temporales locales
son visibles sólo en la sesión actual y las tablas temporales globales son visibles para
todas las sesiones.
Para indicar que la tabla que queremos crear es temporal añadimos a su nombre el
prefijo # (#nbTabla) para tablas temporales locales y el prefijo ## (##nbTabla) tablas
temporales globales.
Por ejemplo:
146
CREATE TABLE #trabajo (col1 INT PRIMARY KEY);
Esta instrucción crea una tabla temporal local llamada trabajo con una sola columna.
Las tablas temporales funcionan casi como las tablas normales con algunas diferencias.
nbTabla no puede tener más de 116 caracteres. Esto se debe a que si se crea una
tabla temporal local en un procedimiento almacenado o una aplicación que varios usuarios
pueden ejecutar al mismo tiempo, el Motor de base de datos tiene que ser capaz de
distinguir las tablas creadas por los distintos usuarios, lo consigue añadiendo
internamente un sufijo numérico a cada nombre de tabla temporal local. El nombre
completo de una tabla temporal tal como se almacena en la tabla sysobjects de tempdb
consta del nombre de la tabla especificado en la instrucción CREATE TABLE y el sufijo
numérico generado por el sistema.
Las tablas temporales globales se quitan automáticamente cuando la sesión que creó la
tabla finaliza y las tareas restantes han dejado de hacer referencia a ellas. La asociación
entre una tarea y una tabla se mantiene sólo durante la vida de una única instrucción
Transact-SQL. Esto significa que la tabla temporal global se quita al finalizar la última
instrucción Transact-SQL que estuviera haciendo referencia activamente a la tabla cuando
finalizó la sesión que la creó.
147
Unidad 8. El DDL, Lenguaje de Definición de Datos (XII)
Su sintaxis es:
Para que las reglas de integridad referencial se cumplan, no se puede eliminar una
tabla señalada por una restricción FOREIGN KEY. Primero se debe quitar la restricción
FOREIGN KEY o la tabla que tiene la clave ajena.
Se pueden quitar varias tablas de cualquier base de datos en una misma sentencia
DROP TABLE. Se irán eliminando en el mismo orden en que aparecen en la lista por lo
que podremos eliminar dos tablas relacionadas con una sola sentencia pero escribiendo la
tabla que contiene la clave ajena primero y después la tabla principal.
Ejemplo:
Elimina la tabla miTabla tanto su definición como los datos, índices definidos sobre ella
y permisos.
148
| <restriccion_tabla>
} [ ,...n ]
| DROP
{
[CONSTRAINT] nbRestriccion
|COLUMN nbColumna
} [,...n ]
| {CHECK|NOCHECK} CONSTRAINT {ALL|nbRestriccion[ ,...n ]}
| {ENABLE|DISABLE} TRIGGER {ALL | nbTrigger [ ,...n ] }
}
[ ; ]
Como muchas de las cláusulas las hemos estudiado con CREATE TABBLE, sólo
incidiremos en lo nuevo.
Para modificar una columna escribiremos la cláusula ALTER COLUMN seguida del
nombre de la columna que queremos modificar y la nueva definición, podemos cambiar su
tipo de datos indicando uno nuevo, hacer que la columna acepte o no valores nulos
(NULL|NOTNULL), cambiar la intercalación (COLLATE).
Con ADD ROWGUIDCOL hacemos que la columna sea GUID de filas y DROP
ROWGUIDCOL hacemos que ya no lo sea.
Ejemplo:
149
Unidad 8. El DDL, Lenguaje de Definición de Datos (XIII)
Para añadir una nueva columna o restricción utilizamos la cláusula ADD seguida de la
definición de lo que queremos añadir, para eso seguimos la misma sintaxis que para
definir las columnas y restricciones de tabla del CREATE TABLE.
Por ejemplo:
Añade una restricción de clave primaria sobre la columna codcli que ya existe en la
tabla.
Una restricción PRIMARY KEY no puede quitarse si existe un índice XML en la tabla.
Una columna no puede quitarse si se utiliza en un índice, en una restricción CHECK,
FOREIGN KEY, UNIQUE o PRIMARY KEY, DEFAULT.
Ejemplo:
Para finalizar, las dos últimas cláusulas nos permiten indicar si se tienen que comprobar
o no determinadas restricciones y habilitar y deshabilitar triggers.
DISABLE TRIGGER ALL deshabilita todos los triggers definidos sobre la tabla.
Cuando deshabilitamos un trigger, éste sigue definido, pero no entra en acción cuando se
produce el evento que debería activarlo. En cualquier momento lo podremos habilitar con
otra instrucción ALTER TABLE.
150
Una vista sólo se puede crear en la base de datos actual.
Para ejecutar CREATE VIEW, se necesita, como mínimo, el permiso CREATE VIEW
en la base de datos y el permiso ALTER en el esquema en el que se está creando la vista.
Sintaxis:
nbVista Es el nombre de la nueva vista. Los nombres de vistas deben seguir las reglas
de los identificadores.
Una vista no tiene por qué ser un simple subconjunto de filas y de columnas de una
tabla determinada. Es posible crear una vista que utilice más de una tabla u otras vistas
mediante una cláusula SELECT de cualquier complejidad.
Crea una vista con los datos de todos los empleados y de sus oficinas.
En este caso hemos tenido que definir alias de campo porque en el origen de la sentencia
SELECT existe duplicidad de nombres.
151
CREATE VIEW oficinas_EO
AS
SELECT * FROM oficinas WHERE region = ‘Este’;
UNION ALL
SELECT * FROM oficinas WHERE region = ‘Oeste’;
Por defecto las columnas de la vista heredan los nombres de las columnas de la
sentencia SELECT asociada, pero podemos cambiar estos nombres indicando una lista
de columnas después del nombre de la vista.
En definitiva se puede optar por utilizar la lista de columnas o definir alias de campo en
la sentencia SELECT.
Las columnas que se vayan a modificar en la vista deben hacer referencia directa a los
datos subyacentes de las columnas de la tabla, es decir que las columnas no se pueden
obtener de otra forma, como con una función de agregado: AVG, COUNT, SUM, MIN,
MAX, GROUPING, STDEV, STDEVP, VAR y VARP, o un cálculo.
Las columnas formadas mediante los operadores de conjunto UNION, UNION ALL,
CROSSJOIN, EXCEPT e INTERSECT equivalen a un cálculo y tampoco son
actualizables.
Las columnas que se van a modificar no se ven afectadas por las cláusulas GROUP
BY, HAVING o DISTINCT.
152
DROP VIEW [nbEsquema.]nbVista[ ,...n ] [ ; ]
Se eliminan las vista de la base de datos actual. Cuando eliminamos una vista
eliminamos su definición y los permisos asociados a ella.
Se pueden quitar varias vistas en una misma sentencia DROP VIEW escribiendo los
nombres de las vistas a eliminar separados por comas.
Para ejecutar DROP VIEW, como mínimo, se necesita el permiso ALTER en SCHEMA
o el permiso CONTROL en OBJECT.
Ejemplo:
Si eliminamos una tabla mediante DROP TABLE, se deben quitar explícitamente, con
DROP VIEW, las vistas basadas en esta tabla ya que no se quitarán por sí solas.
Funciona de forma parecida al índice de un libro donde tenemos el título del capítulo y
la página donde empieza dicho capítulo, en un índice definido sobre una determinada
columna tenemos el contenido de la columna y la posición de la fila que contiene dicho
valor dentro de la tabla.
Un índice simple está definido sobre una sóla columna de la tabla mientras que un
índice compuesto está formado por varias columnas de la misma tabla (tabla sobre la cual
está definido el índice.
Cuando se define un índice sobre una columna, los registros que se recuperen
utilizando el índice aparecerán ordenados por el campo indexado. Si se define un índice
compuesto por las columnas col1 y col2, las filas que se recuperen utilizando dicho índice
aparecerán ordenadas por los valores de col1 y todas las filas que tengan el mismo valor
de col1 se ordenarán a su vez por los valores contenidos en col2, función igual que la
cláusula ORDER BY vista en el tema de consultas simples.
153
Por ejemplo si definimos un índice compuesto basado en las columnas (provincia,
localidad), las filas que se recuperen utilizando este índice aparecerán ordenadas por
provincia y dentro de la misma provincia por localidad.
Índice único
Índice único es aquel en el que no se permite que dos filas tengan el mismo valor en la
columna de clave del índice. Es decir que no permite valores duplicados.
Inconvenientes
Los índices consumen recursos ya que cada vez que se realiza una operación de
actualización, inserción o borrado en la tabla indexada, se tienen que actualizar todas las
tablas de índice definidas sobre ella (en la actualización sólo es necesaria la actualización
de los índices definidos sobre las columnas que se actualizan).
Por estos motivos no es buena idea definir índices indiscriminadamente.
Hay que evitar crear demasiados índices en tablas que se actualizan con mucha
frecuencia y procurar definirlos con el menor número de columnas posible.
154
Es conveniente utilizar un número mayor de índices para mejorar el rendimiento de
consultas en tablas con pocas necesidades de actualización, pero con grandes
volúmenes de datos. Un gran número de índices contribuye a mejorar el rendimiento
de las consultas que no modifican datos, como las instrucciones SELECT, ya que el
optimizador de consultas dispone de más índices entre los que elegir para
determinar el método de acceso más rápido.
La indización de tablas pequeñas puede no ser una solución óptima, porque puede
provocar que el optimizador de consultas tarde más tiempo en realizar la búsqueda
de los datos a través del índice que en realizar un simple recorrido de la tabla. De
este modo, es posible que los índices de tablas pequeñas no se utilicen nunca; sin
embargo, sigue siendo necesario su mantenimiento a medida que cambian los datos
de la tabla.
Se recomienda utilizar una longitud corta en la clave de los índices agrupados. Los
índices agrupados también mejoran si se crean en columnas únicas o que no
admitan valores NULL.
Un índice único en lugar de un índice no único con la misma combinación de
columnas proporciona información adicional al optimizador de consultas y, por tanto,
resulta más útil.
Hay que tener en cuenta el orden de las columnas si el índice va a contener varias
columnas. La columna que se utiliza en la cláusula WHERE en una condición de
búsqueda igual a (=), mayor que (>), menor que (<) o BETWEEN, o que participa en
una combinación, debe situarse en primer lugar. Las demás columnas deben
ordenarse basándose en su nivel de diferenciación, es decir, de más distintas a
menos distintas.
155
Columna Es el nombre de la columna que forma parte del índice. Se pueden definir
índices compuestos escribiendo entre paréntesis los nombres de las columnas separados
por comas.
Ejemplos:
Crea un índice no agrupado sobre las columnas apellidos y nombre de la tabla Clientes
en la base de datos actual, las filas se ordenarán de forma ascendente por apellido y
dentro del mismo apellido por nombre.
Crea un índice no agrupado sobre las columnas edad y apellidos de la tabla Clientes en
la base de datos actual, las filas se ordenarán de forma descendente por edad y
ascendente por apellido. Aparecerán los clientes de mayor a menor edad y los clientes de
la misma edad se ordenarán por apellido (por orden alfabético).
Crea un índice único sobre la columna col de la tabla Clientes en la base de datos
actual, la columna col no podrá contener valores duplicados.
156
Sintaxis simplificada:
Ejemplo:
9.1. Introducción
Hasta ahora hemos estudiado sentencias SQL orientadas a realizar una determinada
tarea sobre la base de datos como definir tablas, obtener información de las tablas,
actualizarlas; estas sentencias las hemos ejecutado desde el editor de consultas del
MSSMS de una en una o a lo sumo una a continuación de la otra dentro de la misma
consulta formando un lote de instrucciones.
Las sentencias que ya conocemos son las que en principio forman parte de cualquier
lenguaje SQL. Ahora veremos que TRANSACT-SQL va más allá de un lenguaje SQL
cualquiera ya que aunque no permita:
Tipos de datos.
Definición de variables.
Estructuras de control de flujo.
Gestión de excepciones.
Funciones predefinidas.
157
Elementos para la visualización, que permiten mostrar mensajes definidos por el
usuario gracias a la cláusula PRINT.
Estas características nos van a permitir crear bloques de código orientados a realizar
operaciones más complejas. Estos bloques no son programas sino procedimientos o
funciones que podrán ser llamados en cualquier momento.
En SQL Server 2005 podemos definir tres tipos de bloques de código, los procedimientos
almacenados, los desencadenadores (o triggers) y funciones definidas por el usuario.
También están los procedimientos de usuario, los crea cualquier usuario que tenga los
permisos oportunos.
158
Los procedimientos almacenados se crean mediante la sentencia CREATE
PROCEDURE y se ejecutan con EXEC (o EXECUTE). Para ejecutarlo también se puede
utilizar el nombre del procedimiento almacenado sólo, siempre que sea la primera palabra
del lote. Para eliminar un procedimiento almacenado utilizamos la sentencia DROP
PROCEDURE.
Transact-SQL permite abreviar la palabra reservada PROCEDURE por PROC sin que
ello afecte a la funcionalidad de la instrucción.
Ejemplos:
Para eliminar varios procedimientos de golpe, indicamos sus nombres separados por
comas:
159
{[BEGIN] instrucciones [END] }
Como se puede deducir de la sintaxis (no podemos indicar un nombre de base de datos
asociado al nombre del procedimiento) sólo se puede crear el procedimiento almacenado
en la base de datos actual, no se puede crear en otra base de datos.
Si queremos definir un procedimiento temporal local el nombre deberá empezar por una
almohadilla (#) y si el procedimiento es temporal global el nombre debe de empezar por
##.
El nombre completo de un procedimiento almacenado o un procedimiento almacenado
temporal global, incluidas ##, no puede superar los 128 caracteres. El nombre completo
de un procedimiento almacenado temporal local, incluidas #, no puede superar los 116
caracteres.
Transact-SQL permite abreviar la palabra reservada PROCEDURE por PROC sin que
ello afecte a la funcionalidad de la instrucción.
Es equivalente a
Los parámetros son locales para el procedimiento; los mismos nombres de parámetro
se pueden utilizar en otros procedimientos. De manera predeterminada, los parámetros
sólo pueden ocupar el lugar de expresiones constantes; no se pueden utilizar en lugar de
nombres de tabla, nombres de columna o nombres de otros objetos de base de datos.
VARYING Sólo se aplica a los parámetros de tipo cursor por lo que se explicará
cuando se expliquen los cursores.
Indica que se trata de un parámetro de salida. El valor de esta opción puede devolverse
a la instrucción EXECUTE que realiza la llamada.
160
Procedimiento básico
En este caso, como la llamada es la primera del lote (va detrás del GO) podíamos
haber obviado la palabra EXEC y haber escrito directamente:
Dice_Hola
Aquí hemos hecho dos llamadas, una con el valor „Lo que quiera‟ y otra con el valor
„Otra cosa‟.
USE Biblio;
--DROP PROC VerUsuariosPoblacion; --La comentamos la primera
vez
GO
CREATE PROCEDURE VerUsuariosPoblacion @pob CHAR(30),@pro
CHAR(30)
AS
SELECT * FROM usuarios WHERE poblacion=@pob AND provincia =
@pro;
GO
161
EXEC VerUsuariosPoblacion Madrid, Valencia
Indicar el nombre del parámetro en la llamada también nos permite indicar los valores
en cualquier orden, la siguiente llamada es equivalente a la anterior, hemos invertido el
orden y se ejecuta igual:
En este procedimiento todos los parámetros son obligatorios, no deja llamar con un solo
parámetro.
Da error.
Para definir un parámetro opcional tenemos que asignarle un valor por defecto en la
definición del procedimiento.
En este caso, en la llamada sólo hemos indicado un valor, para el primer parámetro, el
parámetro opcional no lo hemos indicado y se rellenará con el valor por defecto que
indicamos en la definición.
162
CREATE PROCEDURE VerUsuariosPoblacion3 @a CHAR(2),@pob
CHAR(30)='Madrid',@pro CHAR(30)
AS
SELECT * FROM usuarios WHERE poblacion=@pob AND provincia =
@pro;
GO
EXEC VerUsuariosPoblacion3 a,@pro='Madrid'
Da error.
Esta forma da error, debemos utilizar la palabra DEFAULT o indicar el nombre de todos
los parámetros que van detrás del opcional.
Para poder recoger el valor devuelto por el procedimiento, en la llamada se tiene que
indicar una variable donde guardar ese valor.
Ejemplo:
Definimos el procedimiento ultimo_contrato que nos devuelte la fecha en que se firmó el
último contrato en una determinada oficina. El procedimiento tendrá pues dos parámetros,
uno de entrada para indicar el número de la oficina a considerar y uno de salida que
devolverá la fecha del contrato más reciente de entre los empleados de esa oficina.
USE Gestion
GO
CREATE PROC ultimo_contrato @ofi INT, @fecha DATETIME OUTPUT
AS
SELECT @fecha=(SELECT MAX(contrato) FROM empleados WHERE
oficina=@ofi)
GO
163
En la llamada, para los parámetros de salida, en vez de indicar un valor de entrada se
indica un nombre de variable, variable que recogerá el valor devuelto por el procedimiento
sin olvidar la palabra OUTPUT:
RETURN
RETURN [expresion_entera]
EXECUTE @variable_donde_recogemos_estado =
nombre_procedimiento @par, ...
Con el procedimiento del punto anterior no se puede utilizar esta forma de devolver un
resultado porque lo que se devuelve es una fecha, no es un valor entero, pero si
quisiéramos definir un procedimiento que nos devuelva el número de empleados de una
oficina podríamos hacerlo de dos formas:
USE Gestion
GO
CREATE PROC trabajadores @ofi INT, @num INT OUTPUT
AS
SELECT @num=(SELECT COUNT(*) FROM empleados WHERE
oficina=@ofi)
GO
164
Para obtener en la variable @var el resultado devuelto por el procedimiento la llamada
sería:
IF condicion
{ sentencia_sql | bloque_sql }
[ ELSE
{ sentencia_sql | bloque_sql } ]
165
Ejemplo: Si nos queremos guardar en una consulta todos los ejemplos para probarlos
en cualquier momento, es conveniente antes de los CREATE PROCEDURE colocar un
DROP PROCEDURE para que la instrucción CREATE no dé error si el procedimiento ya
existe, pero la primera vez la instrucción DROP PROC nos dará error porque el
procedimiento todavía no existe, así que lo mejor es ejecutar el DROP sólo si el
procedimiento existe, utilizando la función object_id(„nombre_de_objeto‟,‟tipo de objeto‟)
que nos devuelve el id del objeto y NULL si el objeto no existe.
WHILE condicion
{ sentencia_sql | bloque_sql }
| BREAK
| CONTINUE
166
CONTINUE Hace que se reinicie el bucle WHILE y omite las instrucciones que haya
después de la palabra clave CONTINUE.
Si condicion1 se cumple:
Si se cumple condicion2:
Si condición3 se cumple:
Si condición3 no se cumple:
Si se cumple condicion4:
Si no se cumple condicion4:
167
Si condicion2 no se cumple:
Si condicion1 no se cumple:
Hemos terminado
9.8. WAITFOR
Bloquea la ejecución de un lote, un procedimiento almacenado o una transacción hasta
alcanzar la hora o el intervalo de tiempo especificado, o hasta que una instrucción
especificada modifique o devuelva al menos una fila. Nosotros estudiaremos los dos
primeros casos.
Ejemplo:
PRINT CONVERT(CHAR(8),Getdate(),108);
WAITFOR DELAY '00:00:03'
PRINT CONVERT(CHAR(8),Getdate(), 108);
WAITFOR DELAY '00:03'
168
PRINT CONVERT(CHAR(8),Getdate(), 108);
9.9. GOTO
Altera el flujo de ejecución y lo dirige a una etiqueta.
Las etiquetas se indican mediante un nombre seguido del carácter:
GOTO NombreEtiqueta
Ejemplo:
BEGIN TRY
{sentencia_sql|bloque_sql}
END TRY
BEGIN CATCH
[{sentencia_sql|bloque_sql}]
END CATCH [ ; ]
169
procedimiento almacenado o desencadenador, el control se devuelve a la instrucción que
llamó al procedimiento almacenado o activó el desencadenador.
No se pueden utilizar instrucciones GOTO para entrar en un bloque TRY o CATCH pero
se puede utilizar para saltar a una etiqueta dentro del mismo bloque TRY o CATCH, o bien
para salir de un bloque TRY o CATCH.
Estas funciones devuelven NULL si se las llama desde fuera del ámbito del bloque
CATCH. Con ellas se puede recuperar información sobre los errores desde cualquier lugar
dentro del ámbito del bloque CATCH. Por ejemplo, en la siguiente secuencia de
comandos se muestra un procedimiento almacenado que contiene funciones de control de
errores. Se llama al procedimiento almacenado en el bloque CATCH de una construcción
TRY…CATCH y se devuelve información sobre el error.
170
-- Verificamos que el procedimiento que vamos a crear no
existe.
IF OBJECT_ID ('CalculaPrecio', 'P' ) IS NOT NULL
DROP PROCEDURE CalculaPrecio;
GO
-- Creamos el procedimiento
CREATE PROCEDURE CalculaPrecio @importe MONEY,@cant
INT,@precio MONEY OUTPUT
AS
BEGIN TRY
SELECT @precio=@importe/@cant;
END TRY
BEGIN CATCH
IF ERROR_NUMBER() = 8134 SELECT @precio=0
ELSE SELECT ERROR_NUMBER() as ErrorNumero,ERROR_MESSAGE() as
MensajeDeError;
END CATCH;
GO
-- Lo utilizamos
DECLARE @resultado MONEY
EXEC CalculaPrecio 1000, 0, @resultado OUTPUT
SELECT 'resul', @resultado
TRY…CATCH detecta todos los errores de ejecución que tienen una gravedad mayor
de 10 y que no cierran la conexión de la base de datos.
TRY…CATCH no detecta:
171
Errores de compilación, como errores de sintaxis, que impiden la ejecución de un
lote.
Errores que se producen durante la recompilación de instrucciones, como errores de
resolución de nombres de objeto que se producen después de la compilación debido
a una resolución de nombres diferida.
USE Gestion;
GO
BEGIN TRY
SELECT * FROM TablaQueNoExiste;
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() as ErrorNumero,ERROR_MESSAGE() as
MensajeDeError;
END CATCH
Intentamos ejecutar una SELECT de una tabla que no existe en la base de datos. El
error no se captura y el control se transfiere fuera de la construcción TRY…CATCH, al
siguiente nivel superior, en este caso salta el mensaje de error del sistema.
Al ejecutar la misma instrucción SELECT dentro de un procedimiento almacenado, el error
se producirá en un nivel inferior al bloque TRY y la construcción TRY…CATCH controlará
el error.
172
END CATCH;
En este caso como el error se produce dentro del procedimiento, el control del error se
devuelve al nivel superior (el que ha realizado el EXECUTE) por lo que es capturado por
el TRY y entra en el bloque CATCH, en vez de que salte el mensaje de error del sistema
saldrá el nuestro.
Desencadenadores DML
Desencadenadores DDL
Desencadenadores LOGON
173
NombreTrigger es el nombre que le queremos dar al desencadenador. No puede
comenzar con los símbolos # o ##.
AFTER indica que el desencadenador sólo se activa cuando todas las operaciones
especificadas en la instrucción SQL desencadenadora se han ejecutado correctamente.
Si se especifica el nombre del esquema del desencadenador hay que calificar también
el nombre de la tabla o vista.
Un desencadenador está diseñado para comprobar o cambiar los datos en base a una
instrucción de modificación o definición de datos; no debe devolver datos al usuario por lo
que se aconseja no incluir en un desencadenador instrucciones SELECT que devuelven
resultados ni las instrucciones que realizan una asignación variable. Si es preciso que
existan asignaciones de variable en un desencadenador, tenemos que utilizar la
instrucción SET NOCOUNT al principio del mismo para impedir la devolución de cualquier
conjunto de resultados.
Los desencadenadores DML usan las tablas lógicas deleted e inserted. Son de
estructura similar a la tabla en que se define el desencadenador, es decir, la tabla en que
se intenta la acción del usuario. Las tablas deleted e inserted guardan los valores
antiguos o nuevos de las filas que la acción del usuario puede cambiar.
Si un desencadenador INSTEAD OF definido en una tabla ejecuta una instrucción en la
tabla que normalmente volvería a activarlo, al desencadenador no se lo llama de forma
recursiva. En su lugar, la instrucción se procesa como si la tabla no tuviera un
desencadenador INSTEAD OF e inicia la cadena de operaciones de restricción y
174
ejecuciones de desencadenadores AFTER. Por ejemplo, si para una tabla se define un
desencadenador como INSTEAD OF INSERT, y éste ejecuta una instrucción INSERT en
la misma tabla, la instrucción INSERT ejecutada por el desencadenador INSTEAD OF no
vuelve a llamar al desencadenador. La instrucción INSERT ejecutada por el
desencadenador inicia el proceso que realiza las acciones de restricción y activa cualquier
desencadenador AFTER INSERT definido para la tabla.
Ejemplo:
USE Gestion8
GO
CREATE TRIGGER ActualizaVentasEmpleados
ON pedidos FOR INSERT
AS
UPDATE empleados SET ventas=ventas+inserted.importe
FROM empleados, inserted
WHERE numemp=inserted.rep;
GO
175
cuyo numemp coincida con el campo rep del pedido insertado (WHERE
numemp=inserted.rep).
Vemos que al insertar un pedido de 100 € del empleado 108, sus ventas han
aumentado en 100€.
ALTER TRIGGER
Ejemplo:
176
DISABLE TRIGGER {[NombreEsquema.]NombreTrigger [,...n] | ALL }
ON {NombreTablaVista | DATABASE | ALL SERVER} [;]
Ejemplo:
177
ENABLE TRIGGER ActualizaVentasEmpleados ON pedidos;
SELECT * FROM empleados WHERE numemp=108;
INSERT INTO pedidos
(numpedido,fechapedido,rep,clie,cant,importe,fab,producto)
VALUES (123456792,getdate(),108,2103,10,400,'Aci',41001)
SELECT * FROM empleados WHERE numemp=108;
Ejemplo:
178