We have a requirement. We need to create a fiscal_week column in a SQL Server table.
Table will have data as Normal_date column as fiscal_date and fiscal_year column which will have year part of the date.
Logic for FISCAL_WEEK is like this:
FIRST FISCAL WEEK WILL START FROM 1 JAN OF EVERY YEAR AND IT WILL BE TILL FIRST FRIDAY.
SECOND WEEK STARTS FROM SATURDAY AND IT WILL RUN TILL NEXT FRIDAY.
THIS WILL GO ON TILL THE END OF YEAR (31 JAN)
We will have data something as below table.
Table With Fields as per Requirement
How would I create query for this ? We will have data from 2010 till 2035 years in the table.
Thanks,
Mahesh
You can use an ad-hoc tally table to create the dates and then the window function sum() over() to calculate the Fiscal_Week
Declare #Date1 date = '2020-01-01'
Declare #Date2 date = '2035-12-31'
Select Fiscal_Date = D
,Fiscal_Year = DatePart(YEAR,D)
,Day_Name = DateName(WEEKDAY,D)
,Fiscal_Week = case when DateName(WEEKDAY,#Date1)='Saturday' then 0 else 1 end
+sum(case when DateName(WEEKDAY,D)='Saturday' then 1 else 0 end) over (partition by DatePart(YEAR,D) order by D)
From ( Select Top (DateDiff(DAY,#Date1,#Date2)+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),#Date1)
From master..spt_values n1,master..spt_values n2,master..spt_values n3
) A
Results
Fiscal_Date Fiscal_Year Day_Name Fiscal_Week
2020-01-01 2020 Wednesday 1
2020-01-02 2020 Thursday 1
2020-01-03 2020 Friday 1
2020-01-04 2020 Saturday 2
2020-01-05 2020 Sunday 2
2020-01-06 2020 Monday 2
...
Other way I found would be as below.
SET DATEFIRST 6
GO
SELECT FISCAL_DATE, DATEPART(WEEK,Fiscal_Date) FISCAL_WEEK FROM ERP.CUSTOM_FISCAL_CALENDER
Related
I am using SQL Server 2014 and I have a table in my Database called Date Dimension.
It has a column called "Date" which contains daily dates from 01 January 2013 to 31 December 2025 (extract of the column given below):
Date
2022-01-01
2022-01-02
2022-01-03
2022-01-04
2022-01-05
2022-01-06
2022-01-07
2022-01-09
...
2022-01-30
2022-01-31
...
I need to create a new column in the Date Dimension Table called "HR_WeekGroup" based on the following logic:
A WeekGroup will start on a Monday and end on a Sunday. WeekGroups will be calculated for EACH Month separately. That is, if the 1st Day of a Month is a Saturday, the WeekGroup for that week will consist of only 2 days (Saturday and Sunday) and if the last Day of that Month is a Monday, the weekGroup will consist of only 1 Day (Monday).
Here is what I'm after (based on the Month of January 2022):
Date HR_WeekGroup
2022-01-01 Wk 01Jan2022-02Jan2022
2022-01-02 Wk 01Jan2022-02Jan2022
2022-01-03 Wk 03Jan2022-09Jan2022
2022-01-04 Wk 03Jan2022-09Jan2022
2022-01-05 Wk 03Jan2022-09Jan2022
2022-01-06 Wk 03Jan2022-09Jan2022
2022-01-07 Wk 03Jan2022-09Jan2022
2022-01-09 Wk 03Jan2022-09Jan2022
... ...
2022-01-30 Wk 24Jan2022-30Jan2022
2022-01-31 Wk 31Jan2022
...
What would be the T-SQL Code that would allow me to create this column?
I had a look at the following pages but I can't figure out how to apply the examples to my specific problem.
What is the SQL syntax to create a column in my Date Dimension Table that will group the dates into this specific week grouping?
How to Group Data by Week in SQL Server
Set DateFirst 1;
-- The above changes the start of the week to Monday.
-- This makes the week as starting on Monday and ending on Sunday
-- DayOfWeek 1 means Monday
-- DayOfWeek 7 means Sunday
with cte as (
select
Date_Column,
MONTH(Date_Column) as [MONTH], -- Month in the year
DAY(Date_Column) as [DAY], -- Day in the month
DATEPART(week,Date_Column) as [WeekOfTheYear], --Week number of the year
DATEPART(dw,Date_Column) as [DayOfWeek], -- The week day of the week
Min(Date_Column) OVER (Partition by DATEPART(week,Date_Column), MONTH(Date_Column), YEAR(Date_Column) ) as [MinDateWeekGroup],
Max(Date_Column) OVER (Partition by DATEPART(week,Date_Column), MONTH(Date_Column), YEAR(Date_Column) ) as [MaxDateWeekGroup]
from date_dimension
--order by YEAR(Date_Column), MONTH(Date_Column), DAY(Date_Column)
)
select
Date_Column,
case when [MinDateWeekGroup] = [MaxDateWeekGroup]
THEN CONCAT('Wk ',
Right('0'+Convert(varchar(10), Day([MinDateWeekGroup])),2),
Convert(char(3), [MinDateWeekGroup], 0),Convert(char(4),YEAR([MinDateWeekGroup])) )
ELSE
CONCAT('Wk ',
Right('0'+Convert(varchar(10), Day([MinDateWeekGroup])),2),
Convert(char(3), [MinDateWeekGroup], 0),Convert(char(4),YEAR([MinDateWeekGroup])),
'-',
Right('0'+Convert(varchar(10), Day([MaxDateWeekGroup])),2),
Convert(char(3), [MaxDateWeekGroup], 0),Convert(char(4),YEAR([MaxDateWeekGroup]))
)
END as [HR_WeekGroup]
from cte
order by YEAR(Date_Column), MONTH(Date_Column), DAY(Date_Column)
Pre Condition: [I am using Week as Sunday as first day of week]
I have table '#TT' as below
Column -> D_Date
----------
2020-12-27 |
2020-12-28 |
2020-12-29 |
2020-12-30 |
2020-12-31 |
2021-01-01 |
2021-01-02 |
I want to get week numbers for date And in case of year end with new year start, I want to mark the first week as 0 for new year if it's days contribution is less than 4 days i.e.
As per US week
2020-12-27 from this **53rd** start
2020-12-28
2020-12-29
2020-12-30
2020-12-31
2021-01-01
2021-01-02 and end at here
In above from 27 till 31 are 5 days of 2020 whereas for 1 and 2 of 2021 are 2 days, thus majority days are from 2020 and not 2021.
Now I want to mark week number for 2021 as 0 for dates 1 & 2 and not 1
I am using below query but it is decreasing my minimum year's week number too. How to achieve this? Please help me.
SELECT
DATEPART( week, D_Date ),
CASE
WHEN COUNT( DATEPART( week, D_Date ) ) > 1 THEN MAX( DATEPART(week, D_Date ) - 1 )
ELSE DATEPART( week, D_Date )
END AS [State]
FROM
#tt
GROUP BY
DATEPART( year, D_Date ),
DATEPART( week, D_Date );
Problem Statement:
I work for a fire department and I am working on statistical analysis of my data. One issue is to generate the number of calls for service for every hour of every day for a calendar year. I need a table that can be joined to the fire incidents that has every day of the year and every hour of each day. What I am hoping for is the following (using military time)
1 January 2017 00:00:00
1 January 2017 00:00:00
1 January 2017 01:00:00
1 January 2017 02:00:00
1 January 2017 03:00:00
1 January 2017 04:00:00
1 January 2017 05:00:00
1 January 2017 06:00:00
1 January 2017 07:00:00
1 January 2017 08:00:00
etc until the end of the year
31 December 2017 21:00:00
31 December 2017 22:00:00
31 December 2017 23:00:00
end of year
this table will allow me to join to the fire incidents table and I will be able to statistically calculate the number of incidents for each hour of the day and for each day of the year. A calculated table is necessary because the fire incidents table has gaps in it. For example; On january 1st at 0100 hours and 0200 hours and 0300 hours no emergency calls came in. Therefore I cannot make a calculation using the fire incidents table because there is no data for when no calls come in. The fire incidents table with gaps looks like:
TimeInterval, IncidentAddress
1 january 2017 00:00:00, 123 Elm Street
1 January 2017 04:00:00, 456 Oak Street
1 January 2017 05:00:00, 789 Maple Street
(Notice there are no fire calls for the hours of 0100, 0200 and 0300. Those are the gaps.)
Because there are gaps in the data where zeros should be the calculated averages necessary for a Poisson distribution are missing. The averages are incorrect.
Desired Output:
My goal is to have a calendar with an hours of day table to join to my fire incidents so my results set returns. Here is a rough draft of a query that returns every row from the calendar table and rows from the fire incidents table if there is matching value.
SELECT
TimeInterval
, COUNT(Incidents) AS [CountOfIncidents] /*this should probably be a COALESCE statement*/
FROM CalendarTable /*all rows from the calendar with hours and rows with data from FireIncidents*/
LEFT OUTER JOIN FireIncidents ON CalendarTable.timeInterval = FireIncidents.TimeInterval
GROUP BY TimeInterval
Query would return what I am hoping to achieve:
TimeInterval, CountOfIncidents
1 january 2017 00:00:00, 5
1 January 2017 01:00:00, 0
1 January 2017 02:00:00, 0
1 January 2017 03:00:00, 0
1 January 2017 04:00:00, 2
1 January 2017 05:00:00, 1
(Notice the hours of 0100, 0200 and 0300 have zero number of calls. This is what I want! Now I can create a histogram showing how many hours had zero calls. Or I can calculate an average value that takes into account zero calls for parts of the day.)
What I have tried:
I have tried the following but I cannot figure out how to create a table out of this and how to make it a finished product as you can see below in the Question paragraph.
DECLARE #DayOfYearNumber INT
DECLARE #HourNumber INT
SET #DayOfYearNumber = 1
SET #HourNumber = 0
PRINT 'Year' + ', ' + 'CalendarDayOfYear' + ', ' + 'HourOfDay'
WHILE #DayOfYearNumber < 366
BEGIN
SET #HourNumber = 0
WHILE #HourNumber < 24
BEGIN PRINT '2017' + ', ' + CONVERT(VARCHAR, #DayOfYearNumber) + ' ' + CONVERT(VARCHAR, #HourNumber)
SET #HourNumber = #HourNumber + 1
END
SET #DayOfYearNumber = #DayOfYearNumber + 1
END
Question:
How do I generate a calendar table in SQL Server 2012 that will have every day of the year and every hour of each day. My example again
1 January 2017 00:00:00
1 January 2017 01:00:00
1 January 2017 02:00:00
1 January 2017 03:00:00
1 January 2017 04:00:00
1 January 2017 05:00:00
1 January 2017 06:00:00
1 January 2017 07:00:00
1 January 2017 08:00:00
etc until the end of the year
31 December 2017 21:00:00
31 December 2017 22:00:00
31 December 2017 23:00:00
end of year
A simple method uses recursion:
with d as (
select cast('2017-01-01' as datetime) as dte
union all
select dateadd(hour, 1, dte)
from d
where dateadd(hour, 1, dte) < '2018-01-01'
)
select d.*
from d
option (maxrecursion 0);
Although recursion is surprisingly fast, if you are going to be needing this multiple times, you might want to consider have a numbers table around or storing this in a temporary or permanent table.
A alternative method to using a rCTE is a Tally Table, as it's not RBAR:
DECLARE #TopDate date = '20550101';
WITH N AS(
SELECT *
FROM (VALUES (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) V(N)),
Tally AS(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 AS I
FROM N N1
CROSS JOIN N N2
CROSS JOIN N N3
CROSS JOIN N N4
CROSS JOIN N N5
CROSS JOIN N N6)
SELECT DATEADD(HOUR, I, '20170101') AS DateValue
FROM Tally
WHERE DATEADD(HOUR, I, '20170101') < #TopDate;
You could achieve it using single query. All you need is tally(number) table:
WITH tally(n) AS (
SELECT ROW_NUMBER() OVER(ORDER BY 1/0)-1
FROM master..spt_values s1, master..spt_values s2, master..spt_values s3
)
-- INSERT INTO calendar(col_name)
SELECT DATEADD(HOUR,n,'20170101') AS d
FROM tally
WHERE DATEADD(HOUR,n,'20170101') <= '20180101'
Rextester Demo
I have the below table.
TableA
DaysInMonth CalDate CalendarMonth MonthEndInd CalDateMonth WorkDay HolidayInd
31 3/26/2018 MAR 2018 N 3/31/2018 1 N
31 3/25/2018 MAR 2018 N 3/31/2018 0 N
How can I calculate the number of WorkDays in a month?
I'm not sure where to start, so I don't have any work to show.
Expected output
DaysInMonth CalDate CalendarMonth MonthEndInd CalDateMonth WorkDay HolidayInd WorkDaysInMonth
31 3/26/2018 MAR 2018 N 3/31/2018 1 N
31 3/25/2018 MAR 2018 N 3/31/2018 0 N
SQL I have the below but how can I add this to my query
SELECT COUNT(*),C.CALENDAR_MONTH FROM HUM.CALENDAR C WHERE 1=1 AND C.WORKDAY = 1 GROUP BY C.CALENDAR_MONTH
You can use an analytic function:
SELECT c.*,
SUM( WorkDay ) OVER ( PARTITION BY CalendarMonth ) AS WorkDaysInMonth
FROM Calendar C;
SQLFIDDLE
In much the same way as SUM you can also use COUNT if you want to limit it to just working days;
SELECT c.*,
COUNT(*) OVER(PARTITION BY calendarmonth ORDER BY calendarmonth) WDIM
FROM calendar
WHERE workday = 1
Depends how you want to use it I guess.
I have a Postgres 9.1 database. I am trying to generate the number of records per week (for a given date range) and compare it to the previous year.
I have the following code used to generate the series:
select generate_series('2013-01-01', '2013-01-31', '7 day'::interval) as series
However, I am not sure how to join the counted records to the dates generated.
So, using the following records as an example:
Pt_ID exam_date
====== =========
1 2012-01-02
2 2012-01-02
3 2012-01-08
4 2012-01-08
1 2013-01-02
2 2013-01-02
3 2013-01-03
4 2013-01-04
1 2013-01-08
2 2013-01-10
3 2013-01-15
4 2013-01-24
I wanted to have the records return as:
series thisyr lastyr
=========== ===== =====
2013-01-01 4 2
2013-01-08 3 2
2013-01-15 1 0
2013-01-22 1 0
2013-01-29 0 0
Not sure how to reference the date range in the subsearch. Thanks for any assistance.
The simple approach would be to solve this with a CROSS JOIN like demonstrated by #jpw. However, there are some hidden problems:
The performance of an unconditional CROSS JOIN deteriorates quickly with growing number of rows. The total number of rows is multiplied by the number of weeks you are testing for, before this huge derived table can be processed in the aggregation. Indexes can't help.
Starting weeks with January 1st leads to inconsistencies. ISO weeks might be an alternative. See below.
All of the following queries make heavy use of an index on exam_date. Be sure to have one.
Only join to relevant rows
Should be much faster:
SELECT d.day, d.thisyr
, count(t.exam_date) AS lastyr
FROM (
SELECT d.day::date, (d.day - '1 year'::interval)::date AS day0 -- for 2nd join
, count(t.exam_date) AS thisyr
FROM generate_series('2013-01-01'::date
, '2013-01-31'::date -- last week overlaps with Feb.
, '7 days'::interval) d(day) -- returns timestamp
LEFT JOIN tbl t ON t.exam_date >= d.day::date
AND t.exam_date < d.day::date + 7
GROUP BY d.day
) d
LEFT JOIN tbl t ON t.exam_date >= d.day0 -- repeat with last year
AND t.exam_date < d.day0 + 7
GROUP BY d.day, d.thisyr
ORDER BY d.day;
This is with weeks starting from Jan. 1st like in your original. As commented, this produces a couple of inconsistencies: Weeks start on a different day each year and since we cut off at the end of the year, the last week of the year consists of just 1 or 2 days (leap year).
The same with ISO weeks
Depending on requirements, consider ISO weeks instead, which start on Mondays and always span 7 days. But they cross the border between years. Per documentation on EXTRACT():
week
The number of the week of the year that the day is in. By definition (ISO 8601), weeks start on Mondays and the first week of a
year contains January 4 of that year. In other words, the first
Thursday of a year is in week 1 of that year.
In the ISO definition, it is possible for early-January dates to be part of the 52nd or 53rd week of the previous year, and for
late-December dates to be part of the first week of the next year. For
example, 2005-01-01 is part of the 53rd week of year 2004, and
2006-01-01 is part of the 52nd week of year 2005, while 2012-12-31 is
part of the first week of 2013. It's recommended to use the isoyear
field together with week to get consistent results.
Above query rewritten with ISO weeks:
SELECT w AS isoweek
, day::text AS thisyr_monday, thisyr_ct
, day0::text AS lastyr_monday, count(t.exam_date) AS lastyr_ct
FROM (
SELECT w, day
, date_trunc('week', '2012-01-04'::date)::date + 7 * w AS day0
, count(t.exam_date) AS thisyr_ct
FROM (
SELECT w
, date_trunc('week', '2013-01-04'::date)::date + 7 * w AS day
FROM generate_series(0, 4) w
) d
LEFT JOIN tbl t ON t.exam_date >= d.day
AND t.exam_date < d.day + 7
GROUP BY d.w, d.day
) d
LEFT JOIN tbl t ON t.exam_date >= d.day0 -- repeat with last year
AND t.exam_date < d.day0 + 7
GROUP BY d.w, d.day, d.day0, d.thisyr_ct
ORDER BY d.w, d.day;
January 4th is always in the first ISO week of the year. So this expression gets the date of Monday of the first ISO week of the given year:
date_trunc('week', '2012-01-04'::date)::date
Simplify with EXTRACT()
Since ISO weeks coincide with the week numbers returned by EXTRACT(), we can simplify the query. First, a short and simple form:
SELECT w AS isoweek
, COALESCE(thisyr_ct, 0) AS thisyr_ct
, COALESCE(lastyr_ct, 0) AS lastyr_ct
FROM generate_series(1, 5) w
LEFT JOIN (
SELECT EXTRACT(week FROM exam_date)::int AS w, count(*) AS thisyr_ct
FROM tbl
WHERE EXTRACT(isoyear FROM exam_date)::int = 2013
GROUP BY 1
) t13 USING (w)
LEFT JOIN (
SELECT EXTRACT(week FROM exam_date)::int AS w, count(*) AS lastyr_ct
FROM tbl
WHERE EXTRACT(isoyear FROM exam_date)::int = 2012
GROUP BY 1
) t12 USING (w);
Optimized query
The same with more details and optimized for performance
WITH params AS ( -- enter parameters here, once
SELECT date_trunc('week', '2012-01-04'::date)::date AS last_start
, date_trunc('week', '2013-01-04'::date)::date AS this_start
, date_trunc('week', '2014-01-04'::date)::date AS next_start
, 1 AS week_1
, 5 AS week_n -- show weeks 1 - 5
)
SELECT w.w AS isoweek
, p.this_start + 7 * (w - 1) AS thisyr_monday
, COALESCE(t13.ct, 0) AS thisyr_ct
, p.last_start + 7 * (w - 1) AS lastyr_monday
, COALESCE(t12.ct, 0) AS lastyr_ct
FROM params p
, generate_series(p.week_1, p.week_n) w(w)
LEFT JOIN (
SELECT EXTRACT(week FROM t.exam_date)::int AS w, count(*) AS ct
FROM tbl t, params p
WHERE t.exam_date >= p.this_start -- only relevant dates
AND t.exam_date < p.this_start + 7 * (p.week_n - p.week_1 + 1)::int
-- AND t.exam_date < p.next_start -- don't cross over into next year
GROUP BY 1
) t13 USING (w)
LEFT JOIN ( -- same for last year
SELECT EXTRACT(week FROM t.exam_date)::int AS w, count(*) AS ct
FROM tbl t, params p
WHERE t.exam_date >= p.last_start
AND t.exam_date < p.last_start + 7 * (p.week_n - p.week_1 + 1)::int
-- AND t.exam_date < p.this_start
GROUP BY 1
) t12 USING (w);
This should be very fast with index support and can easily be adapted to intervals of choice.
The implicit JOIN LATERAL for generate_series() in the last query requires Postgres 9.3.
SQL Fiddle.
Using across joinshould work, I'm just going to paste the markdown output from SQL Fiddle below. It would seem that your sample output is incorrect for series 2013-01-08: the thisyr should be 2, not 3. This might not be the best way to do this though, my Postgresql knowledge leaves a lot to be desired.
SQL Fiddle
PostgreSQL 9.2.4 Schema Setup:
CREATE TABLE Table1
("Pt_ID" varchar(6), "exam_date" date);
INSERT INTO Table1
("Pt_ID", "exam_date")
VALUES
('1', '2012-01-02'),('2', '2012-01-02'),
('3', '2012-01-08'),('4', '2012-01-08'),
('1', '2013-01-02'),('2', '2013-01-02'),
('3', '2013-01-03'),('4', '2013-01-04'),
('1', '2013-01-08'),('2', '2013-01-10'),
('3', '2013-01-15'),('4', '2013-01-24');
Query 1:
select
series,
sum (
case
when exam_date
between series and series + '6 day'::interval
then 1
else 0
end
) as thisyr,
sum (
case
when exam_date + '1 year'::interval
between series and series + '6 day'::interval
then 1 else 0
end
) as lastyr
from table1
cross join generate_series('2013-01-01', '2013-01-31', '7 day'::interval) as series
group by series
order by series
Results:
| SERIES | THISYR | LASTYR |
|--------------------------------|--------|--------|
| January, 01 2013 00:00:00+0000 | 4 | 2 |
| January, 08 2013 00:00:00+0000 | 2 | 2 |
| January, 15 2013 00:00:00+0000 | 1 | 0 |
| January, 22 2013 00:00:00+0000 | 1 | 0 |
| January, 29 2013 00:00:00+0000 | 0 | 0 |