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