CREATE OR REPLACE TRIGGER comprobarHora
BEFORE INSERT
ON PEDIDOS
FOR EACH ROW
declare
v_data int(4);
v_dia varchar(20);
begin
select to_char(sysdate,'hh24mi') into v_data from dual;
select to_char(sysdate, 'day') into v_dia from dual;
if ((v_data BETWEEN (2031, 0830)) OR (v_dia='saturday') OR (v_dia='sunday')) then
raise_application_error(-20001,' No hi ha estoc');
end if;
end;
Can someone help me with this,error information is about line 8.
/
The immediate problem is on line 8 of the PL/SQL section (not the entire statement), which is:
if ((v_data BETWEEN (2031, 0830)) OR (v_dia='saturday') OR (v_dia='sunday')) then
That isn't how the BETWEEN condition is specified, it should be:
if v_data BETWEEN 2031 and 0830 OR v_dia='saturday' OR v_dia='sunday' then
But that still won't work as the lower value has to come first ("If expr3 < expr2, then the interval is empty"). You aren't handling the time going past midnight anyway. You could either test:
if v_data >= 2031 OR v_data <= 0830 OR v_dia='saturday' OR v_dia='sunday' then
or swap the BETWEEN conditions and negate it:
if NOT (v_data BETWEEN 0830 and 2031) OR v_dia='saturday' OR v_dia='sunday' then
As Gordon Lindoff pointed out, you're storing a string in a numeric field, so you're doing an implicit conversion. If you want to keep the 'time' as a number you should convert it explicitly really. You should probably also be making sure you get the day name back in the language you expect, using the third parameter to to_char; and the day name is padded by default, so using the abbreviation is simpler.
CREATE OR REPLACE TRIGGER comprobarHora
BEFORE INSERT
ON PEDIDOS
FOR EACH ROW
declare
v_data int(4);
v_dia varchar(20);
begin
v_data := to_number(to_char(sysdate,'hh24mi'));
v_dia := to_char(sysdate, 'dy', 'NLS_DATE_LANGUAGE=ENGLISH');
if v_data >= 2031 OR v_data <= 0830 OR v_dia='sat' OR v_dia='sun' then
raise_application_error(-20001,' No hi ha estoc');
end if;
end;
/
Related
Im fairly new pl/sql and im trying to create procedure that can take a date parameter from input, find and update the matching join date row(s) from student table I am working on but i cant seem to get a valid date and i'm not sure i properly configured my procedure from input your help would be appreciated, this is my code currently.
CREATE OR REPLACE PROCEDURE dateChanger (p_join_date IN DATE)
IS p_date DATE;
p_month VARCHAR2(3);
p_year VARCHAR2(4);
p_change VARCHAR2(11);
v_month VARCHAR2(3);
BEGIN
p_date := to_date(p_join_date, 'dd-mon-yyyy');
p_month := Extract(MONTH FROM p_date);
p_year := Extract(YEAR FROM p_date);
v_month := to_char(Extract(MONTH FROM student.join_date%TYPE), 'MON');
--expected result 01-(Month from p_date)-(Year from p_date) eg. 01-JUL-2021
p_change := '01-'+to_char(p_month, 'MON')+'-'+to_char(p_year, 'YYYY');-- date put back together
UPDATE Week03.t_student SET join_date = to_date(p_change, 'dd-mon-yyyy')
WHERE v_month = p_month; -- table updated where months match
RETURN(p_date || '-----' || p_change);
END;
/
ACCEPT jDate Date PROMPT 'Please Enter a valid date as dd-MON-YYYY eg. 13-JUL-2021';
DECLARE
dc VARCHAR2(11);
l_date student.join_date%type := &jDate; -- input from user saved to date type variable
BEGIN
dc := DATECHANGER(l_date);
SELECT DATECHANGER(l_date) INTO dc from dual;
END;
/
When i enter the date as 'dd-mon-yyy' or 'yyyy-mon-dd' and many other variations i get a pls 00905 & 00201 error any help would be appreciated. it seems the date input refuses all the variations i've tried.
From documentation:
DATE
Makes reply a valid DATE format. If the reply is not a valid DATE
format, ACCEPT gives an error message and prompts again. The datatype
is CHAR.
That means in this case result of accept of data is char format.
You should change you code a bit.
in announmous block
l_date student.join_date%type := &jDate; => l_date varchar2(100) := '&jDate'
and in procedure
(p_join_date IN DATE) => (p_join_date in varchar2)
Yea so for future references to anyone who may need to complete a similar task to mine here is how i solved my problem thanks a bit to #Arkadiusz Ćukasiewicz
CREATE OR REPLACE PROCEDURE dateChanger (p_join_date IN DATE)
BEGIN
UPDATE Week03.t_student SET join_date = trunc(join_date, 'MM');
END;
/
SELECT * FROM student;
BEGIN
dateChanger(to_date('2018-09-01', 'yyyy-mm-dd'));
END;
/
I have a problem with checking variables, when I try to convert those variables in date to check if statement, when creating stored procedure.
Code like this
create or replace procedure test
as
dan nvarchar2(2) :='1';
mesec nvarchar2(2) := '1';
godina nvarchar2(4) := '2016';
begin
if to_date(dan||mesec||godina,'dd-mon-yy') != sysdate
then
for dan in 1..12 loop
dbms_output.put_line(dan);
end loop;
end if;
end;
These 3 variables are nvarchar2, what is needed by to_date function as a parameter. But when try to convert these variables to a date, and check is it equal to sysdate, give me next error
ORA-01861: literal does not match format string
I don't know how to fix it?
It's going to be a rather long comment so I put it in the answer.
Now, let's start from the beginning. Why did you have that
ORA-01861: literal does not match format string
in the first place.
Your final, representing date, string literal you had after concatenating different parts of the date looked like this
112016
Date format string (dd-mon-yy) used in the to_date() function expects day and month to be represented by two digits: day 01 and month 01 not 1 and 1 as is in your case. So for the to_date() function to be executed successfully it should've looked like this
to_date('01012016', 'dd-mm-yy')
NOT like this(your case)
to_date('112016', 'dd-mm-yy')
Then you would have to take time portion of the date into consideration when comparing those two dates - you would have to get rid of it by applying TRUNC() function to the sysdate (after that, time defaults to midnight, exactly what you need if you want to compare just two dates).
The condition becomes
to_date('01012016', 'dd-mm-yyyy') != trunc(sysdate)
If you wanted to go another way and convert sysdate to string and compare two string literals you would also need either work with two digits day and month (01 and 01 respectively) or leave the as they were (each one of them is represented by one digit) and use FM format modifier in the format string to get rid of leading zeros. So it would look either like this
declare
dan nvarchar2(2) :='01';
mesec nvarchar2(2) := '01';
godina nvarchar2(4) := '2016';
begin
if godina||mesec||dan != to_char(sysdate, 'yyyymmdd');
...
end;
OR
declare
dan nvarchar2(2) :='1';
mesec nvarchar2(2) := '1';
godina nvarchar2(4) := '2016';
begin
if godina||mesec||dan != to_char(sysdate, 'fmyyyymmdd');
...
end;
Here the correct way:
create or replace procedure test
as
dan nvarchar2(2) :='1';
mesec nvarchar2(2) := '1';
godina nvarchar2(4) := '2016';
begin
if godina||mesec||dan != to_char(sysdate,'yyyymmdd')
then
for dan in 1..12 loop
dbms_output.put_line(dan);
end loop;
end if;
end;
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)
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;
I am working on a simple PL/SQL block that asks the user their Date Of Birth and calculates their age. I've used the months_between divided by 12 conversion but I am having trouble with the date conversion and date input from the user for the substitution variable. Please ignore the v_birthday_year as that is another part of the code I have to integrate in later.
I'm not sure the format that the user has to type in, ie 05-07-1980, or 06 Mar 1978 that would be acceptable, and how to calculate their age from what they put in.
My desired program is to write a PL/SQL program to accept the user's birthdate in this format DD-MON-YYYY, and calculates the Age in with 1 Decimal place. So I need it calculated out to one decimal place too.
Here is what I have, but I'm not sure what format to type in the DOB at the substitution variable input and the actual calculation with one decimal place. I'm just trying to see where I'm going wrong and how to correct it.
SET SERVEROUTPUT ON
DECLARE
--v_birthday_year NUMBER(4) := &v_birthday_year;
v_dob DATE := &v_dob
v_your_age NUMBER(3, 1);
BEGIN
v_your_age := TRUNC(MONTHS_BETWEEN(SYSDATE, v_dob))/12;
DBMS_OUTPUT.PUT_LINE ('Your age is ' || v_your_age);
END;
/
You may use any of these (pure sql)
SELECT months_between(sysdate, user_birth_date) /12 FROM dual;
or
SELECT floor(months_between(sysdate, user_birth_date) /12) FROM dual;
or
SELECT EXTRACT(YEAR FROM sysdate) - EXTRACT(YEAR FROM user_birth_date) FROM dual
or
SELECT (sysdate - user_birth_date) / 365.242199 FROM dual
Hope this will help
All SQL examples from others and me are probably better thing to use unless you are learning something about PL/SQL. Here's what I came up with:
DECLARE
v_dob DATE:= to_date('&v_dob', 'MM/DD/YYYY'); -- copy this line and you'll be fine
v_your_age NUMBER(3, 1);
BEGIN
v_your_age := TRUNC(MONTHS_BETWEEN(SYSDATE, v_dob))/12;
DBMS_OUTPUT.PUT_LINE ('Your age is ' || v_your_age);
END;
/
I passed 01/01/2010. Got this in output:
Your age is 3.1
Another SQL example:
SELECT empno, ename, hiredate
, TRUNC(MONTHS_BETWEEN(sysdate, hiredate)/12) years_of_service
FROM scott.emp
/
Create or replace function getAge(birthday date) return number is
v_your_age NUMBER(3, 1);``
BEGIN
v_your_age := floor(MONTHS_BETWEEN(SYSDATE, birthday))/12;
return v_your_age;
END;
/
then you call the function
BEGIN
DBMS_OUTPUT.PUT_LINE ('Your age is ' || getAge(to_date('10/10/2015','DD/MM/YYYY')));
end;
/
select dob,sysdate,trunc(months_between(sysdate,dob)/12) as year,
trunc(mod(months_between(sysdate,dob),12)) as month,
trunc(sysdate - add_months(dob,((trunc(months_between(sysdate,dob)/12)*12) + trunc(mod(months_between(sysdate,dob),12))))) as day
from emp;