I created a function to create link but am encountering errors when creating a variable, tried declaring but declare would have an error
create or replace FUNCTION CREATE_REQUEST_LINK(request_id IN VARCHAR2, task_id IN VARCHAR2)
RETURN VARCHAR2 AS
request_link varchar2(100);
BEGIN
first_half varchar2 := apex_mail.get_instance_url;
second_half varchar2 := 'f?p=&APP_ID.:&APP_PAGE.:&APP_SESSION.::::';
request_link := first_half + second_half;
RETURN request_link;
END WF_CREATE_REQUEST_LINK;
It was returning on Line 8 and 9
Error: PLS-00103: Encountered the symbol "VARCHAR2" when expecting one of the following: := . ( # % ; The symbol "." was substituted for "VARCHAR2" to continue.
Is there a way to fix. I'm kinda new to Oracle Apex
Additional question, am I right in creating the 2nd half, the plan is that the $APP_SESSION. after clicking the link will be changed into the session id after logging in.
You seem to want to declare two local variables first_half and second_half. Variable declarations need to happen in the declaration section of a block.
When you declare a varchar2 variable, you need to provide a length. I'll guess that you want both local variables to have a maximum length of 100.
The string concatenation operator is || not +
So you probably want something like
create or replace FUNCTION CREATE_REQUEST_LINK(
request_id IN VARCHAR2,
task_id IN VARCHAR2)
RETURN VARCHAR2
AS
request_link varchar2(100);
first_half varchar2(100) := apex_mail.get_instance_url;
second_half varchar2(100) := 'f?p=&APP_ID.:&APP_PAGE.:&APP_SESSION.::::';
BEGIN
request_link := first_half || second_half;
RETURN request_link;
END WF_CREATE_REQUEST_LINK;
I am struggling with an issue regarding a CLOB.
I would like to create a trigger (after update) which updates the column of a table which is a CLOB.
This CLOB contains lines in this form :
foo|132|65|12/08/2016|18395|
bar|132|54|15/08/2014|32434343|
I would like to modify the CLOB such that the line beginning with "foo" has the value "18395" divided by 1000. The line will look like
foo|132|65|12/08/2016|18.395|
Is there a quick way to modify my CLOB?
Thank you for your help.
EDIT : I found a way to modify the line of the CLOB what I need to do is just to modify the CLOB to replace
foo|132|65|12/08/2016|18395|
by
foo|132|65|12/08/2016|18.395|
This is rather long but effective.
Firstly create a type:
create or replace TYPE "array_str" AS VARRAY(10) OF VARCHAR(256);
Then create a function that will return a array based in a delimiter:
FUNCTION string_to_array (
string_delimited IN VARCHAR2,
delimiter IN VARCHAR2 DEFAULT ','
) RETURN array_str IS
pls_idx PLS_INTEGER;
split_string array_str := array_str();
pls_del_len PLS_INTEGER := length(delimiter);
BEGIN
IF string_delimited IS NOT NULL THEN
LOOP
-- search for delimiter string
pls_idx := instr(l_string_delimited, delimiter);
-- increase the size of array
split_string.extend;
-- check last search of delimiter is success
IF pls_idx = 0 THEN
split_string(l_split_string.count) := substr(l_string_delimited, 1);
ELSE
split_string(l_split_string.count) := substr(l_string_delimited, 1, pls_idx - 1);
END IF;
-- exit from loop when last string
EXIT WHEN nvl(l_pls_idx, 0) = 0;
string_delimited := substr(l_string_delimited, pls_idx + pls_del_len);
END LOOP;
END IF;
RETURN split_string;
END string_to_array;
Then create your trigger:
CREATE OR REPLACE TRIGGER your_trigger AFTER
UPDATE ON table_name
FOR EACH ROW
DECLARE
clob_array array_str;
clob_str CLOB := '';
BEGIN
clob_array := string_to_array(:new.new_clob, '|');
IF ( clob_array(1) = 'foo' ) THEN
clob_array(5) := to_number(clob_array(5) / 1000);
END IF;
FOR i IN 1..clob_array.count - 1 LOOP clob_str := clob_str
|| to_char(clob_array(i))
|| '|';
END LOOP;
-- do something with clob_str
END;
I am using dynamic SQL where I am dynamically using value of column name to bind and its value to be bind
OLD CODE
<Outer Loop>
FOR i IN lvaDBOBJDTLRecTab.FIRST .. lvaDBOBJDTLRecTab.LAST
LOOP
DBMS_SQL.BIND_VARIABLE ( lvnInsertCursorId, ':RTTEXT2VC100',
lvaDBOBJDTLRecTab(i).DBONAME );
DBMS_SQL.BIND_VARIABLE ( lvnInsertCursorId, ':RTTEXT3VC100',
lvaDBOBJDTLRecTab(i).DBOTYPE );
3.
.
.
.
100
END LOOP;
Instead of writing BIND_VARIABLE for 100 times , I want to dynamically access value of collection. I am able to fetch the value of columns dynamically, which need to be bind (lvsColForBinding), however value of lvsColValForBind
is coming as 'lvrCurDBOBJDTL(i).DBONAME' , 'lvrCurDBOBJDTL(i).DBOTYPE'
and same for rest of the 98 columns,
<Inner Loop>
FOR j IN lvaMappingTab.FIRST..lvaMappingTab.LAST
LOOP
lvsColForBinding := ':'||lvaMappingTab(j).MstRptColCds;
lvsColValForBind := 'lvrCurDBOBJDTL(i).'||lvaMappingTab(j).RptColCd;
DBMS_SQL.BIND_VARIABLE ( lvnInsertCursorId,lvsColForBinding, lvsColValForBind);
END LOOP;
when DBMS_SQL.BIND_VARIABLE is run for each row, as mentioned earlier Column to be bind comes correct but value to be bind, instead of coming as
value of 'XYZ' = lvrCurDBOBJDTL(i).DBONAME it comes as this in single quotes 'lvrCurDBOBJDTL(i).DBONAME' same for all columns.
how can we extract the value of each element in inner loop. what step we need to do to fetch the value of lvsColValForBind?
While debugging through SQLDEveloper Watches, I can see the, element name, value and type, when adding and double clicking the plsql record variable,
what is the SQL behind that, can we use that in coding ?
My first recommendation is that you use dynamic SQL to generate lots of dumb code instead of using a small amount of smart PL/SQL. If code generation doesn't work, you can use ANYDATA and ANYTYPE to create PL/SQL reflection to dynamically iterate through the elements of a record at run time.
Code Generation
Don't write BIND_VARIABLE 100 times, but create a small program to generate the 100 lines of code for you. If the data is ultimately coming from one table and going into another table, the input and output may be predictable based on data dictionary views like DBA_TAB_COLUMNS.
Hopefully a small query like this could help generate all the code for a single table:
--Generate PL/SQL statements for binds.
select
'DBMS_SQL.BIND_VARIABLE(lvnInsertCursorId, '':RTTEXT'||column_id||'VC100'', lvaDBOBJDTLRecTab(i).'||column_name||');'
from dba_tab_columns
where owner = 'SOME_OWNER'
and table_name = 'SOME_TABLE'
order by 1;
Then you can copy-and-paste the output into the PL/SQL block. You'll probably also want a warning, like "do not modify, this code is autogenerated by the procedure CODE_TRON_2000".
This approach will only work if the PL/SQL code is predictable, based on the data dictionary or some other metadata.
PL/SQL Reflection
There's no pure PL/SQL reflection for PL/SQL types* but there's a simple workaround if you're willing to create the record types as SQL objects instead. If all your PL/SQL records are based on object types then ANYDATA and ANYTYPE can be used to dynamically access attributes. Object types and PL/SQL record types are pretty similar, it should be relatively painless to convert one to the other.
For example, if you create an object type that contains a number and a string:
create or replace type v_type is object(a number, b varchar2(1));
This (painful) PL/SQL block shows how to iterate through all the records of a collection, and then iterate through all of the attributes in each record. (The code prints the values for, you'll have to add the binding parts yourself.)
declare
type v_nt_type is table of v_type;
v_values v_nt_type := v_nt_type(v_type(1, 'A'), v_type(2, 'B'));
begin
--For each record:
for i in 1 .. v_values.count loop
declare
v_anydata anydata := anydata.ConvertObject(v_values(i));
v_number number;
v_varchar2 varchar2(4000);
v_result pls_integer;
v_anytype anytype;
v_dummy_num pls_integer;
v_dummy_char varchar2(4000);
v_dummy_anytype anytype;
v_number_of_elements number;
begin
--Get the ANYTYPE and the number of elements.
v_result := v_anydata.getType(v_anytype);
v_result := v_anytype.getInfo
(
prec => v_dummy_num,
scale => v_dummy_num,
len => v_dummy_num,
csid => v_dummy_num,
csfrm => v_dummy_num,
schema_name => v_dummy_char,
type_name => v_dummy_char,
version => v_dummy_char,
numelems => v_number_of_elements
);
--For each element in the record:
for i in 1 .. v_number_of_elements loop
--Find the type of the element:
v_anydata.piecewise;
v_result := v_anytype.getAttrElemInfo(
pos => i,
prec => v_dummy_num,
scale => v_dummy_num,
len => v_dummy_num,
csid => v_dummy_num,
csfrm => v_dummy_num,
attr_elt_type => v_dummy_anytype,
aname => v_dummy_char);
--This is where you do something interesting with the values.
--(The same code merely prints the values.)
if v_result = dbms_types.typecode_number then
v_result := v_anydata.getNumber(num => v_number);
dbms_output.put_line(v_number);
elsif v_result = dbms_types.typecode_varchar2 then
v_result := v_anydata.getVarchar2(c => v_varchar2);
dbms_output.put_line(v_varchar2);
--TODO: Add other potential types here.
end if;
end loop;
end;
end loop;
end;
/
Results:
1
A
2
B
* You're right that there must be some way to find this run time information, if the debugger gets it. But as far as I know there is no way for PL/SQL to retrieve that debug information. Maybe it's only available to an OCI(?) interface?
When you call bind_variable, you are binding an actual value to a placeholder. So if you provide a string that is the name of your variable, well, that string is the value bound to the placeholder.
If the array holds those values, then simply reference the array element and not the name of that element, as in:
DBMS_SQL.BIND_VARIABLE (
lvnInsertCursorId,
lvaMappingTab(j).MstRptColCds,
lvrCurDBOBJDTL(i).lvaMappingTab(j).RptColCd);
But I am pretty sure that's not what you've got there. Hope this helps!
#Jon
Thanks for your inputs, that helped. also I am able to iterate cols without creating OBJECTs using DBMS_SQL.DESCRIBE_COLUMNS.
**Below code still need little bit fine tuning, but mostly working :)
BEGIN
COLS_TRAVERSE('SELECT * FROM ALL_OBJECTS WHERE ROWNUM<=100');
END;
create or replace PROCEDURE COLS_TRAVERSE ( p_query in varchar2 )
AS
v_curid NUMBER;
v_desctab DBMS_SQL.DESC_TAB;
v_colcnt NUMBER;
v_RowNumcnt NUMBER := 1;
v_Colname_var VARCHAR2(10000);
v_name_var VARCHAR2(10000);
v_num_var NUMBER;
v_date_var DATE;
v_row_num NUMBER;
p_sql_stmt VARCHAR2(1000);
BEGIN
v_curid := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(v_curid, p_query, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS(v_curid, v_colcnt, v_desctab);
-- Define columns:
FOR i IN 1 .. v_colcnt LOOP
IF v_desctab(i).col_type = 2 THEN
DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_num_var);
ELSIF v_desctab(i).col_type = 12 THEN
DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_date_var);
ELSE
DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_name_var, 50);
END IF;
END LOOP;
v_row_num := dbms_sql.execute(v_curid);
-- Fetch rows with DBMS_SQL package:
WHILE DBMS_SQL.FETCH_ROWS(v_curid) > 0 LOOP
FOR i IN 1 .. v_colcnt
LOOP
v_Colname_var := v_desctab(i).col_name;
dbms_output.put_line( 'Name:' ||v_Colname_var );
IF (v_desctab(i).col_type = 1) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_name_var);
dbms_output.put_line( 'String Value:' || v_name_var );
ELSIF (v_desctab(i).col_type = 2) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_num_var);
dbms_output.put_line( 'Number Value:' || v_num_var);
ELSIF (v_desctab(i).col_type = 12) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_date_var);
dbms_output.put_line( 'Date Value:' || v_date_var );
END IF;
END LOOP;
dbms_output.put_line( 'End of Row Number # ' ||v_RowNumcnt );
v_RowNumcnt := v_RowNumcnt+1;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(v_curid);
END;
/
DBMS_OUT PUT
Name:OWNER
String Value:SYS
Name:OBJECT_NAME
String Value:ORA$BASE
Name:SUBOBJECT_NAME
String Value:
Name:OBJECT_ID
Number Value:134
Name:DATA_OBJECT_ID
Number Value:
Name:OBJECT_TYPE
String Value:EDITION
Name:CREATED
Date Value:30-03-18
Name:LAST_DDL_TIME
Date Value:30-03-18
Name:TIMESTAMP
String Value:2018-03-30:21:37:22
Name:STATUS
String Value:VALID
Name:TEMPORARY
String Value:N
Name:GENERATED
String Value:N
Name:SECONDARY
String Value:N
Name:NAMESPACE
Number Value:64
Name:EDITION_NAME
String Value:
Name:SHARING
String Value:NONE
Name:EDITIONABLE
String Value:
Name:ORACLE_MAINTAINED
String Value:Y
Name:APPLICATION
String Value:N
Name:DEFAULT_COLLATION
String Value:
Name:DUPLICATED
String Value:N
Name:SHARDED
String Value:N
Name:CREATED_APPID
Number Value:
Name:CREATED_VSNID
Number Value:
Name:MODIFIED_APPID
Number Value:
Name:MODIFIED_VSNID
Number Value:
End of Row Number # 1
Name:OWNER
String Value:SYS
Name:OBJECT_NAME
String Value:DUAL
Name:SUBOBJECT_NAME
String Value:
Name:OBJECT_ID
Number Value:143
Name:DATA_OBJECT_ID
Number Value:143
Name:OBJECT_TYPE
String Value:TABLE
Name:CREATED
Date Value:30-03-18
Name:LAST_DDL_TIME
Date Value:31-03-18
Name:TIMESTAMP
String Value:2018-03-30:21:37:22
Name:STATUS
String Value:VALID
Name:TEMPORARY
String Value:N
Name:GENERATED
String Value:N
Name:SECONDARY
String Value:N
Name:NAMESPACE
Number Value:1
Name:EDITION_NAME
String Value:
Name:SHARING
String Value:METADATA LINK
Name:EDITIONABLE
String Value:
Name:ORACLE_MAINTAINED
String Value:Y
Name:APPLICATION
String Value:N
Name:DEFAULT_COLLATION
String Value:USING_NLS_COMP
Name:DUPLICATED
String Value:N
Name:SHARDED
String Value:N
Name:CREATED_APPID
Number Value:
Name:CREATED_VSNID
Number Value:
Name:MODIFIED_APPID
Number Value:
Name:MODIFIED_VSNID
Number Value:
End of Row Number # 2
I'm trying to code a PL/SQL function to receive by parameter a number in VARCHAR2 type and return a hour in format 'HH24:MI:SS'. eg: If I send by parameter '6', the function will return 06:00:00. The problem is the function returned NULL value but If I write the same process in a simple PL/SQL block it works fine. Do you have any idea?. Thanks :)
CREATE OR REPLACE FUNCTION format_hour_test (
p_hour VARCHAR2)
RETURN VARCHAR2
IS
v_hour_time TIMESTAMP;
v_hour_char VARCHAR2(50);
BEGIN
IF
REGEXP_LIKE(p_hour, '(0-9)') THEN
v_hour_time := TO_TIMESTAMP(p_hour, 'HH24');
END IF;
v_hour_char := TO_CHAR(v_hour_time, 'HH24:MI:SS');
RETURN v_hour_char;
END format_hour_test;
/
SHOW ERRORS;
When I write a anonymus block with the same process this works fine.
SET SERVEROUTPUT ON
DECLARE
v_hour VARCHAR2(20) := '6';
v_hour_time TIMESTAMP := TO_TIMESTAMP(v_hour, 'HH24');
v_result VARCHAR2(20) := TO_CHAR(v_hour_time, 'HH24:MI:SS');
BEGIN
DBMS_OUTPUT.PUT_LINE(v_result);
END;
You have the wrong type of brackets in your regular expression; it should be:
IF REGEXP_LIKE(p_hour, '[0-9]') THEN
You used grouping parentheses, insteaf of matching/class brackets. You would get a match if you passed in the string '0-9', which wouldn't then be converted to a date/time.
Read more.
Your anonymous block is not the same process. In the anonymous block you do not have if regexp_like... In regular expressions, parens group, so you are looking for the string 0-9. So v_hour_time is not being set in the procedure.
I think you are looking for [0-9], which is match one of the characters in the range. Alternatively, [[:digit:]] https://docs.oracle.com/cd/B12037_01/server.101/b10759/ap_posix001.htm#i690819
Your v_hour_time is never initialized, because of your REGEXP_LIKE conditional:
select (case when REGEXP_LIKE('6', '(0-9)') then 'true' else 'false' end) as res from dual;
returns:
RES
-----
false
I am trying to create a simple function that takes in 3 parameters, 2 numbers and a string. I have written the function but am not getting the expected results from a simple select statement when using the LIKE comparison for the string.
The select from the function below returns no rows when executed with the string input value set to ebts, but if I run this as a standalone select state it returns 2 rows which what I would expect. Have used dbms output to determine if whitespace were being passed but all looks OK.
CREATE OR REPLACE FUNCTION OPC_OP.sitezone_exists
(in_site_id IN NUMBER, in_zone_id IN NUMBER, in_mod VARCHAR2)
RETURN NUMBER
IS
v_count_rec NUMBER;
v_return NUMBER;
v_mod VARCHAR2(4) := in_mod;
BEGIN
SELECT COUNT(*)
INTO v_count_rec
FROM AW_ACTIVE_ALARMS
WHERE AW_ACTIVE_ALARMS.site_id = in_site_id
AND AW_ACTIVE_ALARMS.zone_id = in_zone_id
AND AW_ACTIVE_ALARMS.module LIKE 'v_mod%';
IF v_count_rec > 0
THEN
DBMS_OUTPUT.PUT_LINE('count'||v_count_rec||'=========='||v_mod||'=============');
v_return:= 1;
RETURN (v_return);
ELSE
DBMS_OUTPUT.PUT_LINE('count'||v_count_rec||'=========='||v_mod||'=============');
v_return:= 0;
RETURN (v_return);
END IF;
END sitezone_exists;
When passing in values 12, 12, ebts the output displayed is:
count 0 ==========ebts=============
RetVal = 0
If I run the same select subtituting only passing in the above values the query returns 2 rows - I have removed the like clause of the function and it then returns 2 rows, any idea why the like part of clause is failing to match with rows.
You are trying to match a string literal with this:
AND AW_ACTIVE_ALARMS.module LIKE 'v_mod%';
Change it to:
AND AW_ACTIVE_ALARMS.module LIKE v_mod||'%';
MarioAna has the right answer, IMO, but as an aside (which is too long for a comment), your function can be better written.
You're duplicating the dbms_output code, plus it's considered best practice to have a single RETURN in a function (although I would argue that more might be ok - one in the body and one per exception in the exception block...), so you could rewrite it as:
CREATE OR REPLACE FUNCTION OPC_OP.sitezone_exists
(in_site_id IN NUMBER, in_zone_id IN NUMBER, in_mod VARCHAR2)
RETURN NUMBER
IS
v_count_rec NUMBER;
v_return NUMBER;
v_mod VARCHAR2(4) := in_mod;
BEGIN
SELECT COUNT(*)
INTO v_count_rec
FROM AW_ACTIVE_ALARMS aaa
WHERE aaa.site_id = in_site_id
AND aaa.zone_id = in_zone_id
AND aaa.module LIKE v_mod||'%';
DBMS_OUTPUT.PUT_LINE('count '||v_count_rec||'=========='||v_mod||'=============');
IF v_count_rec > 0 THEN
v_return := 1;
ELSE
v_return:= 0;
END IF;
RETURN (v_return);
END sitezone_exists;
/