Oracle DBMS_SESSION SET_CONTEXT does not store a value - oracle

I am trying to produced a system in Oracle where a context stores a value; if a table is updated (update/insert/delete) in any session, the value should be increased. The problem I am having is that even though I'm certain I've set it up correctly, it doesn't seem to work - the context does not seem to actually store the value. I'm using Oracle 11.2.0.1.0.
For the minimum possible example:
I have a context (the ACCESSED GLOBALLY clause should make it so the values are accessible across all Oracle sessions, which is what I want):
CREATE OR REPLACE CONTEXT MM_CONTEXT USING PCKG_TESTGLOBALS ACCESSED GLOBALLY;
I have a debugging table:
CREATE TABLE DATALOG (
DATALOG_SEQ NUMBER,
AT_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
MESSAGE VARCHAR2(4000)
);
I have a sequence to support the DATALOG table:
CREATE SEQUENCE SQ_DATALOG;
Now the PCKG_TESTGLOBALS package is required which has access to the MM_CONTEXT context and can update elements of it:
CREATE OR REPLACE PACKAGE PCKG_TESTGLOBALS IS
PROCEDURE Log(FunctionName IN VARCHAR2, Msg IN VARCHAR2);
PROCEDURE SetParameter(p_name IN VARCHAR2, p_value IN VARCHAR2);
FUNCTION GetTABLEID RETURN NUMBER;
END PCKG_TESTGLOBALS;
/
CREATE OR REPLACE PACKAGE BODY PCKG_TESTGLOBALS IS
CONTEXT_NAME CONSTANT VARCHAR2(100) := 'MM_CONTEXT';
PROCEDURE Log(FunctionName IN VARCHAR2, Msg IN VARCHAR2) IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO DATALOG(DATALOG_SEQ, MESSAGE) VALUES (SQ_DATALOG.NEXTVAL, FunctionName || ':' || Msg);
COMMIT;
END;
PROCEDURE SetParameter(p_name IN VARCHAR2, p_value IN VARCHAR2) IS
ActualValue VARCHAR2(10000);
BEGIN
Log('SetParameter', 'ENTERED');
Log('SetParameter', 'SETTING "' || p_name || '" TO "' || p_value || '"');
DBMS_SESSION.SET_CONTEXT(CONTEXT_NAME, p_name, p_value);
ActualValue := SYS_CONTEXT(CONTEXT_NAME, p_name);
Log('SetParameter', 'READ "' || p_name || '" AS "' || ActualValue || '"');
Log('SetParameter', 'EXITED');
END;
PROCEDURE Initialise IS
iTmp NUMBER;
BEGIN
Log('Initialise', 'ENTERED');
IF SYS_CONTEXT(CONTEXT_NAME, 'LOWNID') IS NULL THEN
iTmp := DBMS_RANDOM.RANDOM;
Log('Initialise', '"LOWNID" has no value, writing "' || iTmp || '"');
PCKG_TESTGLOBALS.SetParameter('LOWNID', iTmp);
END IF;
Log('Initialise', 'EXITED');
END;
FUNCTION GetTABLEID RETURN NUMBER IS
ReadValue VARCHAR2(32767);
BEGIN
Log('GetTABLEID', 'ENTERED');
ReadValue := SYS_CONTEXT(CONTEXT_NAME, 'LOWNID');
Log('GetTABLEID', 'READ VALUE OF "LOWNID" AS "' || ReadValue || '"');
Log('GetTABLEID', 'EXITED');
RETURN TO_NUMBER(ReadValue);
END;
BEGIN
Initialise;
END PCKG_TESTGLOBALS;
/
So to explain the functions in PCKG_TESTGLOBALS:
Log - Logs a message for debugging reasons.
SetParameter - Takes a name/value pair and stores this using DBMS_SESSION.SET_CONTEXT in the MM_CONTEXT
Initialise - For a session variable LOWNID it checks if the variable is null, and if it is, sets it to a random value, using SetParameter. Initialise is called when the package is first used in a session.
GetTABLEID - this returns the value stored in the session variable LOWNID.
Finally there is a trigger called TR_ONDML_TL_LOWN which is on the table LOWN - the structure of which doesn't matter here, any table would do - and fires after any DML, INSERT or UPDATE or DELETE.
CREATE OR REPLACE TRIGGER TR_ONDML_TL_LOWN
AFTER INSERT OR UPDATE OR DELETE ON LOWN
DECLARE
iTmp NUMBER;
BEGIN
PCKG_TESTGLOBALS.Log('TR_ONDML_TL_LOWN', 'ENTERED');
iTmp := PCKG_TESTGLOBALS.GetTABLEID;
PCKG_TESTGLOBALS.Log('TR_ONDML_TL_LOWN', 'Read Value "' || iTmp || '"');
iTmp := NVL(iTmp, 1) + 1;
PCKG_TESTGLOBALS.Log('TR_ONDML_TL_LOWN', 'Updated Value "' || iTmp || '"');
PCKG_TESTGLOBALS.SetParameter('LOWNID', iTmp);
PCKG_TESTGLOBALS.Log('TR_ONDML_TL_LOWN', 'EXITED');
END TR_ONDML_TL_LOWN;
/
The purpose of this trigger is thus: Whenever the table LOWN is updated, the value of the session variable LOWNID is read back, and has 1 added to it, and is written back in.
If in a new session I do a few consecutive UPDATEs on the LOWN table, I get these results in my debugging table (SELECT MESSAGE FROM DATALOG ORDER BY DATALOG_SEQ)
Initialise:ENTERED
Initialise:"LOWNID" has no value, writing "805223597"
SetParameter:ENTERED
SetParameter:SETTING "LOWNID" TO "805223597"
SetParameter:READ "LOWNID" AS ""
SetParameter:EXITED
Initialise:EXITED
TR_ONDML_TL_LOWN:ENTERED
GetTABLEID:ENTERED
GetTABLEID:READ VALUE OF "LOWNID" AS ""
GetTABLEID:EXITED
TR_ONDML_TL_LOWN:Read Value ""
TR_ONDML_TL_LOWN:Updated Value "2"
SetParameter:ENTERED
SetParameter:SETTING "LOWNID" TO "2"
SetParameter:READ "LOWNID" AS ""
SetParameter:EXITED
TR_ONDML_TL_LOWN:EXITED
TR_ONDML_TL_LOWN:ENTERED
GetTABLEID:ENTERED
GetTABLEID:READ VALUE OF "LOWNID" AS ""
GetTABLEID:EXITED
TR_ONDML_TL_LOWN:Read Value ""
TR_ONDML_TL_LOWN:Updated Value "2"
SetParameter:ENTERED
SetParameter:SETTING "LOWNID" TO "2"
SetParameter:READ "LOWNID" AS ""
SetParameter:EXITED
TR_ONDML_TL_LOWN:EXITED
You can see from the example that it passes in the value correctly to DBMS_SESSION.SET_CONTEXT, but it doesn't seem to store the value at all. What am I doing wrong? Thanks.

