COUNT monthly sales for every year - sql-server

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)

Related

Stored procedure to add 30 days using DATEDIFF within while loop condition in Date Dimension table

I want to add 30 consecutive days of data in my Date Dimension table using DATEDIFF() but I am getting blank result. Can you please help me correct the code below to get the desired result?
CREATE TABLE dbo.dateDimension (
DateKey INT NOT NULL
,DateValue DATE NOT NULL
,CYear SMALLINT NOT NULL
,CMonth TINYINT NOT NULL
,CONSTRAINT PK_DimDate PRIMARY KEY ( DateKey )
);
GO
CREATE PROC dbo.dateTest
#StartDate DATETIME
AS
WHILE (DATEDIFF(day, #StartDate, GETDATE()) <=30)
BEGIN
INSERT into dbo.dateDimension
SELECT CAST( YEAR(#StartDate) * 10000 + MONTH(#StartDate) * 100 + DAY(#StartDate) AS INT)
,#StartDate
,YEAR(#StartDate)
,MONTH(#StartDate)
SET #StartDate = DATEADD(d,1,#StartDate)
END;
GO
EXECUTE dbo.dateTest '2010-01-01'
SELECT * FROM dbo.dateDimension
The issue is that this logic:
DATEDIFF(day, #StartDate, GETDATE())
gives 3739 days with your current start date, so its never less than 30. Personally I would simply count it as follows:
DECLARE #StartDate DATETIME = '2010-01-01', #Count INT = 0;
WHILE #Count <= 30 BEGIN
INSERT into dbo.dateDimension
SELECT CAST( YEAR(#StartDate) * 10000 + MONTH(#StartDate) * 100 + DAY(#StartDate) AS INT)
, #StartDate
, YEAR(#StartDate)
, MONTH(#StartDate);
SET #StartDate = DATEADD(d,1,#StartDate);
set #Count = #Count + 1;
END;
SELECT *
FROM dbo.dateDimension;
If you are using SQL Server 2016 or above, this solution will not use a while loop, instead it uses a CTE to generate 30 rows numbered I to 30 and then uses the date to convert to yyyymmdd.
DECLARE #NUM_DAYS INT=30;
DECLARE #STARTDATE DATETIME='2020-01-01';
WITH CTE AS(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS SRL
FROM STRING_SPLIT(REPLICATE(',',#num_days-1), ',') AS A
)
INSERT INTO dbo.dateDimension
SELECT
CONVERT(INT, CONVERT(CHAR(8), DATEADD(DAY, SRL-1, #STARTDATE), 112))
, #STARTDATE
, YEAR(#STARTDATE)
, MONTH(#STARTDATE)
FROM CTE
SELECT * FROM dbo.dateDimension

How to set columns from dynamic query results

I am writing a query to calculate running totals of a few things over time. The time increments are in weekly buckets (every monday), however as time goes on, nothing needs to be in the past. This means my weekly buckets will be floating and always staying in the future. I have seen a couple links for how to setup a PIVOT function, however all of those examples the columns are fixed values. How do I get my query results to be displayed as my column names?
Here is my code which outputs what I need my column names to be. I set the increment to "1" to get just 4 results for testing, but will probably open it up to 12 months.
DECLARE #Startdate as date
DECLARE #Enddate as date
SET #Startdate = getdate()
SET #Enddate = DATEADD(Month,1,#StartDate)
;WITH cte(myDate) AS ( SELECT CASE WHEN DATEPART(Day,#StartDate) = 1 THEN #StartDate
ELSE DATEADD(Week,DATEDIFF(Week,0,#StartDate)+1,0) END AS myDate
UNION ALL
SELECT DATEADD(Week,1,myDate)
FROM cte
WHERE DATEADD(Week,1,myDate) <= #EndDate )
SELECT CONVERT(date,myDate) AS BuildWeek
FROM cte
OPTION (MAXRECURSION 0)
You will have to go dynamic.
You are missing some vital details. So I hope this will help with some guidance.
Below is a modified version of an answer from earlier today.
Declare #Startdate as date,#Enddate as date
Set #Startdate = getdate()
Set #Enddate = DATEADD(Month,1,#StartDate)
Declare #SQL varchar(max) = ''
Declare #Col varchar(max) = '
,[Title] = sum(case when Date between ''[DateR1]'' and ''[DateR2]'' then [YourField] else null end)'
;with cte(myDate) as (
Select Case When DatePart(Day,#StartDate) = 1 Then #StartDate Else DateAdd(Week,DateDiff(Week,0,#StartDate)+1,0) end as myDate
Union All
Select DateAdd(Week,1,myDate)
From cte
Where DateAdd(Week,1,myDate) <= #EndDate
)
Select #SQL = #SQL + Replace(Replace(Replace(#Col,'[DateR1]',DateR1),'[DateR2]',DateR2),'Title',Title)
From (Select DateR1 = cast(myDate as Date)
,DateR2 = DateAdd(DAY,6,cast(myDate as Date))
,Title = 'Week Of '+Substring(DateName(WEEKDAY,myDate),1,3)+' '+Substring(DateName(MONTH,myDate),1,3)+' '+cast(Day(myDate) as varchar(5))
From cte
) A
Set #SQL = 'Select ID'+#SQL+'
From YourTable
Group By ID
'
Print #SQL
--Exec(#SQL)
Returns the following SQL which would be executed
Select ID
,[Week Of Mon Sep 26] = sum(case when Date between '2016-09-26' and '2016-10-02' then [YourField] else null end)
,[Week Of Mon Oct 3] = sum(case when Date between '2016-10-03' and '2016-10-09' then [YourField] else null end)
,[Week Of Mon Oct 10] = sum(case when Date between '2016-10-10' and '2016-10-16' then [YourField] else null end)
,[Week Of Mon Oct 17] = sum(case when Date between '2016-10-17' and '2016-10-23' then [YourField] else null end)
From YourTable
Group By ID

Passing in Week Day name to get nearest date in SQL

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

Pivot table between two dates using SQL Server 2008 R2

I have the following table:
Example:
create table test
(
col_dt1 date,
col_dt2 date
)
Inserting some records:
insert into test values('2014-11-07','2014-12-01');
select * from test;
col_dt1 col_dt2
----------------------
2014-11-07 2014-12-01
Expected Result:
col_dt1 col_dt2 07 November 2014 08 November 2014 .................... 01 December 2014
-----------------------------------------------------------------------------------------------
2014-11-07 2014-12-01 1 0 .................... 1
I got stuck to get all dates between two dates to have the stuff column in pivot table.
DECLARE #Start_Date DATE, #End_Date DATE, #QUERY NVARCHAR(MAX), #SUBQUERY NVARCHAR(MAX), #ROWCOUNT int
CREATE TABLE #TempTable(
ID int IDENTITY(1,1) NOT NULL,
Start_Date date,
End_Date date,
Dates NVARCHAR(50),
HasDate int
)
CREATE TABLE #TempTable2(
Start_Date date,
End_Date date,
Dates NVARCHAR(50),
HasDate int
)
SET #Start_Date = (SELECT TOP 1 col_dt1 from test)
SET #End_Date = (SELECT TOP 1 col_dt2 from test)
INSERT INTO #TempTable
SELECT
#Start_Date as Start_Date,
#End_Date as End_Date,
RIGHT(REPLICATE('0', DAY(DATEADD(DAY,number,#Start_Date))) + CAST(DAY(DATEADD(DAY,number,#Start_Date)) AS NVARCHAR(2)), 2)
+' '+DATENAME(MONTH,DATEADD(DAY,number,#Start_Date))+' '+CONVERT(NVARCHAR(50),YEAR(DATEADD(DAY,number,#Start_Date))) as Start_Date,
HasDate =
CASE
WHEN DATEADD(DAY,number,#Start_Date)=#Start_Date THEN 1
WHEN DATEADD(DAY,number,#Start_Date)=#End_Date THEN 1
ELSE 0
END
FROM master..spt_values
WHERE type = 'P'
AND DATEADD(DAY,number,#Start_Date) <= #End_Date
INSERT INTO #TempTable2 SELECT [Start_Date],[End_Date],[Dates],HasDate FROM #TempTable
SELECT * FROM #TempTable
SET #QUERY=''
SET #QUERY+='SELECT * FROM #TempTable2
PIVOT
(
Max(HasDate)
FOR Dates IN ('
SET #SUBQUERY=''
SET #ROWCOUNT=1
WHILE #ROWCOUNT <= (SELECT COUNT(*) FROM #TempTable)
BEGIN
SET #SUBQUERY=#SUBQUERY+'['+(SELECT CONVERT(NVARCHAR(50),Dates)as Dates FROM #TempTable WHERE ID=#ROWCOUNT)+']'
IF (#ROWCOUNT<>(SELECT COUNT(*) FROM #TempTable))
BEGIN
SET #SUBQUERY=#SUBQUERY+','
END
SET #ROWCOUNT=#ROWCOUNT+1
END
SET #QUERY=#QUERY+#SUBQUERY+')
)AS tblPivot'
PRINT(#QUERY)
EXECUTE(#QUERY)
DROP TABLE #TempTable
DROP TABLE #TempTable2
You can try this.

sql query calculating no of employees joined each financial year i.e from 1-04-2002 to 31-03-2003

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

Resources