I want to make list which will display values, like text item but in list.
My code:
DECLARE
rg_dept RecordGroup;
rg_dname VARCHAR2(4) := 'Name';
dlist_ID Item := Find_Item('PROJESCT.LIST_ID');
nDummy NUMBER;
BEGIN
rg_dept := Find_Group(rg_dname);
-- Delete any existing Group first
IF NOT Id_Null(rg_dept) THEN
Delete_Group(rg_dept);
END IF;
-- Now create a Record Group using a SQL query
-- Your Query must have a Label and a Value (two Columns)
-- and the data types must match your item type
rg_dept := Create_Group_From_Query(rg_dname,'SELECT department_name, to_char(department_id) FROM departments');
--Clear the existing List
Clear_List(dlist_ID);
-- Populate the Record Group
nDummy := Populate_Group(rg_dept);
-- Populate the List Item
Populate_List(dlist_ID ,rg_dept);
END;
If I make list item and add this code, form will display nothing; if I remove list, all is ok.
P.S. Trigger: when-list-item-change
I should recommend you to populate your lists from e.g. When-new-Form-Instance. As usual you will get a good idea what you need to do from Forms help (menu 'Help/Online Help'). This is a procedure I have made to simple populate list only by specifying the name of the list item and the select statement to populate the lsit item with.
PROCEDURE populate_list_item (
p_item_name VARCHAR2
, p_select VARCHAR2
) IS
l_rg_id RECORDGROUP;
l_list_id ITEM;
l_err_num PLS_INTEGER;
FUNCTION create_temp_group (
p_select VARCHAR2
) RETURN RECORDGROUP IS
l_rg_id RECORDGROUP;
l_group_name VARCHAR2(30) := 'TMP$RG';
BEGIN
l_rg_id := FIND_GROUP(l_group_name);
--Make sure that record group don't alreay exist
IF NOT ID_NULL(l_rg_id) THEN
DELETE_GROUP(l_rg_id);
END IF;
--Populate the temporary record group
l_rg_id := CREATE_GROUP_FROM_QUERY(l_group_name, p_select);
RETURN l_rg_id;
END create_temp_group;
BEGIN
l_rg_id := create_temp_group(p_select);
l_err_num := Populate_Group(l_rg_id);
--Allow for no data found in the selection query
IF l_err_num NOT IN (0, 1403) THEN
RAISE Form_Trigger_Failure;
END IF;
l_list_id := Find_Item(p_item_name);
IF ID_NULL(l_list_id) THEN
RAISE Form_Trigger_Failure;
END IF;
Populate_List(l_list_id, l_rg_id);
Delete_Group(l_rg_id);
END populate_list_item;
The When-New-Form-Instance is a form level trigger and should be under the form (instead of block or item):
The best thing to do is to build all your record groups at design time. It does not slow down the performance unless you have some huge, already slow form. Then populate your list items at run time. Always Copy/paste the code from Forms help.
--Oracle Forms Example: Create a record group from a query, and populate it.
DECLARE
rg_name VARCHAR2(40) := 'Salary_Range';
rg_id RecordGroup;
errcode NUMBER;
BEGIN
/*
** Make sure group doesn't already exist
*/
rg_id := Find_Group( rg_name );
/*
** If it does not exist, create it and add the two
** necessary columns to it.
*/
IF Id_Null(rg_id) THEN
rg_id := Create_Group_From_Query( rg_name,
'SELECT SAL-MOD(SAL,1000) BASE_SAL_RANGE,'
||'COUNT(EMPNO) EMPS_IN_RANGE '
||'FROM EMP '
||'GROUP BY SAL-MOD(SAL,1000) '
||'ORDER BY 1');
END IF;
/*
** Populate the record group
*/
errcode := Populate_Group( rg_id );
END;
Related
I'm new to oracle forms and i have a problem.
I have two multiple records blocks with same fields in the form.
I query data for one block, it gets populated .
Is where a way to copy all rows from populated block to 'copy' block?
Yes, there is a way. One is to manually type those values over there, but that's probably not what you want.
Another is to create a button (let's call it BTN_COPY) and create WHEN-BUTTON-PRESSED trigger on it. It'll look like code that follows (based on items that belong to Scott's EMP table); note that I can't test it, but - I hope it'll be OK.
declare
-- local variables; should contain all items you'd want to copy
l_empno emp.empno%type;
l_ename emp.ename%type;
l_job emp.job%type;
-- l_currec will contain current row number in the first block
l_currec number := 0;
-- l_exit will be used if we're at the end of the first block
l_exit varchar2(1) := 'N';
begin
loop
-- go to the source (first block) and the [last row you were in + 1]
go_block('first');
l_currec := l_currec + 1;
go_record(l_currec);
-- check whether this is the last row of the first block; if so, exit the loop
if :system.last_record = 'TRUE' then
l_exit := 'Y';
end if;
-- save current row's items
l_empno := :first.empno;
l_ename := :first.ename;
l_job := :first.job
-- go to the second block's bottom and create a new record
go_block('second');
last_record;
create_record;
-- put stored values into the second block's items
:second.empno := l_empno;
:second.ename := l_ename;
:second.job := l_job;
-- exit the loop if this was the last record to be copied
exit when l_exit = 'Y';
end loop;
end;
I am working with oracle forms 6i.
Simply I am using database block with these items :
Employees: employee_id , job_id , department_id , manager_id
note: employees is the data-block name .
For example: when end user click enter-query button and write 50 in department_id item and then click execute-query button; data block will return all employees who are in department 50.
My question is : How can I get the WHERE CLAUSE that oracle forms generate when returned desired data?...
I used this code in pre-query trigger
:parameters.whr:=get_block_property('employees',default_where);
But it returned no results
You're close, but not close enough. It is the GET_BLOCK_PROPERTY you need, but use its LAST_QUERY parameter. It will return SQL statement of the last query in the specified block.
Alternatively, use system variable :SYSTEM.LAST_QUERY (returns the same result).
Here's an example( the following code might be put in KEY-EXEQRY trigger of employees block ):
declare
l_lastq varchar2(4000);
l_where_position number;
l_where_clause varchar2(4000);
begin
execute_query;
l_lastq := :system.last_query;
l_where_position := instr(lower(l_lastq), 'where');
if l_where_position > 0 then
l_where_clause := substr(l_lastq, l_where_position, length(l_lastq));
message('WHERE clause: ' || l_where_clause);
end if;
end;
Our product has several components that can be installed separately and supports oracle. But we do not grant Create type privilege. So during installation of one of the components, I need to ask customer to install the component, add Create type privilege and then run my component.
In the SQL file that will create functions I was planning to give following code:
CREATE OR REPLACE FUNCTION get_some_data (input INT)
RETURN my_table
AS
my_table_var my_table := my_table ();
ret_code INT := 0;
BEGIN
ret_code := create_my_type_and_table ();
IF 1 = ret_code
THEN
NULL; -- add some data to my_table_var here
END IF;
RETURN my_table_var;
END get_some_data;
/
The function create_my_type_and_table would use execute immediate to create record and table type.
Obviously, the problem is since function get_some_data says it will return my_table, compilation fails.
I wanted to know:
is there a way out?
the reason why I want to create and return table is because I need to return multiple fields. All of them are int. Is there a way I can return multi dimensional array, perhaps system collection? I tried sys.odcinumberlist but I did not find a way by which I can return 4 columned sys.odcinumberlist.
If you want some anonymous generic stuff, you could use TABLE OF [TYPE].
Here an example how to build a table of number which is used by another type "table of table of number".
Like every type, you can create them within your schema to use it between different plsql-blocks or return-value in functions.
declare
TYPE tableOfNumber is Table of Number; -- Define a row of numbers
TYPE tableOfTableOfNumer is Table of tableOfNumber; -- define a table of rows
tableMaster tableOfTableOfNumer := tableOfTableOfNumer(); -- init tables
tableChild1 tableOfNumber := tableOfNumber();
tableChild2 tableOfNumber := tableOfNumber();
begin
tableChild1.Extend; -- add a new number-field to our table
tableChild1(1) := 0; -- set the value to the new field
tableChild1.Extend;
tableChild1(2) := 1;
tableChild2.Extend;
tableChild2(1) := 2;
tableChild2.Extend;
tableChild2(2) := 3;
tableMaster.Extend; -- add a new 'row' to out table
tableMaster(1) := tableChild1; -- fill the new 'row' with the fields
tableMaster.Extend;
tableMaster(2) := tableChild2;
-- loop through our 'table'
for r in 1 .. tableMaster.Count
LOOP
for c in 1 .. tableMaster(r).Count
LOOP
dbms_output.put_line(tableMaster(r)(c));
END LOOP;
END LOOP;
end;
If you want to create a function you have to:
1. Declare types in you schema
(an grant rights to the users)
CREATE TYPE tableOfNumber AS TABLE OF NUMBER;
CREATE TYPE tableOftableOfNumber AS TABLE OF tableOfNumber;
2. Create you function:
(here with same code like the plsql-block)
CREATE OR REPLACE FUNCTION DINTER.MyFunction
RETURN tableOftableOfNumber
Is
tableMaster tableOftableOfNumber := tableOftableOfNumber(); -- init tables
tableChild1 tableOfNumber := tableOfNumber();
tableChild2 tableOfNumber := tableOfNumber();
begin
tableChild1.Extend; -- add a new number-field to our table
tableChild1(1) := 0; -- set the value to the new field
tableChild1.Extend;
tableChild1(2) := 1;
tableChild2.Extend;
tableChild2(1) := 2;
tableChild2.Extend;
tableChild2(2) := 3;
tableMaster.Extend; -- add a new 'row' to out table
tableMaster(1) := tableChild1; -- fill the new 'row' with the fields
tableMaster.Extend;
tableMaster(2) := tableChild2;
RETURN tableMaster;
end MyFunction;
/
3. Call the function:
declare
tableMaster tableOfTableOfNumber;
begin
tableMaster := myfunction();
-- loop through our 'table'
for r in 1 .. tableMaster.Count
LOOP
for c in 1 .. tableMaster(r).Count
LOOP
dbms_output.put_line(tableMaster(r)(c));
END LOOP;
END LOOP;
end;
Really need help regarding Ref Cursor. I have a Stored Procedure GET_PERSONROLES that have parameter type ref cursor. I just wanted to pupulate this ref cursor manually like inserting a row to the refcursor.
Can I insert a row into a refcursor though a loop?
Thank you in advance.
The procedure depends on this publicly declared type:
create or replace package types
as
type cursorTypePersonRole is ref cursor;
end;
Here is my pseudo-codeL
create or replace PROCEDURE GET_PERSONROLES
(
P_CURSOR IN OUT types.cursorTypePersonRole
) AS
REFCUR SYS_REFCURSOR;
TYPE REFTABLETYPE IS RECORD (
IS_MANAGER_LEVEL1 VARCHAR2(1),
IS_MANAGER_LEVEL2 VARCHAR2(1)
);
TYPE TABLETYPE IS TABLE OF REFTABLETYPE;
PERSONROLES_TABLETYPE TABLETYPE;
BEGIN
--calls another stored proc to populate REFCUR with data without problem
MY_STOREDPROC('12345', REFCUR);
LOOP
FETCH REFCUR BULK COLLECT INTO PERSONROLES_TABLETYPE;
EXIT WHEN PERSONROLES_TABLETYPE.COUNT = 0;
FOR indx IN 1 .. PERSONROLES_TABLETYPE.COUNT
LOOP
-- I'm able to query perfectly the values of IS_MANAGER_LEVEL1 and IS_MANAGER_LEVEL 2
-- I'm aware that the below codes are wrong
-- However this means I wanted to insert these values to a row of the cursor if possible
-- Do some logic to know what data will be assigned in the row.
if PERSONROLES_TABLETYPE(indx).IS_MANAGER_LEVEL1 = 'Y' then
P_CURSOR := <DO SOME LOGIC AND ASSIGN THE VALUE TO THE ROW>
end if;
if PERSONROLES_TABLETYPE(indx).IS_MANAGER_LEVEL2 = 'Y' then
P_CURSOR := <DO SOME LOGIC AND ASSIGN THE VALUE TO THE ROW>
end if;
END LOOP;
END LOOP;
CLOSE REFCUR;
END GET_PERSONROLES;
A ref cursor is not a variable: it is a pointer to a result set which is consumed by the act of reading it. The result set itself is immutable.
Immutability makes sense, because it reflects Oracle's emphasis on read consistency.
The simplest way to produce the output you appear to want is to create a SQL Type
open P_CURSOR for
select IS_MANAGER_LEVEL1,
IS_MANAGER_LEVEL2
from table ( PERSONROLES_TABLETYPE );
This will work in 12c; in earlier versions to use the table() call like this you may need to declare REFTABLETYPE and TABLETYPE as SQL types( rather than in PL/SQL).
"Ok edited it now"
Alas your requirements are still not clear. You haven't given us the structure of the output ref cursor or shown what other processing you want to undertake.
However, given the title of your question, let's have a guess. So:
create or replace PROCEDURE GET_PERSONROLES ( P_CURSOR IN OUT types.cursorTypePersonRole) AS
REFCUR SYS_REFCURSOR;
TYPE REFTABLETYPE IS RECORD (IS_MANAGER_LEVEL1 VARCHAR2(1),
IS_MANAGER_LEVEL2 VARCHAR2(1));
TYPE TABLETYPE IS TABLE OF REFTABLETYPE;
PERSONROLES_TABLETYPE TABLETYPE;
personrole_rec PersonRole%rowtype;
type personrole_nt is table of PersonRole%rowtype;
personroles_recs personrole_nt := new personrole_nt() ;
BEGIN
MY_STOREDPROC('12345', REFCUR);
FETCH REFCUR BULK COLLECT INTO PERSONROLES_TABLETYPE;
FOR indx IN 1 .. PERSONROLES_TABLETYPE.COUNT LOOP
/* in the absence of requirements I'm just making some stuff up */
if PERSONROLES_TABLETYPE(indx).IS_MANAGER_LEVEL1 = 'Y' then
personrole_rec.whatever1 := 'something';
else
personrole_recc.whatever1 := null;
end if;
if PERSONROLES_TABLETYPE(indx).IS_MANAGER_LEVEL2 = 'Y' then
personrole_rec.whatever2 := 'something else';
else
personrole_recc.whatever2 := null;
end if;
if personrole_rec.whatever1 is not null
or personrole_rec.whatever2 is mot null then
personroles_recs.exend();
personroles_recs(personroles_recs.count()) := personroles_rec;
end if;
END LOOP;
CLOSE REFCUR;
open p_cursor for
select * from table ( personroles_recs );
END GET_PERSONROLES;
This code uses a second collection to store the desired output. Like your code it reads the populated collection and evaluates the attributes of each row. If a value which means the criteria it sets an attribute in a rowtype variable. If one or both attributes are set it populates a new row in a second collection. At the end of the procedure it opens the ref cursor using a table() function call on the second collection.
Note that you do not need the nested loop: you're not using the LIMIT clause so your coder reads the entire cursor into the collection in one swoop.
The implemented rules may not be exactly what you want (because you haven't explained exactly what you want) but this should give you the general idea.
Note that, depending on exactly what processing is masked by <DO SOME LOGIC AND ASSIGN THE VALUE TO THE ROW>, the simpler approach could still be feasible:
open P_CURSOR for
select case when IS_MANAGER_LEVEL1 = 'Y' then 'YES' else 'NO' end,
case when IS_MANAGER_LEVEL2 = 'Y' then 'YES' else 'NO' end
from table ( PERSONROLES_TABLETYPE );
I have a statement level trigger that fires whenever INSERT UPDATE or DELETE operations are performed on a table (called customers). I want to display a message (to DBMS_OUTPUT) containing the number of rows that were inserted/updated/deleted.
I just want one message for each triggering statement, eg
'4 rows were inserted into customers table'.
How can I access the number of rows that are affected by the triggering statement from INSIDE the trigger declaration, ie XXX in the code below:
CREATE OR REPLACE TRIGGER customer_changes_trigger_2
AFTER INSERT OR UPDATE OR DELETE ON customers
DECLARE
v_operation VARCHAR(10);
v_number_rows NUMBER;
BEGIN
v_number := XXX;
IF INSERTING THEN
v_operation := 'inserted';
END IF;
IF UPDATING THEN
v_operation := 'updated';
END IF;
IF DELETING THEN
v_operation := 'deleted';
END IF;
DBMS_OUTPUT.PUT_LINE
(v_number_rows|| ' rows were ' || v_operation || ' from customers.');
END;
Can't find anything in the documentation, any help appreciated!
One way is to use a global variable to track the number of rows as there is no other way to get the row count from a statement level trigger. You would then need three triggers... one statement level to initialise the variable before the statement is run, one row level to add one to the variable for each row, one statement level to use the row count however you wish. First, set up the variable and a few procedures to help it:
create or replace package PKG_ROWCOUNT is
NUMROWS number;
procedure INIT_ROWCOUNT;
procedure ADD_ONE;
function GET_ROWCOUNT
return number;
end PKG_ROWCOUNT;
/
create or replace package body PKG_ROWCOUNT as
procedure INIT_ROWCOUNT is
begin
NUMROWS := 0;
end;
procedure ADD_ONE is
begin
NUMROWS := Nvl(NUMROWS, 0) + 1;
end;
function GET_ROWCOUNT
return number is
begin
return NUMROWS;
end;
end PKG_ROWCOUNT;
/
The first trigger to initialise the variable:
create or replace trigger CUSTOMER_CHANGES_TRIGGER_1
before insert or update or delete
on CUSTOMERS
begin
PKG_ROWCOUNT.INIT_ROWCOUNT;
end;
The second to update per row:
create or replace trigger CUSTOMER_CHANGES_TRIGGER_2
after insert or update or delete
on CUSTOMERS
for each row
begin
PKG_ROWCOUNT.ADD_ONE;
end;
/
The third to display the total:
create or replace trigger CUSTOMER_CHANGES_TRIGGER_3
after insert or update or delete
on CUSTOMERS
begin
Dbms_output.
PUT_LINE(PKG_ROWCOUNT.GET_ROWCOUNT || ' rows were affected.');
end;
I'm not 100$ sure if it's available inside AFTER trigger body, but you can try examining sql%rowcount