PLSQL - Store a select query result in variable throw error - sql

I want to store a select query result in a variable in PLSQL.
SQL>var v_storedate VARCHAR2(19);
SQL>exec :v_storedate := 'select cdate from rprt where cdate between cdate AND TO_CHAR(sysdate, 'YYYY/MM/DD-HH24-MI-SS-SSSSS') and ryg='R' and cnum='C002'';
As
SQL>select cdate from rprt where cdate between cdate AND TO_CHAR(sysdate, 'YYYY/MM/DD-HH24-MI-SS-SSSSS') and ryg='R' and cnum='C002';
Returns : 2013/04/27-10:06:26:794
But it throws error:
ERROR at line 1: ORA-06550: line 1, column 121: PLS-00103: Encountered
the symbol "YYYY" when expecting one of the following:
* & = - + ; < / > at in is mod remainder not rem <an exponent (**)> <> or != or ~= >= <= <> and or like LIKE2_ LIKE4_ LIKEC_ between ||
multiset member SUBMULTISET_ The symbol "*" was substituted for "YYYY"
to continue. ORA-06550: line 1, column 148: PLS-00103: Encountered the
symbol ") and ryg=" when expecting one of the following: . ( * # % & =
- + ; < / > at in is mod remainder not rem <an exponent (**)> <> or != or ~= >= <= <> and or like LIKE2_ LIKE4_ LIKEC_ between

If you want to store the result of the query then you need to use a select ... into; at the moment you're trying to store the text of the actual query, not its result. If you wanted to do that you would need to escape the single-quote characters as the other answers have pointed out, and increase the variable size.
var v_storedate VARCHAR2(19);
exec select cdate into :v_storedate from rprt where cdate between cdate AND TO_CHAR(sysdate, 'YYYY/MM/DD-HH24-MI-SS-SSSSS') and ryg='R' and cnum='C002';
print v_storedate
Which would be easier to deal with using a normal anonymous block rather than SQL*Plus' execute shorthand. You should also give an explicit date format mask when converting it to a string:
begin
select to_char(cdate, 'YYYY/MM/DD-HH24:MI:SS')
into :v_storedate
from rprt
where cdate between cdate AND TO_CHAR(sysdate, 'YYYY/MM/DD-HH24-MI-SS-SSSSS')
and ryg='R' and cnum='C002';
end;
/
If you want the fractional seconds then you need to make your variable bigger, as 19 chars will only take you to the seconds.
Either way though you're risking getting either multiple results (which will give ORA-02112) or no results (which will give ORA-01403). As your where clause doesn't make much sense and the table contents aren't known I don't know which is more likely. As be here now pointed out your cdate comparison is always going to be true, plus you're doing an implicit date conversion in there which will break at some point. There isn't enough information to fix that for you.
You can't get fractional seconds from a date value anyway, only from a timestamp; which cdate seems to be. But even then the format element for that is FF[0-9]. SSSSSS is the number of seconds since midnight. But as the whole to_char() bit looks wrong that's somewhat moot. Also, if you really do need a comparison with the current time, you should probably be comparing with systimestamp rather than sysdate to be consistent - and then not doing any conversion of that.
If you only want the date part:
var v_storedate VARCHAR2(10);
begin
select to_char(cdate, 'YYYY/MM/DD')
into :v_storedate
...
You can still use exec if you want to, but it's less readable once the statement gets longer than your terminal line length:
var v_storedate VARCHAR2(10);
exec select to_char(cdate, 'YYYY/MM/DD') into :v_storedate from ... where ... ;

In PL/SQL a better approach to literals with single quotes in them is the quotation syntax: http://docs.oracle.com/cd/B28359_01/appdev.111/b28370/fundamentals.htm#CBJJDDCG
begin
variable := q'#select cdate from rprt where cdate between cdate AND TO_CHAR(sysdate, 'YYYY/MM/DD-HH24-MI-SS-SSSSS') and ryg='R' and cnum='C002'#'
...
end
... or with matching delimiters ...
begin
variable := q'[select cdate from rprt where cdate between cdate AND TO_CHAR(sysdate, 'YYYY/MM/DD-HH24-MI-SS-SSSSS') and ryg='R' and cnum='C002']'
...
end
You might try that in SQL*Plus also ... not sure if it works there.

Escape your inner apostrophes by doubling them!

Double the inner apostrophes.
And Change Data Type of v_storedate to VARCHAR2(140);

Related

ansi SQL date function in Oracle

I realize I could use a combination of to_char and to_date and what not as a work-around, but I'm trying to figure out why this doesn't work. I am using Oracle 12.1
select '2016-10-01'
from dual
union all
select to_char(2016)||'-10-01'
from dual;
Each side of the union produces identical output: 2016-10-01. If I then try to use the ANSI date syntax (as described here: http://blog.tanelpoder.com/2012/12/29/a-tip-for-lazy-oracle-users-type-less-with-ansi-date-and-timestamp-sql-syntax/ ), it only works on the first one, not the second one:
select date '2016-10-01'
from dual
This returns: 10/1/2016
But if I try this:
select date to_char(2016)||'-10-01'
from dual;
I get on:
ORA_00936 missing expression error.
I can code a work-around, but I'm stumped as to why one works and the other does not.
It won't work that way because DATE is not a function but a literal.
The terms literal and constant value are synonymous and refer to a fixed data value.
Date Literals
You can specify a DATE value as a string literal, or you can convert a character or numeric value to a date value with the TO_DATE function. DATE literals are the only case in which Oracle Database accepts a TO_DATE expression in place of a string literal.
You could use TO_DATE function.
select TO_DATE(to_char(2016)||'-10-01', 'YYYY-MM-DD')
from dual;
Rextester Demo
EDIT:
Using dynamic-SQL:
DECLARE
i DATE;
stmt VARCHAR2(100);
BEGIN
stmt := q'{SELECT DATE '}' || TO_CHAR(2016) || '-01-01' || q'{' FROM dual}';
EXECUTE IMMEDIATE stmt INTO i;
DBMS_OUTPUT.PUT_LINE('i =>' || i);
END;

Sysdate as char instead of date

I have a query.
i have a config table AB where a row is marked as "sysdate-360"
col1 ||col2
AB || sysdate-360
BC || sysdate -2
When i write a procedure to get date value from the config table AB i used.
v_date varchar(20);
cursor c1 as
select col2 from AB;
then
for ab_date in c1
loop
select ab_date.col2 into v_date from dual;
v_sql := 'delete from any_table where load_date <='||v_date;
execute immediate v_sql ;
commit;
end loop;
The procedure is compiled but when i execute I'm getting below error
ORA-01722: invalid number
ORA-06512: at "procedure", line 46
ORA-06512: at line 1
01722. 00000 - "invalid number"
*Cause:
*Action:
The sysdate -360 is considered as char but not as date since SYSDATE is itself a date right?
Please help.
If you handle only expressions SYSDATE +/- N I'd suggest to modify the config table as follows
ID SYSDATE_OFFSET
-- --------------
AB -360
BC -2
So you have a numeric offset to sysdate which can be queried this way:
select sysdate + (select SYSDATE_OFFSET from config where id = 'AB') s_360
from dual;
S_360
-----------------
25.02.16 21:46:50
So you may open a cursor with the query above.
If you can't change the table - define a view that removes the string sysdate and converts to number!
This looks quite fine to me. But why do you select into v_date? You get a string from the cursor which you can concatenate directly:
for ab_date in c1 loop
v_sql := 'delete from any_table where load_date <=' || ab_date.col2;
execute immediate v_sql ;
commit;
end loop;
At last this is simply the concatanation of two strings 'delete from any_table where load_date <=' and 'sysdate-360' which makes 'delete from any_table where load_date <= sysdate-360' - exactly the SQL string you want.
(That would look even better with a proper column name such as date_expression instead of col2.)
More elaborate explanation: The cursor gets you the string 'sysdate-360'. The query select ab_date.col2 into v_date from dual; is simply v_date := ab_date.col2;. But is v_date a string? If not, you get a conversion error, because 'sysdate-360' is just a string, nothing more. If you expect the DBMS to see that the string contains 'sysdate' which also happens to be the name of a system variable for the current time and then convert this magically, you expect to much.
Try the to_date function
TO_DATE(v_date)

All rows with date <= 90 days in oracle based on varchar2 date string [duplicate]

I have the following query that I am attempting to use as a COMMAND in a crystal report that I am working on.
SELECT * FROM myTable
WHERE to_date(myTable.sdate, 'MM/dd/yyyy') <= {?EndDate}
This works fine, however my only concern is that the date may not always be in the correct format (due to user error). I know that when the to_date function fails it throws an exception.. is it possible to handle this exception in such a way that it ignores the corresponding row in my SELECT statement? Because otherwise my report would break if only one date in the entire database is incorrectly formatted.
I looked to see if Oracle offers an isDate function, but it seems like you are supposed to just handle the exception. Any help would be greatly appreciated. Thanks!!
Echoing Tony's comment, you'd be far better off storing dates in DATE columns rather than forcing a front-end query tool to find and handle these exceptions.
If you're stuck with an incorrect data model, however, the simplest option in earlier versions is to create a function that does the conversion and handles the error,
CREATE OR REPLACE FUNCTION my_to_date( p_date_str IN VARCHAR2,
p_format_mask IN VARCHAR2 )
RETURN DATE
IS
l_date DATE;
BEGIN
l_date := to_date( p_date_str, p_format_mask );
RETURN l_date;
EXCEPTION
WHEN others THEN
RETURN null;
END my_to_date;
Your query would then become
SELECT *
FROM myTable
WHERE my_to_date(myTable.sdate, 'MM/dd/yyyy') <= {?EndDate}
Of course, you'd most likely want a function-based index on the MY_TO_DATE call in order to make this query reasonably efficient.
In 12.2, Oracle has added extensions to the to_date and cast functions to handle conversions that error
SELECT *
FROM myTable
WHERE to_date(myTable.sdate default null on conversion error, 'MM/dd/yyyy') <= {?EndDate}
You could also use the validate_conversion function if you're looking for all the rows that are (or are not) valid dates.
SELECT *
FROM myTable
WHERE validate_conversion( myTable.sdate as date, 'MM/DD/YYYY' ) = 1
If your data is not consistent and dates stored as strings may not be valid then you have 3 options.
Refactor your DB to make sure that the column stores a date datatype
Handle the exception of string to date in a stored procedure
Handle the exception of string to date in a (complex) record selection formula
I would suggest using the first option as your data should be consistent.
The second option will provide some flexibility and speed as the report will only fetch the rows that are needed.
The third option will force the report to fetch every record in the table and then have the report filter down the records.
I have the same problem... an old legacy database with varchar fields for dates and decades of bad data in the field. As much as I'd like to, I can't change the datatypes either. But I came up with this solution to find if a date is current, which seems to be what you're doing as well:
select * from MyTable
where regexp_like(sdate, '[0-1][0-9].[0-3][0-9].[0-9][0-9][0-9][0-9]')
-- make sure it's in the right format and ignore rows that are not
and substr(sdate,7,10) || substr(sdate,1,2) || substr(sdate,4,5) >= to_char({?EndDate}, 'YYYYMMDD')
-- put the date in ISO format and do a string compare
The benefit of this approach is it doesn't choke on dates like "February 30".
Starting from Oracle 12c there is no need to define a function to catch the conversion exception.
Oracle introduced an ON CONVERSION ERROR clause in the TO_DATE function.
Basically the clause suppress the error in converting of an invalid date string (typical errors are ORA-01843, ORA-01841, ORA-011861, ORA-01840) and returns a specified default value or null.
Example of usage
select to_date('2020-99-01','yyyy-mm-dd') from dual;
-- ORA-01843: not a valid month
select to_date('2020-99-01' default null on conversion error,'yyyy-mm-dd') from dual;
-- returns NULL
select to_date('2020-99-01' default '2020-01-01' on conversion error,'yyyy-mm-dd') from dual;
-- 01.01.2020 00:00:00
Solution for the Legacy Application
Let's assume there is a table with a date column stored as VARCHAR2(10)
select * from tab;
DATE_CHAR
----------
2021-01-01
2021-99-01
Using the above feature a VIRTUAL DATE column is defined, that either shows the DATE or NULL in case of the conversion error
alter table tab add (
date_d DATE as (to_date(date_char default null on conversion error,'yyyy-mm-dd')) VIRTUAL
);
select * from tab;
DATE_CHAR DATE_D
---------- -------------------
2021-01-01 01.01.2021 00:00:00
2021-99-01
The VIRTUAL column can be safely used because its format is DATE and if required an INDEX can be set up on it.
select * from tab where date_d = date'2021-01-01';
Since you say that you have "no access" to the database, I am assuming that you can not create any functions to help you with this and that you can only run queries?
If that is the case, then the following code should get you most of what you need with the following caveats:
1) The stored date format that you want to evaluate is 'mm/dd/yyyy'. If this is not the case, then you can alter the code to fit your format.
2) The database does not contain invalid dates such as Feb 30th.
First, I created my test table and test data:
create table test ( x number, sdate varchar2(20));
insert into test values (1, null);
insert into test values (2, '01/01/1999');
insert into test values (3, '1999/01/01');
insert into test values (4, '01-01-1999');
insert into test values (5, '01/01-1999');
insert into test values (6, '01-01/1999');
insert into test values (7, '12/31/1999');
insert into test values (8, '31/12/1999');
commit;
Now, the query:
WITH dates AS (
SELECT x
, sdate
, substr(sdate,1,2) as mm
, substr(sdate,4,2) as dd
, substr(sdate,7,4) as yyyy
FROM test
WHERE ( substr(sdate,1,2) IS NOT NAN -- make sure the first 2 characters are digits
AND to_number(substr(sdate,1,2)) between 1 and 12 -- and are between 0 and 12
AND substr(sdate,3,1) = '/' -- make sure the next character is a '/'
AND substr(sdate,4,2) IS NOT NAN -- make sure the next 2 are digits
AND to_number(substr(sdate,4,2)) between 1 and 31 -- and are between 0 and 31
AND substr(sdate,6,1) = '/' -- make sure the next character is a '/'
AND substr(sdate,7,4) IS NOT NAN -- make sure the next 4 are digits
AND to_number(substr(sdate,7,4)) between 1 and 9999 -- and are between 1 and 9999
)
)
SELECT x, sdate
FROM dates
WHERE to_date(mm||'/'||dd||'/'||yyyy,'mm/dd/yyyy') <= to_date('08/01/1999','mm/dd/yyyy');
And my results:
X SDATE
- ----------
2 01/01/1999
The WITH statement will do most of the validating to make sure that the sdate values are at least in the proper format. I had to break out each time unit month / day / year to do the to_date evaluation because I was still getting an invalid month error when I did a to_date on sdate.
I hope this helps.
Trust this reply clarifies...
there is no direct EXCEPTION HANDLER for invalid date.
One easy way is given below once you know the format like DD/MM/YYYY then below given REGEXP_LIKE function will work like a charm.
to_date() also will work, when invalid_date is found then cursor will goto OTHERS EXCEPTION. given below.
DECLARE
tmpnum NUMBER; -- (1=true; 0 = false)
ov_errmsg LONG;
tmpdate DATE;
lv_date VARCHAR2 (15);
BEGIN
lv_date := '6/2/2018'; -- this will fail in *regexp_like* itself
lv_date := '06/22/2018'; -- this will fail in *to_date* and will be caught in *exception WHEN OTHERS* block
lv_date := '07/03/2018'; -- this will succeed
BEGIN
tmpnum := REGEXP_LIKE (lv_date, '[0-9]{2}/[0-9]{2}/[0-9]{4}');
IF tmpnum = 0
THEN -- (1=true; 0 = false)
ov_errmsg := '1. INVALID DATE FORMAT ';
DBMS_OUTPUT.PUT_LINE (ov_errmsg);
RETURN;
END IF;
tmpdate := TO_DATE (lv_date, 'DD/MM/RRRR');
--tmpdate := TRUNC (NVL (to_date(lv_date,'DD/MM/RRRR'), SYSDATE));
tmpnum := 1;
EXCEPTION
WHEN OTHERS
THEN
BEGIN
tmpnum := 0;
ov_errmsg := '2. INVALID DATE FORMAT ';
DBMS_OUTPUT.PUT_LINE (ov_errmsg || SQLERRM);
RETURN;
END;
-- continue with your other query blocks
END;
-- continue with your other query blocks
DBMS_OUTPUT.PUT_LINE (tmpnum);
END;

