martes, 5 de junio de 2012

Nuevo Operador Pivot en Oracle 11g R2


Nuevo Operador Pivot en Oracle 11g R2

Muchas nuevas funcionalidades ha incorporado la versión 11gR2 y esta es una, que para los que tenemos algunos años en el sector, la encontramos de suma utilidad.
Este operador permite resolver lo que se llaman consultas de referencia de tablas cruzadas con gran facilidad.
En primer lugar que es una consultas de tablas de referencias cruzadas?
Las consultas cross table o también llamadas tablas de doble entrada, tablas multidimensionales, tablas dinámicas o Pivot y son utilizadas en entornos de reportes, BI y Olap.
Estas consultas se utilizan asociadas a funciones agrupadas tales como SUM, COUNT, MIN, MAX, etc.
Pero a diferencia de lo que ocurre con una sentencia común, con este tipo de funciones, necesitamos que los resultados no sean mostrados en cada fila, sino que sea mostrado por columna o sea convertir cada fila en una columna.
En las figuras que siguen mostramos la diferencia de concepto. Este primer ejemplo muestra una consulta donde obtenemos el total de ventas agrupados por el canal de venta campo Chanel_id y por cada trimestre del año campo Calendar_Quarter_Desc para las ventas del año 1998.
Ejemplo de una sentencia clásica:
Select Channel_Id, Calendar_Quarter_Desc, Sum(Amount_Sold) 
From Sales S
Join Times T
On S.Time_Id=T.Time_Id
Where S.Time_Id In (Select Time_Id From Times Where Calendar_Year=1998)
Group By Channel_Id, Calendar_Quarter_Desc
Order By Channel_Id, Calendar_Quarter_Desc;

Este sería el resultado de la consulta anterior
resultado de la consulta
Figura 1. Resultado de la consulta
Difiere de este que es el resultado de una consulta cross table
resultado de la consulta cross table
Figura 2 Resultado de la consulta cross table
Como se ve en la figura 1, el resultado de la fila CHANNEL_ID se repite por cada trimestre en total 4 por CHANNEL_ID.

En la figura 2 vemos el cambio cada campo CHANNEL_ID aparece una única vez y los resultados aparecen ahora en columnas separadas una por trimestre.
Como se resolvía en anteriormente
Ante esta situación lo que podíamos hacer era una consulta muy poco intuitiva y dificil de mantener, que cumplía con nuestro fin.
La consulta era la siguiente;
Select Channel_Id,
        Sum(Case Calendar_Quarter_Desc
            When '1998-01' Then Amount_Sold
            Else 0
            End)"1988-01",
        Sum(Case Calendar_Quarter_Desc
            When '1998-02' Then Amount_Sold
            Else 0
            End)"1988-02",
       Sum(Case Calendar_Quarter_Desc
            When '1998-01' Then Amount_Sold
            Else 0
            End)"1988-03",
      Sum(Case Calendar_Quarter_Desc
            When '1998-01' Then Amount_Sold
            Else 0
            End)"1988-04"
From Sales S
      Join Times T
      On S.Time_Id=T.Time_Id
Where S.Time_Id In (Select Time_Id From Times Where Calendar_Year=1998)
Group By Channel_Id
order by "1988-01" Desc;

El resultado se muestra en al figura 3
resultado de la consulta cross table
Figura 3. Resultado de la consulta cross table
Como vemos el resultado mostrado por la figura 3, es el esperado.
Ahora en que nos diferencia una sentencia u otra al fin de cuentas es el mismo resultado.
La primera conclusión que podemos sacar, la más inmediata es que es mucho más difícil de mantener la segunda consulta que la primera.
Ante cualquier cambio que necesite involucra varia líneas de código y podría ser extremadamente largo si el resultado de la consulta debiera mostrar 20 columnas.. Dejando afuera por ahora otros temas, como performance, podemos ver que si usamos la primer sentencia. Toda la carga del proceso la conversión de fila en columna es responsabilidad de la aplicación.
Y acá vamos a una regla fundamental, "Todo lo que pueda resolver una sentencia SQL se debe resolver con una sentencia SQL".
A partir de la versión ORACLE 11G provee un comando nos permite una salida como la mostrada en la figura 2 y mucho más fácil de mantener. Para ello incorpora el operador PIVOT.
Sintaxis
Select * from table t                −> Origen de datos
Pivot                                −> Operador Pivot
( fn_agregada_1() Alias_1,
  Fn_agregada_2() Alias_2,           −> Funciones agrupadas a mostrar
  Fn_agregada_n () Alias_n
 
 For (campo_1 Alias_1 ,
      Campo_2 Alias_2 ,…,     −> Campos donde se genera el pivot
      Campo_n Alias_n) 
      In(Lista de valores)    −> Filtros para generar las columnas
 )

