to_char(sysdate,'Day') returns the current day of the week. What I want is to get the date of the most recent "sunday" that passed. Of course we can go for a complex query to get it done. But is there any simple way I'm not aware of?
You can do it with
SELECT NEXT_DAY(SYSDATE-8, 'SUN') FROM DUAL;
here
SYSDATE-8
returns the day before 8 days &
NEXT_DAY(mydate, 'SUN')
returns the next sunday to it
You can do it with a query as simple as this:
SELECT dt
FROM ( SELECT SYSDATE - LEVEL - 1 dt
FROM DUAL
CONNECT BY LEVEL < 8)
WHERE TO_CHAR ( dt, 'd' ) = 1
I think NEXT_DAY(SYSDATE-6, 'SUN') would work. Assuming current day as sunday, I go back 6 days (i.e. last monday) and so when I look for the next sunday, I'd get the sysdate itself. Whereas, next_day(sysdate-8,'SUN') could be used to get the last day of previous week. Thanks for your efforts.
SELECT dt
FROM ( SELECT to_date('5/23/2014') - LEVEL + 1 dt
FROM DUAL
CONNECT BY LEVEL < 8)
WHERE TO_CHAR ( dt, 'd' ) = 1
I changed LEVEL - 1 dt to LEVEL + 1 dt to get it to work to find the previous Sunday.
Related
I want to get all dates of a month based on day name like if I want to get all FRIDAY dates from current date to a specific end date. I got a solution in SQL but can't find any solution in ORACLE.
You can use hierarchy query with next_day function as follows:
-- input variable
with dataa (start_date, end_date) as
(select date '2020-11-01', date '2020-12-15' from dual)
-- your query starts from here
select next_day((level-1)*7 + (start_date - 1), 'FRIDAY')
FROM DATAA
connect by
next_day((level-1)*7 + (start_date - 1),'FRIDAY') <= end_date
Db<>fiddle here
You can use hierarchical query as
WITH t AS
(
SELECT TRUNC(sysdate,'MM')+level-1 AS Fridays
FROM dual
WHERE TO_CHAR(TRUNC(sysdate,'MM')+level-1,'Dy','NLS_DATE_LANGUAGE=American')='Fri'
CONNECT BY level <= LAST_DAY(TRUNC(sysdate)) - TRUNC(sysdate,'MM') + 1
)
SELECT *
FROM t
WHERE Fridays BETWEEN :data1 AND :date2
Demo
Using connect by then filtering by Friday is a simple solution. In the example below I am using July 1st as the start date and December 1st as the end date.
SELECT DATE '2020-7-1' + LEVEL AS friday_dates
FROM DUAL
WHERE TO_CHAR (DATE '2020-7-1' + LEVEL, 'DY') = 'FRI'
CONNECT BY LEVEL <= DATE '2020-12-1' - DATE '2020-7-1';
Suppose I enter WeekNo: 14 the query should return the From Date: April 4th 2016, since the week 14 starts from 4th April to 10th April
select to_date('14','iw') FROM dual;
something like this ? (it work for current year) there discard data from another years
with dates as (select to_char(
to_date('1.01.'||extract(year from sysdate),'dd.mm.yyyy' ) + level -1
,'IW') we,
to_date('1.01.'||extract(year from sysdate),'dd.mm.yyyy' ) + level -1 da
from dual
connect by level <= 365 + 10 )
select * from (
select case
when -- we = null if number of week in jan > 1,2,3,4....
((to_number(we) > 40 )
and extract(year from sysdate) = extract(year from da)
and extract(month from da) = '01') or
-- we = null when current year < year of da
(extract(year from sysdate) != extract(year from da))
then
null
else we
end we,
da
from dates
)
where we = 14
and rownum = 1
Dealing with ISO-Weeks is not trivial, for example January, 1st 2016 is week 53 of 2015, see
select to_char(date '2016-01-01', 'iyyy-"W"iw') from dual;
So, providing only the week number without the (ISO-) year is ambiguous - although it is obvious as along as you are not around new-years date.
Some time ago I wrote this function to get the date from ISO-Week.
FUNCTION ISOWeekDate(week INTEGER, YEAR INTEGER) RETURN DATE DETERMINISTIC IS
res DATE;
BEGIN
IF week > 53 OR week < 1 THEN
RAISE VALUE_ERROR;
END IF;
res := NEXT_DAY(TO_DATE( YEAR || '0104', 'YYYYMMDD' ) - 7, 'MONDAY') + ( week - 1 ) * 7;
IF TO_CHAR(res, 'fmIYYY') = YEAR THEN
RETURN res;
ELSE
RAISE VALUE_ERROR;
END IF;
END ISOWeekDate;
Of course you can just select NEXT_DAY(TO_DATE( YEAR || '0104', 'YYYYMMDD' ) - 7, 'MONDAY') + ( week - 1 ) * 7;, however this would not be error-safe if somebody uses the wrong year.
If it is not an issue to append the year to the week you are looking for, you can also use this :
SELECT (TRUNC ( TO_DATE (SUBSTR ('201627', 1, 4) || '0131', 'YYYY'|| 'MMDD'), 'IYYY')
+ ( 7 * ( TO_NUMBER (SUBSTR ('201627', 5)) - 1)) ) AS iw_Monday
FROM dual
With in this example 201627 being the YYYYIW you are looking for.
It will return the date of the MONDAY of that week.
Found it on Oracle forums, there are a couple of other solutions there. I found this one to be the most elegant.
The advantage is that you do everything from the SELECT, and you don't need either a function or PL/SQL or a WHERE clause.
Disadvantages : you must append the year and specify your search week 2 times, unless you use a variable
Here is a simple and direct computation, taking advantage of various Oracle date functions. Since it compares to what Oracle already counts as ISO week etc., it shouldn't be subject to any of the difficulties other solutions correctly point to and address with additional code.
The "magic number" 14 in the formula should instead be a bind variable, perhaps :iw, or some other mechanism of inputting the ISO week number into the query.
select trunc(sysdate, 'iw') - 7 * (to_number(to_char(trunc(sysdate), 'iw')) - 14) as dt
from dual;
DT
----------
2016-04-04
1 row selected.
I have dates stored like String in database.
The format is 'yyyy-ww' (example: '2015-43').
I need to get the first day of the week.
I tried to convert this string into date but there is no 'ww' option for the function "to_date".
Do you have an idea to perform this convertion?
EDIT
Test results based on the answers -
Thanks for your anwsers, but I have many problems to apply your solutions to my context:
select
TRUNC ( 2015 + ((43 - 1) * 7), 'IW' )
from dual
==> Error : ORA-01722: invalid number
select
TRUNC(to_date('2015','YYYY')+ to_number('01') *7, 'IW')
from dual
==> 2015-02-02 00:00:00
I waited for a date in january
select
trunc(to_date(regexp_substr('2015-01', '\d+',1,2), 'YYYY') + regexp_substr('2015-01', '\d+') * 7, 'IW') dt2
from dual
==> 0039-09-14 00:00:00
select
regexp_substr('2015-01', '\d+',1,2) as res1,
regexp_substr('2015-01', '\d+') * 7 as res2
from dual
==> res1 = 01
==> res2 = 14105
try to use by truncate
with t as (
select '16-2010' dt from dual
)
--
--
select dt,
trunc(to_date(regexp_substr(dt, '\d+',1,2), 'YYYY') + regexp_substr(dt, '\d+') * 7, 'IW') dt2
from t
I have dates stored like String in database.
You should never do that. It is a bad design. you should store date as DATE and not as a string. For all kinds of requirements for date manipulations Oracle provides the required DATE functions and format models. As and when needed, you could extract/display the way you want.
I need to get the first day of the week.
TRUNC (dt, 'IW') returns the Monday on or before the given date.
Anyway, in your case, you have the literal as YYYY-WW format. You could first extract the year and week number and combine them together to get the date using TRUNC.
TRUNC ( year + ((week_number - 1) * 7)
, 'IW
)
So, the above should give you the Monday of the week number passed for that year.
SQL> WITH DATA AS
2 ( SELECT '2015-43' str FROM dual
3 )
4 SELECT TRUNC(to_date(SUBSTR(str, 1, 4),'YYYY')+ to_number(SUBSTR(str, instr(str, '-',1)+1))*7, 'IW')
5 FROM DATA
6 /
TRUNC(TO_
---------
23-NOV-15
SQL>
Similar to Lalit's, however, I think I've corrected the math (his seemed to be off a bit when I tested .. )
with w_data as (
select sysdate + level +200 d from dual connect by level <= 10
),
w_weeks as (
select d, to_char(d,'yyyy-iw') c
from w_data
)
SELECT d, c, trunc(d,'iw'),
TRUNC(
to_date(SUBSTR(c, 1, 4)||'0101','yyyymmdd')-8+to_char(to_date(SUBSTR(c, 1, 4)||'0101','yyyymmdd'),'d')
+to_number(SUBSTR(c, instr(c, '-',1)+1)-1)*7 ,'IW')
FROM w_weeks;
The extra columns help show the dates before, and after.
I would do the following:
WITH d1 AS (
SELECT '2015-43' AS mydate FROM dual
)
SELECT TRUNC(TRUNC(TO_DATE(REGEXP_SUBSTR(mydate, '^\d{4}'), 'YYYY'), 'YEAR') + (COALESCE(TO_NUMBER(REGEXP_SUBSTR(mydate, '\d+$')), 0)-1) * 7, 'IW')
FROM d1
The first thing the above query does is get the first four digits of the string 2015-43 and truncates that to the closest year (if you convert convert 2015 using TO_DATE() it returns a date within the current month; that is SELECT TO_DATE('2015', 'YYYY') FROM dual returns 01-FEB-2015; we need to truncate this value to the YEAR in order to get 01-JAN-2015). I then add the number of weeks minus one times seven and truncate the whole thing by IW. This returns a date of 01-OCT-2015 (see SQL Fiddle here).
According ISO the 4th of January is always in week 1, so your query should look like
Select
TRUNC(TO_DATE(REGEXP_SUBSTR(your_column, '^\d{4}')||'-01-04', 'YYYY-MM-DD')
+ 7*(REGEXP_SUBSTR(your_column, '\d$')-1), 'IW')
from your_table;
However, there is a problem. ISO year used for Week number can be different than actual year. For example, 1st Jan 2008 was in ISO week number 53 of 2007.
I think a proper working solution you get only when you generate ISO weeks from date value.
WITH w AS
(SELECT TO_CHAR(DATE '2010-01-04' + LEVEL * INTERVAL '7' DAY, 'IYYY-IW') AS week_number,
TRUNC(DATE '2010-01-04' + LEVEL * INTERVAL '7' DAY, 'IW') AS first_day
FROM dual
CONNECT BY DATE '2010-01-04' + LEVEL * INTERVAL '7' DAY < SYSDATE)
SELECT your_Column, first_day
FROM w your_table
JOIN w ON week_number = your_Column;
Your date range must bigger than 2010-01-04 and not bigger than current day.
This is what I used:
select
to_date(substr('2017/01',1,4)||'/'||to_char(to_number(substr('2017/01',6,2)*7)-5),'yyyy/ddd') from dual;
Does anyone know how can I calculate the number of weekdays between two date fields? I'm using oracle sql developer. I need to find the average of weekdays between multiple start and end dates. So I need to get the count of days for each record so I can average them out. Is this something that can be done as one line in the SELECT part of my query?
This answer is similar to Nicholas's, which isn't a surprise because you need a subquery with a CONNECT BY to spin out a list of dates. The dates can then be counted while checking for the day of the week. The difference here is that it shows how to get the weekday count value on each line of the results:
SELECT
FromDate,
ThruDate,
(SELECT COUNT(*)
FROM DUAL
WHERE TO_CHAR(FromDate + LEVEL - 1, 'DY') NOT IN ('SAT', 'SUN')
CONNECT BY LEVEL <= ThruDate - FromDate + 1
) AS Weekday_Count
FROM myTable
The count is inclusive, meaning it includes FromDate and ThruDate. This query assumes that your dates don't have a time component; if they do you'll need to TRUNC the date columns in the subquery.
You could do it the following way :
Lets say we want to know how many weekdays between start_date='01.08.2013' and end_date='04.08.2013' In this example start_date and end_date are string literals. If your start_date and end_date are of date datatype, the TO_DATE() function won't be needed:
select count(*) as num_of_weekdays
from ( select level as dnum
from dual
connect by (to_date(end_date, 'dd.mm.yyyy') -
to_date(start_date, 'dd.mm.yyyy') + 1) - level >= 0) s
where to_char(sysdate + dnum, 'DY',
'NLS_DATE_LANGUAGE=AMERICAN') not in ('SUN', 'SAT')
Result:
num_of_weekdays
--------------
2
Checkout my complete working function code and explanation at
https://sqljana.wordpress.com/2017/03/16/oracle-calculating-business-days-between-two-dates-in-oracle/
Once you have created the function just use the function as part of the SELECT statement and pass in the two date columns for Start and End dates like this:
SELECT Begin_Date, End_Date, fn_GetBusinessDaysInterval(Begin_Date, End_Date) AS BusinessDays FROM YOURTABLE;
I have a table with date field and there will be multiple entries for each day. I need to get the count of weekdays having at least one entry in that weekday.
If the date range is 27-Feb-12 till 2-Apr-12 then there are 26 week days and it needs to return 26 if we have entries in all weekdays and a lesser count if there is no entry on a particular day.
Say a table Transaction with tid, type, createddate.
Can someone please suggest me a good SQL approach ? I am running on a Oracle DB
Try breaking down the problem into multiple smaller problems. Here's how..(I am using march-10 instead of april 2nd in your example, to keep the data set smaller).
Get all the dates between the two dates.
select to_date('27-feb-2012','dd-mon-yyyy') + level -1
from dual
connect by level < ( to_date('10-mar-12','dd-mon-yy') -
to_date('27-feb-2012','dd-mon-yyyy') +2
)
TO_DATE('
---------
27-FEB-12
28-FEB-12
29-FEB-12
01-MAR-12
02-MAR-12
03-MAR-12
04-MAR-12
05-MAR-12
06-MAR-12
07-MAR-12
08-MAR-12
next, get the weekdays out of this result set..
with all_days as (
select to_date('27-feb-2012','dd-mon-yyyy') + level -1 date1
from dual
connect by level < ( to_date('10-mar-12','dd-mon-yy') -
to_date('27-feb-2012','dd-mon-yyyy') +2
)
)
select date1,
to_char(date1,'D'),
to_char(date1,'Day') Day
from all_days
where to_number(to_char(date1,'D')) between 2 and 6 -- monday through friday
/
DATE1 T DAY
--------- - ---------
27-FEB-12 2 Monday
28-FEB-12 3 Tuesday
29-FEB-12 4 Wednesday
01-MAR-12 5 Thursday
02-MAR-12 6 Friday
05-MAR-12 2 Monday
06-MAR-12 3 Tuesday
07-MAR-12 4 Wednesday
08-MAR-12 5 Thursday
09-MAR-12 6 Friday
--and finally, your exists logic to check if there is any data for this date in your table.
with all_days as (
select to_date('27-feb-2012','dd-mon-yyyy') + level -1 date1
from dual
connect by level < ( to_date('10-mar-12','dd-mon-yy') -
to_date('27-feb-2012','dd-mon-yyyy') +2
)
)
select date1,
to_char(date1,'D'),
to_char(date1,'Day') Day
from all_days
where to_number(to_char(date1,'D')) between 2 and 6 -- monday through friday
and exists (
select 1
from your_table yt
where yt.date_column = all_days.date_column
)
There are 25 total business days between your dates in 2013 - I used current year. With my hypothetical data you will get 17 total working days with entries. To see all days comment SUM() part and uncomment Start_Date... and other columns. Then you can count visually to check results:
SELECT SUM(CASE WHEN Entry_Val > 0 THEN 1 ELSE 0 END) entry_days --, Start_Date, Wk_Day, Day#, entry_val, lvl
FROM
(
SELECT TO_CHAR(TO_DATE('27-FEB-12') + LEVEL-1, 'DD-MON-YYYY') Start_Date
, TO_CHAR(TO_DATE('27-FEB-12') + LEVEL-1, 'DY') Wk_Day
, TO_CHAR(TO_DATE('27-FEB-12') + LEVEL-1, 'D' ) Day#
, MOD(LEVEL, 3) Entry_Val -- Hypot. entry day - count only days with entry_val > 0 --
, LEVEL lvl -- added for clarity
FROM dual CONNECT BY LEVEL <= (SELECT to_date('02-APR-12') - to_date('27-FEB-12') days_btwn FROM dual)
)
WHERE Wk_Day NOT IN ('SAT', 'SUN') -- OR -- Day# NOT IN (1, 7)
/
ENTRY_DAYS
--------
17
I'm interpreting this question a little differently. #Pradeep, is this what you're after?
SELECT COUNT(DISTINCT TRUNC(CreatedDate))
FROM Transaction
WHERE TRUNC(CreatedDate) BETWEEN DATE '2012-02-27' AND DATE '2012-04-02'
AND TO_CHAR(CreatedDate, 'DY') NOT IN ('SAT', 'SUN')
If you have entries for all Monday-Friday days between 2/27/2012 and 4/2/2012, the result of this query will be 26. It won't matter how many entries there are for any given day, as long as each day has at least one.
If you don't have any records for two of the days (say 2/28 and 3/22) the result will be 24.
Note that I used TRUNC in the first and third lines above in case your CreatedDate column includes a time component. If it doesn't you can do without the TRUNC.
select to_date('02-04-2012','DD-MM-YYYY')-
to_date('27-02-2012','DD-MM-YYYY')-
2*(to_char(to_date('02-04-2012','DD-MM-YYYY'),'WW')-
to_char(to_date('27-02-2012','DD-MM-YYYY'),'WW'))+1 from dual;