date format picture ends before converting entire input string error

I have this procedure:
create or replace Procedure return_rows_LECTURE_BY_DATE (in_date in date, out_cursor OUT SYS_REFCURSOR) As
Begin
OPEN out_cursor for
select *
FROM COURSE_LECTURE
WHERE LECT_DATE_TIME_START >= to_timestamp(in_date, 'dd-mm-yyyy')
and LECT_DATE_TIME_START < to_timestamp(in_date+1, 'dd-mm-yyyy')
ORDER BY LECT_DATE_TIME_START;
End;
input: date, output: lectures on this date.
The dates in the table (view) is TIMESTAMP.
I want to run this procedure. I tried this:
declare
k SYS_REFCURSOR;
--t DATE:= to_date('2010-12-14:09:56:53', 'YYYY-MM-DD:HH24:MI:SS') ;
res COURSE_LECTURE%rowtype;
begin
return_rows_LECTURE_BY_DATE(to_date('2010-12-14', 'YYYY-MM-DD'),k);
loop
FETCH k into res;
Exit when k%notFound;
DBMS_OUTPUT.PUT_LINE(res.COURSE_NAME );
end loop;
end;
But I got this error:
Error report - ORA-01830: date format picture ends before converting
entire input string ORA-06512: at "HR.RETURN_ROWS_LECTURE_BY_DATE",
line 4 ORA-06512: at line 6
01830. 00000 - "date format picture ends before converting entire input string"
You are converting the date into a timestamp, by using TO_TIMESTAMP(), which takes a character as a parameter. You should use CAST() instead, which converts one datatype to another; for instance:
WHERE LECT_DATE_TIME_START >= CAST(in_date AS TIMESTAMP)
You should be doing this with all of your conversions from dates to timestamps; so to_timestamp(in_date+1, 'dd-mm-yyyy') becomes CAST((in_date + 1) AS TIMESTAMP).
The problem is with statement to_timestamp(in_date, 'dd-mm-yyyy') the format provided is too short you can use it without any format condition to_timestamp(in_date).