Consideraciones
Cuando se va a realizar más de una función agregada se le debe poner alias a cada función
En caso contrario se genera un error Oracle ORA- ORA-00918: columna definida de forma ambigua
El título de la columna va a corresponder a la lista de valores o alias de cada valor que figuren en el operador FOR.
Si se especifica el alias para el valor se tomará el alias.
Si figura en alias en las funciones agregadas. Se agregaran el nombre de la columna concatenado con un “_”
Ejemplo:
Select * From (Select Channel_Id,Calendar_Quarter_Desc,
                      Amount_Sold
  From Sales S
  Join Times T
      On S.Time_Id=T.Time_Id
  Where S.Time_Id In (Select Time_Id 
       From Times 
       here Calendar_Year=1998)) T
  Pivot 
  ( Sum(Amount_Sold) Monto_Vendido
 Count (Amount_sold) Cant_Vendida
 For Calendar_Quarter_Desc in ('1998-01',
   '1998-02')
     ) 
order by 2 desc;

Para el caso del ejemplo, se utiliza como fuente de datos la subconsulta que recupera de la tabla Sales los campos CHANEL_ID que indica el id del canal de venta, AMOUNT_SOLD que recupera el monto de cada venta y de la tabla TIMES recupera el campo CALENDAR_QUARTER_DESC que identifica el trimestre del año de la compra y filtra por los trimestres correspondientes al año 1988
Coloca el operador PIVOT y a continuación le indica las funciones agrupadas a realizar, como lo son la suma de los montos vendidos
 Sum(Amount_Sold) Monto_Vendido
  
Y el recuento de ventas
 Count (Amount_sold) Cant_Vendida
  
Al ser más de una las operaciones de agrupamiento cada una debe llevar una alias como se explico anteriormente.
La cláusula For seguida del campo Calendar_Quarter_Desc indica que pivoteará esta fila colocando el valor obtenido en una pseudocolumna la que llevará el nombre del valor indicado en lista de valores dentro del operador IN y de corresponder como este caso la concatenación con el alías de la función agrupada. '1998-01’_MONTO_VENDIDO La Cláusula IN recibe los valores sobre los que aplicara el PIVOT en este caso son una parte de ellos aunque la consulta trae todos los trimestres del año 1988.
Ejecutada la sentencia nos quedaría este resultado.
Figura 4. Resultado de la consulta cross table con el operador Pivot 
Figura 4 Resultado de la consulta cross table con el operador Pivot
Performance:
En los siguientes pasos se muestra el resultado del comando AUTOTRACE con EXPLAIN PLAN.
Para la sentencia que usamos históricamente:
Set Timing on
Set Autotrace on
Select Channel_Id,
        Sum(Case Calendar_Quarter_Desc
            When '1998-01' Then Amount_Sold
            Else 0
            End)"1988-01",
        Sum(Case Calendar_Quarter_Desc
            When '1998-02' Then Amount_Sold
            Else 0
            End)"1988-02",
From Sales S
Join Times T
On S.Time_Id=T.Time_Id
Where S.Time_Id In (Select Time_Id From Times Where Calendar_Year=1998)
Group By Channel_Id
Order By "1988-01" Desc;

Ahora ejecutamos la sentencia que contiene el operador PIVOT
Select * From (Select Channel_Id,Calendar_Quarter_Desc,
   Amount_Sold
  From Sales S
   Join Times T
       On S.Time_Id=T.Time_Id
  Where S.Time_Id In (Select Time_Id 
        From Times 
        Where Calendar_Year=1998)) T
  Pivot 
  ( Sum(Amount_Sold) Monto_Vendido
      Count (Amount_sold) Cant_Vendida
         For Calendar_Quarter_Desc in ('1998-01',
    '1998-02')
  ) 
