SELECT in an IN clause - oracle

I try to convert a comma separated string to number values. And then I try to chceck if a specified number occures there. I'm doing it inside a function. I'm getting error PLS-00103. The simplified snipped is here:
fieldType NUMBER :=1;
myString := '2,3,4,5';
IF fieldType NOT IN (SELECT TO_NUMBER(xt.column_value) FROM XMLTABLE(myString) xt) THEN
--do soemthing
-- Problem occures with the (
--
END IF;

You cannot use select statement in if condition
Link : https://stackoverflow.com/a/10203109/8843451
You need to assign the value to variable and use it
strop number :=0;
select count(*) into strop from (SELECT to_number(trim(COLUMN_VALUE)) as str FROM
xmltable(('"'|| REPLACE(myString, ',', '","')|| '"'))) where str in (fieldType);
if strop >0 then
dbms_output.put_line(fieldType || ' Is a Valid Number');
else
dbms_output.put_line(fieldType || ' Is a InValid Number');
end if;

There's a simpler option (SQL*Plus example; you'd use that 1 in line #2, or whatever you'd want to use):
SQL> DECLARE
2 fieldtype NUMBER := &par_fieldtype;
3 mystring VARCHAR2 (200) := '2,3,4,5';
4 BEGIN
5 IF ',' || mystring || ',' LIKE '%,' || fieldtype || ',%'
6 THEN
7 DBMS_OUTPUT.put_line ('Member');
8 ELSE
9 DBMS_OUTPUT.put_line ('Not a member');
10 END IF;
11 END;
12 /
Enter value for par_fieldtype: 1
Not a member
PL/SQL procedure successfully completed.
SQL> /
Enter value for par_fieldtype: 3
Member
PL/SQL procedure successfully completed.
SQL>

Related

how to pass formatted string to column and same fetch it in package in PL/SQL

I have error master table which contain description like 'Error in table abc in xyz column.' I need to format string for column name which is xyz here. Where ever I need to call this table I will pass column name and then I will get expected description.
Ex - Insert into errorTabl values(01,There is error in {0})
Whenever inside package I need to retrieve value of 01 then I will pass column name col1 so then expected value will be as below :
01 There is error in col1
Request you to please help me for insert and select both statements.
Though this doesn't make sense, maybe the code below could help you to start with something or to clarify your problem.
NOTE: the code below is here just to show you the basics - there is no sense in it for any kind of production. You are the one to adjust it to your context.
So, the package to put and get things into or from errors table:
CREATE OR REPLACE PACKAGE ERRS AS
Procedure putError(p_table IN VarChar2, p_column IN VarChar2);
Function getError(p_table VarChar2, p_column VarChar2) RETURN VarChar2;
END ERRS;
-- ---------------------------------------------------------------------------------
CREATE OR REPLACE PACKAGE BODY ERRS AS
Procedure putError(p_table IN VarChar2, p_column IN VarChar2) AS
BEGIN
Declare
mSql VarChar2(512) := '';
sq VarChar2(1) := '''';
Begin
mSql := 'Insert Into ERRORTABLE values( ' || sq || '01' || sq || ', ' || sq ||
'There is error in table ' || p_table || ' in ' || p_column || ' column' || sq || ')';
Execute Immediate mSql;
Commit;
End;
END putError;
-- -------------------------------------------------------------------------------
Function getError(p_table VarChar2, p_column VarChar2) RETURN VarChar2 IS
BEGIN
Declare
Cursor c IS
Select ERR_DESC From ERRORTABLE Where ERR_DESC Like('%table ' || p_table || ' in ' || p_column || '%');
mRet VarChar2(512) := '';
mDesc VarChar2(512) := '';
Begin
Open c;
LOOP
FETCH c into mDesc;
EXIT WHEN c%NOTFOUND;
mRet := '01 ' || mDesc || Chr(10);
END LOOP;
Close c;
RETURN RTRIM(mRet, Chr(10));
End;
END getError;
END ERRS;
Now the calling code to insert 5 records (once more - this is senseless) and to get you one of them...
set serveroutput on
DECLARE
errMsg VarChar2(512);
BEGIN
ERRS.putError('T_ABC', 'C_XYZ');
ERRS.putError('T_ABC', 'C_MNO');
ERRS.putError('T_ABC', 'C_PQR');
ERRS.putError('T_DEF', 'C_MNO');
ERRS.putError('T_DEF', 'C_XYZ');
--
errMsg := ERRS.getError('T_ABC', 'C_XYZ');
dbms_output.put_line(errMsg);
END;
/* R e s u l t :
anonymous block completed
01There is error in table T_ABC in C_XYZ column
*/
Just needed to pass double colon in insert query so then it will take single colon in table.
Ex - Insert into errorTabl values(01,There is error in ''{0}'')
In table it will be look like
**Id** **Description**
01 There is error in'{0}'.

PL/SQL Function error Encountered the symbol "(" when expecting one of the following

error in using pl/sql function in the blocks of pl/sql
i'm trying to replace a char with a varchar2 using a function
i want to replace 'M' char to 'Male' VARCHAR2 after extract the data from the table using cursor
this is my code
`
CREATE OR REPLACE FUNCTION change_gender(GENDER IN CHAR)
RETURN VARCHAR2(6);
IS
new_gender VARCHAR2(6);
BEGIN
IF (GENDER == 'M') THEN
new_gender := 'Male'
return new_gender;
END;
DECLARE
current_student_address STUDENTS.address%type;
CURSOR students_cursor IS
SELECT ID,NAME,GENDER
FROM STUDENTS
WHERE ADDRESS = current_student_address;
students_row students_cursor%ROWTYPE;
BEGIN
current_student_address := 'Road102';
FOR students_row IN students_cursor LOOP
DBMS_OUTPUT.PUT_LINE(students_row.id || ' ' || students_row.name || ' ' || change_gender(students_row.gender));
END LOOP;
END;
`
and this is the error I'm receiving
Error at line 2: PLS-00103: Encountered the symbol "(" when expecting one of the following:
. # % ; is default authid as cluster order using external
character deterministic parallel_enable pipelined aggregate
result_cache accessible rewrite
It's just =, not ==. Also, you're missing statement terminator (semi-colon), as well as end if. Return datatype can't have size.
As of the function's logic: what about other genders? What will function return then? It must return something (even if it were null).
Therefore, I'd suggest something like this:
Sample data:
SQL> select * from students;
ID NAME ADDRESS GENDER
---------- ------ ------- ----------
1 Little Road102 M
2 Foot Road102 F
Function:
SQL> CREATE OR REPLACE FUNCTION change_gender(GENDER IN CHAR)
2 RETURN VARCHAR2
3 IS
4 new_gender VARCHAR2(6);
5 BEGIN
6 RETURN case when gender = 'M' then 'Male'
7 when gender = 'F' then 'Female'
8 else 'Other'
9 end;
10 end;
11 /
Function created.
Testing:
SQL> set serveroutput on
SQL> DECLARE
2 current_student_address STUDENTS.address%type;
3 CURSOR students_cursor IS
4 SELECT ID,NAME,GENDER
5 FROM STUDENTS
6 WHERE ADDRESS = current_student_address;
7 students_row students_cursor%ROWTYPE;
8 BEGIN
9 current_student_address := 'Road102';
10 FOR students_row IN students_cursor LOOP
11 DBMS_OUTPUT.PUT_LINE(students_row.id || ' ' || students_row.name || ' ' ||
12 change_gender(students_row.gender));
13 END LOOP;
14 END;
15 /
1 Little Male
2 Foot Female
PL/SQL procedure successfully completed.
SQL>

PLS-00302: component 'M' must be declared in oracle 11g [duplicate]

This question already has answers here:
Accessing elements of Oracle PLSQL record type on runtime
(3 answers)
Closed 6 months ago.
I want to convert table type data into single variable with ',' separated
also I dont know what column used in table type. please let us know how to achieve it without passing column name, output must be string type.
DECLARE
TEST_TAB ADT.TEST_TABB2 := ADT.TEST_TABB2(adt.test_obj(2,
'dsd'),
adt.test_obj(3,
'agg'));
l_name clob := '';
CURSOR c IS
SELECT
ATTR_NAME
FROM
ALL_TYPE_ATTRS
WHERE
OWNER = 'ADT'
AND TYPE_NAME = (
SELECT
REFERENCED_NAME
FROM
DBA_DEPENDENCIES
WHERE
REFERENCED_OWNER = 'ADT'
AND NAME = 'TEST_TABB2'
AND REFERENCED_TYPE = 'TYPE');
t nvarchar2(500) := '';
BEGIN
FOR i IN TEST_TAB.FIRST .. TEST_TAB.LAST
LOOP
l_name := l_name || '(';
FOR cur IN c LOOP
t := cur.ATTR_NAME;
dbms_output.put_line(t);
l_name := l_name || ' ' || t ;
l_name := l_name || ', ' || TEST_TAB(i).t;
END LOOP;
l_name := l_name || ')' ;
END LOOP;
dbms_output.put_line(l_name);
END;
You could use dynamic SQL, concatenating in the attribute name, and passing the object as a bind variable:
...
BEGIN
FOR i IN TEST_TAB.FIRST .. TEST_TAB.last LOOP
FOR cur IN c LOOP
m := '.' || cur.ATTR_NAME;
execute immediate 'select :o' || m || ' from dual'
into t
using test_tab(i);
dbms_output.put_line(t);
END LOOP;
END LOOP;
END;
or
...
BEGIN
FOR i IN TEST_TAB.FIRST .. TEST_TAB.last LOOP
FOR cur IN c LOOP
execute immediate 'select :o.' || cur.ATTR_NAME || ' from dual'
into t
using test_tab(i);
dbms_output.put_line(t);
END LOOP;
END LOOP;
END;
which both produce output;
2
John
3
Devid
db<>fiddle
The first block above doesn't work in the unpatched version of 11gR2 available in db<>fiddle, but I'm not quite sure why, or whether it would work in a later, patched, version. The second block does work though.

Escaping characters in select statement withing a cursor in Oracle

I have a Function defined in a package that uses a REF CURSOR and IF ELSE logic to assign a select statement to the cursor. The Function executes with an ORA-01722: invalid number error. I believe this is due to a mistake in the escape characters:
FUNCTION getReportData(
P_DATE_FROM IN DATE,
P_DATE_TO IN DATE,
PERIOD_TYPE IN INTEGER)
RETURN RPTTCI1328_TABLE PIPELINED IS TYPE cursorOutput IS REF CURSOR;
CUR_ROW RPTTCI1328_ROW;
cur_getByPeriodFilter cursorOutput;
c_stmt_str VARCHAR2 (4000);
BEGIN
IF PERIOD_TYPE = 1 THEN
c_stmt_str :=
' SELECT TO_CHAR(tcis_case.offence_datetime, ''yyyy'') YEAR, tcis_case.status, COUNT(tcis_case.ticket_no) TICKET_NO, ' ||
' SUM(tcis_part_payment.AMT_ORIGINAL) ORIGINAL, ' ||
' SUM(tcis_part_payment.AMT_PAID) PAID, ' ||
' SUM(CASE WHEN tcis_person.reason_code = ''A'' THEN 1 ELSE 0 END) A, ' ||
' SUM(CASE WHEN tcis_person.reason_code = ''D'' THEN 1 ELSE 0 END) D, ' ||
' SUM(CASE WHEN tcis_person.reason_code = ''F'' THEN 1 ELSE 0 END) F, ' ||
' SUM(CASE WHEN tcis_person.reason_code = ''N'' THEN 1 ELSE 0 END) N, ' ||
' SUM(CASE WHEN tcis_person.reason_code = ''O'' THEN 1 ELSE 0 END) O, ' ||
' SUM(CASE WHEN tcis_person.reason_code = ''INF'' THEN 1 ELSE 0 END) INF, ' ||
' SUM(CASE WHEN tcis_person.reason_code = ''WFR'' THEN 1 ELSE 0 END) WFR ' ||
' FROM tcis_case ' ||
' join tcis_part_payment on tcis_case.tcis_case_id = tcis_part_payment.tcis_case_id ' ||
' join tcis_person on tcis_case.tcis_case_id = tcis_person.tcis_case_id ' ||
' where tcis_person.person_no = 1 ' ||
' AND tcis_case.unit = ''M'' ' ||
' AND tcis_case.sub_unit = ''P'' ' ||
' AND tcis_case.status IN (''P'', ''C'') ' ||
' AND (''' || P_DATE_FROM ||''' IS NULL OR tcis_case.offence_datetime >= TO_DATE(TO_CHAR(''' || P_DATE_FROM ||''', ''yyyy''),''yyyy'')) ' ||
' AND (''' || P_DATE_TO ||''' IS NULL OR tcis_case.offence_datetime < TO_DATE(TO_CHAR(''' || P_DATE_TO ||''', ''YYYY''),''yyyy'')) ' ||
' GROUP BY to_char(tcis_case.offence_datetime, ''yyyy''), tcis_case.status ';
END If;
DBMS_OUTPUT.put_line (c_stmt_str);
OPEN cur_getByPeriodFilter FOR c_stmt_str;
LOOP
FETCH cur_getByPeriodFilter INTO
CUR_ROW.YEAR,
CUR_ROW.STATUS,
CUR_ROW.TICKET_COUNT,
CUR_ROW.ORIGINAL,
CUR_ROW.PAID,
CUR_ROW.A,
CUR_ROW.D,
CUR_ROW.F,
CUR_ROW.N,
CUR_ROW.O,
CUR_ROW.INF,
CUR_ROW.WFR;
EXIT WHEN cur_getByPeriodFilter%NOTFOUND;
PIPE ROW(CUR_ROW);
END LOOP;
CLOSE cur_getByPeriodFilter;
END;
The command from sqlplus is:
select pkg_name.function('DD-MMM-YY','DD-MMM-YY', 1) from dual;
This looks like an excerpt of a larger "code" you composed as a string. This is usually done when there's something dynamic you're working with, such as passing table or column names as procedure's parameters. What is your reason to do that? Maybe I didn't notice it, but - I don't see anything dynamic here.
If that's so, you can skip all those single quotes and write a nice, "normal" SELECT statement, without escaping anything.
However, if you insist on it (or there's really a reason I didn't see), consider using the q-quoting mechanism. This is what you're doing now (using zillion of single quotes; difficult to read, follow and debug):
SQL> create or replace function f_test (par in varchar2)
2 return varchar2
3 is
4 l_str varchar2(200);
5 l_res varchar2(1);
6 begin
7 l_str := 'select ''A'' first_column from dual';
8 execute immediate l_str into l_res;
9 return l_res;
10 end;
11 /
Function created.
SQL> select f_test(null) from dual;
F_TEST(NULL)
-------------------------------------------------------------
A
SQL>
Or, you can do it this way:
SQL> create or replace function f_test (par in varchar2)
2 return varchar2
3 is
4 l_str varchar2(200);
5 l_res varchar2(1);
6 begin
7 l_str := q'[select 'A' first_column from dual]'; --> this
8 execute immediate l_str into l_res;
9 return l_res;
10 end;
11 /
Function created.
SQL> select f_test(null) from dual;
F_TEST(NULL)
------------------------------------------------------------
A
SQL>
So:
use the q keyword,
open a single quote
use e.g. square bracket (if you don't use it in your code; if you do, use something else)
write your query as you usually do
close the bracket
close the single quote
As simple as that.
Comment you posted says that nothing much happened (except that code is now, probably, somewhat simpler).
I don't have your data so I can't test it myself - you'll have to find the culprit yourself, there's just too much code.
Error you got says (for example) this:
SQL> select to_number('A') from dual;
select to_number('A') from dual
*
ERROR at line 1:
ORA-01722: invalid number
Oracle tells you the position of the error - can you find something out of it?
A mistake in the escape characters will lead to a problem with the OPEN of the cursor
ORA-01722: invalid number error is runtime error where you fail to convert a string to a number in the FETCH.
You may try to figure out which column it is by trial and error (replace stepwise each numeric column with null), but is is most probably AMT_ORIGINALor AMT_PAID.
If the column is VARCHAR2 and you see values such as 100,50 or 100.50 the problem is that yours NLS_NUMERIC_CHARACTERSdoes not match.
You must explicit convert the string to number with the proper setting. e.g. for 100.50
sum(to_number(AMT_PAID, '9999999D99', 'NLS_NUMERIC_CHARACTERS=''.,'''))
Check this answer for more details.
Additional Comments
if you pass in a DATE parameter you do not need the double conversion TO_DATE(TO_CHAR( use simple the parameter
you do not need a dynamic cursor, if you would use bind variables which is recommended. See the simplified implementation below
You may want to you a dynamic cursor to get efficient execution plan for the search with and without the date range - see details here
Simplified Implementation
CREATE or replace FUNCTION getReportData(
P_DATE_FROM IN DATE,
P_DATE_TO IN DATE)
RETURN RPTTCI1328_TABLE PIPELINED IS
CURSOR cur_getByPeriodFilter
IS
SELECT to_char(offence_datetime,'YYYY') year,
/* process mumeric strings with decimal point e.g. 100.50 */
sum(to_number(AMT_PAID, '9999999D99', 'NLS_NUMERIC_CHARACTERS=''.,'''))
from tab
where tab.offence_datetime >= P_DATE_FROM and tab.offence_datetime < P_DATE_TO
group by to_char(offence_datetime,'YYYY')
;
CUR_ROW RPTTCI1328_ROW := RPTTCI1328_ROW(null,null);
BEGIN
OPEN cur_getByPeriodFilter;
LOOP
FETCH cur_getByPeriodFilter INTO
CUR_ROW.YEAR,
CUR_ROW.PAID;
EXIT WHEN cur_getByPeriodFilter%NOTFOUND;
PIPE ROW(CUR_ROW);
END LOOP;
END;
/

Oracle - merge multiple rows into one, use column values as row names, dynamic

I am trying to merge several rows into one row. The column values need to become the column names, and it needs to be dynamic as there could be more column values added
Current
key attribute value reason message
1001 Att1 Val1 Reason 1 Message 1
1001 Att2 Val2 Reason 1 Message 1
1001 Att3 Val3 Reason 1 Message 1
1002 Att1 Val4 Reason 2 Message 2
1002 Att2 Val5 Reason 2 Message 2
1002 Att4 Val7 Reason 2 Message 2
1002 Att5 Val8 Reason 2 Message 2
1002 Att6 Val9 Reason 2 Message 2
Want this structure
key Att1 Att2 Att3 Att4 Att5 Att6 reason message
1001 Val1 Val2 Val3 Reason 1 Message 1
1002 Val4 Val5 Val7 Val8 Val9 Reason 2 Message 2
Please help!
To obtain your result you need a PIVOT operation; the main problem is that you need it to be dynamic, so we don't know in advance the number or columns that you will get.
You can try with some dynamic SQL, building a statement that will match your data:
declare
vSQL varchar2(32767) :=
' from (select * from test)
pivot (max(value) for attribute in (';
vAttributes varchar2(32767);
vOutputFields varchar2(32767) := 'key || '' | '' || reason || '' | '' || message || '' | '' || ';
vAllFields varchar2(3) := '*';
vClob clob;
type tabVarchar2 is table of varchar2(32767);
vTabVarchar2 tabVarchar2;
vSQLOneField varchar2(32767);
vSQLMoreFields varchar2(32767);
begin
-- get the columns
select listagg( '''' || attribute || '''', ',') within group (order by attribute),
vOutputFields || listagg( '"''' || attribute || '''"', '|| '' | '' || ') within group (order by attribute)
into vAttributes, vOutputFields
from (select distinct attribute from test) ;
-- build the statement for many fields
vSQLMoreFields := 'select ' || vAllFields || vSQL || vAttributes || '))';
-- print the query
dbms_output.put_line(vSQLMoreFields);
-- fetch it as a unique XML
select DBMS_XMLGEN.getXML(vSQLMoreFields)
into vClob
from dual;
dbms_output.put_line(vClob);
-- build the statement for a unique field
vSQLOneField := 'select ' || vOutputFields || ' from (' || vSQLMoreFields || ')';
-- print the query
dbms_output.put_line(vSQLOneField);
-- fetch as a unique field into a structure
execute immediate vSQLOneField bulk collect into vTabVarchar2;
for i in 1 .. vTabVarchar2.last loop
dbms_output.put_line( vTabVarchar2(i));
end loop;
end;
I mainly see two problems in this:
it's hard to find a way to fecth the result and use it; I made the example fetching in into a unique XML, with separated fields, or into an array of varchar2, with each value containing the entire row;
I make access to the table twice, once to get the columns and once to get the real data, and this can be a performance issue
Another approach would be this one:
CREATE OR REPLACE FUNCTION TransposeTab RETURN SYS_REFCURSOR AS
cur SYS_REFCURSOR;
sqlstr VARCHAR2(10000);
BEGIN
sqlstr := 'SELECT * FROM TABLE1 PIVOT (MAX(val) FOR ATTRIBUTE IN (';
FOR aCol IN (SELECT DISTINCT ATTRIBUTE FROM TABLE1) LOOP
sqlstr := sqlstr || ''''||aCol.ATTRIBUTE||''' AS "'||aCol.ATTRIBUTE||'",';
END LOOP;
sqlstr := REGEXP_REPLACE(sqlstr, ',$','))');
--DBMS_OUTPUT.PUT_LINE ( sqlstr );
OPEN cur FOR sqlstr;
RETURN cur;
END;
In order to get the result, you can do this one.
SELECT TransposeTab FROM dual;
or (only in SQL*Plus)
var rc refcursor
EXEC :rc := TransposeTab;
PRINT rc

Resources