martes, 27 de junio de 2017

SQL Dinámico y Sentencias DDL en PL/SQL

Objetivos:

 Sentencias DDL y PL/SQL.
 SQL Dinámico.
--SQL Dinámico Nativo.
----Sentencia EXECUTE IMMEDIATE.
----Sentencias OPEN-FORFETCH CLOSE
--Package DBMS_SQL.
 SQL Dinámico Nativo vs Package DBMS_SQL.

NOTA: Usamos como ejemplo la Base de Datos: ORCL, la cual viene por defecto en cualquier versión de ORACLE.
______________________________________________________________________________________
SQL Dinámico.
SQL Dinámico es una metodología de programación que genera y ejecuta sentencias SQL en tiempo de ejecución. Es útil cuando se escriben programas flexibles y de propósito general como sistemas que usan Consultas Ad-Hoc (Ad-Hoc Queries), cuando se escriben programas que deben ejecutar instrucciones DDL o cuando en tiempo de compilación no se sabe el texto completo de una instrucción SQL o el número o tipos de datos de sus variables de entrada/salida.

Formas de escribir SQL dinámico PL/SQL:
 SQL dinámico nativo, una característica de lenguaje PL/SQL (es decir, nativa) para crear y ejecutar instrucciones SQL dinámicas.
 Package DBMS_SQL, una API para crear, ejecutar y describir sentencias SQL dinámicas.

Un código SQL dinámico nativo resulta más fácil de leer y escribir que su equivalente utilizando el paquete DBMS_SQL, de igual forma el primero se ejecuta notablemente más rápido (especialmente cuando puede ser optimizado por el compilador). Sin embargo, para escribir código SQL dinámico nativo, debe saber en tiempo de compilación el número y los tipos de datos de las variables de entrada y salida de la instrucción SQL dinámica. Si en tiempo de compilación no conoce esta información, debe utilizar el paquete DBMS_SQL.

En caso de necesitar ambos (Paquete DBMS_SQL y SQL dinámico nativo), puede cambiar entre ellos utilizando la función DBMS_SQL.TO_REFCURSOR y la función DBMS_SQL.TO_CURSOR_NUMBER.

Necesidad de SQL Dinámico.
En PL/SQL, se necesita SQL dinámico para ejecutar lo siguiente:
 SQL cuyo texto es desconocido en tiempo de compilación.
Ejemplo: Una instrucción SELECT que incluye un identificador desconocido en tiempo de compilación (como un nombre de tabla) o una cláusula WHERE en la que el número de subcláusulas es desconocido en tiempo de compilación.

 Cualquier construcción SQL que no sea compatible como SQL Estático.

Si no necesita SQL Dinámico, utilice SQL Estático, el cual tiene las siguientes ventajas:
 Una compilación exitosa comprueba que las sentencias SQL estáticas hagan referencia a objetos de base de datos válidos y que el usuario tenga los privilegios necesarios para acceder a esos objetos.
 La compilación exitosa crea dependencias de objetos de esquema.

Uso de SQL Dinámico Nativo.
SQL dinámico nativo procesa la mayoría de las sentencias SQL dinámicas por medio de la sentencia EXECUTE IMMEDIATE.

Si la sentencia SQL dinámica es una instrucción SELECT que devuelve varias filas, SQL dinámico nativo le ofrece las siguientes opciones:
 Utilice la sentencias EXECUTE IMMEDIATE con la cláusula BULK COLLECT INTO.
 Utilice las sentencias OPEN-FOR, FETCH y CLOSE.

Puede utilizar variables Bind como parámetros dinámicos en las sentencias EXECUTE IMMEDIATE y OPEN. SQL Dinámico Nativo incluye las siguientes capacidades:
 Define la sentencia SQL dinámica.
 PL/SQL soporta instancias Bind de cualquier tipo de datos SQL.
 Maneja variables Bind con modo: IN, IN OUTOUT que están enlazadas por posición, no por nombre.

Nota: Los Atributos de Cursor SQL funcionan de la misma manera con SQL dinámico nativo como con SQL Estático.
______________________________________________________________________________________
Sentencias DDL y PL/SQL.
PL/SQL (Procedural Language/Structured Query Language) es un Lenguaje Procedimental o Lenguaje de Procedimiento combinado con SQL que tiene como fin fundamental facilitar el manejo de los datos en una Base de Datos OraclePL/SQL garantiza la correcta y completa consistencia de la información, todo esto, exponiendo la base de datos sólo a través de una interfaz que oculta los detalles de la implementación.

Por la anterior introducción podemos deducir que una de las razones por la cual no es posible introducir sentencias DDL en un bloque de PL/SQL es porque dicho lenguaje está orientado al manejo de los datos. Es bueno recargar que las sentencias DDL cambian la definición del esquema, o sea, crean, modifican o destruyen objetos de base de datos (Tablas, Columnas, Indices y demás) y una operación como tal, por lo general es realizada una sola vez.

Sin embargo, a veces surgen casos en los cuales se requiere realizar operaciones inusuales tales como la necesidad de ejecutar una operación DDL en un bloque de PL/SQL. Para estas excepciones nos valemos del SQL Dinámico (Dynamic SQL) presentado a continuación.
______________________________________________________________________________________
Sentencia EXECUTE IMMEDIATE.
La instrucción EXECUTE IMMEDIATE ejecuta una sentencia SQL dinámica o un bloque PL/SQL anónimo. Puede usarla para emitir sentencias SQL que no se pueden incluir directamente en PL/SQL o para generar sentencias en las que no se conocen los nombres de tabla, cláusulas WHERE, etc.

Sintaxis:
execute_immediate_statement ::=
EXECUTE_IMMEDIATE dynamic_string
   [ INTO { define_variable [, define_variable ...] | record_name } ]
   [ USING [ IN | OUT | IN OUT ] bind_argument
       [, [ IN OUT IN OUT ] bind_argument] ... ]
   [ {RETURNING | RETURN } INTO bind_argument [, bind_argument]... ];

