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;
/
Related
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>
I'm an Oracle/PL/SQL Developer newbie, and I'm struggling to figure out how to see the output of this query:
DECLARE
ncount NUMBER;
vwhere VARCHAR2(1000) := '';
vselect VARCHAR2(1000) := ' select count(1) from ';
vsearchstr VARCHAR2(1000) := '1301 250 Sage Valley Road NW';
vline VARCHAR2(1000) := '';
istatus INTEGER;
BEGIN
DBMS_OUTPUT.ENABLE;
FOR k IN (SELECT a.table_name, a.column_name FROM user_tab_cols a WHERE a.data_type LIKE '%VARCHAR%')
LOOP
vwhere := ' where ' || k.column_name || ' = :vsearchstr ';
EXECUTE IMMEDIATE vselect || k.table_name || vwhere
INTO ncount
USING vsearchstr;
IF (ncount > 0)
THEN
dbms_output.put_line(k.column_name || ' ' || k.table_name);
ELSE
dbms_output.put_line('no output');
END IF;
END LOOP;
dbms_output.get_line(vline, istatus);
END;
I got this script from https://community.oracle.com/tech/developers/discussion/2572717/how-to-search-a-particular-string-in-whole-schema. It's supposed to find a string (vsearchstr) in the entire database. When I run this in PL/SQL Developer 14.0.6, it spits out no errors, says it took 0.172 seconds, but I don't see any output. I'm expecting the output to show under the Output tab:
I know the string '1301 250 Sage Valley Road NW' exists in the database so it should be finding it. Even if it doesn't, the ELSE block should be outputting 'no output'.
From what I understand, dbms_output.put_line() adds the given string to a buffer, and dbms_output.get_line() prints it to the output target (whatever it's set to). I understand that dbms_output needs to be enabled (hence the line DBMS_OUTPUT.ENABLE) and dbms_output.get_line() will only run after the BEGIN/END block it's in completes (I don't know if this means it has to be put outside the BEGIN/END block, but I couldn't avoid certain errors every time I did).
I've read through various stackoverflow posts about this issue, as well as a few external site:
https://docs.oracle.com/cd/F49540_01/DOC/server.815/a68001/dbms_out.htm#1000449
https://www.tutorialspoint.com/plsql/plsql_dbms_output.htm
...but nothing seems to be working.
How can I see the output, or if there's something wrong in the query above, can you tell what it is?
Thanks.
To enable output from DBMS_OUTPUT in PL/SQL Developer see this answer.
I'm looking for an alternative keyword to user_tab_cols for all schemas in the DB
Use ALL_TAB_COLS and catch the exceptions when you do not have enough privileges to read the table (and use quoted identifiers to match the case of user/table/column names):
DECLARE
found_row PLS_INTEGER;
vsearchstr VARCHAR2(1000) := '1301 250 Sage Valley Road NW';
BEGIN
FOR k IN (SELECT owner,
table_name,
column_name
FROM all_tab_cols t
WHERE data_type LIKE '%VARCHAR%'
-- Ignore columns that are too small
AND data_length >= LENGTH(vsearchstr)
-- Ignore all oracle maintained tables
-- Not supported on earlier Oracle versions
AND NOT EXISTS (
SELECT 1
FROM all_users u
WHERE t.owner = u.username
AND u.oracle_maintained = 'Y'
)
)
LOOP
DECLARE
invalid_privileges EXCEPTION;
PRAGMA EXCEPTION_INIT(invalid_privileges, -1031);
BEGIN
EXECUTE IMMEDIATE 'SELECT 1 FROM "' || k.owner || '"."' || k.table_name || '" WHERE "' || k.column_name || '" = :1 AND ROWNUM = 1'
INTO found_row
USING vsearchstr;
dbms_output.put_line('Found: ' || k.table_name || '.' || k.column_name);
EXCEPTION
WHEN invalid_privileges THEN
NULL;
WHEN NO_DATA_FOUND THEN
dbms_output.put_line('Not found: ' || k.table_name || '.' || k.column_name);
END;
END LOOP;
END;
/
I have already asked almost the same question before:
Finding non-numeric values in varchar column
And I received some great answer's and idea's to make not only fix the issue in my query but also make it much more efficient.
Old Query:
Select * From Table_Name
Where
(NOT REGEXP_LIKE(COLUMN_NAME, '^-?[0-9.]+$')
OR
LENGTH(Function_To_Fetch_Left_Component(COLUMN_NAME)) > (Precision-Scale)
OR
LENGTH(Column_Name) - LENGTH(REPLACE(Column_Name,'.','')) > 1)
New Query:
Select * From Table_Name
WHERE (Translate(column_name,'x0123456789-.','x') is not null
OR column_name = '.'
OR column_name = '-'
OR column_name = '-.'
OR Instr(column_name,'-') > 1
OR column_name like '%-%-%'
OR column_name like '%.%.%'
OR Instr(column_name||'.','.')-1 > (precision-scale) ) ;
Known limitations which are okay:
1.) Doesn't handle scientific notation. Its by design as I want to reject those.
2.) Doesn't check total precision (decimal component) however as while inserting oracle itself round's it off I am fine with it.
3.) Doesn't check for leading zero's.
Please help to see if the above query cover's everything and I haven't missing anything.
Another great solution by one Stackoverflow member however increased execution time by 2.5 times:
Function:
CREATE OR REPLACE function temp_is_number(p_test_value varchar2,
p_scale NUMBER,
p_precision NUMBER) return varchar2 is
l_result varchar2(1);
begin
execute immediate 'DECLARE l_number NUMBER; ' ||
'l_test_number NUMBER(' || p_precision || ',' || p_scale || '); ' ||
'BEGIN ' ||
' l_number := cast(:b1 as number); ' ||
' l_test_number := cast(:b1 as number); ' ||
' IF (l_number = l_test_number) THEN ' ||
' :b2 := ''Y''; ' ||
' ELSE ' ||
' :b2 := ''N''; ' ||
' END IF; ' ||
'EXCEPTION ' ||
' WHEN OTHERS THEN ' ||
' :b2 := ''N''; ' ||
'END;'
using in p_test_value, out l_result;
return l_result;
end;
/
Select Count(1) from Table_Name Where temp_is_number (Column_Name,scale,precision) = 'N';
CREATE OR REPLACE PROCEDURE SP_NEW_PROCEDURE1( )
RETURNS REFTABLE(employees)
LANGUAGE NZPLSQL AS
BEGIN_PROC
DECLARE
l_conditions varchar(1000);
p_rec RECORD;
BEGIN
FOR P_REC IN select empid, mgrid, empname, salary from employees where mgrid = 7
LOOP
l_conditions := 'insert into ' ||
REFTABLENAME ||
' VALUES (' ||
P_REC.EMPID ||
',' ||
P_REC.MGRID ||
',' ||
P_REC.EMPNAME ||
',' ||
P_REC.SALARY ||
' ) ; ' ;
execute immediate l_conditions;
l_conditions := ' ';
END LOOP;
RETURN REFTABLE;
END;
END_PROC;
When I run this:
select SP_NEW_PROCEDURE1()
I get the errors:
ERROR [01000] NOTICE: Error occurred while executing PL/pgSQL function SP_NEW_PROCEDURE1
ERROR [01000] NOTICE: line 24 at execute statement
ERROR [42S22] ERROR: Attribute 'DAN' not found
Can someone help whats wrong ...thanks
This has nothing do with the cursor itself, and everything to do with how you are building your dynamical SQL string.
When building dynamic SQL in a Netezza stored procedure, you can use the quote_ident and quote_literal helper functions to let the system know whether you are passing it a literal, or whether you are passing it an identifier. There is an example in the online documentation here. Essentially all they do is figure out the escaped quotation notation needed.
Since you are trying to put the values stored in the columns of your P_REC record into the VALUES part of an insert statement, you could use quote_literal like this:
CREATE OR REPLACE PROCEDURE SP_NEW_PROCEDURE1( )
RETURNS REFTABLE(employees)
LANGUAGE NZPLSQL AS
BEGIN_PROC
DECLARE
l_conditions varchar(1000);
p_rec RECORD;
BEGIN
FOR P_REC IN select empid, mgrid, empname, salary from employees where mgrid = 7
LOOP
l_conditions := 'insert into ' ||
REFTABLENAME ||
' VALUES (' ||
quote_literal(P_REC.EMPID) ||
',' ||
quote_literal(P_REC.MGRID) ||
',' ||
quote_literal(P_REC.EMPNAME) ||
',' ||
quote_literal(P_REC.SALARY ) ||
' ) ; ' ;
execute immediate l_conditions;
l_conditions := ' ';
END LOOP;
RETURN REFTABLE;
END;
END_PROC;
That being said, using a cursor to loop over records to insert a row one at a time is horribly inefficient in an MPP database like Netezza. Assuming this question is a follow-on from your question about an alternative to a recursive CTE to explore hierarchies, there's nothing wrong with looping in general, but try to avoid doing it record by record. Here is a version that will exploit the MPP nature of the system. For the record, if you are going to return your result set to a REFTABLE, then your only choice is Dynamic SQL.
CREATE OR REPLACE PROCEDURE SP_NEW_PROCEDURE1( )
RETURNS REFTABLE(employees)
LANGUAGE NZPLSQL AS
BEGIN_PROC
DECLARE
l_conditions varchar(1000);
p_rec RECORD;
BEGIN
-- FOR P_REC IN select empid, mgrid, empname, salary from employees where mgrid = 7
-- LOOP
l_conditions := 'insert into ' ||
REFTABLENAME ||
' select empid, mgrid, empname, salary from employees where mgrid = 7 ; ' ;
execute immediate l_conditions;
l_conditions := ' ';
-- END LOOP;
RETURN REFTABLE;
END;
END_PROC;
I suspect that you are building a query that is meant to insert a literal 'DAN' but which does not include the required quote marks, hence it is referencing DAN -- the optimiser is therefore trying to find an attribute of that name.
So the fix is to include the quotation marks when you build the SQL insert statement, or (preferably) to just use static SQL to insert the values instead of execute immediate.
When in doubt, always look at the data, as this would probably have been obvious to you if you inspected the value of l_conditions.
The following function generates the SELECT to produce INSERT STATEMENT of the records of an Oracle table:
CREATE OR REPLACE FUNCTION GEN_INSERT_STATEMENT (IN_TABLE_NAME VARCHAR2)
RETURN CLOB
IS
LC$COLS_SELECT CLOB;
LC$COLS_VALUES CLOB;
LC$COLOUMN CLOB;
CURSOR LCUR$TAB_COLUMNS (IN_TABLE_NAME VARCHAR2)
IS
SELECT COLUMN_NAME, DATA_TYPE, COLUMN_ID
FROM USER_TAB_COLS
WHERE TABLE_NAME = IN_TABLE_NAME
ORDER BY COLUMN_ID;
BEGIN
FOR LREC$TAB_COLUMNS IN LCUR$TAB_COLUMNS (UPPER (IN_TABLE_NAME))
LOOP
LC$COLS_SELECT :=
LC$COLS_SELECT
|| CASE LREC$TAB_COLUMNS.COLUMN_ID WHEN 1 THEN '' ELSE ',' END
|| LREC$TAB_COLUMNS.COLUMN_NAME;
IF INSTR (LREC$TAB_COLUMNS.DATA_TYPE, 'CHAR') > 0
THEN
LC$COLOUMN :=
'''''''''||REPLACE('
|| LREC$TAB_COLUMNS.COLUMN_NAME
|| ','''''''','''''''''''')||''''''''';
ELSIF INSTR (LREC$TAB_COLUMNS.DATA_TYPE, 'DATE') > 0
THEN
LC$COLOUMN :=
'''TO_DATE(''''''||TO_CHAR(' || LREC$TAB_COLUMNS.COLUMN_NAME
|| ',''mm/dd/yyyy hh24:mi:ss'')||'''''',''''mm/dd/yyyy hh24:mi:ss'''')''';
ELSE
LC$COLOUMN := LREC$TAB_COLUMNS.COLUMN_NAME;
END IF;
LC$COLS_VALUES :=
LC$COLS_VALUES
|| CASE LREC$TAB_COLUMNS.COLUMN_ID WHEN 1 THEN '' ELSE ',' END
|| '''||DECODE('
|| LREC$TAB_COLUMNS.COLUMN_NAME
|| ',NULL,''NULL'','
|| LC$COLOUMN
|| ')||''';
END LOOP;
RETURN 'SELECT ''INSERT INTO '
|| IN_TABLE_NAME
|| ' ('
|| LC$COLS_SELECT
|| ') VALUES ('
|| LC$COLS_VALUES
|| ');'' FROM '
|| IN_TABLE_NAME
|| ';';
END;
/
The problem is that this function DOES NOT HANDLE the case in which are existing some VARCHAR2 fields with strings ending with: CHR(0)
Usage:
SELECT GEN_INSERT_STATEMENT ('MY_ORACLE_TABLE_NAME') FROM DUAL;
... generates a SELECT to produce the INSERT statement.
If in a VARCHAR2 field the value ends with CHR(0), the INSERT statement will be truncated exactly where is CHR(0) located.
How can I fix this?
If I understand what you're seeing, you need to either strip the null character from the value:
IF INSTR (LREC$TAB_COLUMNS.DATA_TYPE, 'CHAR') > 0
THEN
LC$COLOUMN :=
'''''''''||REPLACE(REPLACE('
|| LREC$TAB_COLUMNS.COLUMN_NAME
|| ',CHR(0),NULL),'''''''','''''''''''')||''''''''';
which gives your function the output:
SELECT 'INSERT INTO T42 (STR) VALUES ('||DECODE(STR,NULL,'NULL',''''||REPLACE(REPLACE(STR,CHR(0),NULL),'''','''''')||'''')||');' FROM T42;
and with a dummy table called t42 with a single column str containing 'hello' followed by a null character, the output is:
INSERT INTO T42 (STR) VALUES ('Hello');
Or preserve it during the insert:
LC$COLOUMN :=
'''''''''||REPLACE(REPLACE('
|| LREC$TAB_COLUMNS.COLUMN_NAME
|| ','''''''',''''''''''''),CHR(0),''''''||CHR(0)||'''''')||''''''''';
which gives:
SELECT 'INSERT INTO T42 (STR) VALUES ('||DECODE(STR,NULL,'NULL',''''||REPLACE(REPLACE(STR,'''',''''''),CHR(0),'''||CHR(0)||''')||'''')||');' FROM T42;
and finally:
INSERT INTO T42 (STR) VALUES ('Hello'||CHR(0)||'');
So in the second version the null character is removed from the fixed string (anywhere, not just at the end), and is put back as part of the insert statement.
This seems like a horrible approach though, when you could export the data, or let SQL Developer or some other IDE generate the insert statements for you. There maybe other data types and values that give you headaches, and which someone else has already worked hard to overcome. Unless you really need to be able to see the actual statements, using expdp would be far simpler.
Use this:
https://github.com/teopost/oracle-scripts/blob/master/fn_gen_inserts.sql
Usage:
select fn_gen_inserts('select * from tablename', 'p_new_owner_name', 'p_new_table_name')
from dual;
where:
p_sql – dynamic query which will be used to export metadata rows
p_new_owner_name – owner name which will be used for generated INSERT
p_new_table_name – table name which will be used for generated INSERT
You can find original source code here:
http://dbaora.com/oracle-generate-rows-as-insert-statements-from-table-view-using-plsql/