order by 2 desc;

Lo primero que observamos entre los dos resultados es que el EXPLAIN PLAN es similar salvo que el ACCES PATH cambia a HASH GROUP BY PIVOT . El resto es igual. 
Cuando vemos las estadísticas encontramos cambios.
Las llamadas recursivas bajan más del 50% (51.9)
Son menores las lecturas lógicas y físicas
Y se reduce el ordenamiento en la memoria en el orden mayor al 50%
Restricciones:
Todos los ejemplos hasta ahora han pivotado un dominio conocido de los valores en el operador IN.
Si no tuviéramos valores conocidos o queremos que sea dinámica y se adapte a los futuros cambios, no nos servirían los ejemplos que hemos utilizado sería importante que el operador IN reciba una subconsultas como el ejemplo siguiente.
Pivot 
 ( Sum(T.Amount_Sold) Total_Vendidido,
   Count(Amount_Sold) Cant_Vendida
    For Calendar_Quarter_Desc in (Select Calendar_Quarter_Desc 
    from Times t where t.Calendar_Year between 1998 and 2000 )
 )

Esta alternativa genera un error ORA-00936. 00000 - "missing expression" s una restricción importante que la lista de valores sea conocida y no pueda ser modificada sino por medio de la modificación de la consulta.
Fuente:  Gerardo Daniel Tezza.
__________________________________________________________________________________
"No permitas que nadie diga que eres incapaz de hacer algo, ni si quiera yo. Si tienes un sueño, debes conservarlo. Si quieres algo, sal a buscarlo, y punto. ¿Sabes?, la gente que no logra conseguir sus sueños suele decirles a los demás que tampoco cumplirán los suyos"
En busca de la  felicidad

domingo, 6 de febrero de 2011

¿Cómo almacenar un password en una tabla?

Generalmente siempre utilizamos la funcionalidad que ORACLE proporciona para realizar validaciones de usuario, esto es, permitir que el administrador de Oracle se encargue de validar nuestras credenciales.

Un software debería mantener sus propias validaciones, para esto nos podemos apoyar en el paquete dbms_obfuscation_toolkip, utilizar la función o procedimiento MD5 (se encuentra sobrecargado), esta es una función hash irreversible que encripta la contraseña que se ha ingresado como parámetro a la función, y es imposible, que a partir de esta contraseña encriptada podamos regresar a la contraseña origen.

Veamos un ejemplo de como implementar esta funcionalidad.


declare

-- Esta variable tendrá la contraseña
-- que vamos a encriptar

v_pass varchar2(100) := 'notemientoteamo';

-- Estas dos variables contienen la contraseña
-- pasada por la función md5

v_pass_obfuscaton1 varchar2(100);
v_pass_obfuscation2 varchar2(100);

-- Estas dos funciones convierten la contraseña
-- producto del paso md5, a un valor hexadecimal,
-- que es la se aconseja guardemos en la tabla

v_pass_hex1 varchar2(100);
v_pass_hex2 varchar2(100);

begin

v_pass_obfuscation1 := dbms_obfuscation_toolkit.md5(input_string=>v_pass);
v_pass_obfuscation2 := dbms_obfuscation_toolkit.md5(input_string=>v_pass);

select lower(rawtohex(v_pass_e1))
into v_c1
from dual;

select lower(rawtohex(v_pass_e2))
into v_c2
from dual;

update usuarios
set password = v_c1
where username = 1; -- bueno es un ejemplo

dbms_output.put_line(v_pass_hex1);
dbms_output.put_line(v_pass_hex2);

IF v_pass_hex1 = v_pass_hex2 THEN
dbms_output.put_line('IGUALES....');
END IF;

end;
/


Para poder utilizar el paquete dbms_obfuscation_toolkit el paso de parámetros a la función debe realizase por el método nombrado, no posicional; esto porque existen funciones y procedimientos que sobrecargan md5 y el kernel se "confunde" al tratar de darle solución.


v_pass_obfuscation1 := dbms_obfuscation_toolkit.md5(input_string=>v_pass);

