Professional Documents
Culture Documents
SQL AVANZADO
CONTENIDO
1 INTRODUCCIÓN.......................................................................................................................................3
1.1 PROPÓSITO DEL DOCUMENTO................................................................................................................3
1.2 INTRODUCCIÓN AL STRUCTURED QUERY LANGUAGE ..........................................................................3
2 REVISIÓN DE CONCEPTOS BÁSICOS DE SQL.................................................................................4
2.1 COMPONENTES DEL SQL.......................................................................................................................4
2.2 CONSULTAS DE SELECCIÓN ..................................................................................................................4
2.3 CRITERIOS DE SELECCIÓN .....................................................................................................................6
2.4 AGRUPAMIENTO DE REGISTROS Y FUNCIONES AGREGADAS ................................................................7
2.5 CONSULTAS DE MANIPULACIÓN DE DATOS ...........................................................................................9
3 CONSULTAS DE UNIÓN INTERNAS (JOINS)...................................................................................11
3.1 CONSULTAS DE COMBINACIÓN ENTRE TABLAS ...................................................................................11
3.2 INNERS JOINS .......................................................................................................................................12
3.3 OUTER JOINS ........................................................................................................................................14
3.4 CONSULTAS DE AUTOCOMBINACIÓN ..................................................................................................15
3.5 CONSULTAS DE COMBINACIONES NO COMUNES .................................................................................16
4 CONSULTAS DE UNIÓN EXTERNAS (UNIONS) ..............................................................................17
5 SUBCONSULTAS.....................................................................................................................................19
5.1 EMPLEO DE SUBCONSULTAS................................................................................................................19
5.2 GRUPOS CON SUBCONSULTAS..............................................................................................................21
5.3 CASOS PRÁCTICOS ...............................................................................................................................22
6 OPTIMIZACIÓN DE CONSULTAS ......................................................................................................23
6.1 CONSEJOS GENERALES.........................................................................................................................23
6.2 EMPLEO DE ÍNDICES .............................................................................................................................25
6.3 SENTENCIAS CON JOINS. ......................................................................................................................31
6.4 SENTENCIAS CON SUBCONSULTAS.......................................................................................................32
6.5 VISTAS .................................................................................................................................................33
7 OPTIMIZACIÓN ORACLE: DATABASE TUNING ...........................................................................34
7.1 GENERALIDADES PARA LA OPTIMIZACIÓN DE SENTENCIAS SQL ........................................................34
7.2 TIPOS DE TABLAS ................................................................................................................................36
7.3 INDICES, TIPOS DE ÍNDICES..................................................................................................................42
7.4 PLAN DE EJECUCIÓN: INTERPRETACIÓN DE RESULTADOS...................................................................44
7.5 UTILIDADES DE INFORMACIÓN ESTADÍSTICA SOBRE LA EJECUCIÓN DE SENTENCIAS .........................45
7.6 ESTADÍSTICAS ......................................................................................................................................47
8 DICCIONARIO DE DATOS ORACLE..................................................................................................48
1 Introducción
1.1 Propósito del Documento
El presente documento es una introducción que pretende establecer las normas para el mejor uso del gestor de Base
de Datos. Este conocimiento es fundamental para poder desarrollar procesos eficientes evitando cargas innecesarias al
sistema y aprovechando al máximo los recursos disponibles. El objetivo de este documento es, por tanto, servir de
guía a los desarrolladores que vayan a trabajar con Sql para que puedan desde un principio generar procesos
eficientes, ahorrando trabajos de ajuste posteriores.
Hay que recordar que por bueno que haya sido el trabajo de los DBA a la hora de crear y ajustar la base de datos en el
servidor, una aplicación ineficiente hará inútil todo este esfuerzo. El conseguir un rendimiento eficiente es, por tanto,
una tarea compartida de los administradores y los desarrolladores.
Los ejemplos se han tratado de orientar hacia los gestores de bases de datos Oracle y SQL Server.
Usado para todas las funciones de bases de datos, incluyendo administración, creación de esquemas y datos
recuperables
Pero como sucede con cualquier sistema de normalización hay excepciones para casi todo; de hecho, cada motor de
bases de datos tiene sus peculiaridades y lo hace diferente de otro motor, por lo tanto, el lenguaje SQL normalizado
(ANSI) ha sido adaptado por los DBMS según peculiaridades, aunque si se puede asegurar que cualquier sentencia
escrita en ANSI será interpretable por cualquier motor de datos.
En determinadas ocasiones nos puede interesar incluir una columna con un texto fijo en una consulta de selección:
FROM Products
WHERE CategoryID = 3;
SELECT PostalCode, LastName, Address FROM Employees ORDER BY PostalCode, LastName DESC;
2.2.3.1 *
El Motor de base de datos selecciona todos los registros que cumplen las condiciones de la instrucción SQL.
Tip: No conveniente abusar de él ya que obligamos al motor de la base de datos a analizar la estructura de la
tabla para averiguar los campos que contiene, es mucho más rápido indicar el listado de campos deseados.
2.2.3.2 Distinct
Omite los registros que contienen datos duplicados en los campos seleccionados.
2.2.4 Alias
En determinadas circunstancias es necesario asignar un nombre a alguna columna determinada de un conjunto
devuelto. Para resolver todas ellas tenemos la palabra reservada AS que asigna el nombre que deseamos a la columna
deseada. AS no es una palabra reservada de ANSI.
También podemos asignar alias a las tablas dentro de la consulta de selección, en esta caso hay que tener en cuenta
que en todas las referencias que deseemos hacer a dicha tabla se ha de utilizar el alias en lugar del nombre.
Tip: Esta nomenclatura [Tabla].[Campo] se debe utilizar cuando se está recuperando un campo cuyo nombre
se repite en varias de las tablas que se utilizan en la sentencia. Es aconsejable utilizar esta nomenclatura para
evitar el trabajo que supone al motor de datos averiguar en que tabla está cada uno de los campos indicados en
la cláusula SELECT.
Si a cualquiera de las anteriores condiciones le anteponemos el operador NOT el resultado de la operación será el
contrario al devuelto sin el operador NOT.
El operador denominado IS se emplea para comparar dos variables de tipo objeto <Objeto1> Is <Objeto2>. Este
operador devuelve Verdadero si los dos objetos son iguales.
SELECT * FROM Employees WHERE BirthDate > '1947-09-15' AND BirthDate < '1960-04-01';
SELECT * FROM Employees WHERE (BirthDate > '1956-09-15' AND BirthDate < '1965-04-01') OR City =
'London' AND Title = 'Sales Manager';
2.3.4 El Operador In
Este operador devuelve aquellos registros cuyo campo indicado coincide con alguno de los en una lista:
Para la formación de grupos adicionamos, a la forma básica de la sentencia SELECT vista anteriormente, la orden
GROUP BY ubicada antes de ORDER BY, como se muestra a continuación:
COUNT(*) Cantidad de registros que hay en la tabla, incluyendo los valores nulos.
AVG(columna) Calcula valor medio del grupo, sin considerar los valores nulos.
La lista de columnas a mostrar en la consulta puede contener las funciones de grupo, así como la columna o expresión
usada para formar los grupos en la orden GROUP BY. En una misma consulta no se pueden mezclar funciones de
grupo con columnas o funciones que trabajan con registros individuales.
GROUP BY es opcional. Los valores de resumen se omiten si no existe una función SQL agregada en la instrucción
SELECT.
Tip: Los valores Null en los campos GROUP BY se agrupan y no se omiten. No obstante, los valores Null no se
evalúan en ninguna de las funciones SQL agregadas.
Las cargas de pedidos por cliente es un buen ejemplo para mostrar el uso de los grupos:
COUNT(Freight) "CARGA",
MIN(Freight) "MINIMA",
AVG(Freight) "MEDIA",
MAX(Freight) "MAXIMA",
SUM(Freight) "TOTAL"
FROM Customers,Orders
WHERE Customers.CustomerID=Orders.CustomerID
GROUP BY CompanyName;
Tip: El orden en las consultas por grupos, cuando no está presente la orden ORDER BY, está dado por la
columna que forma los grupos. Si deseamos cambiar ese orden, como es el caso de ordenar por el valor total de
ventas, se debe adicionar al final la orden ORDER BY SUM(VALOR).
FROM Orders
HAVING es similar a WHERE, determina qué registros se seleccionan. Una vez que los registros se han agrupado
utilizando GROUP BY, HAVING determina cuales de ellos se van a mostrar.
Tip: Se utiliza la cláusula WHERE para excluir aquellas filas que no desea agrupar, y la cláusula HAVING
para filtrar los registros una vez agrupados.
Tip: La función Count no cuenta los registros que tienen campos Null a menos que coloquemos el carácter
comodín asterisco (*). Si utiliza un asterisco, Count calcula el número total de registros, incluyendo aquellos
que contienen campos Null. Count(*) es considerablemente más rápida que Count(Campo).
Min(expr) ó Max(expr)
Expr pueden incluir el nombre de un campo de una tabla, una constante o una función (la cual puede ser intrínseca o
definida por el usuario pero no otras de las funciones agregadas de SQL).
Sum(expr)
En donde expr representa el nombre del campo que contiene los datos que desean sumarse o una expresión que
realiza un cálculo utilizando los datos de dichos campos.
2.5.1 Delete
Elimina los registros de una o más de las tablas listadas en la cláusula FROM que satisfagan la cláusula WHERE.
Tip: Una vez que se han eliminado los registros utilizando una consulta de borrado, no puede deshacer la
operación. Si desea saber qué registros se eliminarán, primero examine los resultados de una consulta de
selección que utilice el mismo criterio y después ejecute la consulta de borrado. Si elimina los registros
equivocados podrá recuperarlos desde las copias de seguridad.
INSERT INTO Tabla (campo1, campo2, .., campoN) VALUES (valor1, valor2, ..., valorN)
SELECT campo1, campo2, ..., campoN INTO nuevatabla FROM tablaorigen [WHERE criterios]
FROM TablaOrigen
Tip: Con Oracle podemos crear una tabla a partir de una selección, insertando los datos implícitamente
2.5.3 Update
Crea una consulta de actualización que cambia los valores de los campos de una tabla especificada basándose en un
criterio específico.
UPDATE Products SET UnitPrice = UnitPrice * 1.1 WHERE SupplierID = 1 AND CategoryID = 2;
Si en una consulta de actualización suprimimos la cláusula WHERE todos los registros de la tabla señalada serán
actualizados.
Un registro en una tabla hace referencia a un registro en otra tabla, porque el contenido de las columnas de cada tabla
son iguales.
Producto Cartesiano
Si no se especifican en el where las columnas utilizadas para relacionar las tablas, el sistema asume que se desea
obtener la combinación de los registros de cada tabla. Esto se conoce como producto cartesiano.
SELECT campos FROM tb1 INNER JOIN tb2 ON tb1.campo1 comp tb2.campo2
En donde:
tb1, tb2
Son los nombres de las tablas desde las que se combinan los registros.
campo1, campo2
Son los nombres de los campos que se combinan. Si no son numéricos, los campos deben ser del mismo tipo de datos
y contener el mismo tipo de datos, pero no tienen que tener el mismo nombre.
comp
Se puede utilizar una operación INNER JOIN en cualquier cláusula FROM. Esto crea una combinación por
equivalencia, conocida también como unión interna. Las combinaciones de igualdad son las más comunes; éstas
combinan los registros de dos tablas siempre que haya concordancia de valores en un campo común a ambas tablas.
El ejemplo siguiente muestra cómo podría combinar las tablas Employees y Orders basándose en el campo
EmployeeID:
ON Employees.EmployeeID = Orders.EmployeeID;
También se pueden enlazar varias cláusulas ON en una instrucción JOIN, utilizando la sintaxis siguiente:
SELECT campos
SELECT campos
ON Orders.EmployeeID = Employees.EmployeeID)
ON Customers.CustomerID = Orders.CustomerID;
No obstante, los INNER JOIN ORACLE no es capaz de interpretarlos hasta la versión 8i, pero existe una
sintaxis en formato ANSI para los INNER JOIN que funcionan en todos los sistemas. Tomando como
referencia la siguiente sentencia:
ON Employees.EmployeeID = Orders.EmployeeID
order by employees.EmployeeID;
En este caso la consulta muestra todos los registros de la tabla Empleados (Employees) aunque no tengan ningún
registro asociado en la tabla Pedidos (Orders).
ON Customers.CustomerID = Orders.CustomerID;
En este caso muestra todos los registros de la tabla Clientes (Customers) aunque no tengan ningún registro en la tabla
Pedidos (Orders). Puede haber clientes que no hayan realizado ningún pedido.
Referente a los OUTER JOIN, no funcionan en ORACLE hasta la versión 8i. La sintaxis en ORACLE se
emplea añadiendo los caracteres (+) detrás del nombre de la tabla en la que deseamos aceptar valores nulos,
esto equivale a un LEFT JOIN:
Un LEFT JOIN o un RIGHT JOIN puede anidarse dentro de un INNER JOIN, pero un INNER JOIN no puede
anidarse dentro de un LEFT JOIN o un RIGHT JOIN.
Por ejemplo:
ON Products.ProductID = OrderDetails.ProductID)
ON Categories.CategoryID = Products.CategoryID
order by Products.ProductID;
Crea dos combinaciones equivalentes: una entre las tablas Products y Categories, y la otra entre las tablas Products y
OrderDetails. La consulta retorna una lista de todos los productos existententes con su categoría correspondiente y
los pedidos realizados de dichos productos. En el caso de no existir ningún pedido relacionado a alguno de los
productos el campo OrderID aparecerá con valor NULL.
Por ejemplo, para visualizar el número, nombre y puesto de cada empleado (EmployeeID, LastName, Title), junto
con el número, nombre y puesto del supervisor (ReportsTo, LastName, Title )de cada uno de ellos se utilizaría la
siguiente sentencia:
El resultado de la consulta no mostrará a aquellos empleados los cuales no tengan asignados supervisor, es decir, en
aquellos casos para que ReportsTo = NULL.
Modifica el título de cortesía para aquellos empleados los cuales no dependen de ningún supervisor, es decir ellos
mismos son su propio supervisor.
WHERE OrderDate
La select muestra el nombre del producto, la categoría, el precio unitario del producto, el número de pedido, la fecha
de pedido y el ID del cliente que lo realizó, de todos los pedidos realizados entre el periodo comprendido entre ’01-
07-1996 y el 30-07-1996’ ambos inclusive. Ordenados por la fecha en que se realizó el pedido.
Son instrucciones SELECT, el nombre de una consulta almacenada o el nombre de una tabla almacenada precedido
por la palabra clave TABLE.
Puede combinar los resultados de dos o más consultas, tablas e instrucciones SELECT, en cualquier orden, en una
única operación UNION. El ejemplo siguiente combina una tabla existente llamada Nuevas Cuentas y una instrucción
SELECT:
Tip: Si no se indica lo contrario, no se devuelven registros duplicados cuando se utiliza la operación UNION,
no obstante puede incluir el predicado ALL para asegurar que se devuelven todos los registros. Esto hace que
la consulta se ejecute más rápidamente. Todas las consultas en una operación UNION deben pedir el mismo
número de campos, no obstante los campos no tienen porqué tener el mismo tamaño o el mismo tipo de datos.
Se puede utilizar una cláusula GROUP BY y/o HAVING en cada argumento consulta para agrupar los datos
devueltos. Puede utilizar una cláusula ORDER BY al final del último argumento consulta para visualizar los datos
devueltos en un orden específico.
Recupera los nombres (CompanyName) y las ciudades (City) de todos proveedores (Suppliers) y clientes
(Customers) de USA.
Recupera los nombres (CompanyName) y las ciudades (City) de todos proveedores (Suppliers) y clientes
(Customers) de USA., ordenados por el nombre de la ciudad (City).
Recupera los nombres (CompanyName) y las ciudades (City) de todos proveedores (Suppliers) y clientes
(Customers) de USA, y los apellidos (LastName) y ciudades (City) de todos los empleados cuya región es WA.
5 Subconsultas
Tip: Las consultas normalmente son ineficientes, por tanto deberán utilizarse como último recurso ya que
siempre es mejor tratar de realizar las consultas mediantes joins
Para efectuar algunas tareas que en otro caso sería imposible realizar utilizando un join (por ejemplo usar una
función agregada)
Puede devolver un valor único o una lista de valores y en dependencia de esto se debe usar el operador del tipo
correspondiente.
Puede contener una sola columna, que es lo más común, o varias columnas. Este último caso se llama
subconsulta con columnas múltiples. Cuando dos o más columnas serán comprobadas al mismo tiempo, deben
encerrarse entre paréntesis.
Tip: Una subconsulta es una instrucción SELECT anidada dentro de una instrucción SELECT,
SELECT...INTO, INSERT...INTO, DELETE, o UPDATE o dentro de otra subconsulta.
Se puede utilizar una subconsulta en lugar de una expresión en la lista de campos de una instrucción SELECT o en
una cláusula WHERE o HAVING. En una subconsulta, se utiliza una instrucción SELECT para proporcionar un
conjunto de uno o más valores especificados para evaluar en la expresión de la cláusula WHERE o HAVING.
El predicado IN se emplea para recuperar únicamente aquellos registros de la consulta principal para los que algunos
registros de la subconsulta contienen un valor igual. El ejemplo siguiente devuelve todos los productos vendidos con
un descuento igual o mayor al 25 por ciento:
Inversamente se puede utilizar NOT IN para recuperar únicamente aquellos registros de la consulta principal para los
que no hay ningún registro de la subconsulta que contenga un valor igual.
El predicado EXISTS (con la palabra reservada NOT opcional) se utiliza en comparaciones de verdad/falso para
determinar si la subconsulta devuelve algún registro. Supongamos que deseamos recuperar todos aquellos clientes
que hayan realizado al menos un pedido:
Tip: Es útil utilizar también alias del nombre de la tabla en una subconsulta para referirse a tablas listadas en
la cláusula FROM fuera de la subconsulta.
ORDER BY ProductName;
ORDER BY ProductName;
Selecciona el nombre de todos los empleados que han reservado al menos un pedido.
Obtiene una lista con el nombre y el precio unitario de todos los productos con el mismo precio que el producto
Chang.
FROM Products
Products.productName = 'Chang')
Supongamos que en nuestra tabla de empleados deseamos buscar todos los empleados cuyas edades superen a la
media de la compañía:
Funciona como la orden WHERE, pero sobre los resultados de las funciones de grupo, en oposición a las
columnas o funciones para registros individuales que se seleccionan mediante la orden WHERE. O sea, trabaja
como si fuera una orden WHERE, pero sobre grupos de registros.
El ejemplo a diseñar para nuestra aplicación es la consulta ¿cuál fue el artículo más vendido y en qué cantidad?. En
este caso, la orden HAVING de la consulta principal selecciona aquellos artículos (GROUP BY) que tienen una venta
total (SUM(valor)) igual a la mayor venta realizada por artículo (MAX(SUM(valor))) que devuelve la subconsulta.
GROUP BY column
Un caso práctico, si deseamos localizar aquellos empleados con igual nombre y visualizar su código
correspondiente, la consulta sería la siguiente:
ORDER BY Employees.FirstName;
Este tipo de consulta se emplea en situaciones tales como saber qué productos no se han vendido.
La sintaxis es sencilla, se trata de realizar una unión interna entre dos tablas seleccionadas mediante un LEFT
JOIN, estableciendo como condición que el campo relacionado de la segunda sea Null.
6 Optimización de consultas
El lenguaje SQL es no procedimental, es decir, en las sentencias se indica que queremos conseguir y no como lo tiene
que hacer el interprete para conseguirlo. Esto es sólo teoría, pues en la práctica a todos los gestores de SQL hay que
especificar sus propias reglas para optimizar el rendimiento.
Por tanto, muchas veces no basta con especificar una sentencia SQL correcta, sino que además, hay que indicarle
como tiene que hacerlo si queremos que el tiempo de respuesta sea el mínimo. En este apartado veremos como
mejorar el tiempo de respuesta de nuestro interprete ante unas determinadas situaciones:
Como norma más general, una sentencia está optimizada cuando implica realizar el número más pequeño posible de
accesos a los datos.
Hay que procurar que las condiciones de la cláusula WHERE sean lo más restringidas posibles; debemos evitar
accesos a filas innecesarias.
Hay que especificar siempre las columnas a las que queremos acceder (No hacer nunca SELECT *...) evitando
así recuperar columnas que luego no utilizaremos y por que el gestor debe leer primero la estructura de la tabla
antes de ejecutar la sentencia.
En la medida de lo posible hay que evitar que las sentencias SQL estén embebidas dentro del código de la aplicación.
Es mucho más eficaz usar vistas o procedimientos almacenados por que el gestor los guarda compilados. Si se trata
de una sentencia embebida el gestor debe compilarla antes de ejecutarla.
Tip: Si utilizamos varias tablas en la consulta, especificar siempre a que tabla pertenece cada campo, le
ahorraremos al gestor el tiempo de localizar a que tabla pertenece el campo.
En lugar de:
Usar :
Normalizar las tablas, al menos hasta la tercera forma normal, para asegurar que no hay duplicidad de datos y se
aprovecha al máximo el almacenamiento en las tablas. Si hay que desnormalizar alguna tabla pensar en la
ocupación y en el rendimiento antes de proceder.
Los primeros campos de cada tabla deben ser aquellos campos requeridos y dentro de los requeridos primero se
definen los de longitud fija y después los de longitud variable.
Es muy habitual dejar un campo de texto para observaciones en las tablas. Si este campo se va a utilizar con poca
frecuencia o si se ha definido con gran tamaño, por si acaso, es mejor crear una nueva tabla que contenga la clave
primaria de la primera y el campo para observaciones.
Si deseamos interrogar por campos pertenecientes a índices compuestos es mejor utilizar todos los campos de todos
los índices. Supongamos que tenemos un índice formado por el campo NOMBRE y el campo APELLIDO y otro
índice formado por el campo EDAD. La sentencia WHERE NOMBRE='Juan' AND APELLIDO Like '%' AND
EDAD = 20 sería más optima que WHERE NOMBRE = 'Juan' AND EDAD = 20 por que el gestor, en este segundo
caso, no puede usar el primer índice y ambas sentencias son equivalentes porque la condición APELLIDO Like '%'
devolvería todos los registros.
Un Null no implica un cero o un espacio en blanco; es un valor que significa “información no disponible”·
Is Null debe ser utilizado para determinar valores Null contenidos en una columna; la sintaxis “= Null” es válida
pero no recomendada
Los valores Null se consideran en el ordenamiento y en los grupos, Algunas columnas son definidas para permitir
valores NULL
Un caso en el que los índices pueden resultar muy útiles es cuando realizamos peticiones simultáneas sobre varias
tablas. En este caso, el proceso de selección puede acelerarse sensiblemente si indexamos los campos que sirven de
nexo entre las dos tablas.
Los índices pueden resultar contraproducentes si los introducimos sobre campos triviales a partir de los cuales no se
realiza ningún tipo de petición ya que, además del problema de memoria ya mencionado, estamos ralentizando otras
tareas de la base de datos como son la edición, inserción y borrado. Es por ello que vale la pena pensárselo dos
veces antes de indexar un campo que no sirve de criterio para búsquedas o que es usado con muy poca frecuencia por
razones de mantenimiento.
La mayoría de consultas se optimizan si acceden a través de un índice. Ante una sentencia que no realice un acceso
por índice y que consideremos ineficiente se ha de considerar el introducir un índice en la base, y si éste ya existe,
averiguar porqué no lo utiliza y reescribir la sentencia para que lo haga. En relación a todo esto, hay que tener en
cuenta las siguientes consideraciones:
Utilizar índice para consultas muy restrictivas. El volumen máximo de datos recuperados sería del orden del 15 -
20 % de los registros de la tabla.
Para esto hay que conocer siempre el volumen (no es igual el 20 % de una tabla de 1M registros que de una tabla
de 20M registros) , naturaleza y distribución de los datos a tratar (una sentencia que funciona bien con algunos
valores puede ser inadmisible si hay un valor muy mayoritario), sin olvidar las previsiones de crecimiento; en
este sentido, la información de una base de desarrollo puede ser totalmente errónea, la aplicación se tiene que
ajustar con una carga de datos realista.
Referenciar la columna o columnas del índice en la cláusula WHERE. El analizador no utilizará índice en caso
contrario.
Se debe tratar que los índice tengan la mayor selectividad posible (es decir el número de filas obtenido por cada
valor del clave del índice). A mayor selectividad del índice mayor probabilidad de que sea usado por el
optimizador de consultas.
Se deben usar los índices con moderación, es decir, unos pocos índices pueden ser útiles pero demasiados
índices pueden afectar negativamente al rendimiento porque hay que mantenerlos actualizados cada vez que se
realizan operación de inserción, actualización y borrado en los datos.
No podemos indexar todos los campos de una tabla extensa ya que doblaríamos el tamaño de la base de datos.
Igualmente.
Utilizar el menor número posible de columnas en el índice (índices estrechos), ya que estos ocupan menos
espacio y necesitan menos sobrecarga de mantenimiento.
Las referencias de las columnas de un índice compuesto en la cláusula WHERE tienen que estar en el mismo
orden que en el índice. No es obligatorio incluir condiciones sobre todas, pero sí sobre la primera, y si hay más
condiciones deberían ir sobre las siguientes columnas en el orden del índice. Lo ilustraremos con un ejemplo:
FECHA_ENTRADA = valor
Ó la siguiente:
NOMBRE_CLIENTE = valor
Ó la siguiente:
NUM_CLIENTE = valor
Ó la siguiente:
FECHA_ENTRADA = valor
FECHA_ENTRADA = valor
NOMBRE_CLIENTE = valor
No modificar la columna del índice en el WHERE con funciones o expresiones. Esto deshabilita el uso del
índice. Por ejemplo, no se usarían índice condiciones en el WHERE de la forma:
SUBSTR(NOMBRE, 1, 5) = ...
CLIENTE||'_01' = ...
TO_CHAR(FECHA_ENTRADA, 'DD/MM/YYYY')=...;
Notar que podemos encontrar casos tan sencillos como los siguientes:
En relación con esto, hay que tener en cuenta que las conversiones automáticas que realiza Oracle al comparar
datos de tipos diferentes entran dentro de esta categoría y por tanto inhabilitan el uso del índice. De aquí:
Si hay que hacer conversiones de tipos en la cláusula WHERE, las haremos nosotros explícitamente con las
funciones al efecto como TO_CHAR, TO_DATE, etc., y si es posible, siempre sobre datos fijos, no sobre
columnas.
No utilizar el operador LIKE con un valor cuyo primer carácter sea el comodín '%'; ya que esto obliga a acceder
a toda la tabla. En cambio, Sí que se utilizará índice si la cadena empieza por un carácter fijo. Ejemplo:
(Índice: NOMBRE_CLIENTE)
Tip: No utilizar LIKE con columnas que no sean de tipo carácter. En este caso, Oracle realiza
implícitamente una conversión de tipo, por tanto, nunca se utilizará índice.
Si en algún caso, queremos obligar al optimizador a realizar un acceso a toda la tabla a pesar de que en la
condición del WHERE se referencie a una columna indexada (Es el caso de tablas muy pequeñas o con un índice
poco restrictivo), podemos utilizar una técnica tan sencilla como:
TOTAL + 0 = ...
Evitar los operadores NOT EQUAL (!=) ya que siempre acceden a toda la tabla al presuponer el analizador que
se va a acceder a la mayoría de los registros. Así:
(Índice: ZONA)
SELECT CLIENTE FROM CLIENTES WHERE ZONA > 5 AND ZONA < 5;
No hay problema en utilizar las otras condiciones NOT, como por ejemplo:
Donde la condición: NOT ZONA > 5 se transformará en ZONA =< 5 y utilizará el índice sobre ZONA de antes.
Para acceder vía índice con la cláusula ORDER BY hay que atender a las mismas consideraciones que las hechas
para WHERE:
La columna del índice no puede estar modificada por una expresión o función.
Las columnas de un índice compuesto han de estar en el mismo orden que en el índice, siendo válidas también
las otras restricciones hechas para el caso de WHERE.
Si la(s) columna(s) de la cláusula ORDER BY no están indexadas o no se puede utilizar el índice, se realiza un
'Sort' de la tabla (o de las filas devueltas por la cláusula WHERE).
Se puede utilizar un acceso por índice con las funciones MAX y MIN en consultas simples (no Joins) si:
Por ejemplo:
(Índice IMPORTE)
Condiciones ligadas mediante OR. El analizador de Oracle interpreta una cláusula WHERE con n condiciones
unidas con OR como n consultas independientes, cada una con una condición, realizando una concentración al
final. Si queremos tener accesos por índice en este caso, será necesario que cada condición referencie a una
columna indexada.
También se transforma en N consultas independientes una condición con el operador: IN ( Conjunto de valores ),
y por tanto se utilizará un índice si la columna de la condición está indexada.
Evitar siempre que sea posible las funciones de grupo: DISTINCT, GROUP BY.
Nunca utilizan índice y realizan siempre una ordenación (Sort) de la tabla: Las operaciones de Sort son unas de
las que más cargan el servidor, especialmente si tiene que recurrir al tablespace temporal al llenarse la
SORT_AREA de la SGA, ya que entonces hay I/O extra sobre el disco.
Se pueden substituir por una Join aunque ello puede complicar mucho la consulta (hasta hacerla ineficiente) o no
ser posible. En todo caso, siempre hay que incluir una cláusula WHERE previa que restrinja lo más posible las
filas a ordenar y que sí que utilizará un índice para ello si es posible. Por esta razón, es preferible GROUP BY
frente a DISTINCT.
Con GROUP BY utilizar siempre que sea posible WHERE en lugar de HAVING para introducir la condición.
Así la sentencia:
GROUP BY ZONA;
HAVING ZONA = 5;
agrupará todos los registros descartando luego los que no tengan ZONA = 5, sin utilizar el índice
No obstante, existen casos en que la presencia de la columna no garantiza el uso de su índice, ya que éstos son
ignorados, como en las siguientes situaciones cuando la columna indexada es:
Modificada por alguna función, excepto por las funciones MAX(columna) o MIN(columna).
Usada en una comparación con el operador LIKE a un patrón de consulta que comienza con alguno de los signos
especiales (% _).
Finalmente debemos aclarar que los registros cuyo valor es NULL para la columna indexada, no forman parte del
índice. Por lo tanto, cuando el índice se activa estos registros no se muestran como resultado de la consulta.
Para el uso de Joins, aparte de lo dicho antes para índices, hemos de considerar lo siguiente:
En general, es preferible utilizar, si es posible, UNION (mejor UNION ALL si da el resultado buscado, ya que no
realiza 'sort' sobre las tablas, mientras que UNION sí), en lugar de accesos con Join, al menos con tablas de
volúmenes parecidos.
Hay que tener en cuenta la relación entre la tabla conductora y la secundaria: 1 a n (ídem m a n) ó 1 a 1 (ídem n a
1). En el primer caso, accederemos a n filas de la tabla secundaria por cada una de la conductora, mientras que en
el segundo sólo se recuperará una fila de la tabla secundaria por fila de la conductora. Siempre es preferible este
segundo caso, y se ha de intentar obtenerlo mediante funciones de agrupación o eliminación de duplicados, si es
posible obtener el mismo resultado.
Si realizamos una Join entre una vista y una tabla, la vista ha de actuar como conductora.
No es recomendable realizar una Join entre vistas, sobre todo si podemos hacerla sobre las tablas.
o Si la tabla conductora es mucho más pequeña que la secundaria, es preferible hacer un NL Join.
o Si es la tabla secundaria la que es mucho más pequeña que la conductora, montaremos un Hash Join sobre
la pequeña.
o En principio, si el volumen de las tablas es parecido, también es preferible usar un Hash sobre la tabla
secundaria. Siempre que se use un Hash Join hay que tener en cuenta las columnas seleccionadas, ya que
todas ellas formarán parte de la tabla Hash. Hay que intentar que ésta sea lo más pequeña posible para
reducir el trabajo sobre temporal.
Los operadores MINUS, UNION y INTERSECT no utilizan nunca índices, realizan una ordenación sobre los
datos y optimizan las dos consultas por separado.
Los operadores NOT IN y NOT EXISTS siempre realizan un Full Scan de la tabla de la subconsulta, es más
efectivo utilizar un Outer Join (Producto externo), o bien una subconsulta con MINUS, UNION o INTERSECT.
En optimización por reglas, el analizador reescribe la sentencia en un formato más general. Con subconsultas, las
normas generales serían:
o Subconsultas no correlacionadas: Aquellas en que las tablas y condiciones de cada consulta son
independientes. Como, por ejemplo:
SELECT CLIENTE, ID_CLI FROM CLIENTES WHERE ID_CLI IN (SELECT ID_CLI FROM
FACTURAS WHERE MES = 'ENERO')
Este tipo de subconsultas se transforman en una Join donde la tabla conductora es la de la consulta interna.
Sólo son convenientes si la consulta interna es muy restrictiva.
ELECT CLIENTE, ID_CLI FROM CLIENTES WHERE ID_CLI EXISTS (SELECT ID_CLI FROM
FACTURAS WHERE FACTURAS.ID_CLI = CLIENTES.ID_CLI AND MES = 'ENERO')
Este tipo de subconsultas de transforman en una Join donde la tabla conductora es la de la consulta principal
que utilizará índices si es posible. Sólo son convenientes si la consulta principal es muy restrictiva.
A pesar de lo dicho, no hay que descartar las subconsultas; en algunos casos, MINUS, UNION o INTERSECT
resultan ser la mejor optimización.
6.5 Vistas
Una vista es una tabla virtual que está definida por una consulta que consiste en una instrucción SELECT. Esta tabla
virtual está creada con datos de una o más tablas reales y, para los usuarios, una vista parece una tabla real. De hecho,
una vista puede ser tratada del mismo modo que una tabla normal.
En realidad, una vista se almacena simplemente como una instrucción SQL previamente definida. Cuando se accede a
la vista, el DBMS une la instrucción SQL que se ejecuta en ese momento con la consulta que se use para definir la
vista.
Se pueden crear varios tipos de vistas, cada uno de los cuales tienen ventajas en ciertas situaciones. El tipo de vista
que se ha de crear depende completamente de para qué se quiera usar la vista. Se pueden crear vistas de cualquiera de
las siguientes maneras:
Información de agregación
La ventaja de utilizar vistas es que se pueden crear vistas que tienen atributos diferentes sin tener que duplicar los
datos.
Siempre ofrecen los datos actualizados. La instrucción SELECT que define una vista sólo se ejecuta cuando se
accede a la vista, por tanto, todos los cambios de la tabla subyacente se reflejan en la vista.
Puede tener un nivel diferente de seguridad del que posea la tabla subyacente. La consulta que define la vista se
ejecuta con el nivel de seguridad que posea el usuario que crea la vista. De esta manera, se puede crear una vista
que enmascare los datos que no se quieran mostrar a ciertas clases de usuarios.
Es preciso identificar aquellas sentencias SQL que consumen la mayor parte de los recursos del sistema y que
ralentizan los tiempos de respuesta. Oracle nos proporciona la utilidad SQL_TRACE que combinada con la utilidad
TKPROF, nos van a permitir determinar cuales de las sentencias de nuestra aplicación son ineficientes.
Los resultados de salida de las utilidades mencionadas deben ser analizados para tomar las medidas necesarias que
mejoren el rendimiento. Sin embargo, independientemente de los resultados obtenidos podemos tener en cuenta los
siguientes criterios generales.
En las sentencias que se utilice más de una tabla, realizar los equijoins de las mismas con = y con el operador
AND.
Evitar el uso de funciones SQL en la cláusula WHERE. Cualquier expresión que utilice una función provoca que
el optimizador no utilice el índice correspondiente.
WHERE a.campo1 = b.campo2 è en este caso el optimizador utilizaría el índice al ejecutar la sentencia.
Sin embargo en el caso de utilizar una función WHERE b.campo2 = SUBSTR(a.campo1,1,4) el optimizador no
utilizaría el índice lo que redundaría en una pérdida de eficiencia.
Prestar atención a las conversiones implícitas de datos. Si queremos usar el índice definido sobre la columna
colvchar de tipo VARCHAR2
Colvchar = <num_expr>
Evitar uso de IN y EXITS. En caso de ser necesario su uso, es preferible el IN en aquellos casos en que la
sentencia principal recupere un gran número de filas; en caso contrario, es decir, cuando es la subconsulta la que
presenta una alta cardinalidad es más eficiente el uso de EXITS.
Ejemplo:
FROM clientes c
FROM pedidos p
El plan de ejecución de la sentencia, recupera los 27.000 registros de la tabla clientes y los filtra con la tabla
pedidos, lo que hace que la ejecución sea ineficiente.
FROM clientes c
FROM pedidos p
El plan de ejecución de la sentencia, recupera los pedidos del cliente 1000 para realizar posteriormente el
join con la tabla clientes, lo que redunda en un mejor rendimiento de la sentencia.
No usar índices de manera generalizada. Los índices suelen mejorar el rendimiento de las sentencias de consulta,
pero ralentizan las operaciones de inserción, borrado y actualización.
Una vez definidas las particiones, las sentencias SQL acceden y manipulan los datos de la misma manera que si la
tabla no estuviese particionada. Las tablas particionadas son especialmente útiles en entornos de Datawarehouse
donde la cantidad de datos que se almacenan y analizan es enorme.
Cada una de las particiones en que dividimos una tabla tiene los mismos atributos lógicos que las demás, es decir,
tiene las mismas columnas, los mismos tipos de datos y las mismas restricciones de integridad; sin embargo, cada
partición puede tener diferentes atributos físicos (pctfree, pctused, tablespace,…).
Ventajas de particionar
Se reducen los tiempos de respuesta en las operaciones de manipulación DML puesto que la operación se realiza
sobre una partición en concreto y no sobre toda la tabla.
Se reduce el tiempo de acceso a los datos si estos accesos afectan a una o varias particiones y no a toda la tabla.
Las operaciones de mantenimiento se simplifican puesto que se pueden realizar sobre particiones.
Se puede particionar sin modificar las aplicaciones existentes. Se puede convertir una tabla no particionada en
particionada sin necesidad de modificar ninguna de las sentencias DML que acceden y manipulan los datos de
dicha tabla.
Partition Key
Debemos establecer en base a cual de las columnas o conjuntos de columnas se va a realizar la partición, es lo que se
denomina partition key. A través de la partition key Oracle 9i determina sobre que partición va a realizar las
operaciones DML.
La partition key:
Métodos de particionamiento
Range Partitioning.
List Partitioning.
Hash Partitioning.
Composite Partitioning.
Range Partitioning
Método de particionamiento, que mapea los datos en particiones, definidas sobre rangos del partition key.
Cada partición tiene una cláusula VALUES LESS THAN que indica los límites de la partición.
Todas las particiones excepto la primera tienen un valor inferior del rango definido de manera implícita, por la
partición anterior.
Para la última partición se puede definir un MAXVALUE que indica el valor máximo admitido en dicha
partición.
(comercial_id NUMBER(4),
comercial_nombre VARCHAR2(40),
region_venta VARCHAR2(6),
importe_venta NUMBER(6),
fecha_venta DATE)
(TO_DATE (‘01/05/2004’,’DD/MM/YYYY’)),
(TO_DATE (‘01/09/2004’,’DD/MM/YYYY’)),
TO_DATE (‘01/01/20005’,’DD/MM/YYYY’)));
List Partitioning
Método de particionamiento, que mapea los datos en particiones, definidas sobre una lista de valores del partition
key. Las ventajas de este método es que permite organizar los datos del modo adecuado a nuestras necesidades.
(comercial_id NUMBER(4),
comercial_nombre VARCHAR2(40),
region_venta VARCHAR2(6),
importe_venta NUMBER(6),
fecha_venta DATE)
’CANTABRIA’,’MADRID’),
‘VALENCIA’,’BALEARES’),
‘MURCIA’));
Hash Partitioning
Método de particionamiento para datos que no admiten una partición por rangos o por lista. Es la mejor opción de
particionamiento en los siguientes casos:
Los tamaños de las particiones varían sustancialmente de unas a otras lo que hace difícil equilibrarlas de manera
manual.
Ejemplo: Tabla ventas particionada agrupando los datos por el identificador del comercial.
(comercial_id NUMBER(4),
comercial_nombre VARCHAR2(40),
region_venta VARCHAR2(6),
importe_venta NUMBER(6),
fecha_venta DATE)
PARTITIONS 4;
Composite Partitioning
Método que combina los métodos anteriores, realizando particiones por rango, y dentro de cada partición sub-
particiones hash o basadas en listas.
(comercial_id NUMBER(4),
comercial_nombre VARCHAR2(40),
region_venta VARCHAR2(6),
importe_venta NUMBER(6),
fecha_venta DATE)
SUBPARTITION TEMPLATE (
‘CANTABRIA’,’MADRID’),
‘VALENCIA’,’BALEARES’),
‘C. LA MANCHA’))
(TO_DATE (‘01/05/2004’,’DD/MM/YYYY’)),
(TO_DATE (‘01/09/2004’,’DD/MM/YYYY’)),
TO_DATE (‘01/01/20005’,’DD/MM/YYYY’)));
Ejemplo: Tabla temporal que mantiene las filas (PRESERVE ROWS) una vez que la transacción ha finalizado.
(campo1 NUMBER)
Ejemplo: Tabla temporal que elimina las filas (DELETE ROWS) una vez que la transacción ha finalizado.
(campo1 NUMBER)
Simples
Compuestos
Bit map
Los índices aumentan la velocidad de ejecución de las consultas cuando se recupera entre un 10% - 15% de los
datos de la tabla.
Para tablas con gran volumen de datos, los índices ralentizan tanto las consultas como las inserciones,
actualizaciones y borrados.
Las primary keys y las unique key constraints generan implícitamente un índice.
En índices compuestos ordenar las columnas por el orden en que son accedidas con mayor frecuencia.
Las columnas que con frecuencia se usan para ordenar los resultados de las consultas deberán tener asociado un
índice.
Todas las columnas que formen parte de una foreign key deben ser indexadas para evitar problemas de bloqueos.
Se pueden crear índices basados en cálculos más o menos complejos entre columnas de las tablas, e incluso aplicando
funciones PL/SQL. La función usada para crear el índice puede ser una expresión aritmética, una expresión que
contenga una función definida en un paquete o una función PL/SQL… La mejora del rendimiento se obtiene cuando
las consultas se realizan en base a dichos cálculos.
Ejemplo. El siguiente ejemplo muestra como definir un índice basado en una función PL/SQL.
(emp_id NUMBER(2),
emp_nombre VARCHAR2(40));
SELECT emp_nombre
FROM empleados
Índices bitmap
Un índice proporciona un puntero a las filas de las tablas que contienen el valor buscado. En un índice normal esto se
consigue almacenando una lista de rowid para cada valor clave que se corresponde con las filas que contienen dicho
valor.
En un índice bitmap no se almacena una lista de ROWID sino un valor 0 o 1 para cada una de las columnas que
componen el índice. Por lo tanto la eficiencia de estos índices aumenta de manera considerable cuando el número de
condiciones que existen en la cláusula WHERE es elevado.
EXPLAIN PLAN muestra el plan de ejecución elegido por el Optimizador de Oracle para ejecutar las sentencias
SELECT, INSERT, UPDATE y DELETE. El plan de ejecución es la secuencia ordenada de operaciones que Oracle
realiza para ejecutar la sentencia correspondiente.
EXPLAIN PLAN utiliza la tabla PLAN_TABLE para almacenar los resultados del plan de ejecución. Para crear
dicha tabla en nuestro esquema será necesario ejecutar el script UTLXPLAN.SQL almacenado por lo general en el
path $ORACLE_HOME/rdbms/admin..
A cada una de las sentencias analizadas le asignamos un identificador diferente, para poder consultar el plan de
ejecución de cada una de ellas de manera individual.
EXPLAIN PLAN
Columna Descripción
MERGE JOIN Operación que acepta dos conjuntos de filas, ordenado cada uno por un valor
específico; combina las filas de uno con las del otro y devuelve el resultado.
NESTED LOOPS Operación sobre dos conjuntos de registros. Compara cada fila de un conjunto con la
del otro, devolviendo las filas que cumplen la condición.
BY GLOBAL INDEX ROWID Recupera los registros por el índice global en tablas particionadas
BY LOCAL INDEX ROWID Recupera los registros por el índice local en tablas particionadas.
Esta información es recogida en un fichero con extension .trc. Dicho fichero se almacena en el path indicado por el
parámetro USER_DUMP_DEST. El fichero generado con la traza no es editable por lo que esta utilidad debe ser
combinada con TKPROF.
Podemos activar la traza con SQL_TRACE para una sesión determinada de la siguiente manera:
Y se desactivaría:
También puede ser activada desde un proceso PL/SQL del siguiente modo:
BEGIN
DBMS_SESSION.SET_SQL_TRACE (TRUE);
<plsql_procedure_call>;
DBMS_SESSION.SET_SQL_TRACE(FALSE);
END;
En el caso de querer trazar una sesión determinada de un usuario determinado, es preciso obtener previamente el
valor de los campos SID y SERIAL# de la vista del sistema V$SESSION. Se activaría la traza ejecutando la
sentencia:
EXECUTE DBMS_SYSTEM.SET_SQL_TRACE_IN_SESSION(sid,serial#,TRUE);
Y se desactivaría:
EXECUTE DBMS_SYSTEM.SET_SQL_TRACE_IN_SESSION(sid,serial#,FALSE);
7.5.2 TKPROF
El fichero con la traza obtenido con SQL_TRACE no es editable. Para poder analizar su contenido es preciso ejecutar
la utilidad TKPROF. Esta utilidad se encuentra normalmente en el path $ORACLE_HOME/BIN y su nombre varía
según las versiones de Oracle (normalmente tkprof.exe).
Para poder ejecutar TKPROF es requisito previo que el parámetro TIMED_STATICS tenga valor TRUE.
Sintaxis
TKPROF <fichero_traza><fichero_salida>
Donde <fichero_traza> es el fichero generado por SQL_TRACE y fichero_salida> el fichero formateado por
TKPROF y a partir del cual se pueden analizar los resultados de la traza.
7.6 Estadísticas
Como administradores de base de datos, podemos generar estadísticas que cuantifiquen la distribución de los datos y
los modos de almacenamiento de las tablas, índices, vistas, particiones, … El optimizador basado en costes (CBO-
Cost Based Optimizer) utiliza esta información para estimar cual es el plan óptimo de ejecución de las sentencias
SQL.
Las estadísticas se almacenan en el diccionario de datos y pueden ser exportadas de una base de datos a otra, de
manera que se pueden simular las condiciones de ejecución de un entorno de desarrollo en uno de producción y
viceversa.
Es conveniente generar estadísticas de manera periódica para un rendimiento óptimo de la base de datos. En
particular deben generarse cuando:
Generación de estadísticas
El optimizador basado en costes (CBO) utiliza las estadísticas para determinar los métodos de acceso más efectivos.
Por tanto, si el tamaño y la distribución de los datos en las tablas cambia frecuentemente, el generar las estadísticas
regularmente asegura que estas representen con precisión el estado de los datos.
Procedimiento Descripción
Oracle accesa el diccionario de datos para hallar la información acerca de los usuarios, esquemas y estructuras
almacenadas.
Oracle modifica el diccionario de datos cada vez que una instrucción DDL es usada.
Hay tres grupos de vistas que contienen información similar y que se distinguen por su prefijo:
Prefijo Descripción
Estas vistas presentan al usuario una perspectiva global de la base de datos. Le da al usuario
ALL_
información sobre todos los objetos en la base de datos a los que tiene acceso.
Código de funciones y
user_source all_source
procedimientos
Roles dba_roles
Estas son sólo algunas de las tablas y vistas. Se puede obtener un listado de todas con
pero la lista es muy larga, y probablemente sea necesario filtrarla, por ejemplo:
Contenido INFORMATION_ESCHEMA.
Vistas VIEWS
Tables
Table Constraints