I need to check monthly sales (count not sum) by area for long ranges of dates (5+ years), something like an Excel pivot table, currently I'm working it on Pandas but nobody here works with it so I'm trying to generate a view or a stored procedure in SQLServer for anyone who requires it. In this table sales are stored associated to an area and which product was.
I'm able to list and group AREA, SALES, MONTH AND YEAR, but as I mentioned, it would be easier to read if months or years where vertically aligned (there is about 100k records yearly and Excel lags at that point).
CREATE TABLE SALESHS
(
IDAREA INT,
DATEREG [NVARCHAR](50) NOT NULL,
IDPROD [NVARCHAR](50) NOT NULL
);
GO
-- Insert rows into table 'SALESHS'
INSERT INTO SALESHS
(
IDAREA, DATEREG, IDPROD
)
VALUES
(
1, '12/03/2019', 'xplpc'
),
(
1, '15/03/2019', 'ndtlctm'
),
(
2, '12/04/2019', 'wntd'
)
GO
SELECT IDAREA,
COUNT(IDAREA) AS CANT,
DATEREG, --DATE AS DD/MM/YYYY
DATEPART(MM,CAST(DATEREG AS DATETIME)) AS MONTH,
DATEPART(YYYY,CAST(DATEREG AS DATETIME)) AS YEAR,
FROM saleshs
WHERE DATEREG > 201712
GROUP BY DATEREG , idarea
ORDER BY DATEREG
Which returns this:
IDAREA AMOUNT MONTH YEAR PER_PRO
----------------------------------------
1 2 03 2019 201904
2 1 04 2019 201904
Expected results:
IDAREA JAN2019 FEB2019 MAR2019 APR2019
--------------------------------------
1 0 0 2 0
2 0 0 0 1
I know the basics of SQL and I don't expect a full answer either, but anything that could help me build this view it's appreciated. I've tried PIVOT also but I can't count, distinct and sum in the same query.
You can try Conditional Aggregation
SELECT IDAREA,
SUM( CASE WHEN YEAR(CAST(DATEREG AS DATETIME))= 2019 AND
MONTH(CAST(DATEREG AS DATETIME))=1 THEN
1
ELSE
0
END) JAN2019,
SUM( CASE WHEN YEAR(CAST(DATEREG AS DATETIME))= 2019 AND
MONTH(CAST(DATEREG AS DATETIME))=2 THEN
1
ELSE
0
END) FEB2019,
SUM( CASE WHEN YEAR(CAST(DATEREG AS DATETIME))= 2019 AND
MONTH(CAST(DATEREG AS DATETIME))=3 THEN
1
ELSE
0
END) MAR2019,
SUM( CASE WHEN YEAR(CAST(DATEREG AS DATETIME))= 2019 AND
MONTH(CAST(DATEREG AS DATETIME))=4 THEN
1
ELSE
0
END) APR2019
FROM saleshs
WHERE YEAR(CAST(DATEREG AS DATETIME))> 2017
GROUP BY IDAREA
ORDER BY IDAREA
Demo
--Build the column names for Pivot using dynamic SQL
DECLARE #YourChoice date
set #YourChoice = '2017/12/13' --change here to what date you want the earliest
declare #count int = 0
declare #columnstr varchar(max)
declare #columnpivot varchar(max)
declare #onecolumnname varchar(20)
set #columnstr = ''
set #columnpivot = ''
set #onecolumnname = ''
while #count <= DATEDIFF(MONTH,#YourChoice,GETDATE())
begin
set #onecolumnname = concat(cast(datename(month,dateadd(month,#count,#YourChoice)) as varchar(50)),cast(year(dateadd(month,#count,#YourChoice)) as varchar(10)))
set #columnstr = #columnstr + 'coalesce([' + #onecolumnname+ '],0) as '+#onecolumnname+', '
set #columnpivot = #columnpivot + '['+#onecolumnname+'], '
set #count = #count + 1
end
set #columnstr = left(#columnstr,len(#columnstr)-1)
set #columnpivot = '('+left(#columnpivot,len(#columnpivot)-1)+')'
--Pivot time!
declare #str varchar(max)
set #str =
'select IDAREA,' + #columnstr +' from (
select count(s.idarea) as amount,IDAREA,columnname from (
select *,datename(month,cast(substring(datereg,7,4)+''-''+substring(datereg,4,2)+''-''+substring(datereg,1,2) as datetime)) + SUBSTRING(datereg,7,4) as columnname
from SALESHS )s
group by IDAREA,columnname)s1
pivot
(
max(s1.amount)
for s1.columnname in '+#columnpivot+'
) p'
exec (#str)
Test Result 1 ('2017/12/13'):
DB<>Fiddle
Test Result 2 ('2018/12/14'):
DB<>Fiddle
I have created a Dynamic SQL to solve this particular issue. Query can be adjusted on which YEAR to display in the result and which Column to count in the PIVOT Section of the query. Date format in the i have used is below query MM/DD/YYYY.
You can run the code HERE
Below is the SQL Query.
CREATE TABLE SALESHS
( IDAREA INT,
DATEREG date NOT NULL,
IDPROD [NVARCHAR](50) NOT NULL
);
/* Insert rows into table 'SALESHS' */
INSERT INTO SALESHS
( IDAREA, DATEREG, IDPROD)
VALUES
( 1, '03/12/2019', 'xplpc'),
( 1, '03/15/2019', 'ndtlctm'),
( 2, '04/12/2019', 'wntd')
/* Create Calendar Table to capture all the dates for first day of month from start Date to end date */
CREATE TABLE Calendar
(
[CalendarDate] DATE
,[MonthName] AS FORMAT(CONVERT(DATE, DATEADD(m, DATEDIFF(m, 0, CalendarDate), 0)), 'MMM-yyyy')
,[MonthNo] AS FORMAT(CalendarDate,'MM')
,[Year] AS FORMAT(CalendarDate,'yyyy')
,DateKey AS CONCAT(FORMAT(CalendarDate,'yyyy'), FORMAT(CalendarDate,'MM'))
)
DECLARE #Date DATE, #StartDate DATE, #EndDate DATE
SET #Date = '01/01/2012'
SET #StartDate = CONVERT(DATE, DATEADD(m, DATEDIFF(m, 0, #Date), 0)) /* Set Start date to first day of the month for given date */
SET #EndDate = '04/01/2019'
WHILE #StartDate <= #EndDate
BEGIN
INSERT INTO Calendar (CalendarDate)
SELECT #StartDate
SET #StartDate = DATEADD(m, 1, #StartDate)
END
/* Variable to hold unique Months to be used in PIVOT clause */
DECLARE #UniqueMonthsToPivot NVARCHAR(MAX) = N''
/* Extract unique Month names with pivot formattings */
SELECT #UniqueMonthsToPivot = #UniqueMonthsToPivot + ', [' + COALESCE(MonthName, '') + ']'
FROM (SELECT DISTINCT MonthName FROM Calendar) DT
/* Remove first comma and space */
SELECT #UniqueMonthsToPivot = LTRIM(STUFF(#UniqueMonthsToPivot, 1, 1, ''))
/* Variable to hold pivot column names with alias to be used in SELECT Clause */
DECLARE #PivotMonthsToSelect NVARCHAR(MAX) = N''
/* Generate column names list for SELECT list with SUM Function and NULL handling.
YEAR in the where condition can be adjust to select and show only certain year or month in Select list.
Order by CalendarDate is important to define the sequence of columns in the result */
SELECT #PivotMonthsToSelect = #PivotMonthsToSelect + ', SUM(ISNULL([' + COALESCE(MonthName, '') + '], 0)) AS [' + MonthName + ']'
FROM Calendar WHERE Year >= 2012
Order by CalendarDate
/* Variable to hold t-sql query */
DECLARE #SQLStatement NVARCHAR(MAX) = N''
/* Generate dynamic PIVOT query here */
SET #SQLStatement =
N'SELECT IDAREA'
+ #PivotMonthsToSelect +
'FROM (
SELECT *
,CASE WHEN IDAREA IS NULL THEN 0 ELSE 1 END AS IDAREA_Dup
FROM Calendar C
LEFT JOIN SALESHS S ON C.CalendarDate = CONVERT(DATE, DATEADD(m, DATEDIFF(m, 0, DATEREG), 0))
) AA
PIVOT
( SUM(IDAREA_Dup)
FOR MonthName IN ('+ #UniqueMonthsToPivot +')
) P
WHERE IDAREA IS NOT NULL
GROUP BY IDAREA
'
/* Check the generated dynamic t-sql PIVOT query below */
--PRINT (#SQLStatement)
/* Execute the generated dynamic t-sql PIVOT query below */
EXEC (#SQLStatement)
I'm working on a query that deals with a frequency value (i.e. Mondays, Tuesdays, etc. - Think assignments).
So in my query I currently have a result of
jobId:1, personId:100, frequencyVal: 'Mondays'
jobId:2, personId:101, frequencyVal: 'Saturdays'
What I need is the next the 4 future(or current) dates for the frequencyVal.
So if today is 1/3/2015
I would need my result set to be
jobId:1, personId:100, frequencyVal: 'Mondays', futureDates: '1/5,1/12,1/19,1/26'
jobId:2, personId:102, frequencyVal: 'Saturdays', futureDates: '1/3,1/10,1/17,1/24'
I was looking at the following post:
How to find the Nearest (day of the week) for a given date
But that sets it for a specific date. And I'm looking at this being a web application and I want the dates for the current date. So if I try to run this query next Tuesday the future dates for jobId:1 would remove the 1/5 and add the 2/2.
Is there a way to pass in a weekday value to get the next nearest date?
I prefer a calendar table for this kind of query. Actually, I prefer a calendar table over date functions for most queries. Here's a minimal one. The one I use in production has more columns and more rows. (100 years of data is only 37k rows.)
create table calendar (
cal_date date not null primary key,
day_of_week varchar(15)
);
insert into calendar (cal_date) values
('2015-01-01'), ('2015-01-02'), ('2015-01-03'), ('2015-01-04'),
('2015-01-05'), ('2015-01-06'), ('2015-01-07'), ('2015-01-08'),
('2015-01-09'), ('2015-01-10'), ('2015-01-11'), ('2015-01-12'),
('2015-01-13'), ('2015-01-14'), ('2015-01-15'), ('2015-01-16'),
('2015-01-17'), ('2015-01-18'), ('2015-01-19'), ('2015-01-20'),
('2015-01-21'), ('2015-01-22'), ('2015-01-23'), ('2015-01-24'),
('2015-01-25'), ('2015-01-26'), ('2015-01-27'), ('2015-01-28'),
('2015-01-29'), ('2015-01-30'), ('2015-01-31'),
('2015-02-01'), ('2015-02-02'), ('2015-02-03'), ('2015-02-04'),
('2015-02-05'), ('2015-02-06'), ('2015-02-07'), ('2015-02-08'),
('2015-02-09'), ('2015-02-10'), ('2015-02-11'), ('2015-02-12'),
('2015-02-13'), ('2015-02-14'), ('2015-02-15'), ('2015-02-16'),
('2015-02-17'), ('2015-02-18'), ('2015-02-19'), ('2015-02-20'),
('2015-02-21'), ('2015-02-22'), ('2015-02-23'), ('2015-02-24'),
('2015-02-25'), ('2015-02-26'), ('2015-02-27'), ('2015-02-28')
;
update calendar
set day_of_week = datename(weekday, cal_date);
alter table calendar
alter column day_of_week varchar(15) not null;
alter table calendar
add constraint cal_date_matches_dow
check (datename(weekday, cal_date) = day_of_week);
create index day_of_week_ix on calendar (day_of_week);
Set the privileges so that
everyone can select, but
almost nobody can insert new rows, and
even fewer people can delete rows.
(Or write a constraint that can guarantee there are no gaps. I think you can do that in SQL Server.)
You can select the next four Mondays after today with a very simple SQL statement. (The current date is 2015-01-05, which is a Monday.)
select top 4 cal_date
from calendar
where cal_date > convert(date, getdate())
and day_of_week = 'Monday'
order by cal_date;
CAL_DATE
--
2015-01-12
2015-01-19
2015-01-26
2015-02-02
For me, this is a huge advantage. No procedural code. Simple SQL that is obviously right. Big win.
Your sample table
create table #t
(
jobId int,
personId int,
frequencyVal varchar(10)
);
insert into #t values (1,100,'Mondays'),(2,101,'Saturdays');
QUERY 1 : Select nearest 4 week of days in current month for particular week day
-- Gets first day of month
DECLARE #FIRSTDAY DATE=DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0)
;WITH CTE as
(
-- Will find all dates in current month
SELECT CAST(#FIRSTDAY AS DATE) as DATES
UNION ALL
SELECT DATEADD(DAY,1,DATES)
FROM CTE
WHERE DATES < DATEADD(MONTH,1,#FIRSTDAY)
)
,CTE2 AS
(
-- Join the #t table with CTE on the datename+'s'
SELECT jobId,personId,frequencyVal,DATES,
-- Get week difference for each weekday
DATEDIFF(WEEK,DATES,GETDATE()) WEEKDIFF,
-- Count the number of weekdays in a month
COUNT(DATES) OVER(PARTITION BY DATENAME(WEEKDAY,CTE.DATES)) WEEKCOUNT
FROM CTE
JOIN #t ON DATENAME(WEEKDAY,CTE.DATES)+'s' = #t.frequencyVal
WHERE MONTH(DATES)= MONTH(GETDATE())
)
-- Converts to CSV and make sure that only nearest 4 week of days are generated for month
SELECT DISTINCT C2.jobId,C2.personId,frequencyVal,
SUBSTRING(
(SELECT ', ' + CAST(DATEPART(MONTH,DATES) AS VARCHAR(2)) + '/' +
CAST(DATEPART(DAY,DATES) AS VARCHAR(2))
FROM CTE2
WHERE C2.jobId=jobId AND C2.personId=personId AND C2.frequencyVal=frequencyVal AND
((WEEKDIFF<3 AND WEEKDIFF>-3 AND WEEKCOUNT = 5) OR WEEKCOUNT <= 4)
ORDER BY CTE2.DATES
FOR XML PATH('')),2,200000) futureDates
FROM CTE2 C2
SQL FIDDLE
For example, in Query2 the nearest date(here we take example as Saturday) of
2015-Jan-10 will be 01/03,01/10,01/17,01/24
2015-Jan-24 will be 01/10,01/17,01/24,01/31
QUERY 2 : Select next 4 week's dates for particular week day irrelevant of month
;WITH CTE as
(
-- Will find the next 4 week details
SELECT CAST(GETDATE() AS DATE) as DATES
UNION ALL
SELECT DATEADD(DAY,1,DATES)
FROM CTE
WHERE DATES < DATEADD(DAY,28,GETDATE())
)
,CTE2 AS
(
-- Join the #t table with CTE on the datename+'s'
SELECT jobId,personId,frequencyVal, DATES,
ROW_NUMBER() OVER(PARTITION BY DATENAME(WEEKDAY,CTE.DATES) ORDER BY CTE.DATES) DATECNT
FROM CTE
JOIN #t ON DATENAME(WEEKDAY,CTE.DATES)+'s' = #t.frequencyVal
)
-- Converts to CSV and make sure that only 4 days are generated for month
SELECT DISTINCT C2.jobId,C2.personId,frequencyVal,
SUBSTRING(
(SELECT ', ' + CAST(DATEPART(MONTH,DATES) AS VARCHAR(2)) + '/' +
CAST(DATEPART(DAY,DATES) AS VARCHAR(2))
FROM CTE2
WHERE C2.jobId=jobId AND C2.personId=personId AND C2.frequencyVal=frequencyVal
AND DATECNT < 5
ORDER BY CTE2.DATES
FOR XML PATH('')),2,200000) futureDates
FROM CTE2 C2
SQL FIDDLE
The following would be the output if the GETDATE() (if its Saturday) is
2015-01-05 - 1/10, 1/17, 1/24, 1/31
2015-01-24 - 1/24, 1/31, 2/7, 2/14
There's no built-in function to do it. But you can try this, you may place it inside a Scalar-Valued Function:
DECLARE #WeekDay VARCHAR(10) = 'Monday';
DECLARE #WeekDayInt INT;
SELECT #WeekDayInt = CASE #WeekDay
WHEN 'SUNDAY' THEN 1
WHEN 'MONDAY' THEN 2
WHEN 'TUESDAY' THEN 3
WHEN 'WEDNESDAY' THEN 4
WHEN 'THURSDAY' THEN 5
WHEN 'FRIDAY' THEN 6
WHEN 'SATURDAY' THEN 7 END
SELECT CONVERT(DATE, DATEADD(DAY, (DATEPART(WEEKDAY, GETDATE()) + #WeekDayInt) % 7, GETDATE())) AS NearestDate
UPDATE:
Looks like radar was right, here's the solution:
DECLARE #WeekDay VARCHAR(10) = 'Monday';
DECLARE #WeekDayInt INT;
DECLARE #Date DATETIME = GETDATE();
SELECT #WeekDayInt = CASE #WeekDay
WHEN 'SUNDAY' THEN 1
WHEN 'MONDAY' THEN 2
WHEN 'TUESDAY' THEN 3
WHEN 'WEDNESDAY' THEN 4
WHEN 'THURSDAY' THEN 5
WHEN 'FRIDAY' THEN 6
WHEN 'SATURDAY' THEN 7 END
DECLARE #Diff INT = DATEPART(WEEKDAY, #Date) - #WeekDayInt;
SELECT CONVERT(DATE, DATEADD(DAY, CASE WHEN #Diff >= 0 THEN 7 - #Diff ELSE ABS(#Diff) END, #Date)) AS NearestDate
Try this - based on king.code's answer to get the nearest date.
create table #t
(
jobId int,
personId int,
frequencyVal varchar(10)
);
insert into #t values (1,100,'Mondays'),(2,101,'Saturdays');
WITH cte(n) AS
(
SELECT 0
UNION ALL
SELECT n+1 FROM cte WHERE n < 3
)
select #t.jobId, #t.personId, #t.frequencyVal, STUFF(a.d, 1, 1, '') AS FutureDates
from #t
cross apply (SELECT CASE #t.frequencyVal
WHEN 'SUNDAYS' THEN 1
WHEN 'MONDAYS' THEN 2
WHEN 'TUESDAYS' THEN 3
WHEN 'WEDNESDAYS' THEN 4
WHEN 'THURSDAYS' THEN 5
WHEN 'FRIDAYS' THEN 6
WHEN 'SATURDAYS' THEN 7
END)tranlationWeekdays(n)
cross apply (select ',' + CONVERT(varchar(10), CONVERT(date,dateadd(WEEK, cte.n,CONVERT(DATE, DATEADD(DAY, (DATEPART(WEEKDAY, GETDATE()) + tranlationWeekdays.n) % 7, GETDATE()))))) from cte FOR XML PATH('')) a(d);
drop table #t;
Try this,
DECLARE #YEAR INT=2015
DECLARE #MONTH INT=1
DECLARE #DAY INT=1
DECLARE #DATE DATE = (SELECT DateFromParts(#Year, #Month, #Day))
DECLARE #TOTAL_DAYS INT =(SELECT DatePart(DY, #DATE));
WITH CTE1
AS (SELECT T_DAY=(SELECT DateName(DW, #DATE)),
#DATE AS T_DATE,
#DAY AS T_DDAY
UNION ALL
SELECT T_DAY=(SELECT DateName(DW, DateAdd(DAY, T_DDAY + 1, #DATE))),
DateAdd(DAY, T_DDAY + 1, #DATE) AS T_DATE,
T_DDAY + 1
FROM CTE1
WHERE T_DDAY + 1 <= 364)
SELECT DISTINCT T_DAY,
Stuff((SELECT ',' + CONVERT(VARCHAR(30), T_DATE)
FROM CTE1 A
WHERE A.T_DAY=CTE1.T_DAY AND A.T_DATE > GetDate() AND A.T_DATE<(DATEADD(WEEK,4,GETDATE()))
FOR XML PATH('')), 1, 1, '') AS FUTURE
FROM CTE1
ORDER BY T_DAY
OPTION (MAXRECURSION 365)
This is a simpler way I think, and I think it fits your requirements.
Note that I have changed your frequency_val column to an integer that represents the day of the week from SQL servers perspective and added a calculated column to illustrate how you can easily derive the day name from that.
declare #t table
(
jobId int,
personId int,
--frequencyVal varchar(10)
frequency_val int,
frequency_day as datename(weekday,frequency_val -1) + 's'
);
declare #num_occurances int = 4
declare #from_date date = dateadd(dd,3,getdate()) -- this will allow you to play with the date simply by changing the increment value
insert into #t
values
(1,100,1),--'Mondays'),
(2,101,6),--'Saturdays');
(3,101,7),--'Saturdays');
(4,100,2)--'Mondays'),
--select * from #t
;with r_cte (days_ahead, occurance_date)
as (select 0, convert(date,#from_date,121)
union all
select r_cte.days_ahead +1, convert(date,dateadd(DD, r_cte.days_ahead+1, #from_date),121)
from r_cte
where r_cte.days_ahead < 7 * #num_occurances
)
select t.*, r_cte.occurance_date
from
#t t
inner join r_cte
on DATEPART(WEEKDAY, dateadd(dd,##DATEFIRST - 1 ,r_cte.occurance_date)) = t.frequency_val
Having seen the use of DATENAME in some of the answers already given, I'd like to point out that return values of DATENAME might vary depending on your current language setting, but you can save the current language setting and ensure usage of us_english so you can be confident to use English weekday names.
Now here is my slightly different approach to get the 4 next dates that fall on a certain (known) weekday, using a user defined table valued function that allows to create a number sequence table (yes this is a pretty dull function, you have to pass MaxValue greater MinValue, but that could be easily enhanced, if needed, but hey, it does the job). Using that function span a table over 28 values (next 28 days should indeed include the next 4 relevant weekdays ;)), apply DATEADD on GETDATE and reduce the result set with WHERE to only those values that have the right weekday:
CREATE FUNCTION GetIntSequence(#MinValue INT, #MaxValue INT)
RETURNS #retSequence TABLE
(
IntValue INT NOT NULL
)
BEGIN
DECLARE #i INT = (SELECT #MinValue)
WHILE #i <= #MaxValue
BEGIN
INSERT INTO #retSequence (IntValue) SELECT #i
SELECT #i = #i + 1
END
RETURN
END
GO
DECLARE #weekDay NVARCHAR(MAX) = 'Monday' --(or Tuesday, wednesday, ...)
--save current language setting
DECLARE #languageBackup NVARCHAR(MAX) = (SELECT ##LANGUAGE)
--ensure us english language setting for reliable weekday names
SET LANGUAGE us_english;
SELECT FourWeeks.SomeDay FROM
(
SELECT
DATEADD(DAY, IntValue, GETDATE()) AS SomeDay
FROM dbo.GetIntSequence(1, 28)
) AS FourWeeks
WHERE DATENAME(WEEKDAY, SomeDay) = #weekDay
--restore old language setting
SET LANGUAGE #languageBackup;
GO
DROP FUNCTION dbo.GetIntSequence
I need to get the number of weekends between dates in sql as a function. I have tried but stuck up somewhere in the logic.
CREATE FUNCTION fnc_NumberOfWeekEnds(#dFrom DATETIME, #dTo DATETIME)
RETURNS INT AS
BEGIN
Declare #weekends int
Set #weekends = 0
While #dFrom <= #dTo Begin
If ((datepart(dw, #dFrom) = 1))
Set #weekends = #weekends + 1
Set #dFrom = DateAdd(d, 1, #dFrom)
End
Return (#weekends)
END
I tried out this logic with several edge cases and it seems to work.
SELECT DATEDIFF(d, #dFrom, #dTo)/7+1
+ CASE WHEN DATEPART(dw,#dFrom) IN (1,7) THEN -1 ELSE 0 END
+ CASE WHEN DATEPART(dw,#dTo) IN (1,7) THEN -1 ELSE 0 END
You can change the CASE statements depending on how you want to handle cases where the start or end date is in a weekend. In my case I'm not including the weekend if the start or end date is a Saturday or Sunday.
Try replacing the if statement with this:
If ((datepart(dw, #dFrom) = 1) OR (datepart(dw, #dFrom) = 7))
You should also check the end of the week to get the result.
DECLARE #date_from DATETIME,
#date_to DATETIME
/*TEMPORARY TABLE*/
DECLARE #DATES AS TABLE (
GDate DATETIME
)
SELECT #date_from ='2019-09-10'
SELECT #date_to ='2019-10-10'
/*DATE GENERATED*/
WITH dates
AS (
SELECT #date_from AS dt
UNION ALL
SELECT DATEADD(D, 1, dt)
FROM dates
WHERE dt < #date_to
)
/*INSERTED DATES INTO TEMP TABLE */
INSERT INTO #DATES
SELECT CONVERT(DATETIME, dt) AS Gdate FROM dates
/*Get Records from temp table*/
SELECT Gdate FROM #DATES
Used below logic to calculate the no of Saturdays or Sundays between a start date and end date.
CREATE FUNCTION dbo.WEEKEND_COUNT
(
#Start_Date datetime,
#End_Date datetime
)
RETURNS int
AS
BEGIN
Declare #count int = 0;
while #Start_Date<=#End_Date
Begin
IF DatePart(WEEKDAY,#Start_Date) = 1 or DatePart(WEEKDAY,#Start_Date) = 7
SET #count=#count+1
SET #Start_Date=DateAdd(d,1,#Start_Date)
END
return #count
END
--Use below to get the count of Saturdays and Sundays
Select dbo.WEEKEND_COUNT('Your start date','your end date')
This will give you the number of sunday between two dates
SELECT DateDiff(ww, #dFrom, #dTo) as NumOfSundays
I have a table in which joining dates are give in datetime format.
I have to calculate how many employees joined each financial year resp. ie for eg from
1-04-2002 to 31-03-2003.this should work for each year..from 2003 to 2004,2004 to 2005...n so on.
can anybdy help?
thanxx.
You can map start date to financial year using YEAR(DATEADD(M,-3,JoinDate) I think and you can count records with a CTE, e.g.
with EmployeeStartFinYear(FinYear, EmployeeId)
as
(
select year(dateadd(M,-3,JoinDate)), EmployeeId
from Employees
where JoinDate is not null
)
select FinYear, count(EmployeeId)
from EmployeeStartFinYear
group by FinYear
order by FinYear;
Here's my answer. I think it looks horrid, but i think it works. I'll explain the logic behind it.
I declared the Start and End dates just because it's easier to write than a date.
The #Years variable is the difference in years between the start and end date.
#Counter is used to loop through the number of years stored in #Years. #Diff is always one more than #Counter because each time we go through the loop, we want to increment the date range so it's always 1 year, rather than be counting employees that joined in 1 year, then 2 years etc.
#TempTable stores the info we get each time we go through the query.
All the query does is get the count of employees between the Start Date and a year from that start date and puts it into a temp table. Then it looks through again, and gets the employees that started between Start Date + 1 and Start Date + 2.
Sorry if it's horrible and ugly and doesn't work.
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
DECLARE #Years TINYINT
DECLARE #Counter TINYINT
DECLARE #Diff TINYINT
DECLARE #TempTable TABLE
(
FinancialYear VARCHAR(9)
,Employees TINYINT
)
SET #Count = 0
SET #Diff = 1
SET #Years = DATEDIFF(yyyy, #StartDate, #EndDate)
WHILE #Count < #Years - 1
BEGIN
SELECT
CAST(DATEPART(yyyy, DATEADD(yyyy, #Count, #StartDate) AS VARCHAR(4)) + '-' + CAST(DATEPART(yyyy, DATEADD(yyyy, #Diff, #StartDate)) AS VARCHAR(4) AS FinancialYear
,COUNT(employee_id) AS Employees
INTO #TempTable
FROM
Employees
WHERE
join_date >= #StartDate AND join_date < DATEADD(yyyy, 1, #StartDate)
GROUP BY
CAST(DATEPART(yyyy, DATEADD(yyyy, #Count, #StartDate) AS VARCHAR(4)) + '-' + CAST(DATEPART(yyyy, DATEADD(yyyy, #Diff, #StartDate)) AS VARCHAR(4)
SET #Count = #Count + 1
SET #Diff = #Diff + 1
END
SELECT * FROM #TempTable