__________________________________________________________________________________
"No permitas que nadie diga que eres incapaz de hacer algo, ni si quiera yo. Si tienes un sueño, debes conservarlo. Si quieres algo, sal a buscarlo, y punto. ¿Sabes?, la gente que no logra conseguir sus sueños suele decirles a los demás que tampoco cumplirán los suyos"
En busca de la  felicidad


domingo, 30 de enero de 2011

Servicios Heterogeneos

El mecanismo para conectar una fuente de datos oracle con una fuente de datos no oracle, como por ejemplo MSQLServer, MSAccess, mysql, ha sido denominado por Oracle como Servicios heterogéneos de conectividad o Heterogeneus Services (en Inglés), y que básicamente se puede entender como una conexion a otras fuentes de datos a través de los enlaces llamados database links.
Entre los objetivos más importantes de los servicios heterogéneos se encuentran:
- Usar el SQL de oracle para accesar datos de otras fuentes no oracle, como si estuvieran en el servidor Oracle.
- Hacer llamadas a otros servicios, APIS, fuentes de datos de manera transparente desde su ambiente distribuído.
Configurar dicha conexión es una tarea muy sencilla, solo basta tener instalados los respectivos drivers y modificar algunos archivos del servidor Oracle. Veamos cómo crear una conexión con una base de datos MSAccess a través de un ODBC
1. Configurar el ODBC. (supongo que esto ya lo sabes hacer, entonces no me extenderé en eso). Asumamos que el nombre al la conexión ODBC se llama extbd, nombre que usaremos de ahora en adelante.
2. Ahora bien, creamos un archivo que se llamará init.ora, es decir, para nuestro caso nuestra bd remota la vamos a llamar extbd, por lo cual el nuestro archivo se llamará initextbd.ora y lo guardamos en el directorio ORACLE_HOME\hs\admin, donde ORACLE_HOME es el directorio de instalación de Oracle en la máquina, en mi caso es C:\oracle\ora10g\10.1.0\Db_1
Bien, dentro de este archivo vamos a configurar el valor para el parámetro HS_FDS_CONNECT_INFO asignándole el nombre del odbc que fué creado . Para nuestro ejemplo sería:

-------------------------------------------------------------
# This is a sample agent init file that contains the HS parameters that are
# needed for an ODBC Agent.
#
# HS init parameters
#
HS_FDS_CONNECT_INFO = extbd
HS_FDS_TRACE_LEVEL = OFF
#
# Environment variables required for the non-Oracle system
#
#set =
--------------------------------------------------------------

3. El siguiente paso consiste en modificar el archivo listener.ora del servidor Oracle, agregando algunas entradas:
SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC =
(SID_NAME = PLSExtProc)
(ORACLE_HOME = C:\oracle\ora10g\10.1.0\Db_1)
(PROGRAM = extproc)
)
(SID_DESC =
(SID_NAME = extbd)
(ORACLE_HOME = C:\oracle\ora10g\10.1.0\Db_1)
(PROGRAM = hsodbc)
)
)
LISTENER =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC))
)
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = )(PORT = 1521))
(ADDRESS = (PROTOCOL = TCP)(HOST = )(PORT = 1524))
)
)
)
Reiniciar el listener. Para ello, atraves de la consola del Sistema operativo
Lsnrctl stop
Lsnrctl start
4. Agregar la entrada para la fuente de datos no Oracle en el archivo tnsnames.ora
extbd =
(DESCRIPTION=
(ADDRESS=(PROTOCOL=tcp)(HOST=)(PORT=1524))
(CONNECT_DATA=(SID=extbd))
(HS=OK)
)
donde,
HS=OK indica que se usarán servicios heterogéneos
5. Crear el enlace database link desde oracle referenciando el servicio heterogéneo:
Create public database link ext.world connect to admin identified by admin using 'extbd';
Listo, ahora puedes verificar la connexion a la bd externa lanzando una query
SELECT * from latabladelafuenteexterna@ext.world;
__________________________________________________________________________________
"Te quiero. Te quise desde el primer momento en que te vi. Te quise incluso antes de verte por primera vez."
Un lugar en el Sol

jueves, 27 de enero de 2011

Condición de Compilación Oracle 10g