For a session to see a value of a Globally Accessible context, its client_identifier (as per SYS_CONTEXT('userenv','client_identifier')) must match the client_id parameter that was used in the call to DBMS_SESSION.set_context.
If your set_context call doesn't set client_id it defaults to NULL; in this case, the session will only see the new value if its client_identifier is also NULL.
If the session takes any particular value for client_identifier, you must use this same value in the call to set_context.
In your case, you want a global variable that is accessible by all sessions; if your sessions are getting random values for client_identifier, you may need to set it to NULL before running your code, then (perhaps) restore its value before returning control to the caller.

Related

PL-SQL loop throug values to get dynamic query and store results

I've got code as follows:
CREATE OR REPLACE TYPE cat_array_t is varray(2) of varchar(10);
-- cat_array cat_array_t:=cat_array_t('retired','worker');
/
CREATE OR REPLACE FUNCTION Get_Data_Faster(in_cat IN cat_array, in_kw_crt IN kw_crt_array) RETURN get_data_faster_data PIPELINED AS
TYPE r_cursor IS REF CURSOR;
query_results r_cursor;
results_out get_data_faster_row := get_data_faster_row(NULL, NULL);
query_str VARCHAR2(4000);
cat_value VARCHAR2(10);
kw_crt_value VARCHAR2(10);
BEGIN
FOR i IN 1..cat_array.COUNT
LOOP
cat_value := cat_array(i);
kw_crt_value := kw_crt_array(i);
-- query_str := 'SELECT distinct '||seq_number||' as seq, value, item
-- FROM my_table ai';
--
-- query_str := query_str || '
-- WHERE ai.value = '''||the_value||''' AND ai.item = '''||the_item||'''
-- AND ai.param = ''BOOK''
-- AND ai.prod in (' || list || ');
query_str := 'select owner_id,property_id ' ||
'from owners ' ||
'where substr(PROPERTY_ID,1,4) =' || chr(39) || kw_crt_value || chr(39) ||
' and Owner_category = ' || chr(39) || cat_value || chr(39);
OPEN query_results FOR query_str;
LOOP
FETCH query_results INTO
results_out.owner_id,
results_out.property_id;
EXIT WHEN query_results%NOTFOUND;
PIPE ROW(results_out);
END LOOP;
CLOSE query_results;
END LOOP;
END;
/
The problem is when I run this I get error.
In log there is "Error: PLS-00201: identifier "CAT_ARRAY" should be declared". Don't how to improve this code. How to pass this line with elements of varray to code
-- cat_array cat_array_t:=cat_array_t('retired','worker');
In addition to the other answers, you're referencing your array with (almost) the name of the type, not the name of the array, e.g.:
FOR i IN 1..cat_array.COUNT
should be
FOR i IN 1..in_cat.COUNT
since you have defined the name of the parameter as in_cat here:
CREATE OR REPLACE FUNCTION Get_Data_Faster(in_cat IN cat_array_t, ....
^^^^^^
You must replace all instances of the variable name cat_array with the correct name in_cat.
You have
CREATE OR REPLACE TYPE cat_array_t ...
but then your function has
CREATE OR REPLACE FUNCTION Get_Data_Faster(in_cat IN cat_array, ...
The type name doesn't match; it should be
CREATE OR REPLACE FUNCTION Get_Data_Faster(in_cat IN cat_array_t, ...
and you don't need/want the separate local PL/SQL type declaration, or the local variable; and you need to refer to the passed-in parameter name (as #Boneist beat me too):
FOR i IN 1..in_cat.COUNT
You may have done the same thing with in_kw_crt IN kw_crt_array.
Of course it returns an error.
CREATE OR REPLACE FUNCTION Get_Data_Faster (in_cat IN cat_array)
RETURN NUMBER ^^^^^^^^^^ type is used here
IS
TYPE cat_array_t IS VARRAY (2) OF VARCHAR (10); --> type is declared here
cat_array cat_array_t:=cat_array_t('emeryt','pracownik');
You're first referencing a type which is declared within the function. You can't do that; create type at SQL level, not within the function.
Your type cat_array and cat_array_t must be SQL type defined as an individual as follows:
CREATE [OR REPLACE ] TYPE type_name AS | IS
VARRAY(max_elements) OF element_type [NOT NULL];
Now, this type can be used globally in your PL/SQL code.

how can we return records from pl/sql stored procedure without taking out parameter

My Question is "How can we return multiple records from pl/sql stored procedure without taking OUT parameter".I got this doubt because if we are using cursors or refcursor in out parameter it may degrade performance.So what is the solution??
As OldProgrammer wrote, i think the performance of a cursor wouldn't be you problem. But here a Solution anyway:
You can return custom types like Table of number. If it's only a list of numbers you could return a table of numbers. If you Want to return rows from a table you could return table of 'tablename'%ROWTYPE. But i guess you want to create some custom types.
CREATE OR REPLACE TYPE PUWB_INT.MyOrderType AS OBJECT
(
OrderId NUMBER,
OrderName VARCHAR2 (255)
)
/
CREATE OR REPLACE TYPE PUWB_INT.MyOrderListType AS TABLE OF MYORDERtype
/
Now we can use them similar to a return myNumberVariable;
Let's build a function (procedures don't have return values):
CREATE OR REPLACE FUNCTION PUWB_INT.MyFunction (SomeInput VARCHAR2)
RETURN MyOrderListType
IS
myOrderList MyOrderListType := MyOrderListType ();
BEGIN
FOR o IN (SELECT 1 AS Id, 'One' AS Name FROM DUAL
UNION ALL
SELECT 2 AS Id, 'Two' AS Name FROM DUAL)
LOOP
myOrderList.EXTEND ();
myOrderList (myOrderList.COUNT) := MyOrderType (o.Id, o.Name || '(' || SomeInput || ')');
END LOOP;
RETURN myOrderList;
END MyFunction;
/
Now we can call the function and get a table of our custom-type:
DECLARE
myOrderList MyOrderListType;
myOrder MyOrderType;
BEGIN
myOrderList := MyFunction ('test');
FOR o IN myOrderList.FIRST .. myOrderList.LAST
LOOP
myOrder := myOrderList (o);
DBMS_OUTPUT.put_line ('Id: ' || myOrder.OrderId || ', Name: ' || myOrder.OrderName);
END LOOP;
END;
Be aware, that the calling schema, has to know the type.

How to ignore null parameter in a Stored Procedure Oracle

I have created a stored procedure where the user can inset 1 or multiple values in the parameter
create or replace procedure MyProcerdure
(
title Film.Title%Type,
country Film.country%Type,
language Film.language%Type,
category Film.category%Type,
refCursor OUT SYS_REFCURSOR )
AS
begin
OPEN refCursor FOR
select Film.Title as FilmTitle,
Film.language as language
Film.category
FROM Film
Where Film.language=language
AND Film.category=category
AND Film.Country=country
//etc...
But I want to allow the fact that the user doesn't have to fill them all and pass them in parameter , which means if the user only enter the language without anything else , return the proper language , and let's say he entered country and language so the result should get WHERE language and country is equal to what he inserted
Is it possible to make such a mechanism in a stored procedure using oracle ?
Thank you
You can simply add some logic in your query to handle the fact that parameters can be null:
CREATE OR REPLACE PROCEDURE MyProcerdure(
p_title Film.Title%TYPE,
p_country Film.country%TYPE,
p_language Film.language%TYPE,
p_category Film.category%TYPE,
po_refCursor OUT SYS_REFCURSOR
) AS
BEGIN
OPEN po_refCursor FOR
SELECT Film.Title AS FilmTitle, Film.language AS language, Film.category
FROM Film
Where ( p_title is null or Film.title = p_title )
AND ( p_country is null or Film.country = p_country )
AND ( p_language is null or Film.language = p_language )
AND ( p_category is null or Film.category = p_category );
END;
The best way to accomplish this is to use dynamic SQL. You can conditionally concatenate the correct filters. You will also want to concatenate an alternate version for when no value was provided.
For example, the following allows you to either filter, or put in a statement that the compiler will ignore but still has the correct number of bind variables in the dynamic SQL.
CREATE OR REPLACE PROCEDURE MyProcerdure
(
title file.title%TYPE,
country file.country%TYPE,
language file.language%TYPE,
category file.category%TYPE,
refCursor OUT SYS_REFCURSOR
) IS
l_stmt VARCHAR2(4000);
BEGIN
l_stmt := 'SELECT f.title AS filetitle,'||
' f.language AS language,'||
' f.category'||
' FROM file f'||
' WHERE 1 = 1';
IF title IS NOT NULL THEN
l_stmt := l_stmt || ' AND f.title = :title';
ELSE
l_stmt := l_stmt || ' AND (1=1 OR :title IS NULL)';
END IF;
-- The others would be done similarly
OPEN refCursor FOR l_stmt USING title, -- The others would go the same order as above
END;
/

Use execute immediate in procedure for DML and passing character value in parameter

I have a delete procedure which is taking table name and some values to delete record from that table, hence I have created a procedure with execute immediate which is forming the delete query by taking the parameter and delete.
But when ever I am passing the char value in the parameter it is getting error :
invalid identifier
as query formed with out single quote for the character value. Please let me know how can I pass char value in the procedure to form a string correctly.
Below is the procedure:
CREATE OR replace PROCEDURE Prd_delete(p_tbl_name IN VARCHAR2,
p_sys VARCHAR2,
p_b_id VARCHAR2,
p_c_date NUMBER)
IS
dlt_query VARCHAR2(200);
BEGIN
dlt_query := 'delete from '
||p_tbl_name
||' where system='
||p_sys
|| ' And batch_id='
||p_b_id
|| ' And cobdate='
||p_c_date;
--dbms_output.put_line(dlt_query);
EXECUTE IMMEDIATE dlt_query;
END;
/
Below is the running command :
exec prd_delete ('TBL_HIST_DATA','M','N1',20141205);
Below is the error :
ORA-00904:"N1" invalid identifier.
How to pass this value correctly ? please suggest.
At first place, why do you need PL/SQL for the DELETE. You could do it in plain SQL.
Why is P_C_DATE a NUMBER, What data type is cobdate COLUMN. A date should always be a DATE. If the column data type is DATE, then you will run into more errors. Always pay attention to declaring correct data types.
With dynamic SQL, before directly executing, it is always a good practice to see whether the query is formed correctly using DBMS_OUTPUT. I would also suggest to use quoting string literal technique to make it even easier.
DBMS_OUTPUT.PUT_LINE(dlt_query);
The issue with the query is that you are missing the single-quotation marks around the VARCHAR2 type.
Modify the query to -
dlt_query := 'delete from '||P_TBL_NAME||' where system='||P_SYS||
' And batch_id='||''''||P_B_ID|| '''' ||
' And cobdate='||P_C_DATE;
you are losing the quotes around N1 during concatination
you can fix by adding quotes before and after , eg.
dlt_query := 'delete from '||P_TBL_NAME||' where system='||P_SYS||
' And batch_id='||''''||P_B_ID|| '''' ||
' And cobdate='||P_C_DATE;
If you have to use the EXECUTE IMMEDIATE statement, you should use bind variables:
CREATE OR REPLACE PROCEDURE prd_delete (P_TBL_NAME IN VARCHAR2,
P_SYS VARCHAR2,
P_B_ID VARCHAR2,
P_C_DATE NUMBER) IS
dlt_query VARCHAR2 (200);
BEGIN
dlt_query := 'delete from ' || P_TBL_NAME || ' where system=:1 and batch_id=:2 and cobdate=:3';
BEGIN
EXECUTE IMMEDIATE dlt_query USING P_SYS, P_B_ID, P_C_DATE;
EXCEPTION
WHEN OTHERS THEN
-- catch exception !!
END;
END;
/

Get the value of a record type from another package in Oracle PL/SQL

packageA Body
MEMBER FUNCTION getValue (indx IN PLS_INTEGER) RETURN VARCHAR2
IS
colData packageB.vldtnR
BEGIN
colData := packageB.getColumnData(indx);
--I want to output the id and name from the specified index
END;
packageB Header
TYPE vldtnR IS RECORD(
id PLS_INTEGER;
name VARCHAR2(50)
)
packageB Body
TYPE vldtnArryT IS TABLE OF vldtnR INDEX BY PLS_INTEGER;
vldtnArry vldnArryT;
FUNCTION getColumnData(indx IN PLS_INTEGER) IS
BEGIN
IF vldtnArry.EXISTS(indx) = TRUE THEN
RETURN vldtnArry(indx);
END IF;
END;
Code Overview:
vldtnArry pertains to vldArryT (PackageB body)
vldtnArryT pertains to vldtnR (PackageB body)
vldtnR is in PackageB header
Question:
How do I output the id and name of an index in packageA?
In colData you have a record, you can call the fields from the record directly as colData.id and colData.name.
As Maksim said, you have a colData variable which is a record type, so you can refer to the fields as colData.id and colData.name. You want to return both as a single string based on the varchar2 return type and the embedded comment, so you can do:
CREATE PACKAGE BODY packageA AS
FUNCTION getValue (indx IN PLS_INTEGER) RETURN VARCHAR2
IS
colData packageB.vldtnR;
BEGIN
colData := packageB.getColumnData(indx);
return 'Index ' || indx || ' ID ' || colData.id
|| ' name "' || colData.name || '"';
END;
END;
/
Which will return something like this, which you can obviously modify to your desired output:
Index 2 ID 13 name "Thirteen"
The code you posted has numerous other problems, hopefully from retyping it here. SQL Fiddle demo which compiles and lets you see the result for a couple of index values, bases on a manually-populated collection.

Resources