SQL check day of week - sql

How to check if it's a weekend in SQL?
I know I can convert sysdate to a number using this
SQL> select to_char(sysdate, 'd') from dual;
TO_CHAR(SYSDATE,'D')
But I'm not really sure how to check if today is a 6 or 7.

Do not use TO_CHAR with the D format model for this as it is dependant on the NLS_TERRITORY session parameter.
For example, when SYSDATE = 2018-09-10 (a Monday):
ALTER SESSION SET NLS_TERRITORY = 'France';
SELECT TO_CHAR( SYSDATE, 'D' ) FROM DUAL;
Outputs 1 but the same query in a different territory:
ALTER SESSION SET NLS_TERRITORY = 'America';
SELECT TO_CHAR( SYSDATE, 'D' ) FROM DUAL;
Outputs 2.
Instead, you can use TRUNC and the IW format model:
SELECT TRUNC( SYSDATE ) - TRUNC( SYSDATE, 'IW' ) FROM DUAL
Outputs 0 for Monday (and 1 for Tuesday ... 6 for Sunday) and is independent of the NLS_TERRITORY setting.
So you could filter this to give weekends as:
SELECT *
FROM DUAL
WHERE TRUNC( SYSDATE ) - TRUNC( SYSDATE, 'IW' ) IN ( 5, 6 )
or
SELECT *
FROM DUAL
WHERE SYSDATE - TRUNC( SYSDATE, 'IW' ) >= 5
If you want the days 1-indexed for consistency with your expected output from TO_CHAR (rather then 0-indexed) then just add 1 to the value.

sysdate is a pseudo column. You don't need to query it, you can evaluate it directly:
IF TO_CHAR(SYSDATE, 'D') IN ('6', '7') THEN
-- Do something
END IF;

I would avoid the ambiguous 'D' format as this varies between territories (the week starts after the weekend where I live), and use
if to_char(sysdate,'fmDY','nls_date_language=English') like 'S%'
then
Regarding the 'D' format, unfortunately to_char doesn't let you specify nls_territory inline, so without an explicit alter session command, it will rely on the session settings at runtime. I've seen production bugs due to this, where the same code worked in London but failed in New York.

And here's the logic in a reusable function, but flipped to ask "Is it a weekday?" (not a weekend). You could add a parameter for the start day of the weekdays (2 is the default in Oracle; Sunday is day #1).
CREATE OR REPLACE FUNCTION is_weekday (date_in IN DATE)
RETURN BOOLEAN
IS
BEGIN
RETURN TO_CHAR (date_in, 'D') BETWEEN 2 AND 6;
END;
/
DECLARE
l_date DATE := DATE '2018-09-10';
BEGIN
DBMS_OUTPUT.put_line ('If your weekend is Saturday and Sunday....');
FOR indx IN 1 .. 7
LOOP
DBMS_OUTPUT.put_line (
TO_CHAR (l_date, 'FMDay, Month DD YYYY')
|| ' is '
|| CASE WHEN NOT is_weekday (l_date) THEN 'not ' END
|| 'a weekday');
l_date := l_date + 1;
END LOOP;
END;
/
Try it in LiveSQL:
https://livesql.oracle.com/apex/livesql/file/content_G8NQSY6NP48NPJX96RLQ51SUE.html

Related

I'm unable to find tabular format result in oracle sql developer