Donde:
 bind_argument: Una expresión cuyo valor se pasa a la instrucción SQL dinámica o una variable que almacena un valor devuelto por la instrucción SQL dinámica.
 define_variable: Una variable que almacena el valor de una columna seleccionada.
 dynamic_string: La cadena literal, variable o expresión que representa una sola sentencia SQL o un bloque PL/SQL. Debe ser de tipo CHAR o VARCHAR2, no NCHAR o NVARCHAR2.
 INTO: Utilizada sólo para consultas de una sola fila, esta cláusula especifica las variables o registros en los que se recuperan los valores de las columnas. Para cada valor recuperado por la consulta, debe haber una variable o campo correspondiente compatible con el tipo en la cláusula INTO.
 record_name: Record %ROWTYPE (o definido por el usuario) que almacena una fila seleccionada.
 RETURNING INTO: Utilizada sólo para sentencias DML que tienen una cláusula RETURNING (sin una cláusula BULK COLLECT), esta cláusula especifica las variables Bind en las que se devuelven los valores de columna. Para cada valor devuelto por la sentencia DML, debe haber una variable correspondiente compatible con el tipo en la cláusula RETURNING INTO.
 USING: Especifica una lista de argumentos bind de entrada y/o salida. El modo de parámetro predeterminado es IN.

Nota: Puede utilizar literales numéricos, de caracteres y cadena como argumentos Bind, pero no puede utilizar literales booleanos (TRUE, FALSE y NULL).

Ejemplos:
CREATE PROCEDURE proc_create_table(
                                    p_table_name    VARCHAR2,
                                    p_col_specs     VARCHAR2
                                  ) IS
BEGIN
    EXECUTE IMMEDIATE 'CREATE TABLE '||p_table_name||
                      '('||p_col_specs||')';
END;
/
/*En el pasado ejemplo creamos el procedimiento: proc_create_table el cual recibe dos parámetros, uno equivalente al nombre de una tabla y el segundo a las especificaciones de dicha tabla.*/
---
BEGIN
    proc_create_table('EMPLOYEE_NAMES',
                       'id NUMBER(4) PRIMARY KEY, name VARCHAR2(40)'
                      );
END;
/
/*En el bloque anterior vemos como podemos implementar el procedimiento: proc_create_table. El ejemplo crea la tabla: EMPLOYEE_NAMES con dos columnas: id y name*/
---
CREATE PROCEDURE proc_add_col(
                                p_table_name    VARCHAR2,
                                p_col_spec      VARCHAR2
                             ) IS
    v_statement     VARCHAR2(100) := 'ALTER TABLE '||p_table_name||
                                      ' ADD '||p_col_spec;
BEGIN
    EXECUTE IMMEDIATE v_statement;
END;
/
/*Ahora creamos el procedimiento: proc_add_col que tiene como función agregar una columna o constraint a una tabla previamente creada.*/
---
BEGIN
    proc_add_col('employee_names', 'salary number(8,2)');
END;
/
/*El pasado ejemplo muestra el uso de proc_add_col. Notar como luego de ejecutar el bloque es agregada la columna salary a tabla employee_names.*/
---
DECLARE
    v_table_name    VARCHAR2(11)    :=  'my_table';
BEGIN
    BEGIN
        EXECUTE IMMEDIATE
                        'CREATE TABLE '||v_table_name||'
                        (
                            code   NUMBER,
                            name   VARCHAR2(15)
                        )';
    EXCEPTION
        WHEN OTHERS THEN
            NULL;
    END;

    FOR i IN 1..3 LOOP
        EXECUTE IMMEDIATE
                        'INSERT INTO '||v_table_name||' (code, name)
                         VALUES (:text_string1, :text_string2)'
        USING i, 'nombre'||i;
    END LOOP;
END;
/
/*En el ejemplo se crea un tabla con un nombre X y posteriormente se insertan 3 registros en dicha tabla. Notar el uso de la cláusula USING combinada con las variables Bind: :text_string1 y :text_string2.*/
---
CREATE FUNCTION proc_del_rows(
                                p_table_name    VARCHAR2
                             )  RETURN NUMBER IS
BEGIN
      EXECUTE IMMEDIATE 'DELETE FROM '||p_table_name;
      RETURN SQL%ROWCOUNT;
END;
/
/*En este ejemplo creamos la función: proc_del_rows que no solo elimina los registros de la tabla que recibe como parámetro, sino que también retorna el numero de registros eliminados.*/---
SET SERVEROUTPUT ON
BEGIN
    DBMS_OUTPUT.PUT_LINE(proc_del_rows('my_table')|| ' rows deleted.');
END;
/
/*Ahora usamos la función proc_del_rows para eliminar todos los registros de la tabla: my_table (creada en uno de los ejemplos anteriores), a continuación vemos el OUTPUT.*/
---OUTPUT:
SET SERVEROUTPUT ON
DECLARE
    TYPE    typ_details IS RECORD
    (
        code        NUMBER,
        name        VARCHAR2(50),
        dept_job    VARCHAR2(50)
    );

    TYPE    typ_tab IS
                    TABLE OF    typ_details
                        INDEX BY BINARY_INTEGER;
    v_tab   typ_tab;
    PROCEDURE   proc_bring__details
                                    (
                                        p_col_value     VARCHAR2,
                                        p_list_emps     OUT typ_tab
                                    )   IS
        BEGIN
            EXECUTE IMMEDIATE
                            '
                            SELECT
                                    e.employee_id,
                                    e.first_name||'' ''||e.last_name  AS name,
                                    (
                                      SELECT  d.department_name
                                      FROM    hr.departments d
                                      WHERE   d.department_id = e.department_id
                                    )
                            FROM    hr.employees e
                            WHERE   e.job_id  = :v_filter
                            UNION
                            SELECT
                                    e.employee_id,
                                    e.first_name||'' ''||e.last_name  AS name,
                                    (
                                      SELECT  j.job_title
                                      FROM    hr.jobs j
                                      WHERE   j.job_id = e.job_id
                                    )
                            FROM    hr.employees e
                            WHERE   TO_CHAR(e.department_id)  = TO_CHAR(:v_filter)
                            '
             BULK COLLECT INTO p_list_emps USING    p_col_value, p_col_value;
        END proc_bring__details;
BEGIN
    proc_bring__details(30, v_tab);
    
    FOR i   IN NVL(v_tab.FIRST, 1)..NVL(v_tab.LAST,0) LOOP
        DBMS_OUTPUT.PUT_LINE
                          (
                            v_tab(i).code||', '||RPAD(v_tab(i).name, 10, ' ')||', '||v_tab(i).dept_job
                          );
    END LOOP;
