Manual Completo de SQL
Manual Completo de SQL
Manual Completo de SQL
Identificadores
Operadores
Operador de asignación
- (resta)
* (multiplicación)
/ (división)
** (exponente)
= (igual a)
Variables
Las variables son nombres para procesar los elementos de los datos.
Declaración:
Nombre_variable tipo [NOT NULL] [:= valor | DEFAULT valor]
Ejemplos:
Por ejemplo:
num_dep emple.dept_no%TYPE
Constantes
Las constantes son como las variables pero no puede modificarse su valor. Se
declaran de la siguiente manera:
SYSDATE
NVL
Devuelve el valor recibido como parámetro en el caso de que expresión sea
NULL,o expresión en caso contrario.
NVL(<expresion>, <valor>)
DECODE
SELECT
DECODE
(co_pais, /* Expresion a evaluar */
TO_DATE
TO_DATE(<expresion>, [<formato>])
En este ejemplo convertimos la expresion ’01/12/2006′ de tipo CHAR a una
fecha (tipo DATE). Con el parámetro formato le indicamos que la fecha está
escrita como día-mes-año para que devuelve el uno de diciembre y no el doce de
enero.
SELECT TO_DATE(’01/12/2006′,
‘DD/MM/YYYY’)
FROM DUAL;
‘DD/MM/YYYY HH24:MI:SS’)
FROM DUAL;
TO_CHAR
TO_CHAR(<expresion>, [<formato>])
SELECT
TO_CHAR(SYSDATE, ‘DD/MM/YYYYY’)
FROM DUAL;
TO_NUMBER
TO_NUMBER(<expresion>, [<formato>])
TRUNC
LENGTH
SELECT
LENGTH(‘HOLA MUNDO’)FROM DUAL;
INSTR
Su sintaxis es la siguiente:
SELECT
INSTR(‘AQUI ES DONDE SE BUSCA’, ‘BUSCA’, 1, 1 )
FROM DUAL;
REPLACE
Obtiene una parte de una expresion, desde una posición de inicio hasta una
determinada longitud.
UPPER
LOWER
ROWIDTOCHAR
SELECT ROWIDTOCHAR(ROWID)
FROM DUAL;
RPAD
LPAD
SELECT
LPAD(‘Hola Mundo’, 50, ‘.’)
FROM DUAL;
RTRIM
SELECT
RTRIM (‘Hola Mundo ‘)
FROM DUAL;
LTRIM
SELECT
LTRIM (‘ Hola Mundo’)
FROM DUAL;
TRIM
SELECT
TRIM (‘ Hola Mundo ‘)
FROM DUAL;
MOD
MOD(<dividendo>, <divisor> )
SELECT MOD(20,15) – Devuelve el modulo de dividir
20/15
FROM DUAL
Bloque PL/SQL
Partes de un bloque:
[ DECLARE | IS / AS ]
<declaraciones>
BEGIN
<instrucciones>
[ EXCEPTION ]
<tratamiento de excepciones>
END;
Excepciones en PL/SQL
DECLARE
– Declaraciones
BEGIN
– Ejecucion
EXCEPTION
WHEN NO_DATA_FOUND THEN
– Se ejecuta cuando ocurre una excepcion de tipo
NO_DATA_FOUND
WHEN ZERO_DIVIDE THEN
– Se ejecuta cuando ocurre una excepcion de tipo
ZERO_DIVIDE
END;
Excepciones predefinidas
Excepcion
Se ejecuta … SQLCODE
El programa intentó
ACCESS_INTO_NULL asignar valores a los
-6530
atributos de un objeto no
inicializado
El programa intentó
COLLECTION_IS_NULL asignar valores a una
-6531
tabla anidada aún no
inicializada
El programa intentó
almacenar valores
duplicados en una
DUP_VAL_ON_INDEX
columna que se mantiene -1
con restricción de
integridad de un índice
único (unique index)
INVALID_CURSOR
El programa intentó -1001
efectuar una operación
no válida sobre un cursor
El programa intentó
LOGIN_DENIED conectarse a Oracle con
-1017
un nombre de usuario o
password inválido
El programa está
referenciando un
elemento de un arreglo
SUBSCRIPT_OUTSIDE_LIMIT
utilizando un número -6532
fuera del rango
permitido (por ejemplo,
el elemento “-1″)
La conversión de una
cadena de caracteres
SYS_INVALID_ROWID
hacia un tipo rowid falló -1410
porque la cadena no
representa un número
Se excedió el tiempo
TIMEOUT_ON_RESOURCE
máximo de espera por un -51
recurso en Oracle
Ocurrió un error
aritmético, de conversión
o truncamiento. Por
VALUE_ERROR ejemplo, sucede cuando
-6502
se intenta calzar un valor
muy grande dentro de
una variable más
pequeña
El programa intentó
ZERO_DIVIDE
efectuar una división por -1476
cero
DECLARE
– Declaraciones
MyExcepcion EXCEPTION;
BEGIN
– Ejecucion
EXCEPTION
– Excepcion
END;
Reglas de Alcance
Como las variables, una excepción declarada en un bloque es local a ese bloque
y global a todos los sub-bloques que comprende.
La sentencia RAISE
DECLARE
– Declaramos una excepcion identificada por
VALOR_NEGATIVO
VALOR_NEGATIVO EXCEPTION;
valor NUMBER;
BEGIN
– Ejecucion
valor := -1;
IF
valor < 0
THEN
RAISE VALOR_NEGATIVO;
END IF;
EXCEPTION
– Excepcion
Con la sentencia RAISE podemos lanzar una excepción definida por el usuario
o predefinida, siendo el comportamiento habitual lanzar excepciones definidas
por el usuario.
DECLARE
err_num NUMBER;
err_msg VARCHAR2(255);
result NUMBER;
BEGIN
EXCEPTION
WHEN OTHERS THEN
err_num := SQLCODE;
err_msg := SQLERRM;
DBMS_OUTPUT.put_line(‘Error:’||TO_CHAR(err_num));
DBMS_OUTPUT.put_line(err_msg);
END;
DECLARE
msg VARCHAR2(255);
BEGIN
msg := SQLERRM(-1403);
DBMS_OUTPUT.put_line(MSG);
END;
RAISE_APPLICATION_ERROR
RAISE_APPLICATION_ERROR(<error_num>,<mensaje>);
Siendo:
DECLARE
v_div NUMBER;
BEGIN
SELECT 1/0 INTO v_div FROM DUAL;
EXCEPTION
WHEN
OTHERS
THEN
RAISE_APPLICATION_ERROR(-20001,‘No se puede dividir por
cero’);
END;
Cursores en PL/SQL
declare
cursor c_paises is
FROM PAISES;
begin
/* Sentencias del bloque …*/
end;
Para procesar instrucciones SELECT que devuelvan más de una fila, son
necesarios cursores explicitos combinados con un estructura de bloque.
Un cursor admite el uso de parámetros. Los parámetros deben declararse junto
con el cursor.
declare
FROM PAISES
Las variables que reciben los datos devueltos por el cursor tienen que contener el mismo
tipo de dato que las columnas de la tabla.
Los cursores implicitos solo pueden devolver una única fila. En caso de que se
devuelva más de una fila (o ninguna fila) se producirá una excepcion. No se
preocupe si aún no sabe que es una excepcion, le valdrá conocer que es el medio
por el que PL/SQL gestiona los errores.
declare
vdescripcion VARCHAR2(50);
begin
SELECT DESCRIPCION
INTO vdescripcion
from PAISES
end;
Los cursores implicitos sólo pueden devolver una fila, por lo que pueden
producirse determinadas excepciones. Las más comunes que se pueden
encontrar son no_data_found y too_many_rows. La siguiente tabla
explica brevemente estas excepciones.
Excepcion Explicacion
Para trabajar con un cursor explicito necesitamos realizar las siguientes tareas:
Declarar el cursor.
CURSOR
nombre_cursor
IS
instrucción_SELEC
T
CURSOR
nombre_cursor(param1 tipo1, …, paramN
tipoN)
IS
instrucción_SELECT
OPEN nombre_cursor;
o bien (en el caso de un cursor con parámetros)
FETCH
nombre_cursor
INTO lista_variables;
– o bien …
FETCH
nombre_cursor
INTO registro_PL/SQL;
CLOSE nombre_cursor;
DECLARE
CURSOR cpaises
IS
SELECT CO_PAIS, DESCRIPCION, CONTINENTE
FROM PAISES;
co_pais VARCHAR2(3);
descripcion VARCHAR2(50);
continente VARCHAR2(25);
BEGIN
OPEN cpaises;
FETCH cpaises INTO co_pais,descripcion,continente;
CLOSE cpaises;
END;
DECLARE
CURSOR cpaises
IS
SELECT
CO_PAIS, DESCRIPCION, CONTINENTE
FROM PAISES;
registro cpaises%ROWTYPE;
BEGIN
OPEN cpaises;
FETCH cpaises INTO registro;
CLOSE cpaises;
END;
DECLARE
CURSOR cpaises (p_continente VARCHAR2)
IS
SELECT
CO_PAIS, DESCRIPCION, CONTINENTE
FROM PAISES
registro cpaises%ROWTYPE;
BEGIN
OPEN cpaises(‘EUROPA’);
FETCH cpaises INTO registro;
CLOSE cpaises;
END;
Atributos de cursores
Por medio de ciclo LOOP podemos iterar a través del cursor. Debe tenerse
cuidado de agregar una condición para salir del bucle:
OPEN nombre_cursor;
LOOP
EXIT
WHEN nombre_cursor%NOTFOUND;
END LOOP;
CLOSE nombre_cursor;
DECLARE
CURSOR cpaises
IS
SELECT
CO_PAIS, DESCRIPCION, CONTINENTE
FROM
PAISES;
co_pais VARCHAR2(3);
descripcion VARCHAR2(50);
continente VARCHAR2(25);
BEGIN
OPEN cpaises;
LOOP
EXIT
WHEN cpaises%NOTFOUND;
dbms_output.put_line(descripcion);
END
LOOP;
CLOSE cpaises;
END;
OPEN nombre_cursor;
WHILE nombre_cursor%FOUND
LOOP
END LOOP;
CLOSE nombre_cursor;
DECLARE
CURSOR cpaises
IS
SELECT
CO_PAIS, DESCRIPCION, CONTINENTE
FROM PAISES;
co_pais VARCHAR2(3);
descripcion VARCHAR2(50);
continente VARCHAR2(25);
BEGIN
OPEN cpaises;
WHILE cpaises%found
LOOP
dbms_output.put_line(descripcion);
END
LOOP;
CLOSE cpaises;
END;
Por último podemos usar un bucle FOR LOOP. Es la forma más corta ya que el
cursor es implicitamente se ejecutan las instrucciones OPEN, FECTH y CLOSE.
END LOOP;
BEGIN
LOOP
dbms_output.put_line(reg.descripcion);
END
LOOP;
END;
CURSOR
nombre_cursor
IS
instrucción_SELEC
T
FOR UPDATE
Para actualizar los datos del cursor hay que ejecutar una sentencia UPDATE
especificando la clausula WHERE CURRENT OF <cursor_name>.
DECLARE
CURSOR cpaises IS
select CO_PAIS, DESCRIPCION, CONTINENTE
from paises
FOR UPDATE;
co_pais VARCHAR2(3);
descripcion VARCHAR2(50);
continente VARCHAR2(25);
BEGIN
OPEN cpaises;
FETCH cpaises INTO co_pais,descripcion,continente;
WHILE cpaises%found
LOOP
UPDATE PAISES
COMMIT;
END;
Ejemplo 1:
BEGIN
DBMS_OUTPUT.PUT_LINE (‘Hola’);
END;
Ejemplo 2:
DECLARE
v_precio number;
BEGIN
from tarticulos
where codigo=100;
dbms_output.put_line (v_precio);
END;
Ejemplo 3:
El siguiente bloque anónimo nos muestra la fecha actual con el formato “Martes,
18 de Marzo de 1998, a las 13:04:55″.
DECLARE
fecha date;
BEGIN
dbms_output.put_line (to_char(sysdate,
END;
Subprogramas
Procedimientos
La sintaxis es la siguiente
IS | AS
bloque de código
Donde “modo” puede contener los valores IN, OUT, IN OUT. Por defecto tiene
el valor IN si no se pone nada. IN indica que el parámetro es de entrada y no se
podrá modificar. OUT indica que el parámetro es de salida con lo que el
procedimiento devolverá un valor en él. IN OUT indica que el parámetro es de
entrada/salida. Con lo que al llamar al procedimiento se le dará un valor que
luego podrá ser modificado por el procedimiento y devolver este nuevo valor.
DECLARE
nombre_parametro tipodatos_parametro;
BEGIN
nombre_parametro tipodatos_parametro :=
valor_de_inicializacion;
END;
/
Funciones
La sintaxis sería
RETURN tipodatos_retorno IS | AS
bloque de código
Donde “modo” puede contener los valores IN, OUT, IN OUT. Por defecto tiene
el valor IN si no se pone nada. IN indica que el parámetro es de entrada y no se
podrá modificar. OUT indica que el parámetro es de salida con lo que el
procedimiento devolverá un valor en él. IN OUT indica que el parámetro es de
entrada/salida. Con lo que al llamar al procedimiento se le dará un valor que
luego podrá ser modificado por el procedimiento y devolver este nuevo valor.
Sin embargo, en este caso solo tendría sentido( por el concepto de función en sí
mismo) declarar parámetros del tipo IN y devolver el valor como retorno de la
función.
Paquetes
{IS | AS}
[PRAGMA SERIALLY_REUSABLE;]
[collection_type_definition ...]
[record_type_definition ...]
[subtype_definition ...]
[collection_declaration ...]
[constant_declaration ...]
[exception_declaration ...]
[object_declaration ...]
[record_declaration ...]
[variable_declaration ...]
[cursor_spec ...]
[function_spec ...]
[procedure_spec ...]
[call_spec ...]
END [package_name];
[PRAGMA SERIALLY_REUSABLE;]
[collection_type_definition ...]
[record_type_definition ...]
[subtype_definition ...]
[collection_declaration ...]
[constant_declaration ...]
[exception_declaration ...]
[object_declaration ...]
[record_declaration ...]
[variable_declaration ...]
[cursor_body ...]
[function_spec ...]
[procedure_spec ...]
[call_spec ...]
[BEGIN
sequence_of_statements]
END [package_name];]
Disparadores (Triggers)
Declaración de triggers
Valor Descripción
INSERT,
Define qué tipo de orden DML provoca la activación del
DELETE,
disparador.
UPDATE
La cláusula WHEN sólo es válida para los disparadores con nivel de fila.
Una misma tabla puede tener varios triggers. En tal caso es necesario conocer el
orden en el que se van a ejecutar.
ACCION
OLD NEW
SQL
Los registros OLD y NEW son sólo válidos dentro de los disparadores con nivel
de fila.
Podemos usar OLD y NEW como cualquier otra variable PL/SQL.
Su comportamiento es el siguiente:
Predicado Comportamiento
Sentencia IF
IF (expresion) THEN
– Instrucciones
ELSIF
(expresion) THEN
– Instrucciones
ELSE
– Instrucciones
END
IF;
Un aspecto a tener en cuenta es que la instrucción condicional anidada es
ELSIF
y no “ELSEIF”.
Sentencia GOTO
En PL/SQL las etiquetas se indican del siguiente modo: << etiqueta >>
DECLARE
flag NUMBER;
BEGIN
flag :=1 ;
IF (flag = 1) THEN
GOTO paso2;
END
IF;
<<paso1>>
dbms_output.put_line(‘Ejecucion de paso 1′);
<<paso2>>
dbms_output.put_line(‘Ejecucion de paso 2′);
END;
Sentencia Case
CASE n
WHEN 1 THEN
accicción1;
WHEN 2 THEN
2;
WHEN 3 THEN
3;
ELSE
otra acción;
END CASE;
DECLARE
A
number;
BEGIN
A
:= 3;
CASE A
WHEN 1 THEN
WHEN 2 THEN
WHEN 3 THEN
ELSE
END CASE;
END;
Bucles
LOOP
WHILE
FOR
El bucle LOOP, se repite tantas veces como sea necesario hasta que se fuerza su salida
con la instrucción EXIT. Su sintaxis es la siguiente
LOOP
– Instrucciones
IF (expresion) THEN
– Instrucciones
EXIT;
END IF;
END LOOP;
– Instrucciones
END LOOP;
– Instrucciones
END LOOP;
Registros PL/SQL
Registros
Tablas de PL
VARRAY
Declaración de un registro.
Los campos de un registro pueden ser inicializados y pueden ser definidos como
NOT NULL. Aquellos campos que no sean inicializados explícitamente, se
inicializarán a NULL.
(
campo <tipo_datos> [NULL | NOT NULL]
[,<tipo_datos>...]
);
El siguiente ejemplo crea un tipo PAIS, que tiene como campos el código, el
nombre y el continente.
(
CO_PAIS NUMBER
,
DESCRIPCION VARCHAR2(50),
CONTINENTE VARCHAR2(20)
);
Los registros son un tipo de datos, por lo que podremos declarar variables de
dicho tipo de datos.
DECLARE
CO_PAIS NUMBER
,
DESCRIPCION VARCHAR2(50),
CONTINENTE VARCHAR2(20)
);
*/
miPAIS PAIS;
BEGIN
/* Asignamos valores a los campos de la variable.
*/
miPAIS.CO_PAIS := 27;
miPAIS.DESCRIPCION := ‘ITALIA’;
miPAIS.CONTINENTE := ‘EUROPA’;
END;
Los registros pueden estar anidados. Es decir, un campo de un registro puede ser
de un tipo de dato de otro registro.
DECLARE
TYPE PAIS IS RECORD
(CO_PAIS NUMBER ,
DESCRIPCION VARCHAR2(50),
CONTINENTE VARCHAR2(20)
);
miPAIS PAIS;
miMONEDA MONEDA;
BEGIN
/* Sentencias
*/
END;
DECLARE
TYPE PAIS IS RECORD …
miPAIS PAIS;
otroPAIS PAIS;
BEGIN
miPAIS.CO_PAIS := 27;
miPAIS.DESCRIPCION := ‘ITALIA’;
miPAIS.CONTINENTE := ‘EUROPA’;
otroPAIS := miPAIS;
END;
DECLARE
miPAIS PAISES%ROWTYPE;
BEGIN
/* Sentencias … */
END;
Tablas PL/SQL
Las tablas de PL/SQL son tipos de datos que nos permiten almacenar varios
valores del mismo tipo de datos.
Es similar a un array
Tiene dos componenetes: Un índice de tipo BINARY_INTEGER que
permite acceder a los elementos en la tabla PL/SQL y una columna de
escalares o registros que contiene los valores de la tabla PL/SQL
Puede incrementar su tamaño dinámicamente.
TYPE
<nombre_tipo_tabla>
IS TABLE OF
<tipo_datos> [NOT NULL]
INDEX BY BINARY_INTEGER ;
Una vez que hemos definido el tipo, podemos declarar variables y asignarle valores.
DECLARE
tPAISES(1) := 1;
tPAISES(2) := 2;
tPAISES(3) := 3;
END;
No es posible inicializar las tablas en la inicialización.
DECLARE
CO_PAIS NUMBER
NOT NULL
,
DESCRIPCION VARCHAR2(50),
CONTINENTE VARCHAR2(20)
);
tPAISES PAISES;
BEGIN
tPAISES(1).CO_PAIS := 27;
tPAISES(1).DESCRIPCION := ‘ITALIA’;
tPAISES(1).CONTINENTE := ‘EUROPA’;
END;
DECLARE
BEGIN
misCiudades(1) := ‘MADRID’;
misCiudades(2) := ‘BILBAO’;
misCiudades(3) := ‘MALAGA’;
FOR i IN misCiudades.FIRST..misCiudades.LAST
LOOP
dbms_output.put_line(misCiudades(i));
END LOOP;
END;
DECLARE
BEGIN
misCiudades(1) := ‘MADRID’;
misCiudades(3) := ‘MALAGA’;
FOR i IN misCiudades.FIRST..misCiudades.LAST
LOOP
IF misCiudades.EXISTS(i) THEN
dbms_output.put_line(misCiudades(i));
ELSE
dbms_output.put_line(‘El elemento no existe:’||TO_CHAR(i));
END IF;
END LOOP;
END;
BEGIN
misCiudades(1) := ‘MADRID’;
misCiudades(3) := ‘MALAGA’;
/* Devuelve 2, ya que solo hay dos elementos con
valor */
dbms_output.put_line(
‘El número de elementos es:’||misCiudades.COUNT);
END;
DECLARE
TYPE ARR_CIUDADES IS TABLE OF
VARCHAR2(50) INDEX BY BINARY_INTEGER;
misCiudades ARR_CIUDADES;
BEGIN
misCiudades(1) := ‘MADRID’;
misCiudades(3) := ‘MALAGA’;
/* Devuelve 1, ya que el elemento 2 no existe */
dbms_output.put_line(
‘El elemento previo a 3 es:’ || misCiudades.PRIOR(3));
END;
DECLARE
TYPE ARR_CIUDADES IS TABLE OF
VARCHAR2(50) INDEX BY BINARY_INTEGER;
misCiudades ARR_CIUDADES;
BEGIN
misCiudades(1) := ‘MADRID’;
misCiudades(3) := ‘MALAGA’;
/* Devuelve 3, ya que el elemento 2 no existe */
dbms_output.put_line(
‘El elemento siguiente es:’ || misCiudades.NEXT(1));
END;
Varrays
Definición de VARRAYS.
TYPE <nombre_tipo> IS
VARRAY (<tamaño_maximo>) OF <tipo_elementos>;
BOOLEAN
NCHAR
NCLOB
NVARCHAR(n)
REF CURSOR
TABLE
VARRAY
Los VARRAY deben estar inicializados antes de poder utilizarse. Para inicializar
un VARRAY se utiliza un constructor (podemos inicializar el VARRAY en la
sección DECLARE o bien dentro del cuerpo del bloque):
DECLARE
Un VARRAY comparte con las tablas de PL todas las funciones válidas para
ellas, pero añade las siguientes:
Para poder crear tablas con campos de tipo VARRAY debemos crear el
VARRAY como un objeto de la base de datos.
Una vez que hayamos creado el tipo sobre la base de datos, podremos utilizarlo
como un tipo de datos más en la creacion de tablas, declaración de variables ….
Vease el siguiente ejemplo:
CREATE
OR REPLACE TYPE PACK_PRODUCTOS AS VARRAY(10) OF
VARCHAR2(60);
CREATE
TABLE OFERTAS
(
CO_OFERTA NUMBER,
PRODUCTOS PACK_PRODUCTOS,
PRECION NUMBER
);
Bulk collect
PL/SQL nos permite leer varios registros en una tabla de PL con un único
acceso a través de la instrucción BULK COLLECT.
DECLARE
TYPE t_descripcion IS TABLE OF PAISES.DESCRIPCION%TYPE;
TYPE t_continente IS TABLE OF PAISES.CONTINENTE%TYPE;
v_descripcion t_descripcion;
v_continente t_continente;
BEGIN
SELECT DESCRIPCION,
CONTINENTE
BULK COLLECT INTO v_descripcion, v_continente
FROM PAISES;
END;
/
Podemos utilizar BULK COLLECT con registros de PL.
DECLARE
TYPE PAIS IS RECORD (CO_PAIS NUMBER ,
DESCRIPCION VARCHAR2(50),
CONTINENTE VARCHAR2(20));
TYPE t_paises IS TABLE OF PAIS;
v_paises t_paises;
BEGIN
SELECT CO_PAIS, DESCRIPCION, CONTINENTE
BULK COLLECT INTO v_paises
FROM PAISES;
END;
/
DECLARE
v_paises t_paises;
BEGIN
SELECT CO_PAIS, DESCRIPCION, CONTINENTE
BULK COLLECT INTO v_paises
FROM PAISES;
END;
/
Transacciones autónomas
En ocasiones es necesario que los datos escritos por parte de una transacción
sean persistentes a pesar de que la transaccion se deshaga con ROLLBACK.
DECLARE
producto PRECIOS%TYPE;
BEGIN
producto := ’100599′;
INSERT INTO PRECIOS
(CO_PRODUCTO, PRECIO, FX_ALTA)
VALUES
Grabar_Log(SQLERRM);
ROLLBACK;
/* Los datos grabados por “Grabar_Log” se escriben en la base
de datos a pesar del ROLLBACK, ya que el procedimiento está
marcado como transacción autonoma.
*/
END;
Es muy común que, por ejemplo, en caso de que se produzca algún tipo de error
queramos insertar un registro en una tabla de log con el error que se ha
produccido y hacer ROLLBACK de la transacción. Pero si hacemos
ROLLBACK de la transacción tambien lo hacemos de la insertción del log.
SQL Dinamico
DECLARE
ret NUMBER;
FUNCTION fn_execute RETURN NUMBER IS
sql_str VARCHAR2(1000);
BEGIN
sql_str := ‘UPDATE DATOS SET NOMBRE = ”NUEVO NOMBRE”
WHERE CODIGO = 1′;
EXECUTE IMMEDIATE sql_str;
RETURN SQL%ROWCOUNT;
END fn_execute ;
BEGIN
ret := fn_execute();
dbms_output.put_line(TO_CHAR(ret));
END;
DECLARE
ret NUMBER;
FUNCTION fn_execute (nombre VARCHAR2, codigo NUMBER)
RETURN NUMBER
IS
sql_str VARCHAR2(1000);
BEGIN
sql_str := ‘UPDATE DATOS SET NOMBRE = :new_nombre
WHERE CODIGO = :codigo’;
EXECUTE IMMEDIATE sql_str USING nombre, codigo;
RETURN SQL%ROWCOUNT;
END fn_execute ;
BEGIN
ret := fn_execute(‘Devjoker’,1);
dbms_output.put_line(TO_CHAR(ret));
END;
DECLARE
str_sql VARCHAR2(255);
l_cnt VARCHAR2(20);
BEGIN
str_sql := ‘SELECT count(*) FROM PAISES’;
EXECUTE IMMEDIATE str_sql INTO l_cnt;
dbms_output.put_line(l_cnt);
END;
Trabajar con cursores explicitos es también muy fácil. Únicamente destacar el uso
de REF CURSOR para declarar una variable para referirnos al cursor generado con
SQL dinamico.
DECLARE
TYPE CUR_TYP IS
REF CURSOR;
c_cursor CUR_TYP;
fila PAISES%ROWTYPE;
v_query VARCHAR2(255);
BEGIN
v_query := ‘SELECT * FROM PAISES’;
DECLARE
TYPE cur_typ IS REF CURSOR;
c_cursor CUR_TYP;
fila PAISES%ROWTYPE;
v_query VARCHAR2(255);
codigo_pais VARCHAR2(3) := ‘ESP’;
BEGIN
PL/SQL y Java
Otra de las virtudes de PL/SQL es que permite trabajar conjuntamente con Java.
Para poder trabajar con Java y PL/SQL debemos realizar los siguientes pasos:
Para crear objetos Java en la base de datos podemos utilizar la uitlidad LoadJava
de ORACLE desde linea de comandos o bien crear objetos JAVA SOURCE en
la propia base de datos.
JAVA SOURCE
NAMED <JavaSourceName>
AS
public class <className>
{
<java code>
…
};
CREATE
OR REPLACE AND COMPILE
JAVA SOURCE
NAMED FuentesJava
AS
public
class
OracleJavaClass
{
public
static
String Saluda(String nombre)
{
return (“Hola desde Java” + nombre);
}
}
;
CREATE
OR REPLACE AND COMPILE
JAVA SOURCE
NAMED FuentesJava
AS
public
class
OracleJavaClass
{
public
static
String Saluda(String nombre)
{
return (“Hola desde Java” + nombre);
}
}
public class
OracleJavaMejorada
{
public static
String SaludoMejorado(String nombre)
{
return (“Saludo mejorado desde Java para:” + nombre);
}
}
;
loadJava -help
Una vez que tenemos listo el programa de Java debemos integrarlo con PL/SQL.
Esto se realiza a través de subprogramas de recubrimiento llamados Wrappers.
CREATE [OR
REPLACE]
FUNCTION|PROCEDURE
<name> [(<params>,...)]
[RETURN
<tipo>]
IS|AS
LANGUAGE
JAVA NAME
‘<clase>.<metodo> [return
<tipo>]‘ ;
CREATE OR
REPLACE
Una vez creado el wrapper, podremos ejecutarlo como cualquier otra funcion o
procedure de PL/SQL. Debemos crear un wrapper por cada función java que
queramos ejecutar desde PL/SQL.
SELECT SALUDA_WRAP(‘DEVJOKER’)
FROM DUAL;
SALUDA_WRAP(‘DEVJOKER’)
———————————————-
Hola desde JavaDEVJOKER
Con el potencial que nos ofrece PL/SQL es importante estar atentos al abanico
de posibilidades del que disponemos. No sucede lo mismo en otros lenguajes
PL, ya que estos se encuentran mucho mas limitados en sus alternativas de
expresión.
Es sumamente fácil, tal vez demasiado a juicio de los gurúes, escribir SQL en
PL/SQL, por cuanto no necesitamos de una interfaz ODBC/JDBC etc. Esto,
sumado a la creencia de que el modelo de datos esta libre de la evolución propia
del software, genera código difícil de mantener y evolucionar.
EL TESTING DE UNIDAD
Si bien existen muchas clases de test, solamente existe un responsable para los
Test de Unidad: el Programador!
Es interesante mencionar que los casos de uso deben escribirse ANTES que la
unidad de programa. Si escribimos las pruebas después de la unidad, vamos a
inclinar la balanza hacia la búsqueda de aciertos y no de los fallos, existe de
hecho, una tendencia psicológica a seleccionar casos de test que el desarrollador
sepa tratados en su unidad.
Ejemplo:
Esta sentencia por estar dentro de un bloque PL/SQL requiere que la interprete el
motor PL/SQL y después el de SLQ POR CADA fila.
UPDATE employee
SET …
WHERE …
LOOP;
Se parsea solo una vez, pues se hace un cambio de contexto entre el motor PL y
el moto SQL, solamente.
UPDATE employee
SET …
WHERE …
También podemos hacer abrir una variable de cursor sobre una estructura,
usando la sentencia BULK COLLECT INTO.
Resumiendo, siempre que necesitemos realizar una DML por cada fila de un
cursor, o una DML dentro de un LOOP, es mejor utilizar un BULK COLLECT
INTO mas allá de si lo hacemos sobre una variable de cursor o directamente con
una sentencia SQL.
Los BULK COLLECT pueden ser usados con cursores Implícitos o Explícitos.
Las colecciones siempre se rellenan secuencialmente, comenzando de 1.
Pl/sql nos permite interactuar con el motor SQL sin otra capa intermedia, como
ODBC/JDBC etc. De esta manera, facilita tal vez demasiado el uso de SQL.
Pero, la capa PL/SQL debería permitir una fácil manipulación del modelo de
datos, y no constituir un obstáculo para la correcta evolución del mismo. Si
todos escriben SQL donde y cuando quieren, entonces el código se torna
inmanejable.
Recordemos hardcodear nuestro código, es malo. Por otro lado, es una verdad
innegable que TODA SENTENCIA SQL ES HARD-CODING! Por lo que
podemos afirmar por transitividad y sin pérdida de generalidad, que SQL es
MALO! Que mas sabemos sobre SQL?
Las sentencias SQL estan sujetas a las reglas del negocio, y las reglas del
negocio cambian por lo que nuestras sentencias SQL cambian.
Ejemplo:
Buenas Prácticas:
El usuario estará mucho mas feliz si no tiene que recordar los literales que guían
la conducta de nuestra aplicación. Como logramos esto? Tenemos dos opciones.
El verdadero desafío es desarrollar una sensibilidad que nos permita hacer del
overloading nuestra primera solución intuitiva frente a los problemas donde la
facilidad de uso sea un objetivo.
Debemos estar atentos para que las N versiones de nuestra unidad sobrecargada
no este repitiendo líneas de código. Debemos factorizar esas repeticiones y de
esa manera lograr la regla mencionada. Normalmente, se llama a este diseño
Diamante, si vemos que en la capa superior tenemos una vista de usuario (Que
no se corresponde exactamente a una unidad de programa, sino a las N unidades
sobrecargadas). En un nivel medio tenemos N versiones de la unidad, y en la
capa inferior tenemos una unidad que factoriza lo común de las anteriores (que
normalmente contendrá un IF desarrollado, a esto se le llama Efecto de Lampara
de Lava, porque si abrimos los if da la sensación de modelar una de estas
lámparas). Es altamente recomendable construir nuestro package con este
criterio.
IS
END valerr;
IS
IS
BEGIN
RETURN g_private;
END private_variable;
BEGIN
EXCEPTION
END valerr;
ERROR at line 1:
Pero no había una sección de cacheo de errores? Por otro lado, para hacerlo mas
confuso, si corremos este programa una vez mas, este no dará error! Porque
sucede esto?
Mirar las áreas donde pueda ser posible la aplicación de FOR ALL &
BULK COLLECT.
Buscar todas las sentencias DML. Si estas están dentro de algún tipo de
LOOP, externalizarlas en un Procedure de ámbito Local en la sección de
declaración, buscando aumentar el uso del procesamiento BULK.
Grave error, sin importar nuestro nivel de Senority siempre debemos hacerlo. La
siguiente sugerencia puede producir un equilibrio práctico: Si no podemos
encontrar el problema después de 30 minutos… Pidamos ayuda!
Siempre es mejor beber agua para hidratar nuestro cuerpo que abusar de la
cafeína…
Nunca compartir nuestro conocimiento! Esto nos da poder y seguridad.
Siempre debemos dejar que nuestra realidad termine de aceitar los consejos que
recibimos, ellos también se equivocan.
Simplificar DONDE las cosas son realizadas. (Reorganizar las estructuras del
código, moviendo este de un lugar a otro a efectos de aumentar su legibilidad,
modificabilidad y rehuso)
Simplicar COMO las cosas son realizadas (Haciendo el código mas elegante,
mas compacto y fácil de seguir).
Prestar atención a los puntos donde se utilice lógica booleana compleja. Es muy
probable que mediante las reglas matemáticas de la lógica Booleana
normalmente conocida por los profesionales, una gran expresión sea equivalente
a otra mucho mas simple. Por ejemplo supongamos una función que retorna lo
siguiente
El sentido común asi como la lógica booleana nos dicen que esa expresión es
equivalente a la siguiente:
Observar aquellos lugares donde el código se extiende demasiado. Esta claro que
existen problemas que requieren la escritura de miles de líneas, no es el volumen
de las líneas lo que esta en discusión, sino la estructura que se le de a las
mismas. Es muy probable que podamos ocultar detalles del mismo en Procedure
o Function anidadas y de esta manera simplificar las cosas. Como referencia,
debemos pensar que desde el BEGIN al END nunca deberíamos tener mas
de 50 líneas, es mas, un promedio de 20 a 30 líneas es ampliamente
recomendado. Si esto se hace con un criterio Top Down, seguramente será
mucho mas sencillo de seguir que un monolítico bloque ejecutable, o muchas
unidades encadenadas sin criterio arquitectural.
Cuidar el acoplamiento de las unidades. Si tenemos unidades de programa
exageradamente parametrizadas, además de restarle claridad al código, es muy
probable que tengamos un problema de coupling en nuestro diseño.
Una de las formas más usuales de mejorar el rendimiento de una sentencia SQL
o PL/SQL es analizar el plan de ejecución que devuelve el optimizador Oracle.
En SQL*Plus se puede obtener dicho plan de ejecución, además de algunas
estadísticas referentes al resultado de la ejecución de la sentencia SQL o PLSQL,
utilizando el comando AUTOTRACE. Para obtener el plan de ejecución no hay
necesidad de ejecutar dicho comando pero, ciertamente, si no lo utilizamos, la
poca amigabilidad del comando que debemos ejecutar (EXPLAIN PLAN), el
formato de dicho comando y lo complejo que resulta analizar el contenido de la
tabla V$SQL_PLAN, hacen que, por mi parte, recomiende encarecidamente el
uso del comando SQL*Plus AUTOTRACE.
Para poder utilizar la opción EXPLAIN del comando AUTOTRACE, es necesario crear
la tabla PLAN_TABLE en el esquema del usuario, es por eso que este comando sólo
puede ser ejecutado por determinados usuarios, aquellos para los que la mencionada
tabla ya ha sido creada. Es importante pues, conocer los usuarios Oracle que han sido
configurados para poder ejecutar el comando AUTOTRACE.
Por otro lado, para acceder a las estadísticas, hay que tener acceso a varias tablas del
sistema en las que se almacenan los datos del rendimiento de las sentencias SQL. Los
DBA pueden dar este acceso utilizando el script plustrce.sql. El nombre de este script
puede variar dependiendo del sistema operativo. El DBA tiene que ejecutar dicho script
como usuario SYS y, asignar al usuario en cuestión, el papel (role) correspondiente.
Una vez que se ha configurado convenientemente un usuario para que pueda acceder al
plan de ejecución y a las estadísticas, basta habilitar el AUTOTRACE para que, al
ejecutar una sentencia SQL, nos aparezca el plan de ejecución así como los
correspondientes valores estadísticos.
Los valores estadísticos más importantes mostrados por la base de datos Oracle, una vez
activado el comando AUTOTRACE, son los siguientes:
En cuanto a lo que se refiere al plan de ejecución, desde este enlace podéis acceder a un
ejemplo de sentencia SQL con su correspondiente plan de ejecución y una breve
interpretación de dicho plan: Ejemplo de plan de ejecución.
Como un primer consejo a la hora de analizar un plan de ejecución, me gustaría indicar
que lo primero que hay que evitar son los FULL SCAN (recorrido de todos los registros
de una tabla). No obstante, hay determinadas circunstancias bajo las que un FULL
SCAN puede ser recomendable; así, cuando una tabla tiene pocos registros, puede ser
conveniente realizar un FULL SCAN, en vez de acceder a la misma a través de un
índice.
El optimizador PLSQL basado en normas utiliza siempre que puede los índices, incluso
cuando las tablas son pequeñas o cuando el número de registros que devuelve la
sentencia SELECT es un porcentaje elevado con respecto al número total de registros de
la tabla, casos para los que es mejor realizar un escaneado total (full scan) ya que la
respuesta es más rápida (mejora el rendimiento). Esto es debido a que el optimizador
basado en normas no hace uso de valores estadísticos, tales como el número total de
registros de una tabla.
El optimizador PL/SQL basado en normas hace uso del siguiente orden de prioridades
para determinar cual va a ser la forma de acceder a las tablas y determinar finalmente
cual va a ser el plan de ejecución:
Siguiendo con el tema que concierne a este post, el optimizador basado en normas
analiza la sintaxis de la sentencia SQL para establecer los distintos métodos de acceso a
las tablas. Básicamente lo que hace es determinar todas las formas de acceso posibles y
escoger aquella que tiene una prioridad menor.
Este esquema siempre asume que un escaneado total (full scan) es el peor método de
accesso (prioridad 15). Sin embargo, ya he mencionado al principio del artículo que esto
no siempre es verdad.
Estos métodos de acceso, así como otros adicionales, están también disponibles para el
optimizador PL/SQL basado en costes. Sin embargo, este optimizador ignora las
prioridades, y determina el coste esperado de ejecución de la sentencia SQL para cada
uno de las formas de acceso posibles basándose en las estadísticas, escogiendo después
aquella forma de acceso con el menor coste estimado. Muchas funcionalidades del
Oracle, como los hash joins, star queries e histogramas, sólo están disponibles para el
optimizador PLSQL basado en costes.
Los hints son pistas que se dan al optimizador SQL de Oracle para que elabore el
plan de ejecución de una sentencia DML (sentencias de manipulación de datos
como select, insert, update, delete, etc) según nosotros le aconsejemos. En este
primer artículo sobre los hints voy a empezar hablando de aquellos que se
utilizan para seleccionar el modo de trabajar del optimizador Oracle. Estos hints,
hablando desde un punto de vista práctico, no son muy utilizados, aunque no por
ello pueden dejar de ser útiles en determinadas circunstancias.
Los hints se incorporan a una sentencia DML en forma de comentario y deben ir justo
detrás del comando principal. Por ejemplo, si se tratara de una sentencia SELECT el
formato sería el siguiente:
Estos son los hints que se pueden utilizar para cambiar el modo de optimización del
optimizador Oracle:
En su día ya indiqué cual es la sintaxis de los hints pero creo que no está de más que la
muestre de nuevo:
Los hints básicos que sirven para determinar el metodo de acceso a los datos de una
tabla Oracle son los siguientes:
SELECT *
FROM empleados
WHERE nombre = ‘Francisco’
AND estado_civil = ‘S’ — Soltero
nos está dando tiempos de ejecución largos, esto querrá decir que:
Los índices sobre columnas de este tipo, con un rango de valores pequeño (soltero,
casado, viudo,…) y con una distribución de valores más o menos homogénea para
algunos de estos valores (podemos pensar que el número de solteros va a ser similar al
de casados y bastante mayor que el de viudos), van a causar problemas en los tiempos
de ejecución. Este tipo de índices sólo son interesantes cuando uno de los posibles
valores que puede tomar la columna es mucho menos numeroso que el resto y la
sentencia SQL la vamos a limitar en base a dicho valor. Por ejemplo, si nuestra
sentencia fuera:
SELECT *
FROM empleados
WHERE nombre = ‘Francisco’
AND estado_civil = ‘V’ — Viudo
Podemos tener la certeza de que los tiempos de ejecución no serían tan malos. En
cambio, cuando hacemos la comparación “estado_civil = ‘S’”, estamos practicamente
haciendo un “full scan” sobre un índice y esto es incluso peor que hacer un “full scan”
sobre una tabla.
¿Qué tenemos que hacer para arreglar la primera sentencia? Debemos forzar al
optimizador a usar el índice sobre la columna “nombre”, para ello tenemos dos
opciones:
SELECT *
FROM empleados
WHERE nombre = ‘Francisco’
AND estado_civil!!” = ‘S’ — Soltero
Esto hace que el optimizador utilice el índice sobre la columna “nombre” aunque exista
otro índice sobre la columna “estado_civil”.
La utilidad de trazado del SQL de las bases de datos Oracle nos permite analizar
el rendimiento de un determinado programa PL/SQL. Esta funcionalidad nos va
a permitir obtener información acerca del rendimiento de todas las sentencias
SQL que se ejecuten durante la ejecución del programa PLSQL.
Para utilizar la herramienta de trazado del PL/SQL de Oracle deberemos seguir cinco
pasos:
TIMED_STATISTICS = TRUE
Existen otros dos parámetros que nos permiten controlar el tamaño y el nombre del
directorio donde se generará el fichero de trazado:
MAX_DUMP_FILE_SIZE = n
USER_DUMP_DEST = nombre_directorio
El valor por defecto del parámetro USER_DUMP_DEST depende del sistema operativo
y no puede ser cambiado a nivel de sesión. Por lo tanto, al ser un parámetro global del
sistema, su valor sólo pueden cambiarlo los administradores de la base de datos
utilizando el comando ALTER SYSTEM.
Para obtener información acerca de los valores que toman los distintos parámetros
podemos ejecutar la siguiente sentencia:
SQL_TRACE = TRUE
Por ello es mucho más recomendable activar la traza a nivel de sesión con el comando:
Por otro lado, los DBA (Database Administrators) pueden activar la traza sobre una
sesión de usuario concreta utilizando el siguiente comando:
Los valores session_id y serial_id adecuados los tiene que identificar el DBA mediante
el análisis de los registros incluidos en la vista V$SESSION, los campos de esta vista
que se corresponden con estos valores son SID y SERIAL#.
Cuando la ejecución del programa que estamos optimizando termina, debemos proceder
a desactivar la traza utilizando cualquiera de los métodos mencionados anteriormente,
sustituyendo la palabra TRUE por FALSE.
El siguiente paso sería utilizar el programa TKPROF para poder interpretar el contenido
binario del fichero de trazado, pero esto será objeto de otro artículo.
Sql loader
Sql Loader es una utilidad que proporciona Oracle para cargar datos a una base
de datos desde un fichero externo, normalmente un fichero de texto aunque
tambien pueden ser ficheros binarios.
? SQL-Loader utiliza dos mecanismos para cargar datos, estos mecanismos son:
Este mecanismo es más rápido que el convencional, pero tiene muchas más
restricciones.
Para los formatos variables, el Sistema Operativo utiliza una marca de final de
registro (como el newline).
? Existen dos tipos de limitadores que se utilizan para delimitar los campos:
1.- Registros físicos ? Se consideran registros físicos cada linia del fichero de
donde deseamos cargar los datos a Oracle.
LOAD DATA
INFILE ………………
Ejemplos:
INFILE *
…
…
BEGIN DATA
…………..
…………..
? Carga de datos a partir de un fichero de datos especificado:
INFILE ‘C:\fichero_de_datos.dat’
…..
…..
INTO TABLE ? En esta sentencia, se especifican las tablas que seran cargadas
con la información. Dependiendo del tipo de carga que se vaya a realizar, se
deben especificar los campos (columnas) de la tabla y su correspondiente
formato.
[BEGIN DATA] ? Se pone esta sentencia cuando los datos a cargar en las
tablas se encuentran en el fichero de control. Esta sentencia se debe poner
siempre despues de la última especificación de control, sino es así, podría
suceder que se interpretasen datos como sentencias de control.
Seguidamente, se muestra un fichero de control más detallado y con los
parámetros más usuales:
INFILE ‘<fichero_datos.dat>’
[INFILE] ‘<fichero_datos2.dat>’
…………
…………
[BADFILE] ‘<fichero_bad.bad>’
[DISCARDFILE] ‘<fichero_discard.dsc>’
Ejemplos:
Para saber que argumentos podemos utilizar con SQL-Loader, podemos utilizar
la ayuda de la siguiente manera:
Imaginemos que los argumentos que estamos utilizando, son los que utilizamos
en la mayoría de ejecuciones de SQL-Loader. Pues, existe una manera de hacer
que estos argumentos sean almacenados en un fichero externo y que por medio
de la cláusula PARFILE sea llamado como si estuviese incluido en el mismo
fichero de control. Este método nos ahorra el tener que escribir largas líneas de
argumentos en la línea de comandos. Además de facilitar su entendimiento.
Lista de Argumentos
LOG ? Se debe poner el nombre del fichero de Log que guarda información del
proceso de carga. La extensión por defecto es .LOG.
BAD ? Se debe poner el nombre del fichero donde se almacenarán las causas de
los errores que provocaron las inserciones durante la carga. La extensión por
defecto es .BAD.
DATA ? Se debe poner el nombre del fichero de datos a cargar. Por defecto se
coge el nombre del fichero de control + la extensión .DAT.