sys_refcursor without column name - sql

I am working on a function to print values from a table.
create or replace FUNCTION UserDetails(p_startDate IN VARCHAR, p_endDate in VARCHAR) RETURN sys_refcursor
AS
v_cursor sys_refcursor;
BEGIN
IF to_date(p_endDate, 'dd-mm-yyyy') - to_date(p_startDate, 'dd-mm-yyyy') > 90 THEN
RAISE invalid_number;
END IF;
OPEN v_cursor FOR SELECT UPPER(name) NAME, MAX(Updated_date) UPDATED_DATE
FROM s_user_data
WHERE Updated_date between to_Date(p_startDate,'DD-MON-YYYY') and to_Date(p_endDate,'DD-MON-YYYY')
ORDER BY Updated_date DESC;
RETURN v_cursor;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE (TO_CHAR (SYSDATE, 'HH24:MI:SS') || ' Error: ' || SQLCODE || ' ' || SQLERRM);
RAISE;
END;
After running the function: select UserDetails('08-MAY-2021','09-MAY-2021') from dual;
I am getting below output:
{<NAME=XYZ,UPDATED_DATE=08-MAY-21 02.58.30.714149000 PM>,<NAME=ABC,UPDATED_DATE=08-MAY-21 02.57.45.664223000 PM>,<NAME=MNOP,UPDATED_DATE=07-MAY-21 07.37.14.197251000 PM>,}
I have to achieve it like below:
{<XYZ,08-MAY-21 02.58.30.714149000 PM>,<ABC,08-MAY-21 02.57.45.664223000 PM>,<MNOP,07-MAY-21 07.37.14.197251000 PM>,}
Is there any way to get the output without column name. Please advise.

If you want to execute from sqlplus, put:
SET HEAD OFF
if you are executing from sql developer or some IDE, Just before of anonym block put the same statement of: SET HEAD OFF, is equivalent to SET HEADING OFF.
In colcusion the artice is here: https://docs.oracle.com/cd/B19306_01/server.102/b14357/ch12040.htm
And the general rule is:
SET HEAD[ING] OFF

Your function is not consistent. Why do you declare input parameters p_startDate and p_endDate as VARCHAR instead of DATE?
In your code you have
to_date(p_endDate, 'dd-mm-yyyy') - to_date(p_startDate, 'dd-mm-yyyy')
and further down
to_Date(p_startDate,'DD-MON-YYYY') and to_Date(p_endDate,'DD-MON-YYYY')
The input values cannot have format dd-mm-yyyy and DD-MON-YYYY at the same time.
Regarding your actual question: It's really not clear what you like to get. How do you call the function?
If your function returns a RefCursor then you must call the function (which does not mean select ... from dual) and process the cursor. Another option could be DBMS_SQL.RETURN_RESULT or pipeline function. But as long as your requirements are not clear, it will be difficult to help you.

Related

PL SQL Function throws error

