Optimización Básica de SQL

Una de las tareas más importantes de las propias de un desarrollador de bases de datos es la de optimización, ajuste, puesta a punto o //tunning//. Hay que tener en cuenta que las sentencias SQL pueden llegar a ser muy complejas y conforme el esquema de base de datos va creciendo, las sentencias son más complejas y confusas. Por es difícil escribir la sentencia correcta a la primera. Por todo ello después de tener cada uno de los procesos escrito, hay que pasar por una etapa de //tunning //en la que se revisan todas las sentencias SQL para poder optimizarlas conforme a la experiencia adquirida.

Tanto por cantidad como por complejidad, la mayoría de las optimizaciones deben hacerse sobre sentencias SELECT, ya que son (por regla general) las responsables de la mayor pérdida de tiempos.


===== Normas básicas de optimización =====

A continuación se dan unas normas básicas para escribir sentencias SELECT optimizadas.

~- Las condiciones (tanto de filtro como de //join//) deben ir siempre en el orden en que esté definido el índice. Si no hubiese índice por las columnas utilizadas, se puede estudiar la posibilidad de añadirlo, ya que tener índices extra sólo penaliza los tiempos de inserción, actualización y borrado, pero no de consulta.
~- Al crear un restricción de tipo PRIMARY KEY o UNIQUE, se crea automáticamente un índice sobre esa columna.
~- Para chequeos, siempre es mejor crear restricciones (//constraints) //que disparadores (//triggers)//.
~- Hay que optimizar dos tipos de instrucciones: las que consumen mucho tiempo en ejecutarse, o aquellas que no consumen mucho tiempo, pero que son ejecutadas muchas veces.
~- Generar un plan para todas las consultas de la aplicación, poniendo especial cuidado en los planes de las vistas, ya que estos serán incluidos en todas las consultas que hagan referencia a la vista.
~~- Generar y optimizar al máximo el plan de las vistas. Esto es importante porque el SQL de una vista, no se ejecuta mientras que la vista no es utilizada en una consulta, así que
~~- todas las consultas de esa vista se ven afectadas por su plan. Hay que tener especial cuidado de hacer //joins// entre vistas.
~- Si una aplicación que funcionaba rápido, se vuelve lenta, hay que parar y analizar los factores que han podido cambiar. Si el rendimiento se degrada con el tiempo, es posible que sea un problema de volumen de datos, y sean necesarios nuevos índices para acelerar las búsquedas. En otras ocasiones, añadir un índice equivocado puede ralentizar ciertas búsquedas. Cuantos más índices tenga una tabla, más se tardará en realizar inserciones y actualizaciones sobre la tabla, aunque más rápidas serán las consultas. Hay que buscar un equilibrio entre el número de índices y su efectividad, de tal modo que creemos el menos número posible, pero sean utilizados el mayor número de veces posible.
~- Utilizar siempre que sea posible las mismas consultas. La segunda vez que se ejecuta una consulta, se ahorrará mucho tiempo de //parsing// y optimización, así que se debe intentar utilizar las mismas consultas repetidas veces.
~- Las consultas más utilizadas deben encapsularse en procedimientos almacenados. Esto es debido a que el procedimiento almacenado se compila y analiza una sola vez, mientras que una consulta (o bloque PL/SQL) lanzado a la base de datos debe ser analizado, optimizado y compilado cada vez que se lanza.
~- Los filtros de las consultas deben ser lo más específicos y concretos posibles. Es decir: es mucho más específico poner WHERE campo = 'a' que WHERE campo LIKE '%a%'. Es muy recomendable utilizar siempre consultas que filtren por la clave primaria u otros campos indexados.
~- Hay que tener cuidado con lanzar demasiadas consultas de forma repetida, como por ejemplo dentro de un bucle, cambiando una de las condiciones de filtrado. Siempre que sea posible, se debe consultar a la base de datos una sola vez, almacenar los resultados en la memoria del cliente, y procesar estos resultados después. También se pueden evitar estas situaciones con procedimientos almacenados, o con consultas con parámetros acoplados (//bind//).
~- Evitar la condiciones IN ( SELECT…) sustituyéndolas por joins: cuando se utiliza un conjunto de valores en la clausula IN, se traduce por una condición compuesta con el operador OR. Esto es lento, ya que por cada fila debe comprobar cada una de las condiciones simples. Suele ser mucho más rápido mantener una tabla con los valores que están dentro del IN, y hacer un join normal. Por ejemplo, esta consulta:

SELECT * FROM datos WHERE campo IN ('a', 'b', 'c', 'd', ... , 'x', 'y', 'z');

se puede sustituir por la siguiente consulta, siempre que la tabla "letras" contenga una fila por cada valor contenido en el conjunto del IN:

SELECT * FROM datos d, letras l WHERE d.campo = l.letra;

También hay que tener cuidado cuando se mete un SELECT dentro del IN, ya que esa consulta puede retornar muchas filas, y se estaría cayendo en el mismo error. Normalmente, una condición del tipo "WHERE campo IN (SELECT...)" se puede sustituir por una consulta con //join//.

~- Cuando se hace una consulta multi-tabla con //joins//, el orden en que se ponen las tablas en el FROM influye en el plan de ejecución. Aquellas tablas que retornan más filas deben ir en las primeras posiciones, mientras que las tablas con pocas filas deben situarse al final de la lista de tablas.
~- Si en la cláusula WHERE se utilizan campos indexados como argumentos de funciones, el índice quedará desactivado. Es decir, si tenemos un índice por un campos IMPORTE, y utilizamos una condición como WHERE ROUND(IMPORTE) > 0, entonces el índice quedará desactivado y no se utilizará para la consulta.
~- Siempre que sea posible se deben evitar las funciones de conversión de tipos de datos e intentar hacer siempre comparaciones con campos del mismo tipo. Si hay que hacer algún tipo de conversión, intenta evitar el uso del //cast// y aplica siempre la función de conversión sobre la constante, y no sobre la columna.
~- Una condición negada con el operador NOT desactiva los índices
~- Una consulta cualificada con la cláusula DISTINCT debe ser ordenada por el servidor aunque no se incluya la cláusula ORDER BY.
~- Para comprobar si existen registros para cierta condición, no se debe hacer un SELECT COUNT(*) FROM X WHERE xxx, sino que se hace un SELECT DISTINCT 1 FROM X WHERE xxx. De este modo evitamos al servidor que cuente los registros.
~~- Si vamos a realizar una operación de inserción, borrado o actualización masiva, es conveniente desactivar los índices, ya que por cada operación individual se actualizarán
~~- los datos de cada uno de los índices. Una vez terminada la operación, volvemos a activar los índices para que se regeneren.
~- La mejor optimización es rediseñar y normalizar la base de datos. Las bases de datos relacionales están diseñadas para funcionar lo más rápidamente posible para un buen diseño relacional, pero con diseños erróneos, se vuelven muy lentas. La mayoría de los problemas de rendimiento tienen un problema de fondo de mal diseño, y muchos de ellos no podrán ser optimizados si no se rediseña el esquema de base de datos.

Toda consulta SELECT se ejecuta dentro del servidor en varios pasos. Para la misma consulta, pueden existir distintas formas para conseguir el mismo resultados, por lo que el servidor es el responsable de decidir qué camino seguir para conseguir el mejor tiempo de respuesta. La parte de la base de datos que se encarga de estas decisiones se llama Optimizador. El camino seguido por el servidor para la ejecución de una consulta se denomina “Plan de ejecución” En //Oracle8// existen dos optimizadores para la decisión del plan de ejecución:

===== Optimizador basado en reglas (RULE) =====

Se basa en ciertas reglas para realizar las consultas. Por ejemplo, si se filtra por un campo indexado, se utilizará el índice, si la consulta contiene un ORDER BY, la ordenación se hará al final, etc. No tiene en cuenta el estado actual de la base de datos, ni el número de usuarios conectados, ni la carga de datos de los objetos, etc. Es un sistema de optimización estático, no varía de un momento a otro.

===== Optimizador basado en costes (CHOOSE) =====

Se basa en las reglas básicas, pero teniendo en cuenta el estado actual de la base de datos: cantidad de memoria disponible, entradas/saludas, estado de la red, etc. Por ejemplo, si se hace una consulta utilizando un campo indexado, mirará primero el número de registros y si es suficientemente grande, entonces merecerá la pena acceder por el índice, si no, accederá directamente a la tabla. Para averiguar el estado actual de la base de datos se basa en los datos del catálogo público, por lo que es recomendable que esté lo más actualizado posible (a través de la sentencia ANALYZE), ya que de no ser así, se pueden tomar decisiones a partir de datos desfasados (la tabla tenía 10 registros hace un mes pero ahora tiene 10.000).

//Oracle8// recomienda que todas las consultas se hagan por costes, aunque hay ciertos casos en los que una consulta no se resuelve (o tarda mucho) por costes y por reglas es inmediata.

¿Y cómo hacer para que una consulta se ejecute por reglas o por costes? Pues hay dos modos de forzar a Oracle a que utilice un optimizador concreto. La primera es modificando la sesión activa para que todas las consultas sean optimizadas de una manera:

ALTER SESSION SET OPTIMIZER_GOAL = [RULE|CHOOSE];

Con esto todas las consultas se ejecutarán utilizando el optimizador indicado. La otra manera es forzando a Oracle a que utilice un optimizador en una consulta concreta. Esto se hace a través de los “hints” o sugerencias.

===== Sugerencias o //hints // =====

Un //hint// es un comentario dentro de una consulta SELECT que informa a Oracle del modo en que tiene que trazar el plan de ejecución. Los //hint// deben ir junto detrás de la palabra SELECT:

SELECT /*+ HINT */ . . .

A continuación se muestra una lista de algunos de los //hints// posibles:

|| Hint || || Descripción ||
|| /*+ CHOOSE */ || || Pone la consulta a costes. ||
|| /*+ RULE */ || || Pone la consulta a reglas. ||
|| /*+ ALL_ROWS */ || || Pone la consulta a costes y la optimiza para que devuelva todas las filas en el menor tiempo posible. Es la opción por defecto del optimizador basado en costes. Esto es apropiado para procesos en masa, en los que son necesarias todas las filas para empezar a trabajar con ellas. ||
|| /*+ FIRST_ROWS */ || || Pone la consulta a costes y la optimiza para conseguir que devuelva la primera fila en el menor tiempo posible. Esto es idóneo para procesos online, en los que podemos ir trabajando con las primeras filas mientras se recupera el resto de resultados. Este //hint// se desactivará si se utilizan funciones de grupo como SUM, AVG, etc. ||
|| /*+ INDEX( tabla índice ) */ || o || Fuerza la utilización del índice indicado para la tabla indicada. Se puede indicar el nombre de un índice (se utilizará ese índice), de varios índices (el optimizador elegirá uno entre todos ellos) o de una tabla (se utilizará cualquier índice de la tabla). ||
|| /*+ ORDERED */ || || Hace que las combinaciones de las tablas se hagan en el mismo orden en que aparecen en el join. ||

Para más información sobre los //hints// consultar el //Oracle 8 Tunning. //

Una misma consulta puede generar distintos planes de ejecución en las siguientes situaciones: ƒ Cambios en las estadísticas de las tablas (COMPUTE STATISTICS) si se utiliza un

optimizador basado en costes. ƒ Uso de los //hints //en si se utiliza el optimizador basado en reglas. ƒ Añadir o eliminar índices de una tabla, si se utiliza el optimizador basado en reglas.

===== Calcular el coste de una consulta =====

Para calcular el coste de una consulta, el optimizador se basa en las estadísticas almacenadas en el catálogo de Oracle, a través de la instrucción:

ANALYZE [TABLE,INDEX] [COMPUTE, ESTIMATE] STATISTICS;

Si no existen datos estadísticos para un objeto (por ejemplo, porque se acaba de crear), se utilizarán valores por defecto. Además, si los datos estadísticos está anticuados, se corre el riesgo de calcular costes basados en estadísticas incorrectas, pudiendo ejecutarse planes de ejecución que a priori pueden parecer mejores. Por esto, si se utiliza el optimizador basado en costes, es muy importante analizar los objetos periódicamente (como parte del mantenimiento de la base de datos). Como las estadísticas van evolucionando en el tiempo (ya que los objetos crecen o decrecen), el plan de ejecución se va modificando para optimizarlo mejor a la situación actual de la base de datos. El optimizador basado en reglas hacía lo contrario: ejecutar siempre el mismo plan, independientemente del tamaño de los objetos involucrados en la consulta. Dentro de la optimización por costes, existen dos modos de optimización, configurables desde el parámetro OPTIMIZER_MODE:

ƒ FIRST_ROWS: utiliza sólo un número determinado de filas para calcular los planes de ejecución. Este método es más rápido pero puede dar resultados imprecisos.

ƒ ALL_ROWS: utiliza todas las filas de la tabla a la hora de calcular los posibles planes de ejecución. Este método es más lento, pero asegura un plan de ejecución muy preciso. Si no se indica lo contrario, este es el método por defecto.

===== Plan de ejecución =====

Aunque en la mayoría de los casos no hace falta saber cómo ejecuta Oracle las consultas, existe una sentencia especial que nos permite ver esta información. El plan de ejecución nos proporciona muchos datos que pueden ser útiles para averiguar qué está ocurriendo al ejecutar una consulta, pero principalmente, de lo que nos informa es del tipo de optimizador utilizado, y el orden y modo de unir las distintas tablas si la instrucción utiliza algún //join//.

Para obtener un plan de ejecución, debemos rellenar una tabla especial (llamada PLAN_TABLE) con un registro para cada paso en el plan de ejecución. En Oracle8, la tabla PLAN_TABLE debe tener la siguiente estructura, aunque cambia en cada versión. Puedes encontrar la definición de la tabla en el //script// UTLXPLAN.SQL, dentro del directorio ORACLE_HOME/rdbms/admin

|| CREATE TABLE PLAN_TABLE( ||
|| STATEMENT_ID || VARCHAR2(30), ||
|| TIMESTAMP || DATE, ||
|| REMARKS || VARCHAR2(80), ||
|| OPERATION || VARCHAR2(30), ||
|| OPTIONS || VARCHAR2(30), ||
|| OBJECT_NODE || VARCHAR2(128), ||
|| OBJECT_OWNER || VARCHAR2(30), ||
|| OBJECT_NAME || VARCHAR2(30), ||
|| OBJECT_INSTANCE || NUMERIC, ||
|| OBJECT_TYPE || VARCHAR2(30), ||
|| OPTIMIZER || VARCHAR2(255), ||
|| SEARCH_COLUMNS || NUMERIC, ||
|| ID || NUMERIC, ||
|| PARENT_ID || NUMERIC, ||
|| POSITION || NUMERIC, ||
|| COST || NUMERIC, ||
|| CARDINALITY || NUMERIC, ||
|| BYTES || NUMERIC, ||
|| OTHER_TAG || VARCHAR2(255), ||
|| OTHER || LONG); ||

Tipos de datos en Oracle

Los tipos de datos soportados por Oracle se agrupan en los siguientes conjuntos.
Alfanuméricos      Numéricos       Fecha     Binarios               Otros
CHAR                 NUMBER         DATE      RAW                   ROWID
VARCHAR2         FLOAT                           LONGRAW 
VARCHAR                                                BLOB 
NCHAR                                                     CLOB
NVARCHAR2                                            NLOB
LONG (Obs.)                                             BFILE

Los valores alfanuméricos van encerrados entre comilla simple: 'Alfanumérico' Los valores numéricos son número simples: 123 Las fechas van encerradas entre comillas simples: '11/11/2011' Los valores binarios no pueden ser representados (son fotos, vídeos)

Tipo de dato CHAR(b)
Almacena cadenas de caracteres de longitud fija, desde 1 a 2.000 bytes de ocupación. El número de caracteres que se pueden almacenar se rige según la siguiente fórmula.
nº caracteres = bytes / character set
Para ASCII, el conjunto de caracteres ocupa un byte, por lo que coincide el número de caracteres máximos con la ocupación del tipo de dato. Si se introduce un valor de 10 caracteres en un campo de CHAR(100), se tendrá que rellenar con 90 posiciones restantes.
Así la siguiente expresión es cierta: 'Hola Andrea' = 'Hola Andrea '
Si se intenta introducir un valor demasiado grande para el campo, se intentará eliminar los espacios finales, y si cabe sin espacios, se introduce. Si aún así no cabe, se retorna un error.

Tipo de dato VARCHAR2(b)
Almacena cadenas de caracteres de longitud variable. Si se define una columna de longitud 100 bytes, y se introduce en ella un valor de 10 bytes, la columna ocupará 10 y no 100 como hacía con el tipo de dato CHAR.

Tipo de dato VARCHAR(b)
En Oracle es equivalente a VARCHAR2, en futuras versiones permitirá distintos criterios de comparación.

Tipo de dato NCHAR(b)
Almacena un valor alfanumérico de longitud fija con posibilidad de cambio de juego de caracteres. Puede almacenar tanto caracteres ASCII, EBCDIC, UNICODE.

Tipo de dato NVARCHAR2(b)
Almacena un valor alfanumérico de longitud variable con posibilidad de cambio de juego de caracteres. Puede almacenar tanto caracteres ASCII, EBCDIC, UNICODE.

Tipo de dato NUMBER(p,s)
Almacena valores numéricos en punto flotante que pueden ir desde 1.0 x 10-130 hasta 9.9…(38 nueves)… 9 x 10125 . El almacenamiento interno de los valores numéricos en notación científica:
Mantisa x 10exponente
La mantisa puede contener cualquier número, entero o decimal, positivo o negativo. El exponente podrá contener cualquier número entero, positivo o negativo. El parámetro p indica la precisión (número de dígitos contando los decimales) que contendrá el número como máximo. Oracle garantiza los datos con precisiones de 1 a 38. El parámetro s indica la escala, esto es, el máximo de dígitos decimales. Hay que tener en cuenta que una columna definida NUMBER(10,5), podrá contener como máximo cualquier número siempre y cuando el número de dígitos enteros más el número de dígitos decimales no supere 10 (y no 15). La escala puede ir de -84 a 127. Para definir número enteros, se puede omitir el parámetro s o bien poner un 0 en su lugar. Se puede especificar una escala negativa, esto lo que hace es redondear el número indicado a las posiciones indicadas en la escala. Por ejemplo un número definido como NUMBER(5,-2), redondeará siempre a centenas. Así si intentamos introducir el valor 1355, en realidad se almacenará 1400.

Tipo de dato FLOAT(b)
Almacena un número en punto decimal sin restricción de dígitos decimales.
El parámetro b indica la precisión binaria máxima que puede moverse en el rango 1 a 126. Si se omite el defecto será 126. Una columna FLOAT(126) es equivalente a una columna NUMBER(38), aunque la diferencia está en que la columna NUMBER no podrá contener decimales y la columna FLOAT si y con cualquier escala.

Tipo de dato DATE
Almacena un valor de fecha y hora.
Para un tipo de dato DATE, Oracle almacena internamente los siguiente datos:
  • Siglo
  • Año
  • Mes
  • Día
  • Hora
  • Minuto
  • Segundo
El formato por defecto de las fechas es: 'DD-MON-YYYY' Esto es:
Dos dígitos para el día Las tres primeras siglas del mes (depende del idioma instalado). Cuatro dígitos para el año.
Por ejemplo:
'11-NOV-2011' '12-APR-1999'
Este formato puede ser alterado en cualquier momento.
Internamente una fecha se almacena como el número de días desde cierto punto de inicio (por ejemplo el año 0).
Esto permite que las fechas puedan ser tratadas en operaciones aritméticas normales:
  • '11-NOV-2011' + 10 = '21-NOV-2011'
  • '12-APR-1999' - 1 = '11-APR-1999'
  • '24-APR-1992' - '1-APR-1992' = 23
Tipos de datos binarios
Permiten almacenar información en formato “crudo”, valores binarios tal y como se almacenan en el disco duro o como residen en memoria. Estas columnas se pueden utilizar tanto para almacenar grandes cantidades de datos (hasta 4Gb.), como para almacenar directamente cualquier tipo de fichero (ejecutables, sonidos, vídeos, fotos, documentos Word, DLLs…) o para transportar datos de una base de datos a otra, ya que el formato binario es el único formato común entre cualquier sistema informático.

Tipo de dato LONG (Obsoleto)
Almacena caracteres de longitud variable hasta 2 Gb. Este tipo de dato se soporta para compatibilidad con versiones anteriores. En Oracle y siguientes versiones se debe usar los tipos de datos CLOB y NLOB para almacenar grandes cantidades de datos alfanuméricos.

Tipo de dato ROWID
Representa una dirección de la base de datos, ocupada por una única fila. El ROWID de una fila es un identificador único para una fila dentro de una base de datos. No hay dos filas con el mismo ROWID. Este tipo de dato sirve para guardar punteros a filas concretas.

Referencias:
  • http://www.tuinformaticafacil.com/oracle-11g/tipos-de-datos-en-oracle-11g
  • http://www.techonthenet.com/oracle/datatypes.php

Configuración de Oracle

A continuación se presenta un documento en el que explica la configuración de una Base de Datos en ORACLE 10g

 

BDD Oracle 

Instalación Oracle 9i 

http://ittc.up.edu.ph/upittc/sites/default/files/download/images/logo_oracle10g.gif

Scripts SQL

Una vez terminada la instalación y configuración del entorno e interfaz del DBMS lo que resta es ingresar el código SQL en el software SQL Developer, el cual tiene una interfaz muy similar a la del SQL Sever 2008 que utilizamos para los laboratorios.
En este artículo resolvemos los ejercicios planteados en el archivo "BDPEDIDOS-PARATODOS_DBMS.sql".
Primero se adaptó el lenguaje del Script dado para nuestro DBMS (Oracle):

Enlace para Descargar el Archivo SQL:
 
Crear BDD

Luego procedimos a la solución de los ejercicios planteados:

/*PROYECTO BASES DE DATOS

DBMS: ORACLE 10g

INTEGRANTES:
    Santiago Sandoval
    Carlos Muñoz
    Lenin Samaniego
    Inti Pilatuña
    Jorge Suarez*/



--1.  Actualizar el precio unitario de los productos de la categoría CARNICOS, subiéndolos en un 10%

select * from categorias;
select * from productos order by categoriaid;

update productos
set preciounit = preciounit * 1.10
where categoriaid in
(select categoriaid from categorias
where nombrecat = "CARNICOS");

--2.  Actualizar el teléfono celular del proveedor cuyo contacto es
--MANUEL ANDRADE, con el valor 099010291
--099234567
select * from proveedores;
WHERE CELUPROV = "099234567";

update proveedores
set celuprov = '099010291'
where contacto = "MANUEL ANDRADE";

--3.  Borrar el producto YOGURT DE SABORES

select * from productos;

delete from productos
where descripcion = "YOGURT DE SABORES";

--4.  Realizar las siguientes consultas:

--4.1 Mostrar todas las órdenes: el id de la orden, el apellido y nombre del empleado
--que la atendió el nombre de la compañía cliente y la fecha de orden

select * from ordenes;
select * from empleados;
select * from clientes;

select ORDENID,APELLIDO,NOMBRE,NOMBRECIA,FECHAORDEN
from ordenes o join empleados e
on o.empleadoid = e.empleadoid
join clientes c
on o.clienteid = c.clienteid
order by ordenid;


--4.2 Mostrar la suma total de cada tipo de producto pedidos en todas las órdenes.

select * from detalle_ordenes;
select * from productos;

select do.PRODUCTOID, SUM(CANTIDAD) AS 'TOTAL PRODUCTO'
from detalle_ordenes do join productos p
on do.productoid = p.productoid
group by do.productoid;

--4.3 Mostrar el número de órdenes atendidas por cada empleado, incluidos los que tienen 0 órdenes.

select * from ordenes WHERE EMPLEADOID = 2;
select * from empleados;

select e.EMPLEADOID, COUNT(*) as "ORDENES POR EMPLEADO"
from empleados e join ordenes o
on e.empleadoid = o.empleadoid
group by e.EMPLEADOID;

--4.4 Muestre los proveedores y la suma de dinero vendido en los productos de ese proveedor. */

select * from proveedores;
select * from detalle_ordenes ORDER by productoid;
select * from productos;

select NOMBREPROV, sum(CANTIDAD*PRECIOUNIT) as 'TOTAL VENDIDO'
from detalle_ordenes do join productos p
on do.productoid = p.productoid
join proveedores pr
on p.proveedorid = pr.proveedorid;
group by NOMBREPROV;

--5. Realizar el siguiente procedimiento almacenado.

--5.1 Escriba un procedimiento almacenado que reciba como parámetro un código de proveedor y
--devuelve el número de órdenes en las que están incluidos productos de ese proveedor.
   
select * from proveedores;
select * from detalle_ordenes order by productoid;
select * from productos;

drop procedure num_ordenes;

CREATE PROCEDURE num_ordenes  @codprov int AS
SELECT  'NUMERO ORDENES' = COUNT(*)
FROM detalle_ordenes do join productos p
ON do.productoid = p.productoid
WHERE p.proveedorid = @codprov
RETURN;

Exec num_ordenes 20;
   

--5.2 Escriba un procedimiento almacenado que reciba como parámetro un nombre de una categoría y
--devuelve el código del producto de esa categoría que tiene más unidades vendidas.

select * from categorias;
select * from productos; 
select * from detalle_ordenes;

DROP PROCEDURE masvendido;

CREATE PROCEDURE masvendido  @nomcat varchar(80) AS
SELECT  'MAS VENDIDO' = do.productoid
FROM detalle_ordenes do join productos p
ON do.productoid = p.productoid
WHERE cantidad >
(select MAX(SUM(CANTIDAD))
from detalle_ordenes do join productos p
on do.productoid = p.productoid
join categorias c
on p.categoriaid = c.categoriaid
where c.nombrecat = @nomcat)
RETURN;

Exec masvendido "LACTEOS";

Archivo SQL

Solución a los ejercicios

A continuación presentamos un video tutorial para la creacion de los scripts en Oracle 10g para Linux

Video de Scripts 

Instalación de Oracle en Linux






Para ver el video en mejor calidad hecer click en el enlace
http://www.youtube.com/watch?v=eXr1mMDc06U

http://www.oracle.com/technetwork/database/express-edition/downloads/102xelinsoft-102048.html

En el link mostrado vamos a proceder a descargar el siguiente paquete: oracle-xe_10.2.0.1-1.0_i386.deb,  luego vamos a aceptar el acuerdo de licencias de Oracle. Para poder descargar cualquier producto desde la página de Oracle de debe tener una cuenta registrada allí, se lo puede hacer fácilmente y así podremos obtener los productos.

Para mayor facilidad a continuación se muestran dos enlaces para que puedan descargar lo necesario para la instalación de Oracle en Linux de 32 bits:

https://www.dropbox.com/s/gl9e57rlz7v37s6/libaio_0.3.104-1_i386.deb
https://www.dropbox.com/s/3ouipthuxho3ak3/oracle-xe_10.2.0.1-1.0_i386.deb


Una vez listos con todo lo necesario aplicamos lo siguiente:

Primero abrimos una Terminal en Ubuntu y procedemos a instalar los paquetes, primero el de las librerías, pero para poder hacerlo debemos estar con privilegios de usuario “root” de Linux; Ubuntu siempre tiene de cierta manera bloqueado al root para evitar q se ingrese, así que debemos habilitarlo con lo siguiente:
1. En la terminal de Linux escribimos lo siguiente:
sudo passwd root
luego de esto nos pedirá ingresar la contraseña del usuario actual que estamos usando, seguido de la nueva contraseña para el usuario root
2. Una vez ingresada la nueva clave del root nos registramos en el sistema como ese usuario, esto se lo logra escribiendo:
su - root
nos pedirá la contraseña del root y listo ya estamos como usuario root.
Una vez hecho esto, inicializamos la instalación del paquete libaio_0.3.104-1_i386.deb para ello simplemente podemos dar doble click sobre el paquete y nos aparecerá el centro de software de Ubuntu q nos ayudará a instalarlo.
Luego de finalizada la instalación del libaio_0.3.104-1_i386.deb ejecutamos el paquete de Oracle xe oracle-xe_10.2.0.1-1.0_i386.deb de la misma forma que el anterior paquete ya que Ubuntu nos ayudará a instalarlo con su centro de software Ubuntu.
Para poder usar el administrador de la base de datos de Oracle primero debemos configurar a nuestro Oracle, así que ejecutamos lo siguiente en la línea de comandos de la terminal de Ubuntu:
sudo /etc/init.d/oracle-xe configure
Con eso arranca un configurador en modo texto que nos hará una serie de preguntas, como el puerto de la aplicación web en el cual por defecto aparecerá 8080, lo dejamos así presionando simplemente “Enter”; también el puerto del servicio del Listener que es el que nos permitirá conectarnos a nuestra base de datos, en el puerto 1521 por defecto; el password que queremos para los usuarios SYS, SYSTEM de Oracle; y si deseamos ejecutar la Base de datos cada vez que se inicie la máquina.
Ya configurado esto, buscamos el lanzador de Oracle para el navegador, haciendo click en Aplicaciones/Oracle Database 10g Express Edition/Ir a la página inicial de base de datos.
En esta parte nos aparecerá el navegador web que tengamos cargando la aplicación del inicio de sesión a la base de datos, escribimos como usuario SYSTEM y su contraseña será la que se haya puesto durante el proceso de configuración.
Iniciado sesión, podremos ver a las opciones de administración que tenemos a disposición.
A nosotros nos involucra más sobre crear scripts para hacer consultas, entre otras operaciones sobre alguna base de datos, así que para ello utilizaremos una herramienta de Oracle llamada SQL Developer, el cual lo podemos obtener gratuitamente desde la página de Oracle en el siguiente link :
Aceptamos el acuerdo de licencias de Oracle y descargamos la versión para Linux de 32 bits en extensión .rpm. Pero para poder usar SQL Developer necesitamos una versión de JDK instalada en Ubuntu y para ellos debemos actualizar e instalar JDK.
Ahora, para poder utilizar un paquete con extensión .rpm en Ubuntu debemos instalar el RPM y el JDK para que pueda abrir ese formato, pero para hacer eso primero debemos actualizar el repositorio de descargas que tiene Ubuntu añadiendo la siguiente línea en la terminal de ubuntu:
sudo add-apt-repository "deb http://archive.canonical.com/ lucid partner"
Luego de esto debemos actualizar el repositorio con la siguiente instrucción:
sudo apt-get update
y esperamos a que termine el proceso. Ya terminado el proceso podremos instalar el JDK que necesita SQL Developer para su funcionamiento:
sudo apt-get install sun-java6-jdk
Cuando se haya terminado de bajar e instalar, debemos instalar otro paquete para poder convertir el .rpm a .deb y poder ejecutarlo de forma normal en 
Ubuntu, así que necesitaremos del paquete Alien, escribimos lo siguiente:
sudo apt-get install alien
Con este paquete podremos hacer la conversión del sqldeveloper-3.0.04.34-1.noarch.rpm, que nos descargamos, hacia el formato que necesitamos y se lo logra ejecutando la siguiente línea en la terminal:
sudo alien –d sqldeveloper-3.0.04.34-1.noarch.rpm
Cuando termine de convertir, nos dirigimos a la carpeta del Root y encontraremos al paquete ya convertido sqldeveloper-3.0.04.34-2_all.deb, lo instalamos desde la terminal con la siguiente línea:
sudo dpkg –i sqldeveloper-3.0.04.34-2_all.deb
Ya terminada la instalación de SQL Developer tendríamos todos los paquetes que necesitamos para que funcione Oracle XE con SQL Developer; pero necesitamos configurar ciertos parámetros para poder lograr la conexión a la base.
Primero debemos configurar nuestras variables de entorno, esto es importante ya que algunos procesos como triggers o stored procedures necesitan de estas variables para poder ser ejecutadas, así que buscamor el fichero .bashcr que se localiza en /etc/
sudo nano /etc/bash.bashcr
y lo editamos añadiendo las siguientes líneas:
# variables para ORACLE
ORACLE_HOME=/usr/lib/oracle/xe/app/oracle/product/10.2.0/server PATH=$PATH:$ORACLE_HOME/bin export ORACLE_HOME export ORACLE_SID=XE export PATH
Guardamos y salimos; ahora bien necesitamos configurar el sqldeveloper enlazando la ruta de ubicación del jdk, para ello debemos verificar que versión de jdk tenemos escribiendo la siguiente línea en la terminal:
ls –l /usr/lib/jvm
Con eso sabremos que versión tenemos y la ruta específica del java, es muy importante esto ya que debemos agregar esta ruta usr/lib/jvm/java-6-sun-1.6.0.26 en el archivo de configuración de sqldeveloper para que pueda funcionar de forma correcta, ahora vamos al directorio de sqldeveloper:
cd /opt/sqldeveloper/sqldeveloper/bin
Y editamos el archivo sqldeveloper.conf así:
sudo nano sqldeveloper.conf
y editamos la línea de SetJavaHome para que quede de la siguiente forma con la 
ruta de java:
SetJavaHome /usr/lib/jvm/java-6-sun-1.6.0.26
Guardamos el archive y listo, ya todo queda configurado.
Ahora solo falta inicializar SQL Develper, con la siguiente sentencia en la terminal:
sqldeveloper
y se ejecutará SQL Developer.
Para realizar la conexión vamos a poner “PEDIDOS” como nombre de conexión, el nombre de usuario será “SYSTEM” y la clave será la que configuramos al comienzo; verificamos los puertos y procedemos a probar la conexión, si no hay fallos guardamos y conectamos.
Y así ya tenemos a nuestra base de datos conectada lista para ejecutar scripts.