END;
/
/*En este ejemplo se hizo lo siguiente: Un bloque anónimo que contiene un tipo Record de 3 atributos,  una tipo tabla que hace referencia al record anterior y un procedimiento anidado (proc_bring_details) con dos parámetros, un parámetro de entrada tipo carácter y otro de salida tipo tabla (previamente creada). El procedimiento anidado ejecuta una consulta dinámica que filtra por el código de departamento o por el tipo de empleo sin necesidad de que el usuario indique que tipo de filtro desea aplicar; Una vez realizada la consulta los resultados son asignados al parámetro tipo tabla mediante la cláusula BULK COLLECT.  La siguiente imagen muestra los resultados dependiendo del valor recibido.*/
---OUTPUT:
______________________________________________________________________________________
Sentencias OPEN-FOR, FETCH y CLOSE.
La sentencia OPEN-FOR ejecuta la instrucción SELECT asociada a una variable tipo cursor. OPEN-FOR asigna recursos de base de datos para procesar la sentencia, identifica el conjunto de resultados (las filas que cumplen las condiciones) y coloca la variable tipo cursor antes de la primera fila en el conjunto de resultados.

Con la cláusula USING (opcional), OPEN-FOR procesa una sentencia SELECT dinámica que devuelve varias filas: asocia una variable tipo cursor con el SELECT, ejecuta la sentencia, identifica el conjunto de resultados, posiciona el cursor antes de la primera fila del resultado y pone en cero el recuento de filas procesadas (%ROWCOUNT).

Directrices:
 La cláusula USING no puede contener el NULL literal. Para evitar esta restricción, utilice una variable no inicializada donde desea utilizar NULL.
 Utilice la instrucción FETCH para recuperar las filas de conjunto de resultados de una en una, varias a la vez o todas a la vez.
 Utilice la instrucción CLOSE para cerrar la variable tipo cursor.

Sintaxis:
open_for_statement ::=
OPEN { cursor_variable_name | :host_cursor_variable_name }
  FOR select_statement [ using_clause ] ;