I'm unable to get output as tabular format.
DECLARE
prev VARCHAR2(10) := 'sysdate+1';
tdy VARCHAR2(10) := 'sysdate';
v_dt VARCHAR2(10);
result resultset;
BEGIN
v_dat :=
CASE
WHEN trunc(sysdate) = trunc(currnet_date) THEN
tdy
ELSE prev
END;
WITH
yesterday AS (
SELECT
src_dim_id,
rec_count
FROM
tab_total_last_updated
WHERE
to_char(dw_insert_datetime, 'mm-dd-yy') = CASE
WHEN TRIM(to_char(v_dt, 'DAY', 'NLS_DATE_LANGUAGE=ENGLISH')) =
'MONDAY' THEN
to_char(v_dt - 3, 'mm-dd-yy')
ELSE
to_char(v_dt - 1, 'mm-dd-yy')
END
AND table_type = 'EXT'
)
SELECT
*
INTO result
FROM
yesterday;
RETURN result;
END;
I'm trying to pass value sysdate into v_dt as per the condition but I'm unable to proceed with my code please help on this one.
You have many typos in your code v_dat should be v_dt, current_date is misspelt.
You cannot RETURN a result as you are using an anonymous PL/SQL block and not a function.
Also, you probably do not want to use PL/SQL and just want an SQL query (and do not need the sub-query factoring clause):
SELECT src_dim_id,
rec_count
FROM tab_total_last_updated
WHERE dw_insert_datetime >= CASE TRUNC(SYSDATE) - TRUNC(SYSDATE, 'IW')
WHEN 0 THEN TRUNC(SYSDATE) - 3 -- Monday
WHEN 6 THEN TRUNC(SYSDATE) - 2 -- Sunday
ELSE TRUNC(SYSDATE) - 1
END
AND dw_insert_datetime < CASE TRUNC(SYSDATE) - TRUNC(SYSDATE, 'IW')
WHEN 0 THEN TRUNC(SYSDATE) - 2 -- Monday
WHEN 6 THEN TRUNC(SYSDATE) - 1 -- Sunday
ELSE TRUNC(SYSDATE)
END
AND table_type = 'EXT';
It is unclear what you are trying to achieve using:
v_dt := CASE
WHEN trunc(sysdate) = trunc(current_date)
THEN SYSDATE
ELSE SYSDATE + 1
END;
But you appear to be trying to correct for differences between the dates on the server and the client. In which case either:
Use CURRENT_DATE throughout the above code instead of SYSDATE; or
Use a specific time zone and replace SYSDATE with, for example, SYSTIMESTAMP AT TIME ZONE 'UTC'.

Incorrect day of the week in Oracle Function