Oracle - literal does not match format string

I have a function which takes 2 date parameters:
CREATE OR REPLACE FUNCTION date_equal
(
date1 IN DATE,
date2 IN DATE
)
RETURN NUMBER IS
equal BOOLEAN;
BEGIN
equal := NVL(date1, '1999-01-01') = NVL(date2, '1999-01-01');
IF equal THEN
RETURN 1;
ELSE
RETURN 0;
END IF;
END date_equal;
/
Now, when I run select on the table which provides data for the function it runs okay:
SELECT TO_DATE(some_date, 'YYYY-MM-DD') FROM tbl
But when I try to use that in function call it fails:
SELECT date_equal(TO_DATE(some_date, 'YYYY-MM-DD'), TO_DATE(some_date, 'YYYY-MM-DD')) FROM tbl
The error message is "literal does not match format string". Does anyone know why would that happen?
When you do
NVL(date1, '1999-01-01')
Oracle tries to convert '1999-01-01' to a date implicitly (since date1 is a date).
For doing this it uses NLS_DATE_FORMAT which may not be yyyy-mm-dd
You can use explicit converting:
NVL(date1, to_date('1999-01-01', 'yyyy-mm-dd'))
or use the ANSI way
NVL(date1, date '1999-01-01')
The error message is most certainly caused by the pieace reading NVL(date1, '1999-01-01').
Try nvl(date1, date '1999-01-01') instead.