Generate calendar until special date with one query - sql

Need to generate calendar list until "11-30-2014" in one query. But not all days, only working days(Monday-Friday) and except holidays. Holiday days are storing in holidays table. Special table dual in oracle DB is used for generate.
SELECT to_date(current_date + level-1,'MM-DD-YY') as Calendar
FROM dual, holidays
WHERE to_date(current_date,'MM-DD-YY')+level-1 <= to_date('11-30-14','MM-DD-YY')
AND to_char(to_date(current_date,'MM-DD-YY')+level-1,'D') NOT IN (6,7)
CONNECT BY level <= 365
MINUS
SELECT to_date(data,'MM-DD-YY')
FROM holidays;
I did this,but I heart this case can be done with 4 lines. More simple. If someone has any idea how to make this easier then thanks!

You have a pointless cross join to the holidays table in your first from clause; you could move your first where condition into the connect by - presumably without the 365 day restriction, which seems to be at odds with your stated requirement; you are using explicit to_date() and implicit to_char() conversions to remove the time element of current_date which introduces an NLS_DATE_FORMAT dependency and would be better with trunc() anyway:
SELECT TRUNC(current_date) + level - 1 as Calendar
FROM dual
WHERE TO_CHAR(TRUNC(current_date) + level - 1, 'DY', 'NLS_DATE_LANGUAGE=ENGLISH')
NOT IN ('SAT', 'SUN')
CONNECT BY TRUNC(current_date) + level - 1 <= date '2014-11-30'
MINUS SELECT data FROM holidays
... which is a bit simpler, but not really fewer lines except where I've cheated - but number of lines shouldn't be a metric, it should be readable and understandable, and if a few extra line breaks aids that, who cares?
You could also do this instead, using not exists rather than minus:
SELECT *
FROM (
SELECT TRUNC(current_date) + level - 1 as Calendar
FROM dual
CONNECT BY TRUNC(current_date) + level - 1 <= date '2014-11-30'
) t
WHERE TO_CHAR(t.calendar, 'DY', 'NLS_DATE_LANGUAGE=ENGLISH') NOT IN ('SAT', 'SUN')
AND NOT EXISTS (SELECT 1 FROM holidays h WHERE h.data = t.calendar)
... which also separates the date generation in the inner select block and keeps the filters together, which I think is a bit easier to understand and maintain.

Related

How to get entries from a SQL table for past 22 weekdays/ 8 Weekends

I want to get entries from the table for the past 22 weekdays or 8 weekends
I have one created_on column which serves as source of information for finding when the entry was created
I use it to extract dow and filter output by weekday and weekend
But I'm not able to figure out how can I get data for exactly 22 weekdays?
An example query would really help
You can use BETWEEN.
Example,
WHERE tablename.created_on BETWEEN "01.06.2022" AND "15.06.2022"
You could use the 'DY' date mask to filter the days and CONNECT BY LEVEL to get the date counts
SELECT t.something, t.created_on
FROM your_table t
WHERE 22 = (SELECT COUNT(1)
FROM DUAL
WHERE TO_CHAR(TRUNC(t.created_on ,'DD') + LEVEL - 1, 'DY') NOT IN ('SAT', 'SUN')
CONNECT BY LEVEL <= SYSDATE - TRUNC(t.created_on,'DD') + 1);

SQL computing and reusing fiscal year calculation in sql query