I have this common function which counts business days between two dates.
BUS_DAY := TRUNC(TO_DATE(P_START_DATE, D_FORMAT));
DATES_DIFF := TRUNC(TO_DATE(P_END_DATE, D_FORMAT)) - BUS_DAY;
SELECT MAX(RNUM) INTO T_DAYS
FROM (
SELECT ROWNUM RNUM
FROM ALL_OBJECTS
)
WHERE ROWNUM <= DATES_DIFF
AND TO_CHAR(BUS_DAY + RNUM, 'DY') NOT IN ('SAT', 'SUN');
My problem is it gives an incorrect day of the week for dates.
For example, today is Oct 7 2020 WEDNESDAY, but the function reads this date as a MONDAY, so it gives incorrect number of business days T_T
Anyone have the same issue or have any idea why oracle is reading dates incorrectly?
You can calculate the value without having to use a row generator and independent of the NLS_DATE_LANGUAGE.
Adapted from my answer here (which is the same problem but also ignoring holidays):
Get the number of days between the Mondays of both weeks (using TRUNC( datevalue, 'IW' ) as an NLS_LANGUAGE independent method of finding the Monday of the week) and multiply by 5/7 to give the week days of the full weeks; then
Add the day of the week (Monday = 1, Tuesday = 2, etc., to a maximum of 5 to ignore weekends) to count the part week for the end date; and
Subtract the day of the week of the start date to remove the counted values beforehand.
Like this:
SELECT ( TRUNC( end_date, 'IW' ) - TRUNC( start_date, 'IW' ) ) * 5 / 7
+ LEAST( end_date - TRUNC( end_date, 'IW' ) + 1, 5 )
- LEAST( start_date - TRUNC( start_date, 'IW' ) + 1, 5 )
AS WeekDaysDifference
FROM your_table
If you are just calculating for a single value in a function then you can avoid a context-switch to SQL and do it all in PL/SQL:
CREATE FUNCTION count_weekdays_between(
p_start_date IN DATE,
p_end_date IN DATE
) RETURN NUMBER DETERMINISTIC
IS
BEGIN
RETURN ( TRUNC( p_end_date, 'IW' ) - TRUNC( p_start_date, 'IW' ) ) * 5 / 7
+ LEAST( p_end_date - TRUNC( p_end_date, 'IW' ) + 1, 5 )
- LEAST( p_start_date - TRUNC( p_start_date, 'IW' ) + 1, 5 );
END;
/
and:
SELECT count_weekdays_between( DATE '2020-09-29', DATE '2020-10-07' )
AS num_week_days
FROM DUAL;
Outputs: 6
db<>fiddle here
Using SELECT ... FROM ALL_OBJECTS is a really ugly workaround.
What about this proposal?
DECLARE
BUS_DAY DATE := TRUNC(SYSDATE - 10);
DATES_DIFF INTEGER := TRUNC(SYSDATE - BUS_DAY);
T_DAYS INTEGER;
BEGIN
SELECT SUM(1)
INTO T_DAYS
FROM dual
WHERE TO_CHAR(BUS_DAY + LEVEL, 'DY', 'NLS_DATE_LANGUAGE = American') NOT IN ('SAT', 'SUN')
CONNECT BY LEVEL <= DATES_DIFF;
DBMS_OUTPUT.PUT_LINE ( 'T_DAYS = ' || T_DAYS );
END;
Looking just at why you see Monday, and ignoring whether this is a good approach - which #MTO has covered - then if p_start_date and p_end_date are dates, using to_date() on them is a bug, as #a_horse_with_no_name said in a comment.
If your NLS settings have YY or RR and d_format is using YYYY then today's date would end up as 0020-10-07, which was a Monday.
As a demo:
declare
P_START_DATE date := date '2020-10-07';
D_FORMAT varchar2(11) := 'DD-MON-YYYY';
BUS_DAY date;
begin
dbms_output.put_line(P_START_DATE || ' => ' || to_char(P_START_DATE, 'SYYYY-MM-DD Day'));
BUS_DAY := TRUNC(TO_DATE(P_START_DATE, D_FORMAT));
dbms_output.put_line(BUS_DAY || ' => ' || to_char(BUS_DAY, 'SYYYY-MM-DD Day'));
end;
/
07-OCT-20 => 2020-10-07 Wednesday
07-OCT-20 => 0020-10-07 Monday
When you do:
BUS_DAY := TRUNC(TO_DATE(P_START_DATE, D_FORMAT));
it's really:
BUS_DAY := TRUNC(TO_DATE(TO_CHAR(P_START_DATE), D_FORMAT));
and that implicit TO_CHAR(P_START_DATE) is using your NLS settings, so it's something like:
BUS_DAY := TRUNC(TO_DATE(TO_CHAR(P_START_DATE, 'DD-MON-RR'), D_FORMAT));
You end up with that intermediate string value as '07-OCT-20'. If you convert that back to a date with a YYYY year component in the format mask then the year is seen as 0020, not 2020:
select to_char(to_date('07-OCT-20', 'DD-MON-YYYY'), 'DD-MON-YYYY') from dual;
07-OCT-0020
You don't need to convert to and from a string, and you're already truncating to set any time part to midnight, so you only need that part:
BUS_DAY := TRUNC(P_START_DATE);
db<>fiddle
Some clients use their own display preferences rather than NLS settings, so you may be seeing the date as 07-Oct-2020 when you query, while the NLS setting has YY or RR. You can query nls_session_parameters to check.

How do I get the day portion(as a string/char) from a date and run tests on it in PL/SQL?

