Oracle : receiving ORA-06550 and PLS-00905 - sql

i have a holiday table which contains the data are
HOLIDAYDA DESCRIPTION
--------- --------------------
19-JAN-11 to
17-JAN-11 to
10-JAN-11 new day
Now I want the first business day of the week. IE: If I pass "12-JAN-2011" as input, I want the o/p as 11-JAN-2011 as the 1st business day because 10-JAN-2011 is holiday.
here is my code :
create or replace procedure sample as
l_dStartDay date;
l_dHolidayDate date;
begin
select trunc(to_date(sysdate),'Day')
into l_dStartday
from dual;
dbms_output.put_line('first day of the week ');
dbms_output.put_line(l_dStartDay);
for i in 2..5 Loop
select holidaydate
from holiday
into l_dHolidayDate
where holidaydate = (l_dStartDay + i);
if(l_dHolidaydate is null) then
dbms_output.put_line(l_dStartDay+i);
end if;
exit;
end loop;
end;
i compiled the above program but with "Procedure created with compilation errors."
Newly Added :
Compliation errors :
LINE/COL ERROR
-------- -----------------------------------------------------------------
9/1 PL/SQL: SQL Statement ignored
9/33 PL/SQL: ORA-00933: SQL command not properly ended
Error:
BEGIN sample; END;
*
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00905: object SYSTEM.SAMPLE is invalid
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored
can any one tell me the reason for the error? if possible tell me the solution?

"i compiled the above program but with
Procedure created with compilation
errors"
If you are using an IDE such as TOAD or SQL Developer it would show the compilation errors automatically. Otherwise they are accessible in SQL*Plus using this command:
SQL> show errors
There are also views such as USER_ERRORS which we can query.
The problem is most likely the SELECT statement, as the INTO clause should follow immediately after the projection:
select holidaydate
into l_dHolidayDate
from holiday
where holidaydate = l_dStartDay + i);
Mind you, this also looks wrong:
select trunc(to_date(sysdate),'Day')
SYSDATE is a DATE already, although the more recent versions of Oracle tend to be more forgiving of using TO_DATE on a DATE column. When truncating the time element from a date it is not necessary to include a format mask as this is the default behaviour:
trunc(some_date_variable)
We only need to include a mask if (say) we want the first day of the month:
trunc(some_date_variable, 'MON')
If you want to find the first day of the week, this will do it:
SQL> select
2 trunc(to_date('01-DEC-2010', 'DD-MON-YYYY'), 'D') start_of_wk
3 from dual
4 /
START_OF_
---------
29-NOV-10
SQL>
Note that the first day of the week is dependent on the territory setting. In some territories the first day of the week is a working day (for instance Monday in the UK) in others it is not (Sunday is day 1 in the US). So it may be necessary to add an offset.
Once you solve the compilation errors you'll find soem runtime errors, probably relating to unhandled NO_DATA_FOUND exceptions. This is because your lookup query won't return NULL when it doesn't find a matching record, it will fail.
This is a simple procedure. It uses a SQL solution, because SQL is the most efficient way of doing things. The inner query uses the CONNECT BY trick to generate a result set of dates. This is then reduced by the MINUS set operator, which will filter out any holidays in that week's range. Finally the outer query returns the earliest date from the query.
create or replace procedure get_first_working_day
( p_tgt_date in date )
is
l_st_day date := trunc(p_tgt_date, 'D');
l_working_day date := trunc(p_tgt_date, 'D');
begin
dbms_output.put_line('first day of week = '||l_st_day);
select min(day_of_wk)
into l_working_day
from ( select l_st_day + (level-1) as day_of_wk
from dual
connect by level <= 5
minus
select holidaydate
from hols
where holidaydate between l_st_day and l_st_day + 4 );
dbms_output.put_line('first working day of week = '||l_working_day
||'::'|| to_char(l_working_day, 'DAY'));
end get_first_working_day;
/
Given this test data (which reflects the byzantine state of British bank holidays) ...
SQL> select holidate from hols
2 order by 1
3 /
HOLIDAYDA
---------
25-DEC-10
26-DEC-10
27-DEC-10
28-DEC-10
01-JAN-11
03-JAN-11
6 rows selected.
SQL>
... here's the procedure in action:
SQL> set serveroutput on size unlimited
SQL>
SQL> exec get_first_working_day (sysdate)
first day of week = 10-JAN-11
first working day of week = 10-JAN-11::MONDAY
PL/SQL procedure successfully completed.
SQL>
SQL> exec get_first_working_day (to_date( '04-JAN-2011', 'DD-MON-YYYY'))
first day of week = 03-JAN-11
first working day of week = 04-JAN-11::TUESDAY
PL/SQL procedure successfully completed.
SQL>
SQL> exec get_first_working_day (to_date( '01-JAN-2011', 'DD-MON-YYYY'))
first day of week = 27-DEC-10
first working day of week = 29-DEC-10::WEDNESDAY
PL/SQL procedure successfully completed.
SQL>
Incidentally, this is very bad practice:
PLS-00905: object SYSTEM.SAMPLE is invalid
Don't use the built-in SYS or SYSTEM accounts for your own work. There is too great a chance of breaking something. Create a new user account instead.

