Related
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.
I have a column in which numbers are stored as string because of the nature of the column where any kind of data type is expected like date, numbers, alpha numeric,
etc.
Now i need to check if the values in that column is in defined range or not here is sample data for testing
create table test (val varchar2(10));
insert into test values ('0');
insert into test values ('67');
insert into test values ('129');
insert into test values ('200');
insert into test values ('1');
Here expected range in which value should be is 0-128 if values are not in range then i need to filter them out for further processing.
For this i have written some queries but none of then is giving requires output.
select *
from test
where val not between '0' and '128';
select *
from test
to_number(val, '9') not between to_number('0', '9') and to_number('128', '9999');
select * from test where
to_number(val, '9') < TO_NUMBER('0', '9')
or
to_number(val, '999') > TO_NUMBER('128', '999')
;
These above queries are producing desired output !! :(
I ma using DB version --
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
Just leave the format out of to_number():
select *
from test
where to_number(val) not between to_number('0') and to_number('128');
The numeric format is needed for conversion to a character. If you pass it in to to_number(), then it expects a number of that format -- and you might get the number of digits wrong.
Or, better yet:
select *
from test
where to_number(val) not between 0 and 128;
Or, even better yet, change the column to contain a number rather than a string.
EDIT:
If the problem is that your value is not a number (which is quite different from your original question), then test for that. This is one situation where case is appropriate in the where clause (because case guarantees the order of evaluation of its arguments:
where (case when regexp_like(val, '[^-0-9]') then 'bad'
when cast(val as number) < 0 then 'bad'
when cast(val as number) > 128 then 'bad'
else 'good'
end) = 'bad'
#GordonLinoff's answer works with the sample data you've shown, but it will error with ORA-01722 "invalid number" if you have any values which do no represent numbers. Your sample data only has good values, but you said that for your real field "any kind of data type is expected like date, numbers, alpha numeric, etc."
You can get around that with a function that attempts to convert the stored string value to a number, and returns null if it gets that exception. A simple example:
create function safe_to_number (p_str varchar2) return number is
begin
return to_number(p_str);
exception
when value_error then
return null;
end;
/
You can then do
select *
from test
where safe_to_number(val) not between 0 and 128;
VAL
----------
129
200
Anything that can't be converted and causes an ORA-06502 value-error exception will be seen as null, which is neither between nor not between any values you supply.
If you need to check date ranges you can do something similar, but there are more errors possible, and you may have dates in multiple formats; you would need to declare exceptions and initialise them to known error numbersto catch the ones you expect to see. This isn't complete, but you could start with something like:
create function safe_to_date (p_str varchar2) return date is
l_formats sys.odcivarchar2list;
format_ex_1 exception;
format_ex_2 exception;
format_ex_3 exception;
format_ex_4 exception;
format_ex_5 exception;
pragma exception_init(format_ex_1, -1840);
pragma exception_init(format_ex_2, -1841);
pragma exception_init(format_ex_3, -1847);
pragma exception_init(format_ex_4, -1858);
pragma exception_init(format_ex_5, -1861);
-- add any others you might get
begin
-- define all expected formats
l_formats := sys.odcivarchar2list('YYYY-MM-DD', 'DD/MM/YYYY', 'DD-MON-RRRR'); -- add others
for i in 1..l_formats.count loop
begin
return to_date(p_str, l_formats(i));
exception
when format_ex_1 or format_ex_2 or format_ex_3 or format_ex_4 or format_ex_5 then
-- ignore the exception; carry on and try the next format
null;
end;
end loop;
-- did not match any expected formats
return null;
end;
/
select *
from test
where safe_to_date(val) not between date '2016-02-01' and date '2016-02-29';
Although I wouldn't normally use between for dates; if you don't have any with times specified then you'd get away with it here.
You could use when others to catch any exception without having to declare them all, but even for this that's potentially dangerous - if something is breaking in a way you don't expect you want to know about it, not hide it.
Of course, this is an object lesson in why you should store numeric data in NUMBER columns and dates in DATE or TIMESTAMP fields - trying to extract useful information when everything is stored as strings is messy, painful and inefficient.
I think the best approach you can try in this condition is use
TRANSLATE function to eliminate the alphanumeric characters. Once its
done all now is OLD school technique to check the data by using NOT
BETWEEN function Hope this helps.
SELECT B.NM
FROM
(SELECT a.nm
FROM
(SELECT '0' AS nm FROM dual
UNION
SELECT '1' AS nm FROM dual
UNION
SELECT '68' AS nm FROM dual
UNION
SELECT '129' AS nm FROM dual
UNION
SELECT '200' AS nm FROM dual
UNION
SELECT '125a' AS nm FROM dual
)a
WHERE TRANSLATE(a.nm, ' +-.0123456789', ' ') IS NULL
)b
WHERE b.nm NOT BETWEEN 1 AND 128;
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.
In a SQL query on Oracle 10g, I need to determine whether a string is numeric or not. How can I do this?
You can use REGEXP_LIKE:
SELECT 1 FROM DUAL
WHERE REGEXP_LIKE('23.9', '^\d+(\.\d+)?$', '')
You ca try this:
SELECT LENGTH(TRIM(TRANSLATE(string1, ' +-.0123456789', ' '))) FROM DUAL
where string1 is what you're evaluating. It will return null if numeric. Look here for further clarification
I don't have access to a 10G instance for testing, but this works in 9i:
CREATE OR REPLACE FUNCTION is_numeric (p_val VARCHAR2)
RETURN NUMBER
IS
v_val NUMBER;
BEGIN
BEGIN
IF p_val IS NULL OR TRIM (p_val) = ''
THEN
RETURN 0;
END IF;
SELECT TO_NUMBER (p_val)
INTO v_val
FROM DUAL;
RETURN 1;
EXCEPTION
WHEN OTHERS
THEN
RETURN 0;
END;
END;
SELECT is_numeric ('333.5') is_numeric
FROM DUAL;
I have assumed you want nulls/empties treated as FALSE.
As pointed out by Tom Kyte in http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:7466996200346537833, if you're using the built-in TO_NUMBER in a user defined function, you may need a bit of extra trickery to make it work.
FUNCTION is_number(x IN VARCHAR2)
RETURN NUMBER
IS
PROCEDURE check_number (y IN NUMBER)
IS
BEGIN
NULL;
END;
BEGIN
PRAGMA INLINE(check_number, 'No');
check_number(TO_NUMBER(x);
RETURN 1;
EXCEPTION
WHEN INVALID_NUMBER
THEN RETURN 0;
END is_number;
The problem is that the optimizing compiler may recognize that the result of the TO_NUMBER is not used anywhere and optimize it away.
Says Tom (his example was about dates rather then numbers):
the disabling of function inlining will make it do the call to
check_date HAS to be made as a function call - making it so that the
DATE has to be pushed onto the call stack. There is no chance for the
optimizing compiler to remove the call to to_date in this case. If the
call to to_date needed for the call to check_date fails for any
reason, we know that the string input was not convertible by that date
format.
Here is a method to determine numeric that can be part of a simple query, without creating a function. Accounts for embedded spaces, +- not the first character, or a second decimal point.
var v_test varchar2(20);
EXEC :v_test := ' -24.9 ';
select
(case when trim(:v_test) is null then 'N' ELSE -- only banks, or null
(case when instr(trim(:v_test),'+',2,1) > 0 then 'N' ELSE -- + sign not first char
(case when instr(trim(:v_test),'-',2,1) > 0 then 'N' ELSE -- - sign not first char
(case when instr(trim(:v_test),' ',1,1) > 0 then 'N' ELSE -- internal spaces
(case when instr(trim(:v_test),'.',1,2) > 0 then 'N' ELSE -- second decimal point
(case when LENGTH(TRIM(TRANSLATE(:v_test, ' +-.0123456789',' '))) is not null then 'N' ELSE -- only valid numeric charcters.
'Y'
END)END)END)END)END)END) as is_numeric
from dual;
I found that the solution
LENGTH(TRIM(TRANSLATE(string1, ' +-.0123456789', ' '))) is null
allows embedded blanks ... it accepts "123 45 6789" which for my purpose is not a number.
Another level of trim/translate corrects this. The following will detect a string field containing consecutive digits with leading or trailing blanks such that to_number(trim(string1)) will not fail
LENGTH(TRIM(TRANSLATE(translate(trim(string1),' ','X'), '0123456789', ' '))) is null
For integers you can use the below. The first translate changes spaces to be a character and the second changes numbers to be spaces. The Trim will then return null if only numbers exist.
TRIM(TRANSLATE(TRANSLATE(TRIM('1 2 3d 4'), ' ','#'),'0123456789',' ')) is null
I have the following SQL that I would like to run in Oracle SQL Developer against an Oracle 10g server:
WITH openedXml AS (
SELECT extractvalue(column_value, '/theRow/First') FIRST,
extractvalue(column_value, '/theRow/Last') LAST,
to_number(extractvalue(column_value, '/theRow/Age')) Age
FROM TABLE(XMLSequence(XMLTYPE('
<theRange>
<theRow><First>Bob</First><Last>Smith</Last><Age>30</Age></theRow>
<theRow><First>Sue</First><Last>Jones</Last><Age>34</Age></theRow>
...
...
...
<theRow><First>Tom</First><Last>Anderson</Last><Age>39</Age></theRow>
<theRow><First>Ali</First><Last>Grady</Last><Age>45</Age></theRow>
</theRange>
').extract('/theRange/theRow')))
)
SELECT *
FROM openedxml
WHERE age BETWEEN 30 AND 35;
When I attempt to run it I get the following error:
Error at Command Line:1 Column:0 Error report: SQL Error: ORA-01704: string literal too long
01704. 00000 - "string literal too long"
*Cause: The string literal is longer than 4000 characters.
*Action: Use a string literal of at most 4000 characters.
Longer values may only be entered using bind variables.
My strings will occasionally be much longer than 4000 characters. Any ideas about how I can get around this problem?
You can't get around this with "plain" SQL.
(But I'd be glad to be proven wrong)
You will need some kind of programming language (e.g. Java, Stored Procedure) to deal with this.
An alternative is to upload the XML data into a table (can be done with SQL*Loader) and the use the column values in your query.
This is one of the limitations of Oracle that is really driving me nuts.
20 years ago this might have been somewhat acceptable, but nowadays...
You will need to use a CLOB as the input to XMLTYPE() instead of a VARCHAR.
Using either dbms_lob.loadclobfromfile to load the xml from a file, or by breaking up the xml into 32000 character chunks and appending to the CLOB.
DECLARE
xmlClob CLOB;
BEGIN
/* Build Clob here */
WITH openedXml AS (
SELECT extractvalue(column_value, '/theRow/First') FIRST,
extractvalue(column_value, '/theRow/Last') LAST,
to_number(extractvalue(column_value, '/theRow/Age')) Age
FROM TABLE(XMLSequence(XMLTYPE(xmlClob).extract('/theRange/theRow')))
)
SELECT *
FROM openedxml
WHERE age BETWEEN 30 AND 35;
END;
Where does that great big chunk of XML come from ? I assume you are not typing it in.
Generally I'd look at a program that reads the source and turns it into a CLOB. That might be a perl/python/whatever script on a client, or it might be a server side routine that pulls the value from a web-server.
You can use sql workaround using insert/updates where each part if less than 4000 chars.
1 Do the insert as an insert with first part is the sql literal up to 4000 chars
2 Do the additional parts as an update concatenating the previous parts with the next part where the next part is up to 4000 chars
3 Repeat step 2 until all the large sql literal is updated.
Example,
Insert into
test_large_literal (col1, col2)
values
(<key val>, <first part of large sql literal>);
update
test_large_literal
set
col2 = col2 || <second part large sql literal>
where
col1 = <key val>;
...
...
update
test_large_literal
set
col2 = col2 || <last part large sql literal>
where
col1 = <key val>;
A possible workaround is to use PL/SQL blocks:
DECLARE
xml VARCHAR2(32000) :=
'<theRange>
<theRow><First>Bob</First><Last>Smith</Last><Age>30</Age></theRow>
<theRow><First>Sue</First><Last>Jones</Last><Age>34</Age></theRow>
...
...
...
<theRow><First>Tom</First><Last>Anderson</Last><Age>39</Age></theRow>
<theRow><First>Ali</First><Last>Grady</Last><Age>45</Age></theRow>
</theRange>';
CURSOR C (p1 INTEGER, p2 INTEGER) IS
SELECT * FROM (
SELECT extractvalue(column_value, '/theRow/First') FIRST,
extractvalue(column_value, '/theRow/Last') LAST,
to_number(extractvalue(column_value, '/theRow/Age')) Age
FROM TABLE(XMLSequence(XMLTYPE(xml).extract('/theRange/theRow'))))
)
WHERE age BETWEEN p1 AND p2;
BEGIN
FOR R IN C (30,35) LOOP
dbms_output.put_line(R.First||', '||R.Last||', '||R.Age);
END LOOP;
END;
(Completely untested)
EDIT:
As an insert, you could try:
DECLARE
xml VARCHAR2(32000) :=
'<theRange>
<theRow><First>Bob</First><Last>Smith</Last><Age>30</Age></theRow>
<theRow><First>Sue</First><Last>Jones</Last><Age>34</Age></theRow>
...
...
...
<theRow><First>Tom</First><Last>Anderson</Last><Age>39</Age></theRow>
<theRow><First>Ali</First><Last>Grady</Last><Age>45</Age></theRow>
</theRange>';
BEGIN
INSERT INTO temp_table(last,first,age)
SELECT last, first, age FROM (
SELECT extractvalue(column_value, '/theRow/First') FIRST,
extractvalue(column_value, '/theRow/Last') LAST,
to_number(extractvalue(column_value, '/theRow/Age')) Age
FROM TABLE(XMLSequence(XMLTYPE(xml).extract('/theRange/theRow'))))
)
WHERE age BETWEEN 30 AND 35;
END;