Can someone just tell me why this doesn't work. As far as I know, I've put the string into the variable and wanted to test it but it didn't seem to work and brought up errors.
SET SERVEROUTPUT ON;
DECLARE
DATETODAY DATE := SYSDATE;
DAYT VARCHAR2(10);
BEGIN
SELECT TO_CHAR(SYSDATE,'DAY') INTO DAYT FROM DUAL;
DBMS_OUTPUT.PUT_LINE('The date today is '||DATETODAY ||' and it is ' ||DAYT);
IF DAYT = 'SATURDAY' OR DAYT = 'SUNDAY' THEN
: DBMS_OUTPUT.PUT_LINE('Today is '||DAYT||' and its is a weekend');
ELSE
: DBMS_OUTPUT.PUT_LINE('Today is '||DAYT||' and its a week day');
END IF;
END;
/
SELECT TO_CHAR (SYSDATE, 'day') DayName,
TO_CHAR (SYSDATE, 'd') DayOfWeek,
TO_CHAR (SYSDATE, 'dd') DayOfMonth,
TO_CHAR (SYSDATE, 'ddd') DayOfYear
FROM DUAL;
DayName is bound to the language of your db. Better use DayOfWeek.
Anyway you should run the example-sql provided to compare your output. Perhaps a UPPER() could also help you.
TO_DATE( date_value, 'DAY' ) returns a fixed-length string (not a variable-length); this means that it is right-padded with space characters:
SQL Fiddle
Query 1:
SELECT TO_CHAR( DATE '2018-05-05', 'DAY' ) AS day,
DUMP( TO_CHAR( DATE '2018-05-05', 'DAY' ) ) As dump
FROM DUAL
Results:
| DAY | DUMP |
|-----------|-----------------------------------------|
| SATURDAY | Typ=1 Len=9: 83,65,84,85,82,68,65,89,32 |
Shows that the final character has ASCII code 32 - a space.
So your code should be:
SET SERVEROUTPUT ON;
DECLARE
DATETODAY DATE := SYSDATE;
DAYT VARCHAR2(10);
BEGIN
DAYT := TO_CHAR( DATETODAY ,'DAY');
DBMS_OUTPUT.PUT_LINE('The date today is '||DATETODAY ||' and it is ' ||DAYT);
IF DAYT = 'SATURDAY ' OR DAYT = 'SUNDAY ' THEN
DBMS_OUTPUT.PUT_LINE('Today is '||DAYT||' and its is a weekend');
ELSE
DBMS_OUTPUT.PUT_LINE('Today is '||DAYT||' and its a week day');
END IF;
END;
/
Which (for my NLS_DATE_FORMAT setting of YYYY-MM-DD HH24:MI:SS) outputs:
The date today is 2018-05-04 14:32:25 and it is FRIDAY
Today is FRIDAY and its a week day
Changing the initial assignment to DATETODAY DATE := DATE '2018-05-05'; then the output is:
The date today is 2018-05-05 00:00:00 and it is SATURDAY
Today is SATURDAY and its is a weekend
However, you could also write it as:
DECLARE
DATETODAY DATE := SYSDATE;
DAYT VARCHAR2(10);
BEGIN
DAYT := TO_CHAR( DATETODAY ,'DAY');
DBMS_OUTPUT.PUT_LINE('The date today is '||DATETODAY ||' and it is ' ||DAYT);
IF DATETODAY - TRUNC( DATETODAY, 'IW' ) >= 5 THEN
DBMS_OUTPUT.PUT_LINE('Today is '||DAYT||' and its is a weekend');
ELSE
DBMS_OUTPUT.PUT_LINE('Today is '||DAYT||' and its a week day');
END IF;
END;
/
As TRUNC( DATETODAY, 'IW' ) will truncate the date back to the start of the ISO week (always midnight on Monday) and this is independent of any NLS_DATE_LANGUAGE or NLS_TERRITORY settings that affect the TO_CHAR function.

Error: PL/SQL: Compilation unit analysis terminated?

