Prácticas de Bases de Datos

Anuncio
Prácticas de
Bases de Datos
o
2 de Sistemas
Fernando Cano Espinosa
Juan David González Cobas (In memoriam)
Curso 2011-2012
1. TOMA DE CONTACTO
1. Virtualización. Para el desarrollo de las prácticas de Bases de Datos vamos a utilizar un
gestor de bases de datos llamado Postgres. Lo haremos sobre Ubuntu, una distribución de
linux, que correrá como una máquina virtual. El software de virtualización es VirtualBox,
recientemente adquirido por Oracle. Por tanto, para comenzar las prácticas, desde Windows
y con vuestra cuenta autenticada por Ident, ejecutáis VirtualBox y arrancáis la máquina virtual denominada Linux. Una vez hecho esto se os comunicará el nombre de usuario y clave,
para poder empezar a utilizar Ubuntu. Accederéis primeramente como usuario privilegiado,
para poder crear vuestro propio usuario, que será con el que trabajaréis a lo largo del curso.
Para crearlo podéis utilizar el comando adduser y suministrar el nombre se usuario con el
que trabajaréis, vuestro nombre completo (el real) y la password. Desde este momento esta
cuenta ya no será necesario utilizar la cuenta privilegiada, a no ser que necesitemos realizar
alguna labor administrativa.
2. Qué es postgreSQL. De forma sencilla diremos que postgreSQL es un gestor de bases de
datos. En http://www.postgresql.org tendréis acceso a todo la información que necesitéis. postgreSQL, normalmente llamado postgres, es una ’aplicación’ que nos permite entre otras muchas
cosas definir y manipular bases de datos. Se trata de software libre que corre sobre LinuxUnix aunque existen versiones para otros sistemas operativos (MAC OS, Windows). Como gestor es posiblemente uno de los que incluye un SQL (lenguaje con el que realizaremos nuestras operaciones) más estándar. Para manejar postgreSQL disponemos, entre otras
muchas opciones, de un intérprete de comandos (un terminal-based front-end), el psql, con el
que podremos trabajar en modo texto. Existen otras aplicaciones en modo gráfico como pgadmin3 que podremos utilizar en alguna ocasión. También es posible trabajar con prostgreSQL
a través de la web con php (phppgadmin). Nosotros vamos a trabajar con la más sencilla. Dependiendo del ordenador que nos ha tocado es posible que no dispongamos de un servidor
postgres bien configurado. Para ello será necesario seguir las instrucciones del documento
configuracion_postgres.pdf que se encontrará en la página web de las prácticas.
3. Ejecutar psql. Para empezar cada alumno ya debería tener su propia cuenta en Linux. Una
vez iniciada la sesión abrimos un terminal y vamos a ejecutar la siguiente secuencia de comandos. Ejecutamos el psql de la forma
$ psql -U username base_de_datos
NOTA: en lo que sigue vamos a utilizar como username un usuario de la base de datos que
debe estar creado en cada máquina y que tiene como nombre ’alumno’ y de password ’alumna’. Si no estuviera creado podemos crearlo tal y como se detalla en configuracion_postgres.pdf.
En el caso del parámetro base_de_datos, se trata de una base de datos que ya exista. En una
instalación normal de prostgreSQL existe una base de datos de nombre template1 a la que
siempre es posible conectarnos. Por lo tanto nuestra orden será:
$ psql -U alumno template1
Una vez en el intérprete veremos el prompt
template1=>
que nos indica que la base de datos a la que estamos conectados es template1. Se trata de
una base de datos patrón que no vamos a utilizar. El símbolo > significa que no somos superusuarios de postgreSQL, en otro caso se nos mostrará el símbolo #.
4. Un rápido ejemplo. Ahora, sin salir del psql vamos a crear nuestra propia base de datos
mediante la orden
template1=> create database miprueba;
No olvidar el ’;’ para finalizar la orden. Ya tenemos creada nuestra base de datos de nombre
miprueba. Si lo que queremos ahora es conectarnos a ella debemos ejecutar
1
template1=> \c miprueba;
En este momento el prompt debe indicar el nombre de nuestra base de datos, que se encuentra vacía.
miprueba=>
Vamos a crear una tabla t1 con dos columnas id , nombre, una de tipo entero (int) y otra de
tipo cadena (text). Ejecutaremos
miprueba=> create table t1 (id int, nombre text);
Como la tabla está vacía vamos a insertar un par de filas:
miprueba=> insert into t1 values (1, ’Ana’);
miprueba=> insert into t1 values (2, ’Luis’);
Comprobamos que el contenido de la tabla es el que esperamos.
miprueba=> select id, nombre from t1;
Ante un imprevisto cambio de sexo vamos a modificar nuestros datos de la forma:
miprueba=> update t1 set nombre=’Luisa’ where id=2;
Ahora, ahítos de alegría, volvemos a dejar las cosas como al principio y salimos ejecutando
la secuencia de órdenes:
miprueba=> delete from t1;
miprueba=> drop table t1;
miprueba=> \c template1
template1=> drop database miprueba;
template1=> \q
5. El entorno de trabajo. El intérprete de comandos psql puede parecer un poco incómodo para
aquellos que no estén acostumbrados a trabajar con este tipo de entornos, por ello vamos a
ver un par de detalles para agilizar nuestro trabajo. Como muchas órdenes pueden ocupar
más que una línea, es normal hacer uso de un editor. Podemos definir el editor que preferimos (kwrite, kate, vi, emacs, gedit, joe, pico, etc.), para ello desde la línea de comandos de
Linux, ejecutaremos la orden
$ export PSQL_EDITOR=mieditor_preferido
Si entramos de nuevo en el intérprete psql, nos daremos cuenta que con las flechas del cursor
podemos recuperar las órdenes que dimos anteriormente; psql guarda un histórico de nuestras órdenes por lo que fácilmente podemos volver a crear nuestra base de datos miprueba.
Podemos optar por no usar un editor y trabajar con múltiples lineas de tal forma que mientras que no finalicemos la orden, el intérprete nos presentará un prompt que va cambiando
en la sucesivas líneas. Por ejemplo si queremos insertar un cliente nuevo, en una sola línea
podemos poner.
miprueba=> insert into t1 values (3,’Pedrito’);
Pero vamos a ver lo que sucede si lo hacemos en diferentes líneas:
miprueba=> insert
miprueba-> into t1
miprueba-> values (3,
miprueba(> ’Pedrito
miprueba’> ’
miprueba(> )
miprueba-> ;
Observar que el prompt va cambiando dependiendo del estado en que que nos encontremos
( =>, ->, ’>) y que la orden se finaliza con el ’;’.
Cada orden se guarda en un buffer, que se llama el ’query buffer’. Si invocamos al editor nos
permitirá modificar la última orden. Para hacer esto tan sólo tendremos que ejecutar \edit o
2
sencillamente \e. Al salir del editor el intérprete ejecutará lo que hemos escrito. Invocar al
editor y veréis el insert anterior, y podéis insertar otro cliente.
Otra alternativa, posiblemente más cómoda, sobre todo si lo que queremos es ejecutar un
fichero entero de órdenes, consiste en mantener un editor abierto en una ventana, y en otra
ventana, tener el intérprete de psql. Supongamos que el fichero que editamos se corresponde
con /home/pepe/bdatos/miFicheroDeOrdenes.sql; nos colocamos en el directorio /home/pepe/bdatos,
(dentro de psql, con la orden \cd, nos podemos cambiar al directorio que especifiquemos, sin
necesidad de salir del intérprete); y para ejecutar el fichero bastaría poner en el prompt
miprueba=>\i miFicheroDeOrdenes.sql
6. Crear la base de datos. En el directorio de prácticas de la asignatura existen varios ficheros
que deberíais copiar en un directorio vuestro. Estos son:
createPedidos.sql: es un scrpit sql que contiene las órdenes de creación de la base de
datos y las tablas.
insertPedidos.sql: es un scrpit sql que inserta valores en las tablas.
Pedidos.pdf y pedidos_uml.pdf: enunciado, diagrama Entidad Relación y tablas resultantes de la base de datos de pedidos
Una vez hecho desde el psql ejecutar las órdenes:
template1=> \i createpedidos.sql
pedidos=> \i insertpedidos.sql
En este momento deberíais tener creada la base de datos e insertados los datos en las tablas
y por tanto listos para empezar a realizar consultas.
7. Los metadatos. Ahora vamos a ver algunas órdenes para ir manejándonos.
template1=> \l - muestra las base de datos
template1=> \dt - muestra las tablas de la actual base de datos
template1=> \d tabla - muestra información de la tabla
template1=> \du - muestra las usuarios
template1=> \e - edita el query-buffer
template1=> \i - ejecuta un fichero
template1=> \o - redirecciona la salida
template1=> \h - muestra la ayuda SQL
Ejecutar algunas de estas órdenes. Aún así se recomienda tener a mano, ya sea en papel o en
pantalla, los nombres de las tablas y de las columnas.
8. Practicamos. Vamos a conectarnos a la base de datos de pedidos y a ejecutar varias órdenes
sencillas sobre la tabla de clientes desde el psql. Para ello se puede editar la orden en la
propia línea de comandos o utilizando el editor elegido.
a) mostrar toda la tabla
select *
from clientes;
b) mostrar el id_cliente y el nombre de todos los clientes
select id_cliente,nombre
from clientes;
c) mostrar toda la información de los clientes de Gijón (ojo con las mayúsculas y minúsculas)
select *
from clientes
where ciudad= ’GIJON’;
3
d) mostar el id_cliente y el nombre de todos los clientes que sean de Gijón o de Mieres
select id_cliente, nombre
from clientes
where ciudad= ’GIJON’ or ciudad = ’MIERES’;
9. Borrar la base de datos y volver al mismo punto en el que estábamos. Teniendo los scripts de
creación e inserción de datos es fácil reconstruir nuestra base de datos de forma rápida.
10. Vamos a utilizar la ayuda y ver algunos ejemplos de cómo se expresa la sintaxis de órdenes
SQL. Para mostrar la ayuda de postgreSQL abrimos con un navegador el fichero
file:///usr/share/doc/postgresql-doc-8.4/html/index.html
La sintaxis de las órdenes SQL se encuentra descrita en la P art V I de la documentación.
También podemos llegar a ella directamente abriendo el fichero:
file:///usr/share/doc/postgresql-doc-8.4/html/reference.html
Es posible, no hayamos instalado la documentación, y que no encontremos estos ficheros.
Para instalar la documentación bastaría con ejecutar:
$ sudo apt-get install postgresql-doc-8.4
pero siempre nos quedará París o la página oficial
http://www.postgresql.org
Es muy importante familiarizarse con esta notación, y no solo para aprender SQL. Se trata,
más o menos, de una gramática libre de contexto, y lo que se nos muestra son las reglas de
producción. En esta notación tenemos que recordar que:
Los símbolos terminales (palabras que aparecen tal cual en las órdenes) se expresan en
mayúsculas( ej. SELECT ).
Los símbolos no terminales (símbolos que producen cadenas de otros símbolos) se expresan en minúsculas (ej. condition, expression)
Las barras verticales expresan distintas alternativas.
Los corchetes expresan que lo que hay dentro es opcional.
Las llaves expresan alguna de las alternativas que se incluyen debe aparecer.
Los puntos suspensivos se utilizan para describir una repetición, generalmente separados por comas, del símbolo que se encuentra a la izquierda de los puntos suspensivos.
Al aparecer entre corchetes se describe una lista de uno a más elementos.
Para cada orden encontraremos generalmente con los siguientes apartados
El nombre de la orden.
La sintaxis de la orden (synopsis).
Una descripción.
Una explicación de los parámetros.
Unas notas.
Unos ejemplos.
La compatibilidad con otras versiones de SQL.
Algunos aspectos de la sintaxis no son fáciles de encontrar en esta documentación, pero
dado que se trata de un SQL bastante estándar cualquier sintaxis nos puede servir para crear
consultas sencillas. Aún así es recomendable echar un vistazo al Chapter 7. Queries donde
podemos ir desmenuzando la sintaxis del select.
4
11. Seguimos probando algunas órdenes sencillas:
Mostrar (SELECT ) los productos cuyo precio esté entre 90 y 120. Disponemos del predicado between para ello.
select *
from productos
where precio between 90 and 120;
Insertamos (IN SERT ) un nuevo producto
insert into productos (id_producto, nombre, precio)
values (7,’KIWI’, 100);
Observar que no hemos introducido un valor para existencias. Para ello hemos escrito
los nombres (y el orden) de las columnas que vamos a introducir. También podríamos
haber utilizado la palabra reservada null para dicha columna.
Actualizamos (U P DAT E) las existencias del nuevo producto e incrementamos el precio
en un 10 %
update productos
set existencias= 50, precio = precio * 1.1
where id_producto=7;
Borramos (DELET E) el nuevo producto
delete from productos
where id_producto=7;
5
2. EL SELECT COMO UNA FORMA DE VIDA
1. Nos conectamos a nuestra base de datos de pedidos (crearla si fuera necesario)
2. Es fundamental ver y entender bien el significado del Producto (join) de tablas. Cuando
queremos recuperar información que se encuentra repartida entre distintas tablas vamos a
basarnos en dos ideas: la primera es la de repetir columnas en diferentes tablas para poder
enlazar dicha información y la segunda es utilizar la operación producto cartesiano de conjunto (entendiendo que una tabla en un conjunto de filas).
Para realizar el producto cartesiano (cross join) de pedidos con clientes podemos expresarlo así:
select *
from pedidos , clientes;
o de forma algo más elegante así:
select *
from pedidos cross join clientes;
Para ver más claramente lo que hace esta consulta vamos a mostrar las columnas pedidos.id_cliente
y clientes.id_cliente
select
pedidos.id_cliente,
clientes.id_cliente
from pedidos cross join clientes;
Añadimos al from la tabla de empleados y mostramos también pedidos.id_empleado y
empleados.id_empleado. Observamos cuántas filas obtenemos. Y nos damos cuenta que
equivale al producto de los cardinales de los tres conjuntos de filas (tablas).
select
p.id_cliente,
c.id_cliente,
p.id_empleado,
e.id_empleado
from clientes c cross join pedidos p cross join empleados e;
Estudiar sobre la anterior consulta
select *
from pedidos cross join clientes;
qué condición que debe imponerse a las filas en la cláusula W HERE para obtener un
resultado coherente.
select *
from pedidos cross join clientes
where pedido.id_cliente = clientes.id_cliente;
Modificar esta consulta de forma que no sea necesario imponer la condición en el W HERE.
Para ello podemos realizar el producto natural (N AT U RAL JOIN ). O también el IN N ER
JOIN que como veremos presenta dos alternativas, una con utilizando la cláusula ON
y otra con la cláusula U SIN G.
Con un N AT U RAL JOIN
select *
from pedidos natural join clientes;
6
el problema del N AT U RAL JOIN es que siempre que encuentre dos columnas con el
mismo nombre, impone la condición de que sean iguales.
Con un IN N ER JOIN con la la clausula ON .
select *
from pedidos inner join clientes
on pedido.id_cliente = clientes.id_cliente;
Con un IN N ER JOIN con la la clausula U SIN G. Suele ser la más adecuada.
select *
from pedidos inner join clientes using (id_cliente);
3. Obtener el id_pedido, el nombre del cliente para todos los pedidos.
select
id_pedido,
clientes.nombre
from pedidos inner join clientes using(id_cliente);
4. Obtener el id_pedido, el nombre del cliente y el nombre del empleado para los pedidos atendidos por empleados de Mieres.
select
id_pedido,
clientes.nombre,
empleados.nombre
from pedidos inner join clientes using (id_cliente)
inner join empleados using (id_empleado)
Where empleados.ciudad=’MIERES’;
5. Obtener el id_pedido, el nombre del cliente y el nombre del empleado para los pedidos realizados el 2 de septiembre de 2006 que han sido atendidos por empleados de Mieres.
select
id_pedido,
clientes.nombre,
empleados.nombre
from pedidos inner join clientes using (id_cliente)
inner join empleados using (id_empleado)
Where empleados.ciudad=’MIERES’ and pedidos.fecha=’2006-09-02’;
6. Introducir las variables de tupla (con el primer objetivo de ahorrarse escribir mucho).
select
p.id_pedido,
c.nombre,
e.nombre
from pedidos p inner join clientes c using (id_cliente)
inner join empleados e using (id_empleado)
Where e.ciudad=’MIERES’ and p.fecha=’2006-09-02’;
7. Ver cómo se renombran las columnas para distinguir entre el nombre del cliente y del empleado.
select
p.id_pedido,
c.nombre as cliente,
e.nombre as empleado
from pedidos p inner join clientes c using (id_cliente)
inner join empleados e using (id_empleado)
Where e.ciudad=’MIERES’ and p.fecha=’2006-09-02’;
7
8. Obtener el id_pedido, el nombre del cliente, el nombre de los productos y cantidad de los
mismos para todos los pedidos.
select
p.id_pedido,
c.nombre as nom_cli,
pro.nombre as nom_pro,
cantidad
from pedidos p inner join clientes c using (id_cliente)
inner join detalles_pedido dp using(id_pedido)
inner join productos pro using (id_producto);
9. Nombre del cliente y nombre del empleados que comparten ciudad
select
c.nombre as NOM_CLI,
e.nombre as "NOMBRE EMPLEADO"
from clientes c inner join empleados e on c.ciudad = e.ciudad;
10. Parejas de nombres de clientes que comparten ciudad
select
c1.nombre as cliente1,
c2.nombre as cliente2
from clientes c1 cross join clientes c2
where c1.ciudad = c2.ciudad and c1.id_cliente < c2.id_cliente;
O lo que es lo mismo:
select
c1.nombre as cliente1,
c2.nombre as cliente2
from clientes c1 inner join clientes c2 using (ciudad)
where c1.id_cliente < c2.id_cliente;
O lo que es lo mismo:
select
c1.nombre as cliente1,
c2.nombre as cliente2
from clientes c1 inner join clientes c2
on (c1.ciudad = c2.ciudad and c1.id_cliente < c2.id_cliente);
8
3.
EL SELECT Y SUS AMIGOS
1. Comparaciones de cadenas (Pattern Matching, LIKE). Para comparar cadenas tenemos el
operador LIKE que podemos utilizar en combinación con los caracteres ’ %’ y ’_’ que tienen,
respectivamente, el mismo significado que el ’*’ y ’?’ en linux y otros sistemas operativos.
Mostrar el identificador y nombre de aquellos productos que cumplen lo siguiente:
• empiezan por ’P’
• terminan en ’AS’
• contienen las subcadena ’TA’
En postgreSQL podemos utilizar el ILIKE para que no se distingan mayúsculas de minúsculas (case insensitive) o si realmente lo queremos hacer con estilo, utilizar expresiones regulares.
select id_producto,
nombre
from productos
where
nombre like ’P%’ and
nombre ilike ’%As’ and
nombre ilike ’%TA%’;
Es más potente el similar to:
select id_producto,
nombre
from productos
where
nombre similar to ’P%(TAS|TA%AS)’; -- ’P%TA(S|%AS)’
Y mucho más mediante profesional mediante expresiones regulares tipo POXIS:
select
from
where
id_producto,
nombre
productos
nombre ~* ’^(P.*(TAS|TA.*AS))$’;
El operador ~ realmente comprueba si la cadena contiene una subcadena denotada por el
patrón (al añadirle el *, conseguimos que la comparación sea case insensitive). Por eso para
conseguir que la cadena completa sea exactamente igual que el patón se le añade el principio
de cadena (^) y el final de cadena ($).
2. El operador IN (NOT IN )
Mostrar el identificador y nombre de aquellos productos pedidos por ’PEPE’
select
pro.id_producto,
pro.nombre as producto
from pedidos p inner join clientes c using (id_cliente)
inner join detalles_pedido dp using(id_pedido)
inner join productos pro using (id_producto)
where c.nombre like ’PEPE’;
Mostrar el identificador y nombre de aquellos productos NO pedidos por ’PEPE’
select
id_producto,
nombre
from productos
where id_producto not in (
select id_producto
from clientes c inner join pedidos p using (id_cliente)
inner join detalles_pedido dp using(id_pedido)
where c.nombre = ’PEPE’);
9
En muchos casos el IN permite hacer intersecciones de conjuntos y el N OT IN nos sirve
para la diferencia de conjuntos.
3. Otras comparaciones
Mostrar el identificador y nombre de aquellos productos que cuestan más que alguno
de los que ha pedido ’PEPE’
select
id_producto,
nombre
from productos
where precio > any (
select precio
from clientes c inner join pedidos p using (id_cliente)
inner join detalles_pedido dp using (id_pedido)
inner join productos pro using (id_producto)
where c.nombre = ’PEPE’);
De otra manera algo más rápida.
select
id_producto,
nombre
from productos
where precio > (
select min(precio)
from clientes c inner join pedidos p using (id_cliente)
inner join detalles_pedido dp using (id_pedido)
inner join productos pro using (id_producto)
where c.nombre = ’PEPE’);
4. El predicado EXISTS
Mostrar pedidos con su fecha en los que no aparece ningún producto.
select
id_pedido,
fecha_pedido
from pedidos p
where not exists (select *
from detalles_pedido dp
where p.id_pedido=dp.id_pedido);
5. La UNION
Mostrar los nombres de empleados o clientes que viven en MIERES
select nombre
from empleados
where ciudad =’MIERES’
UNION
select nombre
from clientes
where ciudad =’MIERES’;
Añadimos la calle
select nombre , calle
from empleados
where ciudad =’MIERES’
UNION
select nombre ,calle
from clientes
where ciudad =’MIERES’;
Más fino
10
select nombre || ’ es un empleado de ’ || ciudad as "Los de Mieres"
from empleados
where ciudad =’MIERES’
UNION
select nombre || ’ es un cliente de ’ || ciudad
from clientes
where ciudad =’MIERES’;
6. La INTERSECCIÓN (INTERSECT)
Mostrar ciudades en las que viven clientes y empleados.
select ciudad
from empleados
INTERSEC
select ciudad
from clientes;
Prodríamos hacer con un IN
select distinct ciudad
from empleados
where ciudad IN (select ciudad from clientes);
O incluso mediante un CROSS JOIN
select distinct e.ciudad
from empleados e cross join clientes c
where c.ciudad ilike e.ciudad;
7. El MINUS (EXCEPT)
Mostrar ciudades en las que viven empleados y no viven clientes.
select ciudad
from empleados
EXCEPT
select ciudad
from clientes;
Prodríamos hacerlo con un NOT IN
select distinct ciudad
from empleados
where ciudad NOT IN (select ciudad from clientes);
Mostrar productos que no ha pedido PEPE.
select
id_producto,
nombre
from productos
EXCEPT
select pro.id_producto,
pro.nombre
from pedidos p inner join clientes c using (id_cliente)
inner join detalles_pedido dp using(id_pedido)
inner join productos pro using (id_producto)
where c.nombre like ’PEPE’;
11
8. El inexistente CON T AIN S
El CON T AIN S sirve para implementar la inclusión de conjuntos (⊆), a diferencia del IN (∈)
que nos permite comprobar si un elemento pertenece a un conjunto. Por tanto los parámetros
de CON T AIN S son dos conjuntos (el resultado de dos select’s). Se suele utilizar en consultas del tipo ’Para todo’.
Mostrar los clientes que han pedido todos los productos que ha pedido ’PEPE’. Vamos
a definirnos dos conjuntos: A y B.
Conjunto A: productos pedidos por cada cliente (c1 ).
Conjunto B: productos pedidos por clientes cuyo nombre es PEPE.
Si se cumple que B ⊆ A entonces el cliente c1 deber ser mostrado.
select nombre
from clientes c1
where
( select id_producto -- Conjunto A: productos pedidos por el cliente c1
from pedidos p inner join detalles_pedido dp using (id_pedido)
where p.id_cliente = c1.id_cliente)
contains
( select id_producto -- Conjunto B: productos pedidos por PEPE
from clientes c2 inner join pedidos p using (id_cliente)
inner join detalles_pedido dp using (id_pedido)
where c2.nombre ilike ’PEPE’);
Si se observa el predicado contains devuelve TRUE si el conjunto A contiene al conjunto
B. Lamentablemente SQL no implementa este predicado y tiene que ser simulado con
la condición NOT EXISTS (B MINUS A), es decir NOT EXISTS (B EXCEPT A).
select nombre
from clientes c1
where NOT EXISTS (
select id_producto --B: productos pedidos por PEPE
from clientes c2 inner join pedidos p using (id_cliente)
inner join detalles_pedido dp using (id_pedido)
where c2.nombre = ’PEPE’
EXCEPT
select id_producto --A: productos pedidos por cada cliente c1
from pedidos p inner join detalles_pedido dp
using (id_pedido )
where p.id_cliente = c1.id_cliente);
Insistimos: clientes que han sido atendidos por todos los empleados de Gijón.
select nombre
from clientes c1
where NOT EXISTS (
select id_empleado --Conjunto B: empleados de Gijón
from empleados
where ciudad ilike ’Gijon’
EXCEPT
select id_empleado --Conjunto A: empleados que han atendido a c1
from pedidos p
where p.id_cliente=c1.id_cliente);
Demostramos nuestra capacidad de aprendizaje: mostrar aquellos productos que aparecen en todos los pedidos realizados en los primeros 275 días del 2006.
12
select nombre
from productos PRODUCTOX
where NOT EXISTS (
select id_pedido -- B: pedidos de los primeros 275 dias del 2006
from pedidos
where fecha_pedido between ’2006-01-01’
and ’2006-01-01’::date + ’275 days’::interval)
EXCEPT
select id_pedido -- A: pedidos en los que aparece PRODUCTOX
from detalles_pedido dp
where dp.id_producto= PRODUCTOX.id_producto
Como se puede observa postgres incluye tipos de datos (date, interval) y funciones muy
potentes para trabajar con fechas.
9. Algo sobre Valores nulos.
Para poder manejar los valores nulos en SQL tenemos que tener en cuenta algunos criterios.
Además SQL nos ofrece algunos operadores que nos facilitan el tratamiento de los valores
null.
En general, las expresiones que incluyen valores nulos devuelven como resultado null.
En las expresiones lógicas se siguen las reglas mostradas en las tablas del epígrafe 9.1.
Logical Operators.
El operador IS (value IS null) nos devuelve true si value contiene el valor null.
La expresión CASE nos permite, entre otras cosas, controlar que si un resultado es null
podemos cambiarle su valor para poder trabajar mejor con él. Por ejemplo, en algunas
expresiones aritméticas es útil convertir valores nulos en ceros (ver 9.12.1).
La función COALESCE() también nos permite algo parecido a lo anterior pero de otra
forma(ver 9.12.2).
La función N U LLIF () es como la función inversa a la anterior (ver 9.12.3).
Mostrar los identificadores de pedido y los del empleado, pero si no tiene un empleado
asociado mostrar un ’0’.
select
id_pedido,
coalesce (id_empleado, 0) as empleado
from pedidos;
Mostrar los identificadores de pedido y los del empleado, pero si no tiene un empleado
asociado mostrar la cadena ’sin asignar’.
select
id_pedido,
coalesce (id_empleado::text, ’sin asignar’) as empleado
from pedidos;
select
id_pedido,
case
when id_empleado is null then ’sin asignar’
else id_empleado::text
end
from pedidos;
Con el otro tipo de CASE
select
id_pedido,
case id_empleado
when null then ’sin asignar’
else id_empleado::text
end
from pedidos;
13
Mostrar los empleados que no han atendido ningún pedido utilizando el operador NOT
IN, ver qué pasa y arreglarlo.
select
from
where
id_empleado,
nombre
empleados
id_empleado NOT IN (
select id_empleado
from pedidos);
Como vemos la cosa no funciona, debido a que en la lista existe un null.Lo podemos
arreglar así:
select
from
where
id_empleado,
nombre
empleados
id_empleado NOT IN (
select coalesce (id_empleado, 0)
from pedidos);
Pero no deja de ser una chapuza. Nadie nos dice que un posible id_empleado pudiera
tomar el valor 0 y dejaría de funcionar. Resulta más elegante esta solución:
select
from
where
id_empleado,
nombre
empleados
id_empleado NOT IN (
select id_empleado
from pedidos
where id_empleado is not null);
14
10. Empezamos a agrupar (group by, having, count() , max(), etc.)
Mostrar el número de pedidos atendidos por ’MARIA’.
select count (*)
from pedidos p inner join empleados e using(id_empleado)
where e.nombre= ’MARIA’;
Empleado y número de pedidos atendidos por él.
select
id_empleado,
count (*)
from
pedidos
group by id_empleado;
Nombre del empleado y número de pedidos atendidos por él.
select
e.nombre,
count (*)
from pedidos p inner join empleados e using (id_empleado)
group by p.id_empleado, e.nombre;
Fecha y número de empleados que han atendido algún pedido en dicha fecha
select
fecha_pedido,
count (*)
from
pedidos
group by fecha_pedido;
El anterior count() falla, ya que cuenta las filas y por tanto nos cuenta los nulos y los
repetidos.
select
fecha_pedido,
count (id_empleado)
from
pedidos
group by fecha_pedido;
Ya no cuenta los nulos pero sí los repetidos.
select
fecha_pedido,
count (id_empleado)
from
pedidos
group by fecha_pedido;
Ahora ya va.
Identificador de Pedido, fecha del mismo, número de de productos diferentes que incluye y la cantidad media de productos por pedido.
select
p.id_pedido,
p.fecha_pedido,
sum (cantidad) as TOTAL ,
avg (cantidad) as MEDIA
from pedidos p inner join detalles_pedido dp using (id_pedido)
group by p.id_pedido, p.fecha_pedido;
Identificador de Pedido y coste total del mismo para aquellos pedidos que incluyen más
de dos productos distintos.
select
dp.id_pedido,
sum (cantidad * precio) as TOTAL
from productos pro inner join detalles_pedido dp using (id_producto)
group by dp.id_pedido
having count (id_producto) >= 2
Para aquellos productos cuyas existencias que no cubren las cantidades pedidas, obtener el nombre de estos productos, sus existencias, las cantidades pedidas y las cantidades
necesarias que la empresa tiene que comprar para poder cubrir todos los pedidos.
15
select
pro.nombre,
pro.existencias,
sum (cantidad) as TOTAL_PEDIDO,
sum (cantidad) - pro.existencias AS COMPRAR
from productos pro inner join detalles_pedido dp using (id_producto)
group by pro.id_producto, existencias, nombre
having pro.existencias < sum (cantidad)
11. Ordenamos un poco
Nombres de productos y el número de pedidos en los que aparecen, ordenados por el
nombre alfabéticamente y por el número de pedidos de forma descendente
select
pro.nombre,
count (id_pedido) as NUM_PEDIDOS,
sum(cantidad * precio) as TOTAL_PEDIDO
from productos pro inner join detalles_pedido dp
using (id_producto)
group by pro.id_producto, nombre
order by 2 desc, TOTAL_PEDIDO desc, pro.nombre ;
Como se ve en el ejemplo, en la cláusula order by podemos referirnos a una columna por
su nombre, por su alias o etiqueta o por su posición en la cláusula select.
16
4.
Práctica 4: EL SELECT Y LOS JOINS
Nombre de los empleados con los identificadores de los pedidos atendidos por ellos
select
e.nombre,
p.id_pedido
from empleados e inner join pedidos p using (id_empleado);
Nombre de TODOS los empleados con los identificadores de los pedidos atendidos por
ellos
select
e.nombre,
p.id_pedido
from empleados e left outer join pedidos p using (id_empleado);
Productos y el número de pedidos en los que aparecen
select
pro.nombre,
count (id_pedido) AS NUM_PEDIDOS
from productos pro left outer join detalles_pedido dp
using (id_producto)
group by pro.id_producto, nombre;
Vamos a insertar un nuevo producto y a probar un par de consultas.
insert into productos values (222,’NARANJAS’, 10, 20);
select
pro.nombre,
count (*) AS NUM_FILAS
from productos pro left outer join detalles_pedido dp
using (id_producto)
group by id_producto, nombre;
Ahora agrupamos por dp.id_producto y vemos el resultado.
select
pro.nombre,
count (*) AS NUM_FILAS
from productos pro left outer join detalles_pedido dp
using (id_producto)
group by dp.id_producto, nombre;
Como se ve, agrupa los dos productos de nombre NARANJAS, ya que en la columna
dp.id_producto ambos tienen el valor nulo. Y en este caso null=null le asocia el valor
true.
Relaciones entre clientes y empleados. Mostrando TODOS los clientes y TODOS los
empleados aunque no haya realizado ningún pedido. Incluir el identificador del pedido
para entender mejor la consulta
select
c.nombre as cliente,
e.nombre as empleado,
p.id_pedido
from clientes c left outer join pedidos p using(id_cliente)
full outer join empleados e using (id_empleado);
Observar que en la siguiente consulta obtenemos una fila menos. Seguimos mostrando
todos los empleados y todos los clientes, pero ahora la fila asociada al pedido 106 ya no
se muestra. Esto es debido a que al hacer el right de pedidos con empleados se pierde la
información del pedido 106.
select
c.nombre as cliente,
e.nombre as empleado,
p.id_pedido
from clientes c full outer join (pedidos p right outer join empleados e
using (id_empleado)) using(id_cliente);
17
Descargar