Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Álvaro Herrera
1 de febrero de 2004
Índice
1. Introducción 2
3. Índices 18
4. Conclusión 20
1
1. Introducción
Este documento forma parte de una plática sobre el uso de PostgreSQL del CONSOL
2004, http://www.consol.org.mx/2004. El principal objetivo es mostrar la potencia-
lidad de este motor de datos a las personas que hayan trabajado con sistemas menos
capaces.
Gran parte del poder de PostgreSQL se expresa en la elaboración que puede llegar a
obtenerse en la construcción de consultas. Además, el optimizador de consultas consigue
un excelente trabajo buscando las mejores maneras de resolver las consultas; por este
mismo motivo, suele ser mucho más eficiente construir consultas complejas y permitirle
a PostgreSQL buscar los caminos más eficientes, que construir consultas simples y filtrar
o corregir los resultados en la aplicación. En este artı́culo se ilustran algunos de los
mecanismos para efectuar este traspaso de complejidad.
2.1. JOIN
Existen varias formas de JOIN. La más simple es CROSS JOIN, la cual consiste
en un producto cartesiano de los elementos de cada tabla.
1
Existen excepciones, particularmente con datawarehousing, pero en la gran mayorı́a de las aplicacio-
nes esta será la norma.
2
SELECT * FROM departamentos
depto id nombre
1 Desarrollo
2 Administración
SELECT * FROM empleados
empleado id nombre depto id
1 Alberto 1
2 Aldo 1
3 Enrique 2
4 Blanquita 2
5 Carola 2
SELECT * FROM departamentos, empleados
depto id nombre empleado id nombre depto id
1 Desarrollo 1 Alberto 1
2 Administración 1 Alberto 1
1 Desarrollo 2 Aldo 1
2 Administración 2 Aldo 1
1 Desarrollo 3 Enrique 2
2 Administración 3 Enrique 2
1 Desarrollo 4 Blanquita 2
2 Administración 4 Blanquita 2
1 Desarrollo 5 Carola 2
2 Administración 5 Carola 2
En el resultado, se muestra el conjunto de los cruces entre cada uno de los departa-
mentos con cada una de los empleados. Nótese que la columna depto_id está duplicada,
es decir, aparece la versión correspondiente a la tabla de departamentos y la de la tabla
de empleados.
Dado que generalmente esto no tiene mucha utilidad, y que tiene aspecto de ser un
error2 , es mejor usar la sintaxis explı́cita,
SELECT * FROM departamentos CROSS JOIN empleados
El resultado es idéntico.
El siguiente tipo de JOIN es el INNER JOIN. Éste entrega el subconjunto de los
resultados de CROSS JOIN que cumple algunas condiciones dadas. Se pueden expresar
estas condiciones de varias maneras distintas:
2
Personalmente, nunca he visto el uso de esto en una aplicación de la vida real, y cuando aparece este
tipo de comportamiento siempre ha sido debido a un error. Algún uso debe tener.
3
1. En una clásula WHERE:
1 SELECT *
2 FROM departamentos , empleados
3 WHERE departamentos . depto_id = empleados . depto_id
2. En FROM, indicando columnas que se llaman igual en ambas tablas y que deben
tener los mismos valores, con la cláusula USING:
1 SELECT *
2 FROM departamentos
3 JOIN empleados
4 USING ( depto_id )
4
4. En FROM, en todas las columnas que se llamen de la misma manera, con la
cláusula NATURAL:
1 SELECT *
2 FROM departamentos
3 NATURAL JOIN empleados
5
siguiente:
1 SELECT *
2 FROM departamentos
3 LEFT JOIN empleados
4 USING ( depto_id )
6
te:
1 SELECT *
2 FROM departamentos
3 FULL JOIN empleados
4 USING ( depto_id )
7
nera determinada. Para prevenir este problema, siempre debe usar una cláusula ORDER
BY en estas situaciones.
Listado 1: Ilustra el uso de una sola consulta para desplegar múltiples tablas
1 # !/ usr / bin / perl - w
2
3 use DBI ;
4
5 $dbh = DBI - > connect ( ’ dbi : Pg : dbname = alvherre ; user = alvherre ; port
=55432 ’) ;
6 if (! defined $dbh ) {
7 print $DBI :: errstr ;
8 exit ;
9 }
10
11 $sth = $dbh - > prepare ( < < EOSQL ) ;
12 SELECT depto_id , d . nombre as dn , empleado_id , e . nombre
13 FROM departamentos d
14 FULL JOIN empleados e
15 USING ( depto_id )
16 EOSQL
17
18 $sth - > execute ;
19
20 $prev_depto_id = undef ;
21 while ( $hr = $sth - > fetchrow_hashref ) {
22 if ( not defined $hr - >{ ’ depto_id ’ }) {
23 print " Empleado sin depto : " . $hr - >{ ’ nombre ’ }. " \ n " ;
24 $prev_depto_id = undef ;
25 next ;
26 }
27 if ( not defined $prev_depto_id or $prev_depto_id != $hr - >{ ’
depto_id ’ }) {
28 print " Departamento " . $hr - >{ ’ dn ’ }. " \ n " ;
29 }
30 if ( defined $hr - >{ ’ empleado_id ’ }) {
31 print " Empleado : " . $hr - >{ ’ nombre ’ }. " \ n " ;
32 }
33 $prev_depto_id = $hr - >{ ’ depto_id ’ };
34 }
8
Listado 2: Salida del programa de Listado 1
1 Departamento Desarrollo
2 Empleado : Alberto
3 Empleado : Aldo
4 Departamento Administración
5 Empleado : Enrique
6 Empleado : Blanquita
7 Empleado : Carola
8 Empleado sin depto : Francisco
9 Departamento Operaciones
A una sentencia SELECT anidada dentro de otra se le llama subconsulta; cuando este
anidamiento ocurre en la cláusula FROM, se conoce como subconsulta en FROM. Esta
caracterı́stica otorga mucho potencial adicional a la hora de construir consultas complejas.
La sintaxis consiste simplemente en usar un SELECT donde irı́a una tabla:
1 SELECT *
2 FROM ( SELECT * FROM empleados ) AS empleados
3 JOIN ( SELECT * FROM departamentos ) AS departamentos
4 USING ( depto_id )
9
1 SELECT
2 count (*) ,
3 inicio
4 FROM (
5 SELECT
6 servidor_id ,
7 EXTRACT ( epoch FROM inicio - $inicio ) /
8 EXTRACT ( epoch FROM $duracion_tick ) AS inicio
9 FROM
10 intervalos INNER JOIN eventos
11 ON (( foo . inicio , foo . final ) OVERLAPS ( date , fecha +
duracion ) )
12 WHERE
13 nodo_id = $nodoId
14 AND ( fecha , fecha + duracion ) OVERLAPS ( $inicio , $inicio +
$timespan )
15 AND NOT esOkStatus ( status )
16 ) as foo
17 GROUP BY inicio
18 ORDER BY inicio
2.3. UNION
10
nombre
Alberto
Aldo
Blanquita
Carola
Constanza
Enrique
Eva
Francisco
Veronica
Vicente
Observe que, pese a que no se usó una cláusula ORDER BY, los resultados aparecen
ordenados. Esto es un artefacto de implementación; UNION debe, por definición, en-
tregar sólo una copia de cada registro repetido. Para eliminar los repetidos, el motor los
ordena antes de entregarlos. Si desea que aparezcan todas las copias, use UNION ALL.
Esto además permite ahorrar los pasos de ordenamiento y unicidad durante la ejecución.
El siguiente ejemplo despliega la lista de todos los empleados y parientes. Observe que
la segunda columna tiene un significado distinto dependiendo de lo que se encuentre en
la primera; representa un ID de empleado o bien un ID de pariente.
1 SELECT ’e ’ AS tipo , empleado_id , NULL , nombre
2 FROM empleados
3 UNION ALL
4 SELECT ’p ’ , pariente_id , empleado_id , nombre
5 FROM parientes
11
1 SELECT ’p ’ AS tipo , empleado_id , parientes . nombre , depto_id
2 FROM parientes JOIN empleados USING ( empleado_id )
3 UNION ALL
4 SELECT ’e ’ , empleado_id , nombre , depto_id
5 FROM empleados
6 ORDER BY depto_id , empleado_id , tipo
2.4. INTERSECT
12
empleado id proyecto id
2 1
5 1
1 1
6 1
1 2
2 2
3 2
Para encontrar aquellos empleados que participan en ambos proyectos, use
INTERSECT6 .
1 SELECT empleado_id , empleados . nombre
2 FROM empleados
3 JOIN emp_proyectos USING ( empleado_id )
4 JOIN proyectos USING ( proyecto_id )
5 WHERE proyecto_id =1
6 INTERSECT
7 SELECT empleado_id , empleados . nombre
8 FROM empleados
9 JOIN emp_proyectos USING ( empleado_id )
10 JOIN proyectos USING ( proyecto_id )
11 WHERE proyecto_id =2
empleado id nombre
1 Alberto
2 Aldo
2.5. EXCEPT
Finalmente, EXCEPT sirve para calcular la diferencia entre dos conjuntos. Si quiere
encontrar a los integrantes de un equipo que no participan en otro, puede usar
6
Por supuesto, en este caso particular podrı́a usarse una consulta con agrupación, pero eso no es
aplicable al caso general.
13
1 SELECT empleado_id , empleados . nombre
2 FROM empleados
3 JOIN emp_proyectos USING ( empleado_id )
4 JOIN proyectos USING ( proyecto_id )
5 WHERE proyecto_id =1
6 EXCEPT
7 SELECT empleado_id , empleados . nombre
8 FROM empleados
9 JOIN emp_proyectos USING ( empleado_id )
10 JOIN proyectos USING ( proyecto_id )
11 WHERE proyecto_id =2;
empleado id nombre
5 Carola
6 Francisco
Ya vimos cómo pueden usarse las subconsultas en FROM como orı́genes de registros.
Además de eso, se pueden usar los resultados de una consulta para condicionar la búsqueda
en otra consulta en la cláusula WHERE.
La cláusula de subconsulta conceptualmente más simple es IN. Restringe uno o varios
atributos a un conjunto, entregado por la consulta en paréntesis. La versión más sencilla
consiste en entregar una lista de constantes:
1 SELECT *
2 FROM empleados
3 WHERE empleado_id IN (2 , 3)
7
Tenga en cuenta que en versiones antiguas de Postgres, las consultas de la forma IN (SELECT ...)
eran muy ineficientes. Si está usando una versión anterior a 7.4, considere reescribirla usando EXISTS.
14
depto id nombre
2 Administración
También pueden restringirse varios campos simultáneamente. Esto permite hacer co-
rrelaciones mucho más complejas, como en el ejemplo siguiente. Observe que la subconsul-
ta usa valores de la consulta externa; para cada evaluación de la cláusula de la subconsulta
estos valores son constantes.
1 SELECT *
2 FROM alarmas a
3 JOIN direcciones d USING ( direccion_id )
4 WHERE a . nodo_id = $nodoId
5 AND
6 ( a . nodo_id , a . direccion_id ) NOT IN (
7 SELECT n . nodo_id , n . direccion_id
8 FROM horas_inhibicion n
9 WHERE n . nodo_id = a . nodo_id
10 AND n . direccion_id = a . direccion_id
11 AND EXTRACT ( dow FROM now () ) = dia
12 AND now () BETWEEN n . inicio AND n . final
13 )
La extensión natural de la cláusula IN son las cláusulas ANY y ALL. Éstas usan
operadores que se aplican a todos los elementos retornados por la subconsulta. Si al menos
uno de ellos en el caso de ANY o todos ellos en el caso de ALL retornan verdadero, la
cláusula retorna verdadero. Por ejemplo, si agregamos una columna con los sueldos de
cada empleado
SELECT * FROM empleados
empleado id nombre depto id sueldo
1 Alberto 1 1200
2 Aldo 1 900
3 Enrique 2 1900
4 Blanquita 2 3000
5 Carola 2 1000
6 Francisco 1200
Podemos encontrar aquellos empleados que tienen sueldos superiores a todo el depar-
tamento 1:
15
1 SELECT *
2 FROM empleados
3 WHERE sueldo > ALL (
4 SELECT sueldo
5 FROM empleados
6 WHERE depto_id =1
7 )
16
empleado id nombre depto id sueldo
1 Alberto 1 1200
2 Aldo 1 900
3 Enrique 2 1900
5 Carola 2 1000
Ası́ como las subconsultas de FROM, las funciones que retornan conjuntos de regis-
tros pueden ser usadas como fuentes de registros en la cláusula FROM, como si fueran
una tabla adicional. La construcción de estas funciones es tema de otra charla. La invo-
cación puede ser de dos maneras distintas.
La primera manera involucra la creación de un tipo para este efecto. La función
debe retornar SETOF el-tipo. Por ejemplo,
1 CREATE TYPE empleado AS ( id INTEGER , nombre TEXT ) ;
2
3 CREATE FUNCTION e m p l e a d o s _ c o n _ s ob r e s u e l d o ()
4 RETURNS SETOF empleado ... ;
5
6 SELECT * FROM e m p l e a d o s _ c o n _ s ob r e s u e l d o () ;
id nombre
123 John Quijada
666 Guillermo Puertas
1024 Kilo Byte
Tenga en cuenta que para cada tabla que tenga en su base de datos, existe un tipo
con el mismo nombre y que tiene los campos de la tabla. Puede usar esos tipos como en
el ejemplo de arriba, sin necesidad de crearlos explı́citamente.
La segunda manera es equivalente, pero no requiere crear el tipo de antemano, es
decir, se usará un tipo anónimo. Un ejemplo equivalente al anterior serı́a
1 -- Note que la funcion debe retornar SETOF RECORD
2 CREATE FUNCTION e m p l e a d o s _ c o n _ s o b r e s u e l d o _ 2 ()
3 RETURNS SETOF RECORD ...
4
5 SELECT *
6 FROM e m p l e a d o s _ c o n _ s o b r e s u e l d o _ 2 ()
7 AS foo ( id INTEGER , nombre TEXT ) ;
17
Gran parte del poder del motor de datos se expresa en esta posibilidad, y el usuario es
responsable de utilizarla para su beneficio.
3. Índices
El estándar SQL no dice nada sobre los ı́ndices. Por supuesto; ¿cómo podrı́a decir?
Los ı́ndices son un problema de implementación, que tienen que ver más que nada con el
rendimiento, es decir, el tiempo que se tarda el sistema en responder las consultas. Para
SQL, este tiempo es irrelevante, lo único importante es que las consultas se resuelvan
correctamente.
Todos los sistemas de bases de datos, sin embargo, tienen que manejar ı́ndices de una
u otra manera, porque el usuario siempre cree tener el derecho de exigir que la consulta
se resuelva lo más rápido posible.
En PostgreSQL existen cuatro métodos de acceso al sistema de ı́ndices: B-Tree, R-
Tree, GiST y hash. Todos estos métodos son independientes del tipo de dato que se
esté indexando. Además de las capacidades básicas, es decir, indexar un campo de una
tabla, los ı́ndices en PostgreSQL ofrecen las siguientes capacidades:
Índices en múltiples campos Con los métodos GiST y B-Tree se pueden indexar
múltiples campos en un solo ı́ndice. Además, este ı́ndice podrá usarse para ha-
cer recorridos condicionados a un prefijo de la llave de indexación, es decir, si se
tiene un ı́ndice en los campos (a, b, c), el optimizador considerará la opción de
utilizar el ı́ndice cuando una consulta especifique los campos (a, b, c), o bien sólo
(a, b), o bien sólo (a). Como desperfecto, observe que el ı́ndice no podrá usarse con
una condición que especifique los campos (a, c, b).
Observe además que se pueden poner valores NULL en los ı́ndices, aunque una
búsqueda del tipo WHERE campo IS NULL no podrá usar el ı́ndice.
Índices parciales Hay ocasiones en que un ı́ndice que cubra sólo parte de la tabla pue-
de ayudarle a acelerar consultas que requieran acceder esa parte de la tabla. Un
ejemplo tı́pico se aplica cuando se tiene un tabla con datos atribuibles a una fecha,
y las búsquedas más frecuentes ocurren en rangos de fechas bien determinados; si
mantiene ı́ndices en los registros de los últimos dos meses, acelera la búsqueda en
esos registros, y al mismo tiempo permite que los ı́ndices sean livianos evitando in-
dexar los registros más antiguos. Además, puede crear el ı́ndice para el mes siguiente
un par de dı́as antes de que empiece, de manera que esté disponible en cuanto sea
necesario, y eliminar el ı́ndice obsoleto un par de dı́as después.
18
1 CREATE INDEX i nd i c e_ 2 0 04_febrero
2 ON documentos ( docto_id )
3 WHERE (
4 EXTRACT ( year FROM fecha ) = 2004
5 AND EXTRACT ( month FROM fecha ) = 2
6 )
Los ı́ndices funcionales pueden ayudar a optimizar muchas situaciones en las que
se necesita utilizar funciones para hacer búsquedas en las tablas. Recuerde que
teniendo los ı́ndices que sus consultas más frecuentes requieran puede tener un
gran impacto sobre su aplicación, pero también tenga presente que las operaciones
de inserción y modificación tienen que mantener los ı́ndices al dı́a, y por lo tanto
tienen un impacto para estas operaciones. ¡Asegúrese de no tener ı́ndices inútiles!
Finalmente, considere la posibilidad de tener ı́ndices parciales funcionales.
Ordenamiento fı́sico Cuando se hacen búsquedas sobre rangos de valores, o bien cuan-
do las búsquedas caen mayoritariamente sobre un conjunto pequeño de los datos, y
tiene un ı́ndice que agrupe este conjunto, puede obtener mejoras significativas en los
tiempos de respuesta si la tabla está ordenada fı́sicamente siguiendo dicho ı́ndice. El
comando CLUSTER sirve para reordenar fı́sicamente una tabla de acuerdo a un
ı́ndice. Observe que el ordenamiento no se conserva durante operaciones posteriores
INSERT, DELETE o UPDATE; si efectúa estas operaciones sobre la tabla, ne-
cesitará ejecutar CLUSTER nuevamente con cierta periodicidad. Para reordenar
una tabla siguiendo un ı́ndice especı́fico, utilice CLUSTER indice ON tabla.
Observe que CLUSTER recuerda en qué ı́ndice fue reordenada una tabla la últi-
ma vez. Para reordenar una tabla siguiendo esa información, ejecute simplemente
CLUSTER tabla. Para reordenar todas las tablas que han sido reordenadas pre-
viamente, ejecute simplemente CLUSTER.
19
4. Conclusión
Este documento mostró el uso de algunas de las caracterı́sticas de SELECT, in-
cluyendo las varias formas de JOIN, los usos de subconsultas tanto en FROM como
en IN, y los operadores de conjuntos UNION, INTERSECT y EXCEPT. Además,
mostró algunas caracterı́sticas del sistema de ı́ndices de PostgreSQL que pueden ayudar
a optimizar patrones de uso particulares. Utilizando algunas de las técnicas mostradas,
se espera que el lector pueda conseguir optimizar en gran medida sus aplicaciones.
20