Keep Getting the same error on both codes!!
DROP TABLE Date_Dimension CASCADE CONSTRAINTS ;
CREATE TABLE Date_Dimension
(
date_key NUMBER NOT NULL ,
full_date DATE ,
day_of_week NUMBER ,
day_num_in_month NUMBER ,
day_num_overall NUMBER ,
day_name VARCHAR2 (9) ,
day_abbrev VARCHAR2 (3) ,
week_num_in_year NUMBER ,
week_num_overall NUMBER ,
week_begin_date DATE ,
MONTH NUMBER ,
month_number_overall NUMBER ,
month_name VARCHAR2 (9) ,
month_abbrev VARCHAR2 (3) ,
quarter NUMBER ,
YEAR VARCHAR2 (20) ,
century NUMBER
) ;
ALTER TABLE Date_Dimension ADD CONSTRAINT Date_Dimension_PK PRIMARY KEY ( date_key ) ;
Create or replace PROCEDURE sp_DATE_DIMENSION(v_STARTDATE IN INT, v_END_YEAR IN INT) IS
v_STARTDATE DATE;
v_ENDDATE DATE;
v_STARTDATE Date := to_date('2005/01/01' || v_START_YEAR, 'YYYY/MM/DD');
v_ENDDATE Date := to_date('2020/12/31' || v_END_YEAR,'YYYY/MM/DD');
BEGIN
INSERT INTO
Date_Dimension
(date_key,full_date, day_of_week, day_num_in_month, day_num_overall, day_name, day_abbrev, week_num_in_year, week_num_overall, month, month_name, month_abbrev, quarter, year, century)
VALUES
(
'1',TO_DATE(v_STARTDATE, 'yyyy/mm/dd'), TO_NUMBER(v_STARTDATE, 'D'), TO_NUMBER(v_STARTDATE, 'DD'), TO_NUMBER(v_STARTDATE, 'DDD'), TO_CHAR(v_STARTDATE, 'DAY'), TO_CHAR(v_STARDATE, 'DY'), TO_NUMBER(v_STARTDATE, 'IW'), TO_NUMBER(v_STARTDATE, 'WW'), TO_NUMBER(v_STARTDATE, 'MM'), TO_CHAR (v_STARTDATE, 'MONTH'), TO_CHAR (v_STARTDATE, 'MON'), TO_NUMBER (v_STARTDATE, 'Q'), TO_CHAR (v_STARTDATE, 'YEAR'), TO_NUMBER (v_STARTDATE, 'CC')
)
;
IF v_STARTDATE > v_ENDDATE THEN
DBMS_OUTPUT.PUT_LINE ('ERROR IN CODE REGARDING DATES CHOSEN');
ELSE
WHILE v_STARTDATE <= V_ENDDATE LOOP
DBMS_OUTPUT.PUT_LINE ('Date : '||to_char(v_StartDate,'YYYY / MM / DD'));
v_STARTDATE := v_STARTDATE + 1;
END LOOP;
END IF;
END;
Your code gets
PLS-00410: duplicate fields in RECORD,TABLE or argument list are not permitted
You have duplicated the name startdate from the procedure's format argument list as a local variable; and then repeated both of them again. I think you meant the formal argument to just be the year, since it's a number. Your conversion to a date is also then wrong:
to_date('2005/01/01' || v_START_YEAR, 'YYYY/MM/DD')
... doesn't make sense, as you already have the year 2005 hard-coded.
I think for that part you want something more like:
create or replace PROCEDURE sp_DATE_DIMENSION(p_START_YEAR IN NUMBER, p_END_YEAR IN NUMBER) IS
l_START_DATE Date := to_date(p_START_YEAR ||'-01-01', 'YYYY-MM-DD');
l_END_DATE Date := to_date(p_END_YEAR ||'-01-01', 'YYYY-MM-DD');
BEGIN
with other variables references tweaked to match. In your insert you're passing the first value as the string '1' instead of the number 1. You then call to_date() against variables which are already dated; and you call to_number() with a format mask for a date element - for those you need to convert to a string, and then to a number. So that insert woudl become more like:
INSERT INTO Date_Dimension (date_key, full_date, day_of_week, day_num_in_month,
day_num_overall, day_name, day_abbrev, week_num_in_year, week_num_overall,
month, month_name, month_abbrev, quarter, year, century)
VALUES (1,
l_START_DATE,
TO_NUMBER(TO_CHAR(l_START_DATE, 'D')),
TO_NUMBER(TO_CHAR(l_START_DATE, 'DD')),
TO_NUMBER(TO_CHAR(l_START_DATE, 'DDD')),
TO_CHAR(l_START_DATE, 'DAY'),
TO_CHAR(l_START_DATE, 'DY'),
TO_NUMBER(TO_CHAR(l_START_DATE, 'IW')),
TO_NUMBER(TO_CHAR(l_START_DATE, 'WW')),
TO_NUMBER(TO_CHAR(l_START_DATE, 'MM')),
TO_CHAR(l_START_DATE, 'MONTH'),
TO_CHAR(l_START_DATE, 'MON'),
TO_NUMBER(TO_CHAR(l_START_DATE, 'Q')),
TO_CHAR (l_START_DATE, 'YEAR'),
TO_NUMBER(TO_CHAR(l_START_DATE, 'CC'))
);
It isn't good practice to use dbms_output for error messages, as (a) a calling program has no other indication that something is wrong, and (b) even in a simple client call a user may well not even have capture or display of those enabled. It's better to throw an exception:
IF l_START_DATE > l_END_DATE THEN
RAISE_APPLICATION_ERROR (-20001, 'ERROR IN CODE REGARDING DATES CHOSEN');
END IF;
WHILE l_START_DATE <= l_END_DATE LOOP
DBMS_OUTPUT.PUT_LINE ('Date : ' || to_char(l_START_DATE, 'YYYY / MM / DD'));
l_START_DATE := l_START_DATE + 1;
END LOOP;
END;
/
The exception will cause the procedure to terminate early, so you don't need the else part as nothing beyond that is reached anyway if the dates are wrong.
Even so, you probably really want to do the insert inside the loop so you create all the relevant rows; and it would make sense to do the check and exception throw right at the start (maybe comparing the years rather than the dates, but doesn't really matter. And probably other things I've forgotten - hopefully this will put you more towards the right track. You don't really need a procedure for this, or even PL/SQL, as it could be done in plain SQL and a single insert, but hopefully this is an exercise.

Oracle - break dates into quarters

Given 2 dates (StartDate and EndDate), how to do i generate quarterly periods in Pl/SQL.
Example:
Start Date: 01-JAN-2009
End Date: 31-DEC-2009
Expected Output:
StartDate EndDate
01-JAN-2009 31-MAR-2009
01-APR-2009 30-JUN-2009
01-JUL-2009 30-SEP-2009
01-OCT-2009 31-DEC-2009
SELECT ADD_MONTHS( TRUNC(PARAM.start_date, 'Q'), 3*(LEVEL-1) ) AS qstart
, ADD_MONTHS( TRUNC(PARAM.start_date, 'Q'), 3*(LEVEL) ) -1 AS qend
FROM ( SELECT TO_DATE('&start_date') AS start_date
, TO_DATE('&end_date') AS end_date
FROM DUAL
) PARAM
CONNECT BY ADD_MONTHS( TRUNC(PARAM.start_date, 'Q'), 3*(LEVEL) ) -1
<= PARAM.end_date
Rules for params, you may need to adjust the query to suit your purposes:
If start_date is not exact quarter start it effectively uses the quarter contain start date.
If end_date is not exact quarter end then we end on the quarter that ended BEFORE end_date (not the one containing end date).
Here's one way that you can do it with PL/SQL
declare
startDate Date := '01-JAN-2009';
endDate Date := '31-DEC-2009';
totalQuarters number := 0;
begin
totalQuarters := round(months_between(endDate, startDate),0)/3;
dbms_output.put_line ('total quarters: ' || totalQuarters);
for i in 1..totalQuarters loop
dbms_output.put_line('start date: '|| startDate || ' end date:' || add_months(startDate -1,3));
startDate := add_months(startDate,3) ;
end loop;
end;