This is my function
declare
b_date date;
reset_status integer:=0 ;
begin
select lst_reset_dt_tm
into b_date
from TABLE1
where schm_sts in ('READY','NOT READY')
and schm_nm like 'SC%'
and upper(srvr_nm) = ( select upper(machine)
from v$session
where program like '%(PMON)%');
select 1
into reset_status
from TABLE1
where schm_nm = upper('OC1')
and lst_reset_dt_tm > to_date(b_date, 'mm/dd/yyyy')
and upper(srvr_nm) = ( select upper(machine)
from v$session
where program like '%(PMON)%');
dbms_output.put_line(reset_status);
exception
when no_data_found then
dbms_output.put_line(reset_status);
end;
/
spool off
when i compile i get the error
declare
*
ERROR at line 1:
ORA-01858: a non-numeric character was found where a numeric was expected
ORA-06512: at line 16
Basically the lower part of the query is failing ,this however works on an oracle 11g machine but not on oracle 12c. Can anyone help me debug ?
Kaushik is absolutely right, this is an anonymous block, not a function. That said this error occurs when you compare a numeric field to a character field. My suspicion is that svr_nm or machine is a numeric field, I can't tell which. Making both character will solve your issue.
Or the issue is your second SQL statement, I am converting everything using to_char, you just need to do the fields that are numeric. Try running this SQL and see if it works, if it does, strip out all TO_CHAR's for character fields.
DECLARE
b_date DATE;
reset_status INTEGER := 0;
BEGIN
SELECT lst_reset_dt_tm
INTO b_date
FROM table1
WHERE schm_sts IN ('READY', 'NOT READY')
AND schm_nm LIKE 'SC%'
AND UPPER (TO_CHAR (srvr_nm)) = TO_CHAR (
(SELECT UPPER (machine)
FROM v$session
WHERE program LIKE '%(PMON)%')
);
SELECT 1
INTO reset_status
FROM table1
WHERE TO_CHAR (schm_nm) = UPPER ('OC1')
AND lst_reset_dt_tm > TO_DATE (b_date, 'mm/dd/yyyy')
AND TO_CHAR (UPPER (srvr_nm)) = TO_CHAR (
(SELECT UPPER (machine)
FROM v$session
WHERE program LIKE '%(PMON)%')
);
DBMS_OUTPUT.put_line (reset_status);
EXCEPTION
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.put_line (reset_status);
END;
/

function oracle to postgresql

I'm migrating an Oracle database to PostgreSQL, to transfer the tables I had no problem, however I'm having trouble transcribing a function for it to run in Postgres, below the function found in Oracle:
create or replace FUNCTION "FN_HOUR_MINUTE" (P_HOUR IN NUMBER)
RETURN NUMBER
IS
-- PL/SQL Specification
V_RETORN NUMBER(4);
-- Convert hour to minute
-- PL/SQL Block
BEGIN
V_RETORN := 60*TO_NUMBER(SUBSTR(LTRIM(TO_CHAR(P_HOUR,'0000'),' ') ,1,2))+
TO_NUMBER(SUBSTR(LTRIM(TO_CHAR(P_HOUR, '0000'),' '), 3,2));
RETURN V_RETORN;
EXCEPTION
WHEN OTHERS THEN
RETURN NULL ;
END;
I tried writing in postgres as follows:
CREATE OR REPLACE FUNCTION fn_hour_minute(p_hour in NUMERIC)
RETURNS NUMERIC(4) AS $$
DECLARE
v_retorn NUMERIC(4);
BEGIN
v_retorn := 60*TO_NUMBER(SUBSTR(LTRIM(TO_CHAR(p_hour,'0000'),' ') ,1,2))+
TO_NUMBER(SUBSTR(LTRIM(TO_CHAR(p_hour, '0000'),' '), 3,2));
RETURN v_retorn;
END;
$$ LANGUAGE plpgsql;
But gives an error that says the to_number function does not exist.
If you spread the expression into factors:
select TO_CHAR(1234,'0000'),
ltrim(TO_CHAR(1234,'0000')),
substr(ltrim(TO_CHAR(1234,'0000')),1,2),
substr(ltrim(TO_CHAR(1234,'0000')),3,2)
from dual;
TO_CH LTRIM SU SU
----- ----- -- --
1234 1234 12 34
you will see that this is just a very advanced way to calculate such an expression
60 * TRUNC( p_hour / 100 ) + p_hour % 100
You forgot to include the formating for the TO_NUMBER section. Update the TO_NUMBER to TO_NUMBER(SUBSTR(LTRIM(TO_CHAR(p_hour,'0000'),' ') ,1,2), '0000') and it should work.

Formatting Oracle SQL column with non-standard format

