Sei sulla pagina 1di 20

Trucos y no-Trucos con PostgreSQL

Álvaro Herrera

1 de febrero de 2004

Índice
1. Introducción 2

2. SELECT - más allá de lo trivial 2


2.1. JOIN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2.2. Subconsultas en FROM . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3. UNION . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.4. INTERSECT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.5. EXCEPT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.6. Subconsultas en WHERE . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.7. Funciones que retornan tablas . . . . . . . . . . . . . . . . . . . . . . . . 17

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. SELECT - más allá de lo trivial


SELECT es, en general, la operación más frecuente que recibirá una base de datos1 ;
por lo tanto, es importante aprovechar todo el poder que otorga y extraer la mayor
cantidad de información posible en cada invocación.
Tenga en cuenta que gran parte de la funcionalidad utilizada con SELECT es apli-
cable también a las cláusulas INSERT, UPDATE y DELETE.

2.1. JOIN

JOIN es simplemente la manera de seleccionar columnas de dos o más tablas en una


misma lista de resultados, poniendo sus registros lado a lado. Usualmente, hay columnas
traslapadas en cada tabla que son las que especifican el criterio de JOIN, es decir, la
manera en que se construyen los registros.
Tabla 1 traslape Tabla 2

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

depto id nombre empleado id nombre depto id


1 Desarrollo 2 Aldo 1
1 Desarrollo 1 Alberto 1
2 Administración 5 Carola 2
2 Administración 4 Blanquita 2
2 Administración 3 Enrique 2

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 )

depto id nombre empleado id nombre


1 Desarrollo 2 Aldo
1 Desarrollo 1 Alberto
2 Administración 5 Carola
2 Administración 4 Blanquita
2 Administración 3 Enrique
Observe que se entrega sólo una de las copias de la columna que se repite.

3. En FROM, especificando una condición arbitraria con la cláusula ON:


1 SELECT *
2 FROM departamentos
3 JOIN empleados
4 ON ( departamentos . depto_id = empleados . depto_id )

depto id nombre empleado id nombre depto id


1 Desarrollo 2 Aldo 1
1 Desarrollo 1 Alberto 1
2 Administración 5 Carola 2
2 Administración 4 Blanquita 2
2 Administración 3 Enrique 2
Observe que, contrario a lo que sucede con la cláusula USING, aquı́ no se suprime
la columna repetida.

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

depto id nombre empleado id


Observe que el resultado no contiene ningún registro. ¿Por qué sucede esto? La
respuesta es que se busca coincidencia en todas las columnas que tienen el mismo
nombre; en este caso, las columnas depto_id y nombre. Como no hay empleados
que tengan el mismo nombre que un departamento, no hay ninguna coincidencia.
Esta cláusula puede causar sorpresas, por lo que se recomienda evitarla en favor de
USING u ON.

Finalmente tenemos el OUTER JOIN similar a un INNER JOIN, con la excepción


de que se agregan los registros de una tabla que no tengan correspondencia en la otra
poniendo las columnas de la segunda en NULL. Esto ya no es un subconjunto de los
resultados de CROSS JOIN.
Agregaremos un registro a cada tabla para los ejemplos de esta sección.
SELECT * FROM departamentos
depto id nombre
1 Desarrollo
2 Administración
3 Operaciones
SELECT * FROM empleados
empleado id nombre depto id
1 Alberto 1
2 Aldo 1
3 Enrique 2
4 Blanquita 2
5 Carola 2
6 Francisco NULL
Un OUTER JOIN puede hacerse incluyendo los registros de la tabla de la izquierda
(LEFT OUTER JOIN), los registros de la tabla de la derecha (RIGHT OUTER
JOIN) o los de ambas (FULL OUTER JOIN)3 .
Si quisiéramos encontrar todos los departamentos y los empleados que pertenecen
a ellos, incluyendo aquellos departamentos vacı́os, podemos usar una consulta como la
3
La palabra OUTER es siempre opcional.

5
siguiente:
1 SELECT *
2 FROM departamentos
3 LEFT JOIN empleados
4 USING ( depto_id )

depto id nombre empleado id nombre


1 Desarrollo 2 Aldo
1 Desarrollo 1 Alberto
2 Administración 5 Carola
2 Administración 4 Blanquita
2 Administración 3 Enrique
3 Operaciones
Si quisiéramos encontrar el listado todos los empleados y los departamentos a los que
pertenecen, podemos simplemente invertir el OUTER JOIN:
1 SELECT *
2 FROM departamentos
3 RIGHT JOIN empleados
4 USING ( depto_id )

