Professional Documents
Culture Documents
You have a number of options for manipulating the string that Forms passes as a query to the database. For example, the
WHERE Clause and ORDER BY Clause properties can be manipulated at runtime using the SET_BLOCK_PROPERTY
built-in. This allows you to manipulate the block’s sorts and filters at runtime.
The ORDER BY Clause property can be used to change the sort order of records in the block and can also be manipulated at
runtime.
Caution: When setting these properties, you are simply providing material that Forms will concatenate
to form the SQL statement. Therefore, there is nothing to say that the table source must be a single
table. For example, you can use “DEPT,EMP” as the value for the Query Data Source Name property
and “DEPT.DEPTNO = EMP.DEPTNO” in the WHERE Clause property. This allows you to select
columns from either the DEPT or EMP tables without the need for a view in the database. Using the
properties in this way is not a recommended strategy because it is nonstandard and may confuse
someone else trying to maintain the form. Also, in Form Builder release 6.0, there are alternate methods
of having blocks return information from multiple tables simultaneously without resorting to such
workarounds.
Whether you will allow users to change the Order By Clause property at runtime is a GUI design issue. Normally, runtime
specification of the ORDER BY clause is not necessary. Most blocks should have this property set when the block is created,
particularly if a large number of records must be retrieved. Keep in mind that the Order By Clause block property alters the
query statement sent to the database. By adding an ORDER BY clause to the query, there may be a significant performance
impact to consider.
emp.mgr,
emp.hiredate,
emp.sal,
emp.comm,
emp.deptno,
mgr.ename mgr_name,
mgr.job mgr_job
FROM emp emp, emp mgr
WHERE emp.mgr = mgr.empno (+)
This view uses an outer join to retrieve all rows of the EMP table even if they have no value in the MGR column. If you base
the EMP block on this view, you will not need to use a POST-QUERY trigger to query the manager information for each row;
the manager information will be retrieved as part of the same row in the view. Using a view in this way can save a significant
amount of network traffic. This example assumes that you are not updating the manager information, although this would be
possible by using INSTEAD OF triggers (described later) or some other mechanism.
You can use views in different ways—as updateable objects or as non-updateable objects. You can also take advantage of an
Oracle8 feature that allows you to write database (INSTEAD OF) triggers for the view. Other than the extra step of writing
trigger code, there is no difference in syntax for creating views that are in the categories of updateable, non-updateable, or
INSTEAD OF. However, the different uses require different design considerations and Forms techniques and each will be
discussed separately.
Tip: If you need to sort the records in a block on a non-base table item that does not appear in the table
or view that the block is based on, you can create a function in the database that loads the non-base
table item and add that function’s name to the ORDER BY Clause property on the block you wish to
sort.
Updateable Views
An updateable view is a view created where one or more columns support DML commands. There is nothing special required
syntactically to make a view updateable. The Oracle database decides for you which columns are updateable. To find out
which columns are updateable, you can query from the system view USER_UPDATABLE_COLUMNS. The documentation
on updateable views (in Chapter 3 of the Oracle8 Application Developer’s Guide - Fundamentals manual) includes a very
precise technical discussion of key-preserved (or ROW ID preserved) columns. In short, if your view is essentially querying
from a single table with some extra display columns, then you can perform INSERT, UPDATE, and DELETE operations to
columns in the base table as shown in the following code:
CREATE OR REPLACE VIEW emp_v
AS
SELECT e.empno,
e.ename,
e.deptno,
d.dname
FROM emp e, dept d
WHERE e.deptno = d.deptno
In this case, you can insert into this view as if it were the EMP table. However, you cannot insert into the DNAME column.
Another way to think about this is that an updateable view is a view that contains a join between master and detail tables. You
can update columns that are derived from the detail table (that contains the key-preserved columns) of this view. The view has
to be constructed so that the foreign key column comes from the detail table (not the master table). In this example, the detail
table is the EMP table. The master table is the DEPT table and the DEPTNO foreign key column comes from the EMP table.
Therefore, you are able to update all columns from the detail table (EMP) but not from the master table (DEPT).
When creating an updateable view, it will always have the same number of rows as its core (detail) table unless you are
filtering out the records using a WHERE clause. The previous code example could be modified to include:
AND e.deptno < 30
You can still INSERT, UPDATE, or DELETE records in the EMP table using the EMP_V view even though the number of
records in the view is potentially less than the number of records in the table.
One very useful technique using updateable views is to embed a function in a view. To do this, you must first place the
function into a database package. Next, in the package specification, use a PRAGMA RESTRICT_REFERENCES directive.
This function can then be embedded in the updateable view.
The example above could also be rewritten by creating a function called EMPNO_TO_DNAME and storing it in a package
called K_EMP. The view definition would be changed as shown below:
CREATE OR REPLACE VIEW emp_v
AS
SELECT empno,
ename,
deptno,
k_emp.empno_to_dname(empno)
FROM emp
This view now operates in the same way as if you had a base table block based on EMP with a POST-QUERY trigger to look
up the department name. However, since you are basing your block on the updateable view, the number of network round
trips will be reduced. You can still update the columns from the EMP table but you cannot update the column based on the
function. Usually, joining the tables will be more efficient than performing lookups using embedded functions. The reason
both methods have been presented is to point out how every block with a POST-QUERY trigger (that contains a lookup
cursor) can always be restructured as a block based on an updateable view. You can accomplish the lookup by rewriting the
POST-QUERY trigger as a function that you place in the SELECT clause of the view. Using functions for lookups instead of
POST-QUERY triggers requires no extra operations by the database and will require much less network traffic because all
queries are performed on the database server before query results from the view are passed to the form. This demonstrates
why POST-QUERY triggers with cursors should never be used.
Caution: When creating an updateable view and joining to a table based on an optional relationship, be
sure to use an outer join to ensure that all records are retrieved.
Many developers assert that updateable views are more complex and actually slower than tables with POST-QUERY triggers.
This is indeed possible if your updateable view is a multitable join. Even though the overall efficiency of bringing all of the
records back is improved, the perceived performance by users may actually degrade. If this problem exists, the solution is not
to go back to using a POST-QUERY trigger on a block. Instead, the updateable view should be changed. This can be done by
using the same strategy to build the updateable view as was used to build the base table block and POST-QUERY triggers,
namely rewriting the triggers as database functions. When this is done, then you can build your updateable view to select all
columns from the base table plus any additional formula columns. This mechanism will always be faster than a table-based
block with POST-QUERY triggers. The only difference between the two methods is that rather than performing network
round trips for every record, instead, the function calls are all happening within the core database query. This strategy can
dramatically decrease the number of network roundtrips required by an application.
You should base most of your blocks on updateable views. Any time you have a table with a POST-QUERY trigger requiring
database access, it should always be replaced by an updateable view. Not only will network traffic decrease (thereby
improving performance) but you will also achieve better encapsulation of the code, which also improves the chances for
reuse. The same view built in one context can be reused in reports or other system forms. Because of the reuse possibility,
always include all columns from the core table in the updateable view.
The following is an example of an INSTEAD OF trigger (the suffix identifies it as occurring instead of an Insert, Delete, or
Update). For simplicity, this code does not insert or update the manager information in the EMP table, but logic for those
DML statements could easily be added.
CREATE OR REPLACE TRIGGER emp_v_iidu
INSTEAD OF INSERT OR UPDATE OR DELETE
ON EMP_V
FOR EACH ROW
DECLARE
v_message VARCHAR2(100);
BEGIN
IF INSERTING
THEN
v_message := 'Inserting an EMP record using EMP_V_BIUD';
-- The manager record must already exist or the INSERT will fail
INSERT INTO emp (
empno,
ename,
job,
mgr,
hiredate,
sal,
comm,
deptno)
VALUES (
:new.empno,
:new.ename,
:new.job,
:new.mgr,
:new.hiredate,
:new.sal,
:new.comm,
:new.deptno);
ELSIF UPDATING
THEN
v_message := 'Updating an EMP record using EMP_V_BIUD';
-- Assumes the manager name and job are not updateable
-- Also assumes the primary key value of EMP did not change
UPDATE emp
SET ename = :new.ename,
job = :new.job,
mgr = :new.mgr,
hiredate = :new.hiredate,
sal = :new.sal,
comm = :new.comm,
deptno = :new.deptno
WHERE empno = :old.empno;
ELSE -- DELETING
v_message := 'Deleting an EMP record using EMP_V_BIUD';
--
DELETE FROM emp
WHERE empno = :old.empno;
END IF;
EXCEPTION
WHEN OTHERS
THEN
raise_application_error(-20001, 'Error: ' || v_message ||
' - ' || SQLERRM);
END;
/
The trigger defines INSERT, UPDATE, and DELETE statements for each operation that you allow on the view. It uses the
:NEW and :OLD values available in row triggers to build DML statements for the EMP table.
Caution: INSTEAD OF trigger views should only be built by highly skilled developers. It can be very
difficult to debug a complex application if a block based on an INSTEAD OF trigger view contains an
error. Also, since you are not using an updateable view, the automatic check on the correct coding of the
view is not available so you may believe that the view is correct when, in fact, it is not.
An INSTEAD OF trigger view can be used any time there is a complex block based on multiple tables and you want update
capability but an updateable view is not sufficient. This is a very powerful technique that will not be required in most
applications. If you need many INSTEAD OF views to support your applications, you should probably examine your
database design and GUI standards carefully for potential problems.
Non-Updateable Views
It is common to need to view information from the database without the need for updates. You are essentially building an
“onscreen report.” As long as you do not need to update the information, basing your block on a view is the best strategy. The
view can be as complex as necessary including joins, embedded functions, and so forth. To create a screen report, your block
should, in fact, be based on a view. The block should not be based on a table with a POST-QUERY trigger calculating your
additional block items since this causes unnecessary network traffic. Having the structure of the block encapsulated in a view
also promotes reusability of the structure in other forms or printed reports.
An example of a common situation where you might base blocks on non-updateable views is a query on customer activity.
Assume you have three tables where customer activity is stored, structured as follows:
Contact Structure To record telephone calls and human interactions
Letter Structure To store details of all correspondence activity
Financial Structure To track monetary activity
You can create a view that is a union of all three structures sorted by date in order to display a chronological record of all
activity associated with a particular customer.
Another technique you can use in some circumstances is to base the source block on a view and base the DML target on one
of the tables in that view. This allows you to directly update records in that single table. To accomplish this, set the Query
Only property to “Yes” for all items in the block that are not associated with the table.
Trying to write a SQL query to support such a block would be a challenge to any developer. Once written, this query could
take a long time to execute. One solution to this problem is to make the block a non-base table block with a complex client-
side PL/SQL routine to populate the block one item at a time. A better approach is to make a PL/SQL procedure with a table
as an IN/OUT parameter. This method is both relatively easy to use and easy to optimize performance.
-- For SELECT
TYPE rc_empdept IS REF CURSOR
RETURN emp_t;
--
PROCEDURE slct(
p_empqry IN OUT rc_empdept);
--
PROCEDURE ins(
p_emprec IN OUT t_empdept);
--
PROCEDURE upd(
p_emprec IN OUT t_empdept);
--
PROCEDURE del(
p_emprec IN OUT t_empdept);
--
PROCEDURE lck(
p_emprec IN OUT t_empdept);
--
END;
This specification declares the types based on a record variable that represents the columns in the cursor, the ref cursor itself,
and a PL/SQL table of records. The REF CURSOR is used as the return value for the SLCT procedure that queries the table.
The PL/SQL table of records is used to pass values in and out of the DML and lock procedures.
Note: The DML and lock procedures must contain at least one table-of-records parameter because the
form communicates to the procedures using arrays (tables of records).
END IF;
--
v_message := 'Insert of EMP';
INSERT INTO emp(
empno,
job,
hiredate,
deptno)
VALUES (
p_emprec(v_ct).empno,
p_emprec(v_ct).job,
p_emprec(v_ct).hiredate,
p_emprec(v_ct).deptno);
-- add an exception handler here
END IF;
END LOOP;
EXCEPTION
WHEN OTHERS
THEN
raise_application_error(-20002, 'Error: Inserting record ' ||
'using EMPDEPT_MAINT.INS. Inform Technical Support. '|| v_message);
END;
Other DML statements would follow the same pattern of looping through the table of records and performing the DML
statement using the values in a row of the table of records. The following is the LCK procedure that locks all records in the
table of records.
PROCEDURE lck(
p_emprec IN OUT t_empdept)
IS
v_empno emp.empno%TYPE;
BEGIN
FOR v_ct IN 1 .. p_emprec.count
LOOP
SELECT empno
INTO v_empno
FROM emp
WHERE empno = p_emprec(v_ct).empno
FOR UPDATE;
END LOOP;
END;
Forms Work
Once you have written and installed the PL/SQL package to support the block, you are ready to create a block based on the
packaged procedures. In the Data Block Wizard, you select Stored Procedure in the Type page. On the Query page, enter the
name of the REF CURSOR procedure (such as EMPDEPT_MAINT.SLCT). The columns in that REF CURSOR will appear
after you click the Refresh button, as Figure 3 shows.
You set the Insert, Update, Delete, and Lock pages that follow the Query page in a similar way using the names of the
corresponding procedures in your package. The Data Block Wizard will assign the values for the block properties as shown in
Table 1.
Property Notes and “Values”
Query Data Source Type “Procedure”
Query Data Source Name The name of the SELECT procedure (prefaced by the package
name)
Query Data Source Columns The list of columns in the SELECT procedure’s REF CURSOR
Query Data Source Arguments The name of the ref cursor declared as the main parameter of
the SELECT procedure
DML Data Target Type “Procedure”
Insert Procedure Name The name of the INSERT procedure
Insert Procedure Result Set Columns The list of columns from the table of records used as a
parameter to the INSERT procedure
Insert Procedure Arguments The name of the table-of-records parameter used for the
INSERT procedure.
Update Procedure Name, The same type of contents as the corresponding properties for
Update Procedure Result Set Columns, INSERT
Update Procedure Arguments
Delete Procedure Name, The same type of contents as the corresponding properties for
Delete Procedure Result Set Columns, INSERT
Delete Procedure Arguments
Lock Procedure Name, The same type of contents as the corresponding properties for
Lock Procedure Result Set Columns, INSERT
Lock Procedure Arguments
Queries
The Query-By-Example feature allows the user to place the form into Enter Query mode, enter query conditions in the items,
and execute the query to retrieve rows from the database. When you base a block on a table, Forms creates a SELECT
statement from the items in the block and constructs a WHERE clause based on query conditions that the user enters.
When you base a block on a procedure, Forms Runtime will not create the normal SELECT statement so query conditions the
user enters in Enter Query mode will have no effect. However, you can write code to emulate some of the functionality of the
Query-By-Example feature. The code requires a change in the SLCT procedure and a change in the form.
If you want to use additional parameters for the DML and LOCK procedures, you can follow the same steps for those
procedures. When you are considering what query criteria to allow the user to use, be careful that the SQL statements that use
those criteria are as efficient as possible. Otherwise, you can experience a degradation in performance over what you would
see if the block was based only on a table.
You also need to add the parameter to the WHERE clause of the query in the SLCT procedure body as follows:
AND job LIKE p_job || '%';
This code will retrieve in the tables if the parameter value is in the first part of the job title value. You can construct other
scenarios to fit the queries that you decide to support. You can also pass additional parameters and work them into the query
in the same way.
Note: If you change the procedure while the form is open, you may have to reconnect to the database
or close and reopen Form Builder to be able to see the changes.
Caution: If you receive an ORA-03114 error when accessing a procedure in an Oracle8i NT server from
an Oracle Developer release 6 form, you should check with Technical Support for the availability of a
patch to Oracle8 bug number 939287.
ambitious, this capability could be used to build a rudimentary ad hoc query tool. With the exception of query-only blocks
based on recursive tables, FROM clause query blocks are rarely used.
As mentioned earlier, FROM clause queries are somewhat more flexible. However, because they are implemented as
“SELECT FROM (SELECT …)” queries, they may take longer to execute. FROM clause queries should only be used when
the application requires this level of flexibility and cannot be supported by basing the block on a table, view, or procedure
that returns a REF CURSOR.
3. Change the block’s Query Data Source Name property to the SELECT statement. You can optionally fill in the Query
Data Source Columns with the names of the columns in the SELECT statement.
4. Create items with names based on the columns in the SELECT statement.
5. Flag an appropriate item as the primary key by changing the item’s Primary Key property to “Yes”.
6. Set the item properties for Prompt, Data Type, Maximum Length, Canvas, Required, and other properties to appropriate
values.
In the case of a FROM clause query (sometimes misleadingly called a Sub-Query in the help system), the data source is a
SELECT statement and issuing DML to a SELECT statement does not make sense. Therefore, you get an error message if
you use a FROM clause query and do fill in the DML Target Name property. If you want to use DML on a block with a
FROM clause query, you can specify a table or view name in the DML Target Name and turn off the DML for columns that
the SELECT statement constructs. In the example, you would make the following changes in the form:
Set the DML Data Target Name as “EMP”. The DML Target Type is left as the default “Table”.
Set the Query Only property of the HIRE_TYPE item to “Yes” to indicate that this item participates only in the
queries but not in the DML.
Programmatic Control
You can use the SET_BLOCK_PROPERTY built-in to change the DML target and the query source for the block
programmatically as follows:
SET_BLOCK_PROPERTY('block_name', QUERY_DATA_SOURCE_NAME,
'name_of_source');
In this example, block_name is the name of your block and name_of_source is the value (table or view name or SELECT
statement) for the Query Data Source Name property. This is documented to work only if the value of the Data Query Source
Type property is not “Transactional Triggers”.
You can change the DML target using the same built-in with the same format but with the DML_TARGET_NAME property.
This is documented to work only if the DML Data Target Type is set to “Table”. Be sure to commit and clear the block before
attempting to change any of these properties programmatically.
Caution: If you are typing a FROM clause query in the Property Palette, this can be done normally
(SELECT… FROM ….WHERE…). However, if you are populating the property using a SET_BLOCK
_PROPERTY built-in at runtime, you must pass the query enclosed in parentheses. When you are
typing the property in using the Property Palette, Forms adds the parentheses automatically. For
example, if your query was “SELECT empno, ename FROM emp” you would pass the string “(SELECT
empno, ename FROM emp)” to the SET_BLOCK_PROPERTY built-in.