I have an Oracle database where a file location is stored. Unfortunately, it isn't properly formatted.
For example, the file location is C:\images\00\45\34\34.IMG and is stored in the database as: 00453434.
I am able to use CONCAT to put C:\images and .IMG around the column, but I can't format the actual location to put \s in.
I've tried to_char, and to_number but it requires a specified format.
(My crappy attempt: to_char(filename, '09"\"09"\"09"\"09'))
Is there any way in SQL to format freely?
One method... assuming fixed length of each segment meaning each path is 2 digits including file name.
select 'C:\images\'|| substr('00453434',1,2) || '\' ||
substr('00453434',3,2) || '\' ||
substr('00453434',5,2) || '\' ||
substr('00453434',7,2) || '.IMG' as fullPath from dual
If needed at multiple queries, creating a PL/SQL function can also solve your problem. This example also assumes that each path has 2 digits, but supports paths of different lengths:
CREATE OR REPLACE FUNCTION GET_FILENAME(ID IN VARCHAR2, PREFIX IN VARCHAR2, SUFFIX IN VARCHAR2) RETURN VARCHAR2 IS
i PLS_INTEGER;
r VARCHAR2(4000);
BEGIN
r := PREFIX;
FOR i IN 1..LENGTH(ID)/2 LOOP
r := r || '\' || SUBSTR(ID, 2*i-1, 2);
END LOOP;
RETURN r || SUFFIX;
END;
/
The function can then be used within your standard SQL queries (or view definitions) as follows:
WITH TA_FILES AS (
SELECT '12345678' AS ID FROM DUAL
)
SELECT GET_FILENAME(ID, 'C:\images', '.IMG') FROM TA_FILES

NO_DATA_FOUND EXCEPTION in two cursors - Oracle PL/SQL

I need to be able to raise an exception as the title says. The exception I have currently gives me this error:
DBMS_OUTPUT.PUT_LINE (‘No rows found’);
*
ERROR at line 39:
ORA-06550: line 39, column 23:
PLS-00103: Encountered the symbol "`" when expecting one of the following:
( ) - + case mod new not null others <an identifier>
<a double-quoted delimited-identifier> <a bind variable>
table avg count current exists max min prior sql stddev sum
variance execute multiset the both leading trailing forall
merge year month DAY_ hour minute second timezone_hour
timezone_minute timezone_region timezone_abbr time timestamp
interval date
<a string literal with character set specification>
This is my code:
SET SERVEROUTPUT ON FORMAT WRAP SIZE 12000
Declare
v_model VARCHAR2(40);
v_carcategory VARCHAR2(40);
v_totalcars NUMBER;
v_maxdate DATE:=TO_DATE(1, 'J');
Cursor carcur IS
SELECT * FROM i_car;
CURSOR c1(v_car_registration VARCHAR2) IS
SELECT * from i_booking a
WHERE a.registration=v_car_registration;
Begin
For car_rec in carcur
LOOP
v_maxdate:=TO_DATE(1, 'J');
for rec in c1(car_rec.registration)
LOOP
IF rec.date_reserved > v_maxdate
then
v_maxdate:=rec.date_reserved ;
If car_rec.Cost <=50000 THEN v_carcategory := 'Budget Car';
End IF;
If car_rec.Cost BETWEEN 50000 AND 100000 THEN v_carcategory := 'Standard Car';
End IF;
If car_rec.Cost >=100000 THEN v_carcategory := 'Premium Car';
End If;
end IF;
v_totalcars := findtotalcarmodels (car_rec.model_name);
end loop;
DBMS_OUTPUT.PUT_LINE('Registration:'|| ' '|| car_rec.registration);
DBMS_OUTPUT.PUT_LINE('Cost:'|| ' $' || car_rec.Cost);
DBMS_OUTPUT.PUT_LINE('Model Name:'|| ' '|| car_rec.model_name);
DBMS_OUTPUT.PUT_LINE('Car Category:'|| ' '||v_carcategory);
DBMS_OUTPUT.PUT_LINE('Total number of Cars:'|| ' '||v_totalcars);
DBMS_OUTPUT.PUT_LINE('Most Recent Rental Date: '|| ' '||v_maxdate);
DBMS_OUTPUT.NEW_LINE;
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE (‘No rows found’);
END;
/
I am not overly sure about the positioning of my exception. Any advice would be much appreciated.
The immediate problem is that your dbms_output.put_line call is using Microsoft curly quotes rather than the standard single-quote character to delimit the string. You need to use the standard character, not the Microsoft character (all your other strings appear to be using the standard character).
Taking a step back, it makes no sense to catch a no_data_found exception only to call dbms_output. There is no guarantee that data written to dbms_output will ever be sent to the client application or that the client application will ever display it to a user.
It also doesn't appear that you have any code that would potentially raise a no_data_found exception. Opening a cursor that returns 0 rows does not result in an exception. If you expected to receive exactly 1 row, you could write a SELECT INTO statement and that would raise an exception if anything other than 1 row was returned. If your goal here is to determine how many times you iterated through the loop, you could potentially use the %rowcount attribute of the cursor.
Finally, it would make thinks much clearer if you formatted your code so that lines were indented based on what block they were a part of. Code like
FOR rec IN cursor
LOOP
IF <<something>>
THEN
<<do something>>
END IF;
IF <<something else>>
THEN
<<something else>>
END IF;
<<more stuff>>
END LOOP;
is much easier to follow.