using_clause ::=
USING [ IN | OUTIN OUT ] bind_argument
  [ [,] [ [ IN | OUT | IN OUT ] bind_argument ]...

Ejemplo:
CREATE PROCEDURE list_employees(deptid NUMBER) IS
    TYPE emp_refcsr IS REF CURSOR;
    emp_cv          emp_refcsr;
    emprec          employees%ROWTYPE;
    v_statement     VARCHAR2(200) := 'SELECT * FROM employees';
BEGIN
    IF deptid IS NULL THEN
        OPEN emp_cv FOR v_statement;
    ELSE
        v_statement := v_statement || ' WHERE department_id = :id';
        OPEN emp_cv FOR v_statement USING deptid;
    END IF;

    LOOP
        FETCH emp_cv INTO emprec;
        EXIT WHEN emp_cv%NOTFOUND;
        DBMS_OUTPUT.PUT_LINE(emprec.department_id||
                      ' ' ||emprec.last_name);
    END LOOP;
    CLOSE emp_cv;
END;
/*En el pasado ejemplo se creo el procedimiento list_employees que muestra en pantalla la lista de empleados de algún departamento especificado, en caso de pasar un valor nulo se mostrarían todos los registros de la tabla employees. Notar el uso de los REF CURSOR.*/
---OUTPUT:
______________________________________________________________________________________
Paquete DBMS_SQL.
Utilizando DBMS_SQL, puede escribir procedimientos almacenados y bloques PL/SQL anónimos que usan SQL Dinámico, como ejecutar sentencias DDL en PL/SQL, por ejemplo, ejecutar una sentencia DROP TABLE. Las operaciones proporcionadas por este paquete se realizan bajo el usuario actual, no bajo el propietario del paquete (SYS). El paquete DBMS_SQL proporciona los siguientes subprogramas para ejecutar SQL dinámico:

 OPEN_CURSOR para abrir un nuevo cursor y devolver un número de ID del mismo.
 PARSE para analizar la instrucción SQL, es decir, comprueba la sintaxis de la sentencia y la asocia con el cursor abierto. Las sentencias DDL se ejecutan inmediatamente cuando se analizan.
 BIND_VARIABLE para enlazar un valor dado a una variable Bind identificada por su nombre en la sentencia analizada. No es necesario si la sentencia no tiene variables Bind.
 EXECUTE para ejecutar la instrucción SQL y devolver el número de filas procesadas.
 FETCH_ROWS para recuperar la siguiente fila de una consulta (se usa en un bucle para varias filas).
 CLOSE_CURSOR para cerrar el cursor especificado.

Nota: El uso del paquete DBMS_SQL para ejecutar instrucciones DDL puede resultar en un interbloqueo. Por ejemplo, al utilizar dicho paquete para eliminar un procedimiento que todavía está utilizando.

Para procesar dinámicamente una sentencia DML, realice los pasos siguientes:
1. Utilice OPEN_CURSOR para establecer un área en la memoria para procesar una instrucción SQL.
2. Utilice PARSE para establecer la validez de la instrucción SQL.
3. Utilice la función EXECUTE para ejecutar la instrucción SQL. Esta función devuelve el número de filas procesadas.
4. Utilice CLOSE_CURSOR para cerrar el cursor.

Los pasos para ejecutar una instrucción DDL son similares; Pero el paso 3 es opcional porque una instrucción DDL se ejecuta inmediatamente cuando el PARSE se realiza con éxito, es decir, la sintaxis de la sentencia y la semántica son correctas. Si utiliza la función EXECUTE con una instrucción DDL , no haría nada y devolvería un valor de 0 para el número de filas procesadas, ya que las instrucciones DDL no procesan filas.

Ejemplo:
CREATE OR REPLACE FUNCTION delete_all_rows
                                        (
                                            table_name VARCHAR2
                                        )   RETURN NUMBER IS
    csr_id INTEGER;
    rows_del    NUMBER;
BEGIN
    csr_id := DBMS_SQL.OPEN_CURSOR;
    DBMS_SQL.PARSE(csr_id,'DELETE FROM '||table_name, DBMS_SQL.NATIVE);

    rows_del := DBMS_SQL.EXECUTE (csr_id);
    DBMS_SQL.CLOSE_CURSOR(csr_id);
    RETURN rows_del;
END;
/
/*En el anterior ejemplo notamos el uso de paquete DBMS_SQL. Notar que es requerido usar sus métodos para así realizar una operación X. En los dos siguientes métodos mostraos la implementación de la función antes creada.*/
---
CREATE TABLE EMP_COPY AS
    SELECT * FROM employees;



BEGIN
    DBMS_OUTPUT.PUT_LINE('Rows Deleted: ' ||delete_all_rows('EMP_COPY')); 
END;
/
---OUTPUT:
CREATE PROCEDURE insert_row(
                            table_name VARCHAR2,
                            id VARCHAR2,
                            name VARCHAR2,
                            region NUMBER
                           ) IS
    csr_id     INTEGER;
    stmt       VARCHAR2(200);
    rows_added NUMBER;
BEGIN
    stmt := 'INSERT INTO '||table_name||
            ' VALUES (:cid, :cname, :rid)';
    csr_id := DBMS_SQL.OPEN_CURSOR;

    DBMS_SQL.PARSE(csr_id, stmt, DBMS_SQL.NATIVE);
    DBMS_SQL.BIND_VARIABLE(csr_id, ':cid', id);
    DBMS_SQL.BIND_VARIABLE(csr_id, ':cname', name);
    DBMS_SQL.BIND_VARIABLE(csr_id, ':rid', region);
    rows_added := DBMS_SQL.EXECUTE(csr_id);
    DBMS_SQL.CLOSE_CURSOR(csr_id);

    DBMS_OUTPUT.PUT_LINE(rows_added||' row added');
END;
/
/*En este ejemplo notamos otro uso del paquete DBMS_SQL. Como vemos es requerido un paso adicional para poder asociar una variable Bind a la sentencia dinámica.*/
---OUTPUT:
Después que la sentencia es analizada (PARSE), debe llamar al procedimiento DBMS_SQL.BIND_VARIABLE para asignar valores para cada variable Bind que existe en la sentencia. La vinculación de valores debe realizarse antes de ejecutar el código. Para procesar dinámicamente una instrucción SELECT, realice los siguientes pasos después de abrir y antes de cerrar el cursor:
1. Ejecute DBMS_SQL.DEFINE_COLUMN para cada columna seleccionada.
2. Ejecute DBMS_SQL.BIND_VARIABLE para cada variable de enlace en la consulta.
3. Para cada fila, haga lo siguiente:
 Ejecute DBMS_SQL.FETCH_ROWS para recuperar una fila y devolver el número de filas obtenidas. Detenga el procesamiento adicional cuando se devuelve un valor cero.
 Ejecute DBMS_SQL.COLUMN_VALUE para recuperar cada valor de columna seleccionado en la variable PL/SQL para su procesamiento.

Nota: Aunque el proceso de codificación con DBMS_SQL no es complejo, resulta mas lento y mas propenso a errores en comparación con el uso del enfoque SQL Dinámico Nativo.
______________________________________________________________________________________
Comparación: SQL Dinámico Nativo vs Paquete DBMS_SQL.
El SQL Dinámico Nativo proporciona las siguientes ventajas sobre el paquete DBMS_SQL.
 Facilidad de uso: Debido a que el SQL Dinámico Nativo está integrado con SQL, puede utilizarlo de la misma forma en que utiliza SQL Estático dentro del código PL/SQL. El código suele ser más compacto y legible en comparación con el código escrito con el paquete DBMS_SQL.
 Mejora de rendimiento: SQL Dinámico Nativo se desempeña significativamente mejor que DBMS_SQL, en la mayoría de las circunstancias, debido al soporte nativo proporcionado por el intérprete PL/SQL. El enfoque DBMS_SQL utiliza una API de procedimiento y sufre de muchas llamadas a procedimientos y copia de datos de sobrecargas.
 Soporte para tipos definidos por el usuario: SQL Dinámico Nativo soporta todos los tipos soportados por SQL Estático en PL/SQL. Por lo tanto, SQL Dinámico Nativo proporciona soporte para tipos definidos por el usuario, como objetos, colecciones y REFs. El paquete DBMS_SQL no admite tipos definidos por el usuario. Sin embargo, tiene soporte limitado para matrices.
 Soporte para la obtención de registros: con SQL Dinámico Nativo, las filas resultantes de una consulta se pueden obtener directamente en registros PL/SQL. El paquete DBMS_SQL no admite la obtención de estructuras tipo registros.
______________________________________________________________________________________
Fuentes: Oracle Database 10g: Develop PL/SQL Program Units
https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/dynamic.htm#LNPLS01108
https://docs.oracle.com/cd/B12037_01/appdev.101/b10807/13_elems017.htm

jueves, 22 de junio de 2017

Agregando Funcionalidad a los Items.

Objetivos:

 Complementar la funcionalidad de los Input Items usando Triggers y Built-ins.
 Complementar la funcionalidad de los noninput Items usando Triggers y Built-ins.
 Ejemplos Prácticos de:
---Radio Groups y Radio Buttons.
---Check Boxes.
---Hierarchical Trees
---List Items.
---LOVs o List of Values.
---Image Items.
---Java Beans.

NOTA: Usamos como ejemplo la Base de Datos: ORCL (Esquema: OE), la cual viene por defecto en cualquier versión de ORACLE.
______________________________________________________________________________________
Triggers para Items de Interacción.
Hay varios tipos de Items GUI con los que el usuario puede interactuar usando el ratón o presionando una tecla de función. La mayoría de estos Items tienen una funcionalidad predeterminada. Por ejemplo, al seleccionar un Radio Button, el usuario puede cambiar el valor del Item Radio Group.

A menudo, deseará agregar Triggers para proporcionar cierta funcionalidad personalizada cuando se produzcan estos eventos. Por ejemplo:
 Realizar pruebas y acciones apropiadas tan pronto como el usuario haga clic en un Radio Button, en una lista o un Check Box.
 Mostrar una visualización conveniente cuando el usuario hace clic en un Image Item.
 Definir la funcionalidad de un botón al momento de ser presionado.

Los siguientes Triggers se activan mediante a la interacción del usuario con un Item, tal como se describió anteriormente. Estos pueden definirse en cualquier Nivel (Scope: Item, Block, Form).
Trigger
Evento
When-Button-Pressed
El usuario presiona un botón con el mouse o con la tecla [Enter].
When-Checkbox-Changed
El usuario cambia el estado de un Check Box (Checked/Unchecked).
When-Custom-Item-Event
El usuario selecciona o cambia el valor de un componente JavaBean.
When-Radio-Changed
El usuario selecciona un botón/opción diferente en un Radio Group.
When-Image-Pressed
El usuario hace clic en un Image Item.
When-Image-Activated
El usuario hace doble-clic en un Image Item.
When-List-Changed
El Usuario cambia el valor de un List Item.
When-List-Activated
El usuario hace doble-clic a un elemento de un T-list.
When-Tree-Node-Activated
El usuario hace doble-clic o presiona [Enter] sobre el nodo de un Árbol Jerárquico.
When-Tree-Node-Expanded
El usuario expande o colapsa el nodo de un Árbol Jerárquico.
When-Tree-Node-Selected
El usuario selecciona o deselecciona un el nodo de un Árbol Jerárquico.
Tipos de Comandos en Triggers para Items de Interacción.
Puede utilizar sentencias SQL y PL/SQL en estos Trigger; sin embargo, a menudo deseara agregar funcionalidad adicional a los Items llamando subprogramas built-in, los cuales proporcionan una amplia variedad de mecanismos.

Aunque Forms le permite usar sentencias DML (INSERT, UPDATE o DELETE) en cualquier Trigger, pero lo mejor es utilizarlas en Triggers que envuelvan COMMITS. De lo contrario, estas sentencias DML no serian incluidas en el mantenimiento de Form en relación con el procesamiento de confirmación (COMMIT). Esto puede conducir a resultados inesperados y no deseados.

Nota: Durante una excepción no controlada, el Trigger finaliza y envía el mensaje de error al operador. En Items, los Triggers de Interacción no se disparan en eventos de navegación o validación.

Ejemplo de When-Radio-Changed.
Trigger When-Radio-Changed en: CUSTOMERS.GENDER. Cuando el usuario trata de cambiar el sexo, este Trigger confirma si el cliente está casado (married). Si es así, un mensaje advierte al usuario y no se permite el cambio:
DECLARE
v_alert NUMBER;
TYPE typ_gender IS
TABLE OF VARCHAR2(7)
INDEX BY VARCHAR2(7);
v_pre_gender typ_gender;
BEGIN
v_pre_gender('M') := 'F';
v_pre_gender('F') := 'M';

IF UPPER(:CUSTOMERS.MARITAL_STATUS) = 'MARRIED' THEN
:CUSTOMERS.GENDER := v_pre_gender(:CUSTOMERS.GENDER);
v_alert := SHOW_ALERT('ALERT_MARITAL');
END IF;
END;
______________________________________________________________________________________
Definiendo la Funcionalidad de los Input Items.
El pasado ejemplo muestra como agregar funcionalidad a los Radio Groups; Ahora lo haremos con otros Items que aceptan la entrada del usuario.
Check Boxes
Cuando el usuario selecciona o limpia un Check Box, se establece el valor asociado a dicho estado. Es posible que desee realizar acciones basadas en este cambio. Tenga en cuenta que la función CHECKBOX_CHECKED le permite probar el estado de un Check Box sin necesidad de conocer los valores asociados para el Item.

Ejemplo:
En el Item: CONTROL_BLOCK.Case_Sensitive el Trigger: When-Checkbox-Changed (mostrado a continuación) le permite al usuario controlar el modo de letra(Mayúsculas o sin restriction) al introducir datos en el campo name (Nombre).
BEGIN
IF CHECKBOX_CHECKED('CONTROL_BLOCK.CASE_SENSITIVE') THEN
SET_ITEM_PROPERTY('CONTROL_BLOCK.NAME', CASE_RESTRICTION, NONE);
SET_ITEM_PROPERTY('CONTROL_BLOCK.NAME', CASE_RESTRICTION, NONE);
ELSE
SET_ITEM_PROPERTY('CONTROL_BLOCK.NAME', CASE_RESTRICTION, UPPERCASE);
SET_ITEM_PROPERTY('CONTROL_BLOCK.NAME', CASE_RESTRICTION, UPPERCASE);
END IF;
END;
---
List Items.
Puede utilizar el Trigger When-List-Changed para capturar la selección que haga el usuario en una lista de valores. Para los Tlists, puede capturar los doble-clics con When-List-Activated.
Con Forms Builder, puede cambiar los elementos seleccionables en una lista de la siguiente manera:
 Actualizar periódicamente la lista con un Record Group de dos columnas.
 Agregar o eliminar elementos individuales a través de los built-in ADD_LIST_ELEMENT y DELETE_LIST_ELEMENT respectivamente:
ADD_LIST_ELEMENT ('list_item_name', index, 'label', 'value');
DELETE_LIST_ELEMENT ('list_item_name', index) ;
Parámetro
Descripción
Index
Número que identifica la posición del elemento en la lista (1 para el mas alto).
Label
Nombre del elemento.
Value
El nuevo valor para el elemento.
Nota: Puede eliminar el elemento NULL de una lista estableciendo su propiedad Required en Yes.
En tiempo de ejecución, cuando el bloque contiene registros con estado Queried o Changed, Form no permite agregar o eliminar elementos de un List Item.
______________________________________________________________________________________
Definiendo la Funcionalidad de los Noninput Item.
Mostrar LOVs desde botones.
Si ha adjuntado un LOV a un Text Item, el usuario puede invocar dicho LOV desde el Text Item seleccionando Edit> Display List o pulsando la tecla List Values. Sin embargo, resulta más útil y práctico mostrar un LOV al presionar un botón.

Hay dos built-ins que puede usar para invocar un LOV desde un Trigger. Estos son LIST_VALUES SHOW_LOV.

Procedimiento LIST_VALUES.
Este procedimiento invoca el LOV adjunto al Text Item actual. Tiene un argumento opcional, que puede establecerse en RESTRICT, lo que significa que el valor actual del Text Item se utiliza como cadena de búsqueda inicial en el LOV. El valor predeterminado de este argumento es NO_RESTRICT.

Función SHOW_LOV.
Esta función (sin argumentos), invoca el LOV del Item actual. A su vez, puede usar argumentos que le permiten definir qué LOV debe mostrarse, y en cuales coordenadas x y debe aparecer su ventana:
SHOW_LOV ('lov_name', x, y)
SHOW_LOV (lov_id, x, y)

Nota: En el ejemplo anterior, Lov_id es una variable PL/SQL que contiene el ID interno del objeto. El uso de ID internos resulta ser una forma más eficiente de identificar objetos.

Usando la función SHOW_LOV.
La función SHOW_LOV devuelve un valor booleano:
 TRUE indica que el usuario seleccionó un registro del LOV.
 FALSE indica que el usuario descartó el LOV sin elegir un registro, o que el LOV devolvió 0 registros.

Notas:
 Puede utilizar la función FORM_SUCCESS para identificar porque la función SHOW_LOV devolvió FALSE.
 Es recomendable crear el botón que muestra el LOV con una etiqueta adecuada, como "Lista", y ubicarlo en un lugar del Canvas donde el usuario intuitivamente lo asocie con el/los Items que serán alimentados.
 Puede utilizar la función SHOW_LOV para mostrar un LOV que ni siquiera está conectado a un Text Item, esto identificando el LOV en el primer argumento de la función. Cuando se llama desde un botón, este invoca el LOV para que sea independiente de la ubicación del cursor.
 Es recomendable desactivar la propiedad Mouse Navigable en el botón. Cuando se utiliza LIST_VALUES, el cursor debe residir en el Text Item adjunto al LOV. Con SHOW_LOV, esto mantiene el cursor en su ubicación original después de que el LOV esté cerrado, dondequiera que sea.

Ejemplo:
Este Trigger When-Button-Pressed en el Customer_Lov_Button invoca un LOV en un bucle de PL/SQL, hasta que la función devuelve TRUE. Como SHOW_LOV devuelve TRUE cuando el usuario selecciona un registro, el LOV se vuelve a mostrar hasta que lo haga.
LOOP
EXIT WHEN SHOW_LOV('LOV_ANIMALS');
MESSAGE('You must select a value from list');  
END LOOP;
______________________________________________________________________________________
Los Image Items.
Los Image Items que tienen la propiedad Database Item establecida en Yes se llenan automáticamente en respuesta a una consulta de su bloque (de una columna LONG RAW o BLOB en la tabla base).

Por otro lado, los Image Items que no son de base de datos deben ser poblados por otros medios. Por ejemplo, desde un archivo de imagen en el sistema de archivos: READ_IMAGE_FILE built-in.

Tenga en cuenta que al interactuar con los Image Items, dos Trigger pueden dispararse:
 When-Image-Pressed (Un solo clic en la imagen).
 When-Image-Activated (Al hacer doble clic en la imagen).

Nota: El procedimiento READ_IMAGE_FILE carga un archivo de imagen desde el sistema de archivos del servidor de aplicaciones. Si necesita cargar un archivo de imagen desde el sistema de archivos en el cliente, utilice un JavaBean.


Procedimiento READ_IMAGE_FILE.
Este built-in le permite cargar un archivo de imagen, en una variedad de formatos.
Sintaxis:
READ_IMAGE_FILE ('filename', 'filetype', 'item_name');
Parámetro
Descripción
filename
El nombre del archivo de imagen (sin una ruta de acceso especificada, se asume la ruta predeterminada).
filetype
Tipo de archivo de la imagen (Puede utilizar ANY como un valor, pero se recomienda configurar un tipo de archivo específico para obtener un mejor rendimiento).
item_name
Nombre del Image Item (una variable que contiene el Item_id también es válida para este argumento.) (Este parámetro es opcional).
Notas:
El parámetro filetype es opcional; Si lo omite, debe identificar explícitamente el parámetro item_name.
El procedimiento inverso, WRITE_IMAGE_FILE, también está disponible.
El procedimiento WRITE_IMAGE_FILE escribe un archivo de imagen en el sistema de archivos del servidor de aplicaciones. Si necesita escribir un archivo de imagen en el sistema de archivos del cliente, utilice un JavaBean.

Ejemplo:
El siguiente Trigger WHEN-NEW-ITEM-INSTANCE en el Bloque ANIMALS muestra una imagen del animal actual. En este ejemplo se supone que los nombres de archivo relacionados tienen el formato: <animal_name>.jpg.
READ_IMAGE_FILE(:ANIMALS.ANIMAL_NAME||'.jpg','JPEG','ANIMALS.ANIMAL_IMAGE');
---
Observe que como el primer argumento de este built-in es del tipo de datos CHAR y está concatenado con la cadena '.jpg' que representa el tipo de imagen a cargar.

Nota: Si carga una imagen en un Image Item de base de datos utilizando READ_IMAGE_FILE, su contenido se asignará a la columna LONG RAW o BLOB de la base de datos cuando guarde los cambios en el Forms. Puede utilizar esta técnica para rellenar una tabla con imágenes.
______________________________________________________________________________________
Colocación de Árboles Jerárquicos.
El árbol jerárquico muestra datos en forma de un navegador estándar, similar al Navegador de objetos utilizado en Oracle Forms Developer.
Puede rellenar un árbol jerárquico con valores contenidos en un Record Group o en una consulta. Mediante programación, puede agregar, quitar, modificar o evaluar elementos de un árbol jerárquico. También puede usar la paleta de propiedades para establecer las propiedades de poblado del árbol jerárquico.

El Paquete FTREE.
El paquete FTREE contiene built-ins y constantes que permiten interactuar con items de árbol jerárquicos (hierarchical tree items). Para utilizarlos, debe preceder a sus nombres con el nombre del paquete.

Procedimiento SET_TREE_PROPERTY.
Este procedimiento se puede utilizar para cambiar ciertas propiedades para el elemento de árbol jerárquico indicado. También se puede usar para rellenar el elemento de árbol jerárquico indicado de un grupo de registros.
Ftree.Set_Tree_Property (item_name, Ftree.property, value);
Parámetro
Descripción
item_name
Especifica el nombre del objeto creado en tiempo de diseño. El tipo de dato del nombre es VARCHAR2. Una variable que contenga el Item_id también es válida para este argumento.
property
Especifica una de las siguientes propiedades:
RECORD_GROUP: Reemplaza el conjunto de datos del árbol jerárquico con un Record Group y hace que se muestre.
QUERY_TEXT: Reemplaza el conjunto de datos del árbol jerárquico con una consulta SQL y hace que se muestre.
ALLOW_EMPTY_BRANCHES: Los valores posibles son PROPERTY_TRUE y PROPERTY_FALSE.
value
Especifica el valor apropiado para la propiedad que está configurando.
Puede agregar datos a un árbol mediante:
 Rellenandolo con valores contenidos en un Record Group o una consulta utilizando POPULATE_TREE.
 Agregando datos bajo un nodo específico mediante el uso de ADD_TREE_DATA.
 Modificando sus elementos en tiempo de ejecución mediante el uso de subprogramas built-ins.
 Agregando o eliminando nodos y elementos de datos bajo los nodos.

Ejemplo:
Este código podría utilizarse en un trigger When-Button-Pressed para inicialmente llenar el árbol jerárquico con datos. El ejemplo localiza primero el árbol jerárquico. A continuación, se crea un record group y se rellena el árbol jerárquico.
DECLARE
v_htree ITEM;
v_ignore NUMBER;
v_rg_emps RECORDGROUP;
BEGIN
v_htree := Find_Item('tree_block.htree3');
v_rg_emps :=
Create_Group_From_Query('rg_emps',
'SELECT
1,
level,
last_name,
NULL,
to_char(employee_id) '
||' FROM hr.employees ' ||
'CONNECT BY PRIOR employee_id = manager_id ' ||
'START WITH job_id = ''AD_PRES'''
);
v_ignore := Populate_Group(rg_emps);
Ftree.Set_Tree_Property(v_htree, Ftree.RECORD_GROUP,rg_emps);
END;
---
El Record Group o Consulta.
Las columnas de un Record Group o consulta que se utilizan para rellenar un árbol jerárquico son:
Estado inicial: 0 (no expandible, 1 (expandido) o -1 (colapsado)
Profundidad del árbol de nodos: Utilice LEVEL pseudocolumn
Etiqueta para el nodo: lo que el usuario ve
Icono para el nodo: Imagen mostrada, si la hay
Datos: Valor real del nodo
______________________________________________________________________________________
Interactuando con los JavaBeans.
Por lo general un Bean Area item es un elemento visual que agrega cierta funcionalidad a los formularos, un ejemplo es un Bean de Calendario con su propio botón de invocación. De igual manera, los JavaBeans (como el Bean ColorPicker) no siempre tienen componentes visibles, por lo que es posible que deba crear un botón u otro mecanismo para invocarlo
.
Independientemente de si el bean es visible o no, debe haber alguna comunicación entre el Form y las clases Java que componen el bean. En primer lugar, el Form debe ser consciente del bean, ya sea mediante el establecimiento de su propiedad de clase de implementación en tiempo de diseño o mediante el registro del bean y sus eventos en tiempo de ejecución. Una vez que el Form conoce del Bean, la comunicación es posible mediante:
 Invocando los métodos del Bean.
 Obtención y configuración de propiedades del Bean.
El Bean se comunica al Form por:
 Envío de un evento, como el hecho de que el usuario seleccionó una fecha o un color.
 Enviar una lista con la información que necesita el Form, como la fecha o el color seleccionado.
 Devolver un valor de un método invocado.

El Paquete FBEAN.
El paquete FBEAN contiene algunos built-ins que le permiten codificar la forma como los JavaBeans Interactúan con PL/SQL, esto eliminando la necesidad de conocer Java para poder comunicarse con el bean.

Muchos de los built-ins tomar algunos de los siguientes argumentos:
 Item Name o Item Id (obtenido con el FIND_ITEM): El primer argumento para la mayoría de los built-ins de FBEAN.
 Item Instance: Referencia a la instancia del item que debe contener el bean. Esto es aplicable cuando el Bean Area es parte de un bloque de varias filas y se muestra más de una instancia de dicho Bean Area. Puede utilizar el valor ALL_ROWS (o FBEAN.ALL_ROWS) para el valor del Item Instance y así indicar que el comando debe aplicarse a todas las instancias de este Bean Area en el bloque.
Nota: Esto se refiere a la instancia de UI del Bean Area, No al número de filas en el bloque. Por ejemplo, en un bloque con 5 filas mostradas y 100 filas consultadas, habrá 5 instancias del bean numeradas de 1 a 5, no 100 instancias.
 Value (Valor): Puede aceptar los tipos de datos BOOLEAN, VARCHAR2 o NUMBER.

Algunos de los built-ins en el paquete FBEAN:
Tenga en cuenta que en las siguientes sintaxis:
ITEM = Item Name o Item Id;
INSTANCE = Item Instance;
 GET_PROPERTY(ITEM, INSTANCE, PROPERTY_NAME) (devuelve VARCHAR2): Función que recupera el valor de la propiedad especificada.
 SET_PROPERTY(ITEM, INSTANCE, PROPERTY_NAME, VALUE): Establece la propiedad especificada del bean con el valor indicado.
 INVOKE(ITEM, INSTANCE, METHOD_NAME [, ARGUMENTS]): Invoca un método en el bean, opcionalmente pasando argumentos al método.
 REGISTER_BEAN(ITEM, INSTANCE, BEAN_CLASS): Registra el bean con el Form en tiempo de ejecución, haciendo que todos sus atributos y métodos expuestos estén disponibles para el item bean del Form (El último argumento es el nombre de la clase del bean, como 'oracle .forms.demos.beans.ColorPicker').
 ENABLE_EVENT(ITEM, INSTANCE, EVENT_LISTENER_NAME, SUBSCRIBE); El último argumento es un BOOLEAN que indica si se debe suscribir (TRUE) o anular la suscripción (FALSE) al evento.

Recuerde preceder las llamadas a cualquiera de estos built-ins con el nombre del paquete y un punto, como FBEAN.GET_PROPERTY(...). Puede pasar argumentos a estos built-ins como una cadena delimitada o como una lista de argumentos.

Desplegando el Bean.
Debido a que el propio bean es una clase Java o un conjunto de archivos de clase Java separados del módulo Form, es necesario saber dónde colocar estos archivos. Puede localizarlos:
 En el servidor de nivel intermedio (middle-tier server), ya sea en la estructura de directorios referenciada por el parámetro CODEBASE del applet de Form o en CLASSPATH del servidor. CODEBASE es por defecto el subdirectorio forms90\java de ORACLE_HOME.
 Si utiliza JInitiator, en un archivo JAR en el directorio CODEBASE del servidor de nivel intermedio y incluido en el parámetro ARCHIVE para que el archivo JAR se descargue y se almacene en caché en el cliente.
Ejemplo:
Archive_jini = f90all_jinit.jar, colorpicker.jar
(Los parámetros CODEBASE y ARCHIVE se establecen en el archivo formsweb.cfg).

Respondiendo a Eventos.
Al interactúar con un JavaBean, el usuario suele provocar que se produzca un evento. Puede utilizar FBEAN.ENABLE_EVENT para registrar un listener para el evento, de modo que cuando se produzca el evento, Forms active el Trigger When-Custom-Item-Event. En este Trigger, puede codificar una respuesta al evento. Las variables SYSTEM.CUSTOM_ITEM_EVENT y: SYSTEM.CUSTOM_EVENT_PARAMETERS contienen el nombre del evento y la información que el bean está enviando al Form.
Una interacción típica que podría ocurrir incluye los siguientes pasos:
1. El usuario hace clic en el bean area para un bean de Calendario. Este bean area tiene un componente visible en el Form que se parece a un botón. La etiqueta se establece con la fecha de contratación de un empleado.
2. El bean de Calendario se invoca y muestra un calendario inicialmente establecido en la fecha de contratación del empleado.
3. El usuario cambia la fecha en el bean escogiendo un nuevo mes y año, luego haciendo clic en un día, que inicia el evento DateChanged.
4. El Trigger When-Custom-Item-Event obtiene la fecha modificada y la asigna al hire_date del empleado, cambiando también la etiqueta en el bean area"button".
______________________________________________________________________________________
Codificando el Trigger When-Custom-Item-Event.
En un Trigger When-Custom-Item-Event puede codificar la acción a tomar en respuesta a eventos del JavaBeans.

Por ejemplo, cuando un usuario selecciona una fecha del bean de Calendario, se produce el evento DateChange y se dispara el Trigger When-Custom-Item-Event. En el código para el Trigger When-Custom-Item-Even, necesita obtener el nombre del evento. Si se trata del evento DateChange, debe obtener la nueva fecha y asignarla a un item del Form, como la fecha de contratación del empleado. Puede utilizar las variables del sistema que contienen la información del evento y del parámetro:
DECLARE 
hBeanEventDetails ParamList;
eventName VARCHAR2(80);
paramType NUMBER;
eventType VARCHAR2(80);
newDateVal VARCHAR2(80);
newDate DATE := NULL;
BEGIN
hBeanEventDetails := get_parameter_list(:system.custom_item_event_parameters);
eventName := :SYSTEM.custom_item_event;
IF (eventName = 'DateChange') THEN
get_parameter_attr(hBeanEventDetails,'DateValue', ParamType, newDateVal);
newDate := TO_DATE(newDateVal,'DD.MM.YYYY');
END IF;
:employees.hire_date := newDate;
END;
---
El ejemplo anterior es para un bean que utiliza integración codificada manualmente. Si utiliza el paquete FBEAN para integrar el bean, el nombre del valor devuelto al Form siempre se denomina 'DATA'.

Ejemplo:
get_parameter_attr(:SYSTEM.custom_item_event_parameters, 'DATA', paramType, eventData);
(Donde paramType y eventData son variables PL/SQL que se declaran en el Trigger When-Custom-Item-Event, como paramType y newDateVal en el ejemplo anterior).

Obtención de Valores de JavaBeans sin Eventos.
No toda la información de JavaBeans se obtiene a través de eventos. Por ejemplo, un JavaBean puede devolver un valor cuando se invoca uno de sus métodos. Este valor puede asignarse a una variable PL/SQL o a un Item de Forms, de igual forma en que una función devuelve un valor.

Un ejemplo de esto es el bean ColorPicker que contiene un único método que devuelve un valor y no tiene ningún componente visible en el área de bean del Form. Para invocar el bean y obtener un valor de éste, puede utilizar un botón de Forms con código similar al siguiente:
vcNewColor := FBean.INVOKE_CHAR(hColorPicker, 1,'showColorPicker','"Select color for canvas"');
---
El built-in INVOKE_CHAR se utiliza para llamar a un método que devuelve un valor VARCHAR2.
1. El usuario hace clic en el botón para invocar el bean.
2. Se muestra el componente ColorPicker.
3. El usuario selecciona un color.
4. El valor de color (valores RGB en lista separada por comas) se devuelve a la variable vcNewColor. El código puede utilizar el valor del color para establecer el color del Canvas.

______________________________________________________________________________________
Ajunto el Módulo Form creado para esta práctica: Link.
Nota: Los pasos para ejecutar este form están adjuntos en el .rar, aun así los detallo aquí:

Pasos para configurar el JavaBean:
 Descomprimir el archivo: FRM_ADD_FUNCTIONALITY.rar
 Copiar el archivo: colorpicker.jar al directorio:  <ORACLE_HOME>/forms/java
Eje:
F:\Middleware\Oracle_FRHome1\forms\java
 Editar el archivo /forms/server/formsweb.cfg para así incluir el jar en el tag: archive=
Eje:
archive=frmall.jar, colorpicker.jar

Pasos para configurar el Image Item:
 Copiar las imágenes adjuntas a un directorio similar a este:
F:\Oracle_Forms_Projects\FRM_ADD_FUNCTIONALITY\Images\
De igual forma puede editar el Trigger: WHEN-NEW-ITEM-INSTANCE del Bloque: ANIMALS con la ruta desde donde desea que el Forms lea los archivos .jpg.

Todos los archivos están adjuntos, incluyendo mi formsweb.cfgdefault.env para que así se guíen mejor.

El Esquema usado para la práctica fue: OE de ORCL.
______________________________________________________________________________________
Algunas imágenes de la aplicación en ejecución:
Ejemplo del Árbol Jerárquico:
 Ejemplo del Image Item:
Esta es una alerta de seguridad de Java al intentar registrar la clase (Debe presionar Don't Block):
Otra alerta de seguridad de Java, esta porque mi versión de Java no es la mas reciente (Si les ocurre lo mismo presionar Run):
Finalmente el Selector de Color en ejecución desde un aplicación Forms:

______________________________________________________________________________________
Fuente: Oracle Forms Developer 10g: Build Internet Applications.
http://forms.pjc.bean.over-blog.com/