En PL/SQL, y por lo general cualquier lenguaje, cada nueva versión llega con características interesantes, que merecen implementarse. En muchas ocasiones esto implica tener que eliminar código viejo y reemplazarlo por las funcionalidades nuevas. Pues bien, una de estas características, y que surgió con la versión 10g, es la de permitir personalizar una aplicación PL/SQL sin tener que remover código fuente, esto es permitir evaluar la versión con la que se esta trabajando y dependiendo de la versión, utilizar el código de programación más acorde, a esto se le conoce como condición de compilación.

Veamos un ejemplo, el siguiente código

-- Utilizamos la constante ver_le_10_2, expresión estática requisito
-- para ser evaluada en una cóndición de compilación.
-- Esta constante retorna verdadero si la versión es menor o igual
-- a 10 y el release menor o igual a 2


$IF dbms_db_version.ver_le_10_2 $THEN
SELECT CASE p_tipoSECUENCIA
WHEN 'DATOS' THEN sec_datos.nextval
WHEN 'ADMIN' THEN sec_admin.nextval
END
INTO v_secuencia
FROM dual;
$ELSE
v_secuencia :=
CASE p_tipoSECUENCIA
WHEN 'DATOS' THEN sec_datos.nextval
WHEN 'ADMIN' THEN sec_admin.nextval
END;
$END

La condición de compilación en ORACLE se implementa cuando utilizamos una condición de compilación, podemos identificarla cuando al realizar la condición IF, anteponemos el símbolo "$", en este caso:

$IF dbms_db_version.ver_le_10_2 $THEN esto significa que si encuentra una versión anterior o igual a la 10.2 tendrá en cuenta el código correspondiente:

SELECT CASE p_tipoSECUENCIA
WHEN 'DATOS' THEN sec_datos.nextval
WHEN 'ADMIN' THEN sec_admin.nextval
END
INTO v_secuencia
FROM dual;

De lo contrario $ELSE

v_secuencia :=
CASE p_tipoSECUENCIA
WHEN 'DATOS' THEN sec_datos.nextval
WHEN 'ADMIN' THEN sec_admin.nextval
END;

Oracle en tiempo de compilación, tomará el código que se ajuste a la versión current de base de datos. Esto no solamente dará transportabilidad de código a diferentes versiones, sino que estimula a que nosotros como programadores estemos al tanto de las nuevas características y las implementemos de una manera sencilla.

__________________________________________________________________________________
"Cuando tita sintió sobre sus hombros la ardiente mirada de Pedro, comprendió perfectamente lo que debe sentir la masa de un buñuelo al entrar en contacto con el aceite hirviendo: Era tan real la sensación de calor que la invadía, que ante el temor de que como a un buñuelo le empezaran a brotar burbujas por todo el cuerpo, el vientre, el corazón, los senos, bajó la mirada y trató de huir."
Como agua para chocolate

domingo, 16 de enero de 2011

Herencia con oracle 10g

Con Oracle 8, el entusiasmó de los objetos duró poco cuando comprobé que los TYPE OBJECT no soportaban la herencia y por supuesto el polimorfismo, realicé todo un trabajo embebiendo objetos padre dentro del hijo, de esta manera pude emular esta característica.

Acabo de realizar una reingeniería de mi proyecto, utilizando la verdadera herencia que esta versión ofrece (10g) y quiero compartir una de las características más importantes que pude utilizar, el polimorfismo.

Lo que encontraremos en esta entrada es un sencillo ejemplo de herencia, representado por tres objetos: padre, hijo1 e hijo2.

El type padre declara 3 atributos:
  • atributo1
  • atributo2
  • atributo3
Y declara dos métodos (no los implementa):
  • SetAtributo
  • Listar
Nuestros types hijos: hijo1, hijo2, cada uno mantendrá un atributo "propio":

El type hijo1 tiene 1 atributo propio (este type-clase hereda de padre)
  • ahijo1
E implementamos los métodos :
  • SetAtributo
  • Listar
El type hijo2 tiene 1 atributo propio (este type-clase hereda de padre)
  • ahijo2
E implementamos los métodos :
  • SetAtributo
  • Listar
Por favor observemos bien la implementación, que realiza cada tipo de método.

El código:

