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;
/
I am trying to use loop variable as a string and use it directly with EXECUTE IMMEDIATE in PL/SQL loop. Here is my code:
BEGIN
FOR i IN (
SELECT table_name FROM user_tables WHERE LOWER(table_name) LIKE 'tblequityreturnstest_%'
)
LOOP
vqs := 'DROP TABLE'||i||''
EXECUTE IMMEDIATE vqs;
END LOOP;
END;
But I am getting an error :
Error report -
ORA-06550: line 4, column 1:
PLS-00103: Encountered the symbol "EXECUTE" when expecting one of the following:
* & = - + ; < / > at in is mod remainder not rem
<an exponent (**)> <> or != or ~= >= <= <> and or like like2
like4 likec between || member submultiset
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
Your syntax is wrong, try
DECALRE
vqs VARCHAR2(100);
BEGIN
FOR i IN (SELECT table_name FROM user_tables WHERE LOWER(table_name) LIKE 'tblequityreturnstest_%') LOOP
vqs := 'DROP TABLE '||i.table_name;
EXECUTE IMMEDIATE vqs;
END LOOP;
END;
or even simpler
BEGIN
FOR i IN (SELECT table_name FROM user_tables WHERE LOWER(table_name) LIKE 'tblequityreturnstest_%') LOOP
EXECUTE IMMEDIATE 'DROP TABLE '||i.table_name;
END LOOP;
END;
I have a table with several columns, and I would like to eliminate spaces value (' ') from the values. The query is:
update table
set column_name = trim(both ' ' from column_name)
where column_name like '% '
and since the table contains around 55 columns so probably a loop would be a feasible idea instead of writing update statement for each and every column.
First I checked if looping is working:
declare
column_name varchar2(2048);
cursor pointer is
select column_name into column_name from user_tab_cols where table_name = 'TABLE_NAME';
begin
for x in pointer
loop
dbms_output.put_line(x.column_name);
end loop;
end;
yes, it is working. I get column names in dbms_output window.
Now,
Here is what I am trying to do which doesn't seem to work:
declare
column_var varchar2(2048);
cursor pointer is
select column_name into column_var from user_tab_cols where table_name = 'TABLE_NAME';
begin
for x in pointer
loop
update table_name
set x.column_var = trim(both ' ' from x.column_var)
where x.column_var like '% ';
commit;
end loop;
end;
This is, unfortunately not working. This is error I get:
ORA-06550: line 11, column 18:
PLS-00302: component 'COLUMN_VAR' must be declared.
ORA-06550: line 11, column 16:
PL/SQL: ORA-00904: "X"."COLUMN_VAR": invalid identifier
ORA-06550: line 9, column 10:
PL/SQL: SQL Statement ignored
Any idea where I am going off the track?
Thanks in advance :-)
I think you need to provide the actual column name, rather than the string representing the column name in the statement. Would an execute immediate work for you?
declare
column_var varchar2(2048);
cursor pointer is
select column_name into column_var from user_tab_cols where table_name = 'TABLE_NAME';
begin
for x in pointer
loop
execute immediate
'update table_name ' ||
'set '|| x.column_var ||' = trim(both '' '' from '|| x.column_var ||')'||
'where '|| x.column_var ||' like ''% ''';
commit;
end loop;
end;
You need to use dynamic sql for this
BEGIN
FOR t IN (select column_name from user_tab_cols where table_name = 'MyTable')
LOOP
EXECUTE IMMEDIATE
'update MyTable set '||t.column_name||' = trim(both '' '' from '||t.column_name||') where '||t.column_name||' like ''% ''';
END LOOP;
END;
/
I need to check a condition. i.e:
if (condition)> 0 then
update table
else do not update
end if
Do I need to store the result into a variable using select into?
e.g:
declare valucount integer
begin
select count(column) into valuecount from table
end
if valuecount > o then
update table
else do
not update
You cannot directly use a SQL statement in a PL/SQL expression:
SQL> begin
2 if (select count(*) from dual) >= 1 then
3 null;
4 end if;
5 end;
6 /
if (select count(*) from dual) >= 1 then
*
ERROR at line 2:
ORA-06550: line 2, column 6:
PLS-00103: Encountered the symbol "SELECT" when expecting one of the following:
...
...
You must use a variable instead:
SQL> set serveroutput on
SQL>
SQL> declare
2 v_count number;
3 begin
4 select count(*) into v_count from dual;
5
6 if v_count >= 1 then
7 dbms_output.put_line('Pass');
8 end if;
9 end;
10 /
Pass
PL/SQL procedure successfully completed.
Of course, you may be able to do the whole thing in SQL:
update my_table
set x = y
where (select count(*) from other_table) >= 1;
It's difficult to prove that something is not possible. Other than the simple test case above, you can look at the syntax diagram for the IF statement; you won't see a SELECT statement in any of the branches.
Edit:
The oracle tag was not on the question when this answer was offered, and apparently it doesn't work with oracle, but it does work with at least postgres and mysql
No, just use the value directly:
begin
if (select count(*) from table) > 0 then
update table
end if;
end;
Note there is no need for an "else".
Edited
You can simply do it all within the update statement (ie no if construct):
update table
set ...
where ...
and exists (select 'x' from table where ...)
not so elegant but you dont need to declare any variable:
for k in (select max(1) from table where 1 = 1) loop
update x where column = value;
end loop;
I want to the the number of records in all tables that match a specific name criteria. Here is the SQL I built
Declare SQLStatement VARCHAR (8000) :='';
BEGIN
SELECT 'SELECT COUNT (*) FROM ' || Table_Name || ';'
INTO SQLStatement
FROM All_Tables
WHERE 1=1
AND UPPER (Table_Name) LIKE UPPER ('MSRS%');
IF SQLStatement <> '' THEN
EXECUTE IMMEDIATE SQLStatement;
END IF;
END;
/
But I get the following error:
Error at line 1
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at line 3
Script Terminated on line 1.
How do I modify this so that it runs for all matching tables?
Update:
Based on an answer received, I tried the following but I do not get anything in the DBMS_OUTPUT
declare
cnt number;
begin
for r in (select table_name from all_tables) loop
dbms_output.put_line('select count(*) from CDR.' || r.table_name);
end loop;
end;
/
declare
cnt number;
begin
for r in (select owner, table_name from all_tables
where upper(table_name) like ('%MSRS%')) loop
execute immediate 'select count(*) from "'
|| r.owner || '"."'
|| r.table_name || '"'
into cnt;
dbms_output.put_line(r.owner || '.' || r.table_name || ': ' || cnt);
end loop;
end;
/
If you're selecting from all_tables you cannot count on having been given the grants necessary to select from the table name. You should therefore check for the ORA-00942: table or view does not exist error thrown.
As to the cause for your error: You get this error because the select statement returns a result set with more than one row (one for each table) and you cannot assign such a result set to a varchar2.
By the way, make sure you enable dbms_output with SET SERVEROUT ON before executing this block.