I'm guessing that the line
where holidaydate = l_dStartDay + i);
is wrong as it has a ) where it's not supposed to be.

Aside from the errors already mentioned, try removing the 'EXIT' clause as this loop will iterate a fixed number of times. Also, try specifying the block name when ending the block as in the following:
LOOP
...
END LOOP;
END ObjectName;
Where ObjectName is your top level program. Here, it would be 'sample', so:
LOOP
...
END LOOP;
END sample;

Related

Error computing item default value for page item DDL_MONTH_FROM. Contact your application administrator

I am new to Oracle APEX, i am using Oracle APEX version 20.2, i have page item called P720_DDL_MONTH_FROM.
We are getting the value for that page item for date picker Month From is like below query.
From the list of values page attribute:
SELECT MON.NAME D, MON.MONTH R
FROM (SELECT DISTINCT(NAME) "NAME", TO_NUMBER(MONTH) "MONTH"
FROM
MONTH_YEAR) MON
ORDER BY MON.MONTH
From the Default values for pageItem:
DECLARE L_VALUE NUMBER;
BEGIN
SELECT DISTINCT(MONTH) INTO L_VALUE FROM MONTH_YEAR
WHERE UPPER(NAME) IN (SELECT TO_CHAR(ADD_MONTHS(TO_DATE(SYSDATE), -1), 'MON') FROM DUAL);
RETURN L_VALUE;
END;
I am facing an NO DATA FOUND issues while getting the default value for the above sql and plsql block, can anyone please clarify what is the issues from the above query, and let me due to some functionalities may be deprecated in apex latest version
Thanks,
If you got no_data_found, it means that there are no rows that satisfy condition(s) you wrote.
For sample table
SQL> SELECT * FROM month_year;
MONTH NAME
---------- --------------------
4 APR
6 JUN
query returns something (a value for June) (note that you don't need a subquery; directly use TO_CHAR(...)):
SQL> SELECT DISTINCT month
2 FROM month_year
3 WHERE UPPER (name) = TO_CHAR (ADD_MONTHS (TO_DATE (SYSDATE), -1), 'MON');
MONTH
----------
6
SQL>
If you got nothing, you should check contents of the month_year table and see what's going on.

Changing date in Str format to Datetime format in SQL

Really basic question but i have zero experience with SQL. I'm using Tableau to do visualisation with data stored in my company's Oracle server, which contains multiple sheets. The primary table i am working with is named YQ005. One of the fields in the primary table I'm working with contains dates but stored as a String in YYYYMMDD format.
I have to convert this to Date format but doing it through Tableau raises the error "ORA-01843: Not a valid month". How can i do a custom SQL query to select this field, convert it to Date-time format and place this new data in a new column?
Littlefoot has a solid answer but it is definitely not for the inexperienced.
The basic function to convert the string to a date is:
select to_date(yyyymmdd, 'yyyymmdd')
If you are having problems with the month, you can just extract it out to check it:
select (case when substr(yyyymmdd, 5, 2) between '01' and '12'
then to_date(yyyymmdd, 'yyyymmdd')
end)
You can also add a check that the value is all numbers:
select (case when regexp_like(yyyymmdd, '^[0-9]{8}') and
substr(yyyymmdd, 5, 2) between '01' and '12'
then to_date(yyyymmdd, 'yyyymmdd')
end)
Validating dates in Oracle gets much more complicated if you have to validate the whole date -- each month has a different number of days and leap years further complicate matters. But months should always be between 01 and 12.
Error you got means that some values in that table - on 5th and 6th place - don't have a valid month value. For example, it might be 20191823 (there's no 18th month, is there?).
Unfortunately, that's what happens when people store dates as strings. There's no easy way out. If you want to do it with SQL only, you might fail or succeed (if you're VERY lucky). For example, have a look at this example:
SQL> desc yq005
Name Null? Type
----------------------------------------- -------- ----------------
DATUM VARCHAR2(8)
SQL> select * From yq005;
DATUM
--------
20191221
13000815
00010101
19302533 -- 25th month and 33rd day
2013Ab12 -- Ab month
2ooo0513 -- year with letters "o" instead of digits "0"
6 rows selected.
SQL>
A query whose where clause tries to identify invalid values:
SQL> alter session set nls_date_format = 'dd.mm.yyyy.';
Session altered.
SQL> select to_date(datum, 'yyyymmdd') result
2 from yq005
3 where substr(datum, 1, 4) between '0000' and '9999'
4 and substr(datum, 5, 2) between '00' and '12'
5 and substr(datum, 7, 2) between '01' and '31'
6 and regexp_like(datum, '^\d+$');
RESULT
-----------
21.12.2019.
15.08.1300.
01.01.0001.
SQL>
lines #3, 4 and 5 are trying to identify valid year/month/day. The first two are OK, more or less, but - it'll miserably fail on at least half of all months because e.g. 20191131 is "valid", but there are no 31 days in November
line #6 eliminates values that aren't all digits
Just to check that 20191131:
SQL> insert into yq005 values ('20191131');
1 row created.
SQL> select to_date(datum, 'yyyymmdd') result
2 from yq005
3 where substr(datum, 1, 4) between '0000' and '9999'
4 and substr(datum, 5, 2) between '00' and '12'
5 and substr(datum, 7, 2) between '01' and '31'
6 and regexp_like(datum, '^\d+$');
ERROR:
ORA-01839: date not valid for month specified
no rows selected
SQL>
As I said, it won't work; the same goes for other 30-days months, as well as February.
You could try to create a function which converts string to date; if it succeeds, fine. If not, skip that value:
SQL> create or replace function f_valid_date_01 (par_datum in varchar2)
2 return number
3 is
4 -- return 1 if PAR_DATUM is a valid date; return 0 if it is not
5 l_date date;
6 begin
7 -- yyyymmdd is format you expect
8 l_date := to_date(par_datum, 'yyyymmdd');
9 return 1;
10 exception
11 when others then
12 return 0;
13 end;
14 /
Function created.
SQL>
Let's use it:
SQL> select datum original_value,
2 to_char(to_date(datum, 'yyyymmdd'), 'dd.mm.yyyy') modified_value
3 from yq005
4 where f_valid_date_01 (datum) = 1;
ORIGINAL MODIFIED_V
-------- ----------
20191221 21.12.2019
13000815 15.08.1300
00010101 01.01.0001
SQL>
Just the opposite - fetch invalid dates:
SQL> select datum
2 from yq005
3 where f_valid_date_01 (datum) = 0;
DATUM
--------
19302533
2013Ab12
2ooo0513
20191131
SQL>
This is just one option you might use; there certainly are others, just Google for them. The bottom line is: always store dates into a DATE datatype column and let the database take care about (in)valid values.
[EDIT: how to populate a new column with a valid date]
If there's no date datatype column in the table, add it:
SQL> alter table yq005 add new_datum date;
Table altered.
Now run the update; mind the where clause:
SQL> update yq005 set
2 new_datum = to_date(datum, 'yyyymmdd')
3 where f_valid_date_01(datum) = 1;
3 rows updated.
SQL> select * From yq005;
DATUM NEW_DATUM
-------- -----------
20191221 21.12.2019.
13000815 15.08.1300.
00010101 01.01.0001.
19302533
2013Ab12
2ooo0513
20191131
7 rows selected.
SQL>
The best solution would be to have whoever maintains your database alter the table definition to store dates using the DATE datatype instead of some form of string.
But if you can't or don't wish to alter the Oracle schema, then I would try using the DATEPARSE() function in Tableau, as follows (assuming your date field is named XXX_DATE)
In Tableau, rename XXX_DATE to XXX_DATE_ORGINAL
Define a calculated field called XXX_DATE as DATEPARSE("YYYYMMdd", [XXX_DATE_ORIGINAL])
Hide the original field XXX_DATE_ORIGINAL
Now you can use your XXX_DATE field as a date in Tableau
The renaming and hiding of the original field is not strictly necessary. I just find it helps keep the data source understandable. For more info, see the Tableau online help for DateParse

Generate rows till end of the year based on current month's data

I'm having data till current month from 1st day of the year. I have to pick up the current month data and populate the same data by changing the flag till end of the year
I'm planning to create a union all query, but unable to change it dynamically based on current month.
I'm having a table with 8 columns and 'Flag' as 9th column in which one column is date with Month Year data. The ask is I have to pick the current month data and populate the same data with Month year as upcoming month name till the year end for the remaining 7 columns with flag as 'Future'. What's the best way to do it?. For Example I'm having data from Jan'19 till August'19. I have to copy August'19 data and change the date (Month_Year) field as 'September'19 till December'19 and combine it with original table as it has full year data with actual data till August'19 and copied data of August'19 till December'19
Kindly add table structure/dataset. you can attach snippet/picture of the table with data as well.
1. Remember that August will have only 21 days data currently.As such you can repeat the last days data for the remaining number of days.
2.You can also try and use FORALL to insert data back to your table, but it might be tricky as month will have to change 4 times
Based on this a sample code can be:
DECLARE
l_remaining_mnths NUMBER;
TYPE tab_sho IS TABLE OF sho_table%ROWTYPE
INDEX BY BINARY_INTEGER;
l_tab tab_sho;
l_month VARCHAR2(100);
j NUMBER := 0;
BEGIN
SELECT 12 - EXTRACT(MONTH FROM sysdate)
INTO l_remaining_mnths
FROM dual;
SELECT *
BULK COLLECT INTO l_tab
FROM sho_table
WHERE flag = 'current';
dbms_output.put_line('total records-->'||l_tab.COUNT);
FOR i in 1..l_remaining_mnths
LOOP
SELECT TO_CHAR(ADD_MONTHS(sysdate,i),'Month')||19
INTO l_month
FROM dual;
dbms_output.put_line('l_tab(i).month_year -->'||l_tab(i).month_year);
FOR j IN l_tab.FIRST..l_tab.LAST
LOOP
l_tab(j).month_year := l_month;
INSERT INTO sho_table
(
month_year
,column2
,flag
)
VALUES
(
l_tab(j).month_year
,'newvalue'
,'future'
);
END LOOP;
EXIT WHEN SQL%NOTFOUND;
END LOOP;
/*
dbms_output.put_line('Before for all');
FORALL j IN 1..l_tab.COUNT
INSERT INTO sho_table
VALUES l_tab(j);
dbms_output.put_line('after for all');
*/
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('We are in the exception '||SQLERRM);
END;

show specific message when no record come in oracle

This is my Oracle query which gets records on the basis of date where differences of dates should be one month:
select *
from pbxhbl.HBL_TRANSACTIONS
where dat_creation between '10-apr-2013' and '10-jun-2013'
and MONTHS_BETWEEN('10-jun-2013','10-apr-2013') = 1
My question is, when difference is more than one I want to show a message:
'date duration should be one month'
MONTHS_BETWEEN('10-jun-2013','10-apr-2013')
Firstly, '10-jun-2013' is not a DATE, it is a STRING. Oracle will do an implicit data type conversion. Please avoid this, and always use TO_DATE to explicitly convert a string into date.
Secondly, months between the two dates that you have posted will never be 1, it will always be 2.
SQL> SELECT MONTHS_BETWEEN(to_date('10-jun-2013','dd-mon-yyyy'),to_date('10-apr-2013','dd-mon-yyyy')) dt
2 FROM dual;
DT
----------
2
SQL>
So, ideally your query will always return no rows.
SQL> SELECT *
2 FROM dual
3 WHERE MONTHS_BETWEEN(to_date('10-jun-2013','dd-mon-yyyy'),to_date('10-apr-2013','dd-mon-yyyy')) =1;
no rows selected
SQL>
Coming back to your question,
You could use a CASE expression.
However, if you are filtering out rows, then you would not have anything to display. So, remove the filter, such that whenever the value of MONTHS_BETWEEN is more than 1, you could return the message.
For example,
SQL> WITH DATA AS(
2 SELECT to_date('01-03-2015','DD-MM-YYYY') dt1, to_date('01-04-2015','DD-MM-YYYY') dt2 FROM dual UNION ALL
3 SELECT to_date('01-03-2015','DD-MM-YYYY') dt1, to_date('10-04-2015','DD-MM-YYYY') dt2 FROM dual UNION ALL
4 SELECT to_date('01-03-2015','DD-MM-YYYY') dt1, to_date('01-05-2015','DD-MM-YYYY') dt2 FROM dual
5 )
6 SELECT DT1,
7 DT2,
8 CASE
9 WHEN months_between(dt2, dt1) >1
10 THEN 'date duration should be one month'
11 ELSE 'OK'
12 END MESSAGE
13 FROM DATA;
DT1 DT2 MESSAGE
--------- --------- ---------------------------------
01-MAR-15 01-APR-15 OK
01-MAR-15 10-APR-15 date duration should be one month
01-MAR-15 01-MAY-15 date duration should be one month
SQL>
What you need should be done inside a function.
Create a function that takes two parameters as input (date, date) and as an output it returns a record of table type.
Inside a function, before passing your query to output insert a conditional control structure like CASE or IF to check whether or not given dates match your criteria. If yes, simply pass the query to the result, and if no (assuming you would like someone to see it in application) return a query that is built just as the record you would normally return and include your message in one of columns while the rest should remain NULL.
For the above I assumed that raising an error is not what you actually need.
Pseudocode:
CREATE FUNCTION check_dates_for_hbl_transactions(date, date)
RETURNS record
AS $function$
BEGIN
IF ( MONTHS_BETWEEN( $1 - $2 ) > 1 )
THEN RETURN QUERY SELECT 'Date duration should be one month at max'::text, NULL::bigint, ...
ELSE
RETURN QUERY SELECT * FROM HBL_TRANSACTIONS WHERE dat_creation BETWEEN $1 AND $2
END IF;
END
$function$
Important note: The reason I wrote 'message'::text, NULL::bigint, ... was to show that your select statement must match the returning set (in this case all the columns and their types from table HBL_TRANSACTIONS).
Also consider adding an additional check for date $2 to be larger than date $1 argument.
Side note: Consider returning rowtype to make it easier. I do not prefer that method as it is error prone in case of row type being changed. I'd rather have the function to fail and check manually for the cause than have it pass normally as usual with wrong value.

Oracle SQL , how to use current date - a specified date

For getting current date i use this..
select extract(year from sysdate) from dual;
For getting the date that my database hold i use this..
select extract(year from startdate) from staff;
But now i am trying to update a field call serviceYears inside staff, by using
current year - year of start date
to get the amount of years the staff have committed to work. how do i achieve it..
i am using oracle sql
Thanks!
I tried to use
SQL> select dual.sysdate-staff.startdatefrom dual,staff;
select dual.sysdate-staff.startdatefrom from dual,staff
*
ERROR at line 1:
ORA-01747: invalid user.table.column, table.column, or column specification
I also tried
select SYSDATE-to_date('01-jan-2007','dd-mon-yyyy') from dual;
But it return me
SYSDATE-TO_DATE('01-JAN-2007','DD-MON-YYYY')
--------------------------------------------
2136.93719
How do i just get the year?
You can do this
UPDATE STAFF
SET serviceYear = ROUND((sysdate - startDate)/365)
Ex:
select ROUND((sysdate - to_date('01-JAN-2007','DD-MON-YYYY'))/365)
from dual; -- returns 6
select ROUND((sysdate - to_date('01-JAN-2005','DD-MON-YYYY'))/365,2)
from dual; -- returns 7.85
Updated:
SELECT
FLOOR((SYSDATE - TO_DATE('01-JAN-2005','DD-MON-YYYY'))/365) YEARDOWN,
CEIL((SYSDATE - TO_DATE('01-JAN-2005','DD-MON-YYYY'))/365) YearUP
FROM DUAL;
you do not need dual.sysdate - you can just reference sysdate.
select sysdate-staff.startdatefrom from staff
I think you might be better served by a combination of FLOOR and MONTHS_BETWEEN.
SQL> CREATE TABLE t (start_date DATE);
Table created.
SQL> INSERT INTO t VALUES (TO_DATE('20070930','YYYYMMDD'));
1 row created.
SQL> SELECT FLOOR(MONTHS_BETWEEN(TRUNC(SYSDATE), start_date)/12) yrs_between_start_dt_and_today FROM t
2 ;
YRS_BETWEEN_START_DT_AND_TODAY
------------------------------
5
SQL>
You can adjust your rounding as needed. Moreover, this solution plays better with leap years and such, as compared to dividing by a hard-coded 365.
If you can measure it in months, try the months_between function:
http://www.techonthenet.com/oracle/functions/months_between.php
http://docs.oracle.com/cd/B28359_01/server.111/b28286/functions094.htm#i78039
Could take that and divide by 12 to get the year.
SELECT extract(year from sysdate)-extract(year from TDATE)
FROM R_M
CONNECT BY LEVEL <=1