/* Declaración del TYPE padre, este tipo se crea con la característica NOT FINAL, para permitir ser utilizado como "padre" por otros types.

Tener en cuenta que los métodos no son implementados. */

CREATE OR REPLACE TYPE PADRE AS OBJECT (
atributo1 number(5),
atributo2 varchar2(10),
atributo3 date,
member procedure SetAtributo,
member procedure Listar
) not final
/

/* Creación de los tipos HIJO 1, HIJO 2. Estos TYPE heredan del tipo PADRE (UNDER PADRE). Cada tipo conserva un atributo propio (ahijo1, ahijo2), y lo más importante implementan los métodos SetAtributo y Listar. */

CREATE OR REPLACE TYPE HIJO1 UNDER PADRE (
ahijo1 number(5),
overriding member procedure SetAtributo,
overriding member procedure Listar,
member procedure Set1
)
/

CREATE OR REPLACE TYPE BODY HIJO1 IS

OVERRIDING MEMBER procedure SetAtributo IS
BEGIN
atributo1 := 1000;
atributo2 := 'HIJO 1';
atributo3 := sysdate-1000;
ahijo1 := 1553;
set1;
END SetAtributo;

OVERRIDING MEMBER procedure Listar IS
BEGIN
dbms_output.put_line(' Atributo 1 --> 'SELF.atributo1);
dbms_output.put_line(' Atributo 2--> 'SELF.atributo2);
dbms_output.put_line(' Por HIJO1 'SELF.ahijo1);
END Listar;

MEMBER procedure Set1 IS
BEGIN
atributo1 := 4567;
END Set1;

END;
/

CREATE OR REPLACE TYPE HIJO2 UNDER PADRE (
ahijo2 number(5),
overriding member procedure SetAtributo ,
overriding member procedure Listar
)
/

CREATE OR REPLACE TYPE BODY HIJO2 IS

OVERRIDING MEMBER procedure SetAtributo IS
BEGIN
atributo1 := 2000;
atributo2 := 'HIJO 2';
atributo3 := sysdate-2000;
ahijo2 := 9999;
END SetAtributo;

OVERRIDING MEMBER procedure Listar IS
BEGIN
dbms_output.put_line(' Atributo 1 --> 'SELF.atributo1);
dbms_output.put_line(' Atributo 2--> 'SELF.atributo2);
dbms_output.put_line(' Por HIJO2 'SELF.ahijo2);
END Listar;

END;
/

Fin del código que crea los tipos.

Veamos ahora el código para probar el polimorfismo

declare
v_padre padre;
v_hijo1 hijo1;
v_hijo2 hijo2;
begin
-- Inicializamos los hijos (hijo1, hijo2)
v_hijo1 := new hijo1(null,null,null,null);
v_hijo1.SetAtributo;
v_hijo2 := new hijo2(null,null,null,null);
v_hijo2.SetAtributo;

-- Instanciamos el padre con cada hijo y listamos valores.
v_padre := v_hijo1;
v_padre.Listar;
v_padre := v_hijo2;
v_padre.Listar;
end;

Quiero que antes de ejecutarlo, observemos bien el papel de este código:
  1. Se inicializan los objetos hijos, es decir, realizamos el llamado de los métodos SetAtributo tanto del hijo1 como del hijo2.
  2. Se Instancia al padre con el valor de cada objeto hijo, si señor, con el valor de cada objeto hijo, de esta manera este padre podra resolver los métodos de cada uno de sus hijos
Ejecutemos este ejemplo, una salida:

SQL> start probarpoli
Atributo 1 --> 4567 Atributo 2-->HIJO 1 Por HIJO1 Propio 1553
Atributo 1 --> 2000 Atributo 2-->HIJO 2 Por HIJO2 Propio 9999

Procedimiento PL/SQL terminado correctamente.

Quiero que detallen la forma en que podríamos encapsular el comportamiento de los métodos hijos, si son buenos observadores, se darán cuenta que estamos manipulando, desde el padre, atributos que pertenecen a diferentes hijos.
Bueno, este ejemplo aunque sencillo, muestra lo robusto que puede ser nuestro código con este paradigma asumido por PL/SQL.
__________________________________________________________________________________
"Llegar a viejo sin haberse enamorado de verdad..., es como no haber vivido"
Conoces a Joe Black