Storing a variable in Oracle PL SQL

I hope you can help - I'm strying to assign a date to a variable, and then call that variable in my select query. The code I'm posting is only part of what I'm strying to do, I will be calling that variable more than once.
I've tried to google for help, but I'm stuck on using the Select Into statement, as I've got so many selects already.
DECLARE
CurrMonth DATE := '27 may 2012'; -- Enter 27th of current month
BEGIN
SELECT
a.policynumber
,a.cifnumber
,a.phid
,a.policystartdate
,b.sistartdate
,c.dateofbirth
,'28/02/2013' AS TaxYearEnd
--Complete tax year end in the SELECT statement (once for tax year end and once for the age at tax year end)
,round ((months_between('28 feb 2013',c.dateofbirth)/12),8) AS AgeAtTaxYearEnd
,b.sifrequency AS CurrSIFrequ
,b.sivalue AS CurrentSIValue
,b.simode AS CurrentSIMode
,d.anniversarydate AS CurrentAnnDate
,d.anniversaryvalue AS CurrentAnnValue
,b.ruleeffectivedate
,b.sistatus AS CurrentSIStatus
,b.paymentbranchcode AS CurrSIBranchCode
,b.transferaccounttype AS CurrSIAccountType
,b.transferaccountnumber AS CurrSIAccountNo
,SUM(k.unitbalance) AS unitbalance
,a.latestrule
FROM fcislob.policytbl a
,fcislob.policysitbl b
,fcislob.unitholderdetailtbl c
,fcislob.policyanniversaryvaluetbl d
,fcislob.unitholderfundtbl k
WHERE a.policynumber = b.policynumber
AND a.policynumber = d.policynumber
AND b.policynumber = d.policynumber
AND a.phid = c.unitholderid
AND a.phid = k.unitholderid
AND c.unitholderid = k.unitholderid
AND a.ruleeffectivedate = b.ruleeffectivedate
AND a.ruleeffectivedate = d.ruleeffectivedate
AND b.ruleeffectivedate = d.ruleeffectivedate
AND a.latestrule <> 0
AND c.authrejectstatus = 'A'
AND a.phid LIKE 'AGLA%'
AND b.sistatus <> 'C'
AND k.unitbalance >0
AND b.transactiontype = '64'
AND b.sistartdate <= CurrMonth
AND b.sifrequency = 'M'
GROUP BY a.policynumber, a.cifnumber, a.phid, a.policystartdate, b.sistartdate , c.dateofbirth,b.sifrequency, b.sivalue, b.simode, d.anniversarydate, d.anniversaryvalue, b.ruleeffectivedate,
b.sistatus, b.paymentbranchcode, b.transferaccounttype, b.transferaccountnumber, b.policynumber, a.latestrule;
END;
You have a group by clause so you need to group by all collumns which aren't aggregated.
Are you sure you have only one record in the result ?
As #TonyAndrews said, you need the into clause. You need to declare a variable for every collumn and insert into it,
i.e.:
DECLARE
v_policynumber fcislob.policytbl.policynumber%TYPE;
v_cifnumber fcislob.policytbl.cifnumber%TYPE;
v_phid fcislob.policytbl.phid%TYPE;
-- and so on ...
v_sum number;
BEGIN
SELECT SUM(k.unitbalance), a.policynumber, a.cifnumber, a.phid -- and so on ...
INTO v_sum, v_policynumber, v_cifnumber, v_phid -- and so on ...
FROM fcislob.policytbl a -- and so on ...
GROUP BY a.policynumber, a.cifnumber, a.phid -- and so on ...
END;
The way you deal with dates is not "healthy", IMO it's better to use to_date and not realy on NLS parameters
If you are only using PL/SQL to be able persist the date value between several plain select statements, then it will complicate things a lot - switching to select into isn't straightforward if you just want to display the results of the query, particularly if there are multiple rows.
Since you mention you have many selects, I'm guessing you have them in a script file (example.sql) and are running them through SQL*Plus, like sqlplus user/password #example. If so you can keep your plain SQL statements and use positional parameters, substitution variables, or bind variables to track the date.
First option is if you want to pass the date on the command line, like sqlplus user/password #example 27-May-2012:
set verify off
select 'Supplied date is ' || to_date('&1', 'DD-Mon-RRRR') from dual;
This uses the first positional parameter, which is referenced as &1, and converts it to a date as needed in the query. The passed date has to be in the format expected by the to_date function, which in this case I've made DD-Mon-RRRR. Note that you have to enclose the variable in single quotes, otherwise (unless it's a number) Oracle will try to interpret it as a column name rather than a value. (The set verify off suppresses messages SQL*Plus shows by default whenever a substitution variable is used).
You can reference &1 as many times as you want in your script, but you may find it easer to redefine it with a meaningful name - particularly useful when you have multiple positional parameters - and then use that name in your queries.
define supplied_date = &1
select 'Supplied date is ' || to_date('&supplied_date', 'DD-Mon-RRRR') from dual;
If you don't want to pass the date from the command line, you can use a fixed value instead. I'm using a different default date format here, which allows me to use the date literal syntax or the to_date function.
define curr_date = '2012-05-31';
select 'Today is ' || date '&curr_date' from dual;
select 'Today is ' || to_date('&curr_date', 'YYYY-MM-DD') from dual;
You may want to derive the date value, using the result of one query in a later one. You can use the column ... new_value SQL*Plus command to do that; this defines a substitution variable curr_date with the string value from the today column (alias) from any future query, and you can then use it in the same way:
column today new_value curr_date
select to_char(sysdate, 'DD-Mon-YYYY') as today from dual;
select 'Today is ' || to_date('&curr_date', 'DD-Mon-YYYY') from dual;
You can also use bind variables, which you define with the var[iable] command, and set with exec:
var curr_date varchar2(10);
exec :curr_date := '2012-05-31';
select 'Today is ' || to_date(:curr_date, 'YYYY-MM-DD') from dual;
(exec is actually a wrapper around an anonymous PL/SQL block, so it means begin :curr_date := '2012-05-31'; end;, but you only really see that if there's an error). Note that it knows the bind variable is a string, so you don't enclose this in single quotes.
You can mix and match, so if you passed a date on the command line you could assign it to a bind variable with exec :supplied_date := '&1'; or to use current date exec :curr_date := to_char(sysdate, 'YYYY-MM-DD').
Many combinations possible, so you'll need to pick what suits what you're trying to do, and which you find simplest. Most, if not all, of these should work in SQL Developer too, but not sure about other clients.