depto id nombre empleado id nombre


1 Desarrollo 1 Alberto
1 Desarrollo 2 Aldo
2 Administración 3 Enrique
2 Administración 4 Blanquita
2 Administración 5 Carola
6 Francisco
También podrı́amos invertir el orden en que aparecen las tablas, y usar un LEFT
OUTER JOIN, es decir,
1 SELECT *
2 FROM empleados
3 LEFT JOIN departamentos
4 USING ( depto_id )

En este segundo ejemplo las columnas aparecen en otro orden4 .


Por último, se puede obtener la lista de todos los empleados y todos los departamentos,
incluyendo aquellos que no tengan departamentos ni empleados asociados, respectivamen-
4
Por supuesto, en la construcción de aplicaciones nunca debe usarse SELECT *. La lista de columnas
del resultado siempre debe ser explı́cita, para evitar que las aplicaciones queden inservibles si se agregan
o eliminan columnas o éstas cambian de orden dentro de la tabla.

6
te:
1 SELECT *
2 FROM departamentos
3 FULL JOIN empleados
4 USING ( depto_id )

depto id nombre empleado id nombre


1 Desarrollo 1 Alberto
1 Desarrollo 2 Aldo
2 Administración 3 Enrique
2 Administración 4 Blanquita
2 Administración 5 Carola
6 Francisco
3 Operaciones
Esta clase de operaciones puede servir para ahorrar consultas a la hora de construir
un despliegue de varios departamentos y sus empleados, haciendo una tabla por departa-
mento sin hacer una consulta por departamento. Esto permite ahorrarse tantas consultas
como departamentos haya. Vea el Listado 1, y la salida del programa en el Listado 2.
Observe que el código de control del ciclo es más complejo que si se hiciera una consulta
para averiguar los departamentos y luego una consulta por cada departamento para ave-
riguar los empleados. En casos pequeños como este, la complejidad adicional puede ser
injustificada, pero una solución de esta clase puede acarrear una gran mejora de rendi-
miento por el ahorro de consultas en un escenario más complejo. También observe que
si necesita extraer muchos datos acerca de cada departamento, puede ser ineficiente que
éstos aparezcan en el registro de cada empleado, y quizás sea mejor usar una consulta
para extraer los departamentos y otra para extraer todos los empleados, indicando para
cada una a qué departamento pertenecen.
Observe también que hay dos mecanismos para obtener y desplegar la cantidad de
empleados en cada departamento. Una es usar dos consultas (una para departamentos
y otra para empleados), y en la primera hacer el JOIN con empleados, agrupándolos
y calculando count(*). Esto es más limpio, pero la tabla de empleados se recorre dos
veces. Otra es permitir que el lenguaje externo lleve un contador que se reinicie para cada
departamento. Si sabe que el programa recorrerá el resultado completo, no tiene costo
adicional y desde el punto de vista de rendimiento es superior porque no exige a la base
de datos recorrer dos veces la tabla de empleados. Naturalmente, si la aplicación no va a
recorrer completo el conjunto del resultado, esto no se aplica5 .
Finalmente, observe que no necesariamente aparecerán en registros contiguos todos
los empleados de un departamento, inutilizando en gran medida este truco. Esto puede
suceder porque no hemos pedido explı́citamente que los resultados se ordenen de una ma-
5
Por otro lado, si la aplicación no desea recorrer el conjunto completo del resultado, quizás deberı́a
restringir más la consulta que está efectuando.

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

2.2. Subconsultas en FROM

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 )

Por supuesto, el propósito es permitir operaciones más complejas, como ilustra el


siguiente ejemplo.

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

Los resultados de una consulta representan un conjunto de registros. Las cláusulas


presentadas en esta sección permiten operar estos conjuntos. Para poder ser operados,
los conjuntos deben ser compatibles, es decir, tener la misma cantidad de columnas; a su
vez, cada una de ellas debe ser compatible, es decir, debe poder hacerse conversión de
tipos entre ellas (ej. float y integer, varchar(200) y text, etc.).
El primer operador es UNION, el cual crea un conjunto que contiene los elementos
de ambos conjuntos. Para ilustrarlo, agreguemos una tabla a nuestro modelo.
SELECT * FROM parientes
pariente id empleado id nombre
1 1 Veronica
2 2 Constanza
3 3 Eva
4 3 Vicente
5 5 Enrique
1 SELECT nombre FROM empleados
2 UNION
3 SELECT nombre FROM parientes

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