I have a condition in my SQL query, using Oracle 11g database, that depends on a plan starting or ending with in a fiscal year:
(BUSPLAN.START_DATE BETWEEN (:YEAR || '-04-01') AND (:YEAR+1 || '-03-31')) OR
(BUSPLAN.END_DATE BETWEEN (:YEAR || '-04-01') AND (:YEAR+1 || '-03-31'))
For now, I am passing in YEAR as a parameter. It can be computed as (pseudocode):
IF CURRENT MONTH IN (JAN, FEB, MAR):
USE CURRENT YEAR // e.g. 2015
ELSE:
USE CURRENT YEAR + 1 // e.g. 2016
Is there a way I could computer the :YEAR parameter within in an SQL query and reuse it for the :YEAR parameter?
CTEs are easy, you can make little tables on the fly. With a 1 row table you just cross join it and then you have that value available every row:
WITH getyear as
(
SELECT
CASE WHEN to_char(sysdate,'mm') in ('01','02','03') THEN
EXTRACT(YEAR FROM sysdate)
ELSE
EXTRACT(YEAR FROM sysdate) + 1
END as ynum from dual
), mydates as
(
SELECT getyear.ynum || '-04-01' as startdate,
getyear.ynum+1 || '-03-31' as enddate
from getyear
)
select
-- your code here
from BUSPLAN, mydates -- this is a cross join
where
(BUSPLAN.START_DATE BETWEEN mydates.startdate AND mydates.enddate) OR
(BUSPLAN.END_DATE BETWEEN mydates.startdate AND mydates.enddate)
note, values statement is probably better if Oracle has values then the first CTE would look like this:
VALUES(CASE WHEN to_char(sysdate,'mm') in ('01','02','03') THEN
EXTRACT(YEAR FROM sysdate)
ELSE
EXTRACT(YEAR FROM sysdate) + 1)
I don't have access to Oracle so I might have bugs typos etc since I didn't test.
In the code you shared there is a problem and a potential problem.
Problem, implicit conversion to date without format string.
In (BUSPLAN.START_DATE BETWEEN (:YEAR || '-04-01') AND (:YEAR+1 || '-03-31')) two strings are being formed and then converted to dates. The conversion to date is going to change depending on the value of NLS_DATE_FORMAT. To insure that the string is converted correctly to_date(:YEAR || '-04-01', 'YYYY-MM-DD').
Potential problem, boundary at the end of the year when time <> midnight.
Oracle's date type holds both date and time. A test like someDate between startDate and endDate will miss all records that happened after midnight on endDate. One simple fix that precludes use of indexes on someDate is trunc(someDate) between startDate and endDate.
A more general approach is to define date ranges and closed open intervals. lowerBound <= aDate < upperBound where lowerBound is the same asstartDateabove andupperBoundisendDate` plus one day.
Note: Some applications used Oracle date columns as dates and always store midnight, if your application is of that sort, then this is not a problem. And check constraints like check (trunc(dateColumn) = dateColumn) would make sure it stays that way.
And now, to answer the question actually asked.
Using subquery factoring (Oracle's terminology) / common table expression (SQL Server's terminology) one can avoid repetition within a query.
Instead of figuring out the proper year, and then using strings to put together dates, the code below starts by getting January 1 at Midnight of the current calendar year, trunc(sysdate, 'YEAR')). Then it adds an offset in months. When the months are Jan, Feb, Mar, the current fiscal year started last year on 4/1, or nine months before the start of this year. The offset is -9. Else the current fiscal year started 4/1 of this calendar year, start of this year plus three months.
Instead of end date, an upper bound is calculated, similar to lower bound, but with the offsets being 12 greater than lower bound to get 4/1 the following year.
with current_fiscal_year as (select add_months(trunc(sysdate, 'YEAR')
, case when extract(month from sysdate) <= 3 then -9 else 3 end) as LowerBound
, add_months(trunc(sysdate, 'YEAR')
, case when extract(month from sysdate) <= 3 then 3 else 15 end) as UpperBound
from dual)
select *
from busplan
cross join current_fiscal_year CFY
where (CFY.LowerBound <= busplan.start_date and busplan.start_date < CFY.UpperBound)
or (CFY.LowerBound <= busplan.end_date and busplan.end_date < CFY.UpperBound)
And yet more unsolicited advise.
The times I've had to deal with fiscal year stuff, avoiding repetition within a query was low hanging fruit. Having the fiscal year calculations consistent and correct among many queries, that was the essence of the work. So I'd recommend a developing PL/SQL package that centralizes fiscal calculations. It might include a function like:
create or replace function GetFiscalYearStart(v_Date in date default sysdate)
return date
as begin
return add_months(trunc(v_Date, 'YEAR')
, case when extract(month from v_Date) <= 3 then -9 else 3 end);
end GetFiscalYearStart;
Then the query above becomes:
select *
from busplan
where (GetFiscalYearStart() <= busplan.start_date
and busplan.start_date < add_months(GetFiscalYearStart(), 12))
or (GetFiscalYearStart() <= busplan.end_date
and busplan.end_date < add_months(GetFiscalYearStart(), 12))

Number of particular days between two dates

Is there an "easy" way to calculate the count of a particular day between two dates (e.g., say I wanted to know the number of Tuesdays between 1st January, 2000 and today)? Moreover, the same question applies more broadly to different units (e.g., how many 2pms between two dates, how many Februaries, how many 21st Augusts, etc.)... The best I've come up with (for days between dates) is this:
with calendar as (
select to_char(:start + level - 1, 'DAY') dayOfWeek
from dual
connect by level <= ceil(:end - :start)
)
select dayOfWeek, count(dayOfWeek)
from calendar
group by dayOfWeek;
I would have to create a view of this -- hardcoding the start and end dates -- to make it convenient(ish) to use; either that or write a function to do the dirty work. That wouldn't be difficult, but I'm wondering if there's already an Oracle function that could do this, accounting for things like leap days, etc.
EDIT This popped up in the related links when I posted this. That more-or-less answers the question for days and I know there's a months_between function that I could use for particular months. Any other related functions I should know about?
Replace start/end dates with your dates. This query calc number of TUE from Jan 1-2013 till today, which is 18:
SELECT count(*) number_of_tue
FROM
(
SELECT TRUNC(TO_DATE(Sysdate), 'YEAR') + LEVEL-1 start_dt
, To_Char(TRUNC(TO_DATE(Sysdate), 'YEAR') + LEVEL - 1, 'DY') wk_day
, To_Char(TRUNC(TO_DATE(Sysdate), 'YEAR') + LEVEL - 1, 'D') wk_day#
, trunc(Sysdate) end_dt
FROM DUAL
CONNECT BY LEVEL <= trunc(Sysdate) - trunc(Sysdate, 'YEAR') -- replace with your start/end dates
)
WHERE wk_day# = 3 -- OR wk_day = 'TUE' --
/

How To Find First Date of All MOnths In A Year

Hello every One i am trying to get Ist date of All months in A year
Like if Curretn year is 2012 and i want to get folowing results from a query
like
1-JAN-2012
1-FEB-2012
1-APR-2012
.
.
.
.
.
.
1-DEC-2012
Is there any one who can solve my problem thanks in advance
Just for completeness - here's a simpler version:
select ADD_MONTHS(TRUNC(SYSDATE,'Y'),ROWNUM-1)
from dual connect by level <= 12;
A calendar table is easier to use. Where I work, for example, you'd just run this query.
select cal_date
from calendar
where year_of_date = 2012
and day_of_month = 1;
There's a lot to be said for a query being obvious.
Please try the following. You may want to tweak the date format/timezone
select to_date('2012/'||l||'/01', 'yyyy/mm/dd')
from (select level l from dual connect by level < 13)
EDIT: As provided by the op in the comments, the current year needs to be taken rather than hardcoding it. The updated query is
SELECT L || '/01/' || TO_CHAR (SYSDATE, 'YYYY') DATESS FROM
(SELECT LEVEL L FROM DUAL CONNECT BY LEVEL < 13)
If you want to use solely date functions to get the value for a specific year you can use the following, also SQL Fiddle'd:
select add_months(trunc(last_day(add_months(trunc(to_date('2011'
,'yyyy')
, 'y')
, -1)
) + 1
)
, level - 1)
from dual
connect by level <= 12
This turns your "year" into a date, truncates that to the 1st of January as by default to_date('2011', 'yyyy') returns the current date in that year. It then removes a month, taking us to the 1st December 2010. Get's the last day of that month, the 31st December and adds a day, so back to the 1st of January, but I also do a connect by level and add the level - 1, i.e. 0 in January to return the correct day.
Unfortunately, Oracle doesn't have a first_day function, which would make this a lot easier.
It's roughly the same for the current year:
select add_months(trunc(last_day(add_months(trunc(sysdate,'y'),-1)) + 1), level - 1)
from dual
connect by level <= 12

Finding the days of the week within a date range using oracle SQL

Suppose the following table structure:
Event:
id: integer
start_date: datetime
end_date: datetime
Is there a way to query all of the events that fall on a particular day of the week? For example, I would like to find a query that would find every event that falls on a Monday. Figuring out if the start_date or end_date falls on a Monday, but I'm not sure how to find out for the dates between.
Pure SQL is preferred since there is a bias against stored procedures here, and we're calling this from a Rails context which from what I understand does not handle stored procedures as well.
SELECT *
FROM event
WHERE EXISTS
(
SELECT 1
FROM dual
WHERE MOD(start_date - TO_DATE(1, 'J') + level - 1, 7) = 6
CONNECT BY
level <= end_date - start_date + 1
)
The subquery iterates all days from start_date to end_date, checks each day, and if it's a Monday, returns 1.
You can easily extend this query for more complex conditions: check whether an event falls on ANY Monday OR Friday 13th, for instance:
SELECT *
FROM event
WHERE EXISTS (
SELECT 1
FROM dual
WHERE MOD(start_date - TO_DATE(1, 'J') + level - 1, 7) = 6
OR (MOD(start_date - TO_DATE(1, 'J') + level - 1, 7) = 3 AND TO_CHAR(start_date + level - 1, 'DD') = '13')
CONNECT BY
level <= end_date - start_date + 1
)
Note that I use MOD(start_date - TO_DATE(1, 'J') + level - 1, 7) instead of TO_CHAR('D'). This is because TO_CHAR('D') is affected by NLS_TERRITORY and should not be used for checking for a certain day of week.
This query does not use any indexes and always performs a full table scan. But this is not an issue in this specific case, as it's highly probable that a given interval will contain a Monday.
Even if the intervals are 1 day long, the index will return 14% of values, if intervals are longer, even more.
Since INDEX SCAN would be inefficient in this case, and the inner subquery is very fast (it uses in-memory FAST DUAL access method), this, I think, will be an optimal method, both by efficiency and extensibility.
See the entry in my blog for more detail:
Checking event dates
This should do it more simply:
select *
from event
where 2 between to_number(trim(to_char(start_date,'D')))
and to_number(trim(to_char(end_date,'D')))
or (end_date - start_date) > 6

Categories