tipo empleado id ?column? nombre


e 1 Alberto
e 2 Aldo
e 3 Enrique
e 4 Blanquita
e 5 Carola
e 6 Francisco
p 1 1 Veronica
p 2 2 Constanza
p 3 3 Eva
p 4 3 Vicente
p 5 5 Enrique
Con estos datos no es mucho lo que se puede hacer, debido a la inconsistencia. Para
permitir hacer algo más interesante con un resultado de este tipo es necesario algo más
consistente. Por ejemplo,

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

tipo empleado id nombre depto id


e 1 Alberto 1
p 1 Veronica 1
e 2 Aldo 1
p 2 Constanza 1
e 3 Enrique 2
p 3 Eva 2
p 3 Vicente 2
e 4 Blanquita 2
e 5 Carola 2
p 5 Enrique 2
e 6 Francisco
Esto permite usar un bloque de iteración similar al del Listado 1 para construir una
tabla donde se muestre para cada departamento la lista de empleados y los parientes
asociados a él.

2.4. INTERSECT

El segundo operador de conjuntos es INTERSECT, el cual entrega la intersección


de dos conjuntos. Para ver un ejemplo de esto, agreguemos a nuestro modelo de datos
una tabla de proyectos y una que asocie empleados con proyectos.
SELECT * FROM proyectos
proyecto id nombre
1 Plan Z
2 E.P.E. (El Proyecto E.P.E.)
SELECT * FROM emp proyectos

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

2.6. Subconsultas en WHERE

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)

empleado id nombre depto id


2 Aldo 1
3 Enrique 2
Por supuesto, lo realmente interesante es usar una consulta7 :
1 SELECT *
2 FROM departamentos
3 WHERE depto_id IN (
4 SELECT depto_id
5 FROM empleados
6 WHERE nombre = ’ Enrique ’
7 )

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 )

empleado id nombre depto id sueldo


3 Enrique 2 1900
4 Blanquita 2 3000
O bien aquellos que tienen sueldos inferiores a cualquiera del departamento 2, excepto
la gente del departamento 2:
1 SELECT *
2 FROM empleados
3 WHERE sueldo < ANY (
4 SELECT sueldo
5 FROM empleados
6 WHERE depto_id =2
7 )
8 AND ( depto_id < > 2
9 OR depto_id IS NULL )

empleado id nombre depto id sueldo


1 Alberto 1 1200
2 Aldo 1 900
6 Francisco 1200
Observe que puede usar cualquier operador con ANY y ALL; por ejemplo, un ope-
rador de traslape geométrico para encontrar aquellos objetos que traslapan con todos
(ALL) o alguno (ANY) de los resultados de una consulta.
Finalmente, la cláusula EXISTS se utiliza para correlacionar la subconsulta con la
consulta externa. La cláusula retorna verdadero si la subconsulta retorna al menos un
registro. Por ejemplo, los empleados que tienen al menos un pariente registrado pueden
verse con
1 SELECT *
2 FROM empleados
3 WHERE EXISTS (
4 SELECT 1
5 FROM parientes
6 WHERE empleados . empleado_id = parientes . empleado_id
7 )

16
empleado id nombre depto id sueldo
1 Alberto 1 1200
2 Aldo 1 900
3 Enrique 2 1900
5 Carola 2 1000

2.7. Funciones que retornan tablas

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 ) ;

Es importante tener en cuenta que en una sentencia SELECT se pueden mezclar,


según sea necesario, los tres tipos de fuentes de registros: tablas, subconsultas y funciones.

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 )

Índices funcionales A veces puede desear construir consultas condicionadas al valor


que regrese una expresión. Un buen ejemplo es el uso de ı́ndices para optimizar la
búsqueda en campos de texto. Un ı́ndice puede usarse para optimizar un campo de
texto cuando el patrón de búsqueda tiene un prefijo constante (como ’este es mi
prefijo %’, pero no ’ % este texto no tiene prefijo’). Se puede optimizar la búsqueda
en un sufijo si se crea un ı́ndice en la cadena inversa de la cadena almacenada, y
cuando se haga una búsqueda se utilice el inverso del patrón:
1 CREATE INDEX idx_inverso
2 ON direcciones fn_inverso ( url )
3
4 SELECT ...
5 FROM direcciones
6 WHERE fn_inverso ( url ) LIKE fn_inverso ( ’\ % tabla . html ’) ;

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

Potrebbero piacerti anche