Find two most recent comparable dates (same weekday and day of month) - sql-server

I've tried searching without much luck. What I'm looking to do is based on any input date, get two most recent dates that had the same day of the month and same weekday as the input date.
For example, date of 02/07/2018 (dd/mm/yyyy) should return 02/04/2018 and 02/10/2017 as they were both Monday the 2nd and were two most recent occurrences.
I have a date table I can work with, I was considering doing a join on itself to get this but I'd appreciate some help.
EDIT: Forgot to mention I'm using SQL Server 2012

Your date table needs to have calculated the day of the week (the number) for each day. Then it's just a simple matter of JOINing, sorting and picking the top 2 rows.
SQL Fiddle
MS SQL Server 2017 Schema Setup:
SETUP THE DATE TABLE
/********************************CALENDAR********************************/
/*
As always, thank you to Aaron Bertrand for the Calendar Table example.
https://www.mssqltips.com/sqlservertip/4054/creating-a-date-dimension-or-calendar-table-in-sql-server/
*/
SET DATEFIRST 7 ; /* SUNDAY */ /* Make sure the week starts on the same day. */
CREATE TABLE datedim (
theDate date PRIMARY KEY
, theDay AS DATEPART(day, theDate) --int
, theWeek AS DATEPART(week, theDate) --int
, theMonth AS DATEPART(month, theDate) --int
, theYear AS DATEPART(year, theDate) --int
, theDOW AS DATEPART(weekday, theDate) --int
, yyyymmdd AS CONVERT(char(8), theDate, 112) /* yyyymmdd */
) ;
/************************************************************************/
/*
Use the catalog views to generate as many rows as we need. This example
creates a date dimension for 1 Sept 2017 to 1 Aug 2018.
*/
INSERT INTO datedim ( theDate )
SELECT d
FROM (
SELECT d = DATEADD(day, s1.rn - 1, '2017-01-01')
FROM
(
SELECT TOP ( DATEDIFF(day, '2017-01-01', '2018-12-31') )
rn = ROW_NUMBER() OVER (ORDER BY sao.object_id)
FROM sys.all_objects sao
) s1
) s2
/************************************************************************/
SETUP TEST DATA
/* TEST DATA */
CREATE TABLE t1 (id int identity, testDate date) ;
INSERT INTO t1 ( testDate )
VALUES
( '2018-04-02' ) /* This */
, ( '2017-10-02' ) /* This */
, ( '2018-04-02' ) /* Duplicate */
, ( '2017-09-27' )
, ( '2018-07-01' )
, ( '2018-05-02' ) /* Same day, Diff DOW */
, ( '2017-09-02' ) /* Same day, Diff DOW */
, ( '2017-10-09' ) /* Diff day, Same DOW */
, ( '2017-01-02' ) /* Same day, Same DOW, Earlier */
, ( null )
;
I added a couple of cases that will get picked up in the early filters but get filtered out by the end. See the notes in the setup.
Main Query:
DECLARE #enteredDate date = '2018-07-02' /* This is the date you entered. */
SELECT TOP 2 s1.testDate
FROM (
SELECT t1.testDate
, ROW_NUMBER() OVER ( PARTITION BY t1.testDate ORDER BY t1.testDate DESC, t1.id DESC ) AS rn
FROM t1
INNER JOIN datedim dd ON t1.testDate = dd.theDate
AND dd.theDay = DATEPART(day,#enteredDate)
AND dd.theDOW = DATEPART(weekday,#enteredDate)
) s1
WHERE rn = 1 /* Get 1st result for a date. */
ORDER BY s1.testDate DESC
In the main query, the first thing we do is use the ROW_NUMBER() window function to make sure we only get one record if there are duplicate date entries. If your data is guaranteed to not have dupe dates, you can skip this step.
[Results][2]:
| testDate |
|------------|
| 2018-04-02 |
| 2017-10-02 |
I think this problem is an excellent example of how useful a Calendar Table can be.

If you have a dates table, then you can use it. The following pseudo-code based on ANSI SQL functions, because date functions depend on the database:
select d.*
from dates d
where extract(dow from d.date) = extract(dow from date '2018-07-02') and
extract(day from d.date) = extract(day from '2018-07-02') and
d.date < date '2018-07-02'
order by d.date desc
fetch first 2 rows only;

Related

How to Calculate the Total Unique Days Employed for All Jobs - No overlap days counted twice

/* Data Setup */
DROP TABLE IF EXISTS #DaysPerJob;
CREATE TABLE #DaysPerJob
(
GroupID INT, JobDesc VARCHAR(100), StartDate DATE, EndDate DATE
)
INSERT INTO #DaysPerJob(GroupID, JobDesc, StartDate, EndDate)
VALUES
(23293, 'Food Prep', '2017-03-01', '2017-07-17')
, (23293, 'Finisher', '2021-11-19', NULL)
, (23293, 'Cashier', '2021-12-06', '2021-12-10')
, (26208, '3rd SHift Stocker', '2019-09-25', '2020-11-05')
, (26208, 'Order Fulfillment Assoc', '2020-08-05', '2021-04-16')
, (26208, 'Customer Service Rep', '2021-05-10', '2021-10-15')
, (26208, 'Delivery Driver', '2021-11-15', NULL)
, (26208, 'Another Job', '2022-02-23', '2022-03-02')
, (26208, 'Same Day Job Start as Prev Job End', '2022-03-01', NULL)
--SELECT * FROM #DaysPerJob dpj ORDER BY dpj.GroupID, dpj.StartDate, dpj.EndDate
/* Days Per Job Calculations - Attempts */
SELECT dj.GroupID, dj.JobDesc, dj.StartDate, dj.EndDate
, LAG(dj.EndDate) OVER (PARTITION BY dj.GroupID ORDER BY dj.GroupID, dj.StartDate, dj.EndDate) AS PreviousJobEndDate
, DATEDIFF(DAY, dj.StartDate, IsNull(dj.EndDate, GetDate())) AS daysPerJob
FROM #DaysPerJob dj
ORDER BY dj.GroupID, dj.StartDate, dj.EndDate
How do I obtain a SUM of the unique days employed per group?
The SQL Above will give you a table of Job Records. Each Job has a Start Date but not all jobs have an End Date which means they are still employed at that job.
The issue I have been struggling with is how to count the unique days employed. It is VERY easy to simply calculate the number of days per job using the DATEDIFF function however I am not currently able to account for other jobs within the same range as it would count those days twice.
I am ordering by the Start Date and then using LAG I compare the last jobs End Date to the next jobs Start Date. If the current jobs Start Date is <= the last jobs End Date we instead calculate the next jobs days using the last jobs End Date to the current Jobs End Date...
However the above condition had issues... what if my last job did not have an End Date or what if the last jobs End Date was also > the current Jobs End Date? This would mean that the entire current job falls within the same range as the last job and so we should NOT count ANY days and the day count would become 0 so that when the Total SUM of days is calculated it would not count the days in that job. It was THIS last issue that I could not figure out which has now lead me to posting this question here on Stack Overflow.
/* Some SQL below of some things I have tried */
/* Days Per Job Calculations - Attempts */
SELECT dj.GroupID, dj.JobDesc, dj.StartDate, dj.EndDate
, LAG(dj.EndDate) OVER (PARTITION BY dj.GroupID ORDER BY dj.GroupID, dj.StartDate, dj.EndDate) AS PreviousJobEndDate
/* Check if next record is within same date range. The idea here is if the job is within the
| same Range we replace the current Jobs Start Date with the last Jobs End Date
*/
, CASE WHEN ( LAG(dj.EndDate) OVER (PARTITION BY dj.GroupID ORDER BY dj.StartDate, dj.EndDate) ) >= dj.StartDate
AND ( LAG(dj.EndDate) OVER (PARTITION BY dj.GroupID ORDER BY dj.StartDate, dj.EndDate) ) <= dj.EndDate
THEN IsNull( ( LAG(dj.EndDate) OVER (PARTITION BY dj.GroupID ORDER BY dj.StartDate, dj.EndDate) ), GetDate() )
ELSE dj.StartDate
END AS StartDateForSet
/* The below CASE is the same logic as the above CASE but just an output stating if the
| next job was found to be within the same range or if a NEW Set has begun.
*/
, CASE WHEN ( LAG(dj.EndDate) OVER (PARTITION BY dj.GroupID ORDER BY dj.StartDate, dj.EndDate) ) >= dj.StartDate
AND ( LAG(dj.EndDate) OVER (PARTITION BY dj.GroupID ORDER BY dj.StartDate, dj.EndDate) ) <= dj.EndDate
THEN 'InRange'
ELSE 'NewSet'
END AS withinRangeCheck
, DATEDIFF(DAY, dj.StartDate, IsNull(dj.EndDate, GetDate())) AS daysPerJob
/* This is the field that I want to use to eventually SUM using GROUPing and aggregate functions however I first
| need to get it to correctly output the unique days. If the current job falls within the previous jobs date
| range the idea is that this calculation would account for that and move the End Date accordingly so it either
| does NOT count any days within the new job or counts the trailing days should the job end date fall after the previous job.
*/
, DATEDIFF(DAY /* StartDate */
, (CASE WHEN( LAG(dj.EndDate) OVER (PARTITION BY dj.GroupID ORDER BY dj.StartDate, dj.EndDate) ) >= dj.StartDate
AND ( LAG(dj.EndDate) OVER (PARTITION BY dj.GroupID ORDER BY dj.StartDate, dj.EndDate) ) <= dj.EndDate
THEN IsNull( ( LAG(dj.EndDate) OVER (PARTITION BY dj.GroupID ORDER BY dj.StartDate, dj.EndDate) ), GetDate() )
ELSE dj.StartDate
END
)
/* EndDate If Null Use Current Date */
, IsNull(dj.EndDate, GetDate())
) AS DaysEmployedWithinSet
FROM #DaysPerJob dj
ORDER BY dj.GroupID, dj.StartDate, dj.EndDate
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
The Solution to this problem is Below based on the Chosen correct posted answer
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
I really thought there would be more answers to this question however this isn't an easy one... at least it wasn't for me nor was it something my coworkers were able to answer. Regardless there were two answers posted to this question. One post, however close it came, did not produce accurate counts of the days employed. I triple checked the data as well as checking the calculations in Excel and based on the dataset provided in this example the totals should look as they do below in the SQL Server version of using a Recursive CTE to create a dates table.
/* SUM Unique Days in Multiple Date Range Records (SQL Server).sql
| SQL Server Example
| Desc: The below shows how to obtain the unique days employed. Meaning we don't count the
| same day twice should an individual be employed at more than job at any given time.
*/
/* Data Setup */
DROP TABLE IF EXISTS #DaysPerJob;
CREATE TABLE #DaysPerJob
(
GroupID INT, JobDesc VARCHAR(100), StartDate DATE, EndDate DATE
)
INSERT INTO #DaysPerJob(GroupID, JobDesc, StartDate, EndDate)
VALUES
(23293, 'Food Prep', '2017-03-01', '2017-07-17')
, (23293, 'Finisher', '2021-11-19', NULL)
, (23293, 'Starter', '2021-11-21', '2021-12-13')
, (23293, 'Cashier', '2021-12-06', '2021-12-10')
, (26208, '3rd SHift Stocker', '2019-09-25', '2020-11-05')
, (26208, 'Order Fulfillment Assoc', '2020-08-05', '2021-04-16')
, (26208, 'Customer Service Rep', '2021-05-10', '2021-10-15')
, (26208, 'Delivery Driver', '2021-11-15', NULL)
, (26208, 'Another Job', '2022-02-23', '2022-03-02')
, (26208, 'Same Day Job Start as Prev Job End', '2022-03-01', NULL)
;
/* Using a Recursive CTE to produce a dates table to later be JOINed on */
WITH Dates(date) AS
(
SELECT MIN(StartDate) AS date
FROM #DaysPerJob
UNION ALL
SELECT DATEADD(DAY, 1, date)
FROM Dates
WHERE date < GetDate()
)
, ranked AS
( /* Needing to rank each job record in order to later remove the overlapping days when employed at more than one job at one time. */
SELECT j.*, d.*
, ROW_NUMBER() OVER (PARTITION BY j.GroupID, d.date ORDER BY j.GroupID, j.StartDate, IsNull(j.EndDate, GetDate())) AS ranker
FROM Dates d
LEFT JOIN #DaysPerJob j ON j.StartDate <= d.date
AND IsNull(j.EndDate, GetDate()) >= d.date
WHERE j.GroupID IS NOT NULL /* This filter removes all days in the Dates table where there was no employment */
--AND j.GroupID = 26208 --23293
--ORDER BY d.date, j.StartDate, IsNull(j.EndDate, GetDate()), j.GroupID
--OPTION (MaxRecursion 0)
)
/* Non Aggregate Data - UnComment to view */
/*
SELECT * FROM ranked r WHERE r.GroupID IS NOT NULL
ORDER BY r.date, r.StartDate, IsNull(r.EndDate, GetDate()), r.GroupID
OPTION (MaxRecursion 0)
*/
/* Aggregated Data */
SELECT r.GroupID, COUNT(*) AS daysEmployed, MIN(date) AS minStartDate, MAX(date) AS maxEndDate
, DATEDIFF(DAY, MIN(date), MAX(date)) AS TotalDaysInRange
/* To get total number of days NOT employed we simply take the TotalDaysInRange and subtract the daysEmployed */
, DATEDIFF(DAY, MIN(date), MAX(date)) - COUNT(*) AS unEmployedDays
FROM ranked r
WHERE r.ranker = 1
GROUP BY r.GroupID
ORDER BY r.GroupID
OPTION (MaxRecursion 0) /* The default MaxRecursion setting is 100. Generating more than 100 dates using this method will require the Option (MaxRecursion N) segment of the query, where N is the desired MaxRecursion setting. Setting this to 0 will remove the MaxRecursion limitation altogether */
Screenshot of totals grouped by GroupID:
Based on the screenshot as of today's date as of this posting 06.02.22 the totals are:
GroupID 23293 : 335 Days Employed
GroupID 26208 : 929 Days Employed
This SO Post has excellent examples of how to populate a dates table and some of the answers accomplish this feat without needing to use Option (MaxRecursion)
Get a list of dates between two dates using a function
I didn't have access to a SqlServer instance to test this on, so this is SQLite syntax, but I don't think it should be hard to convert this.
The approach I took was to basically use a "Dates" table and then join the DaysPerJob table to it so you get records for each day a GroupId was active. Then you just rank based on the individual day and groupId to use to filter out "overlapped" days of jobs.
/* Just using a recursive CTE to create a DATE table */
/* If you have an existing date table, could use that instead */
WITH dates(date) AS (
SELECT
MIN(StartDate)
FROM DaysPerJob
UNION ALL
SELECT
DATE(date, '+1 day')
FROM dates
WHERE date < date()
)
, ranked AS (
SELECT
d.date
, j.StartDate
, j.EndDate
, j.GroupID
, j.JobDesc
, ROW_NUMBER() OVER (PARTITION BY d.date, j.GroupID) AS ranker
FROM dates d
LEFT JOIN DaysPerJob j
ON date(j.StartDate) <= date(d.date)
AND ifnull(j.EndDate, date()) >= date(d.date)
WHERE j.GroupID IS NOT NULL
)
SELECT COUNT(*) AS days_worked, GroupID
FROM ranked r
WHERE r.ranker = 1
GROUP BY GroupID;
Here is another answer derived after some time to wrangle the data. Please forgive me, I put this into a fromatting that was easier to work with. This should work.
/* Data Setup */
DROP TABLE IF EXISTS #DaysPerJob;
CREATE TABLE #DaysPerJob
(
GroupID INT, JobDesc VARCHAR(100), StartDate DATE, EndDate DATE
)
INSERT INTO #DaysPerJob(GroupID, JobDesc, StartDate, EndDate)
VALUES
(23293, 'Food Prep', '2017-03-01', '2017-07-17')
, (23293, 'Finisher', '2021-11-19', NULL)
, (23293, 'Cashier', '2021-12-06', '2021-12-10')
, (26208, '3rd SHift Stocker', '2019-09-25', '2020-11-05')
, (26208, 'Order Fulfillment Assoc', '2020-08-05', '2021-04-16')
, (26208, 'Customer Service Rep', '2021-05-10', '2021-10-15')
, (26208, 'Delivery Driver', '2021-11-15', NULL)
, (26208, 'Another Job', '2022-02-23', '2022-03-02')
, (26208, 'Same Day Job Start as Prev Job End', '2022-03-01', NULL)
--SELECT * FROM #DaysPerJob dpj ORDER BY dpj.GroupID, dpj.StartDate, dpj.EndDate
/* Days Per Job Calculations - Attempts */
;WITH GapsMarked AS
(
--Mark the start of an (null) value island within a group and rank the data for window functions below and/or joining back
SELECT
GroupID, JobDesc,StartDate, EndDate,
Island = CASE WHEN EndDate IS NULL THEN 1 ELSE 0 END,
RowInGroup=ROW_NUMBER() OVER(PARTITION BY GroupID ORDER BY StartDate, EndDate)
FROM
#DaysPerJob
)
,VirtualGroups AS
(
--Complete the IsIsland within group calculation started above
SELECT
*,
IsIsland = SUM(Island) OVER (PARTITION BY GroupID ORDER BY RowInGroup ROWS UNBOUNDED PRECEDING)
FROM
GapsMarked
)
,MinEndDateInIsland AS
(
--This grabs the Min End Date to compare to the start date of each consecutive island record
SELECT
V1.GroupID, V1.RowInGroup,
EndDateOrMinOverlapped=CASE WHEN MIN(V2.EndDate) >= V1.StartDate THEN MIN(V2.EndDate) ELSE V1.EndDate END
FROM
VirtualGroups V1
LEFT OUTER JOIN VirtualGroups V2 ON V2.GroupID = V1.GroupID AND V2.RowInGroup <= V1.RowInGroup AND V2.IsIsland=0
GROUP BY
V1.GroupID, V1.RowInGroup,V1.StartDate, V1.EndDate
)
--Final output
SELECT
G.GroupID, G.JobDesc, G.StartDate, G.EndDate,
DayCalc=CASE WHEN G.IsIsland=0 THEN DATEDIFF(DAY, G.StartDate,N.EndDateOrMinOverlapped) ELSE NULL END
FROM
MinEndDateInIsland N
INNER JOIN VirtualGroups G ON G.GroupID = N.GroupID AND G.RowInGroup= N.RowInGroup
ORDER BY
G.GroupID, G.RowInGroup

GROUP BY DAY, CUMULATIVE SUM

I have a table in MSSQL with the following structure:
PersonId
StartDate
EndDate
I need to be able to show the number of distinct people in the table within a date range or at a given date.
As an example i need to show on a daily basis the totals per day, e.g. if we have 2 entries on the 1st June, 3 on the 2nd June and 1 on the 3rd June the system should show the following result:
1st June: 2
2nd June: 5
3rd June: 6
If however e.g. on of the entries on the 2nd June also has an end date that is 2nd June then the 3rd June result would show just 5.
Would someone be able to assist with this.
Thanks
UPDATE
This is what i have so far which seems to work. Is there a better solution though as my solution only gets me employed figures. I also need unemployed on another column - unemployed would mean either no entry in the table or date not between and no other entry as employed.
CREATE TABLE #Temp(CountTotal int NOT NULL, CountDate datetime NOT NULL);
DECLARE #StartDT DATETIME
SET #StartDT = '2015-01-01 00:00:00'
WHILE #StartDT < '2015-08-31 00:00:00'
BEGIN
INSERT INTO #Temp(CountTotal, CountDate)
SELECT COUNT(DISTINCT PERSON.Id) AS CountTotal, #StartDT AS CountDate FROM PERSON
INNER JOIN DATA_INPUT_CHANGE_LOG ON PERSON.DataInputTypeId = DATA_INPUT_CHANGE_LOG.DataInputTypeId AND PERSON.Id = DATA_INPUT_CHANGE_LOG.DataItemId
LEFT OUTER JOIN PERSON_EMPLOYMENT ON PERSON.Id = PERSON_EMPLOYMENT.PersonId
WHERE PERSON.Id > 0 AND DATA_INPUT_CHANGE_LOG.Hidden = '0' AND DATA_INPUT_CHANGE_LOG.Approved = '1'
AND ((PERSON_EMPLOYMENT.StartDate <= DATEADD(MONTH,1,#StartDT) AND PERSON_EMPLOYMENT.EndDate IS NULL)
OR (#StartDT BETWEEN PERSON_EMPLOYMENT.StartDate AND PERSON_EMPLOYMENT.EndDate) AND PERSON_EMPLOYMENT.EndDate IS NOT NULL)
SET #StartDT = DATEADD(MONTH,1,#StartDT)
END
select * from #Temp
drop TABLE #Temp
You can use the following query. The cte part is to generate a set of serial dates between the start date and end date.
DECLARE #ViewStartDate DATETIME
DECLARE #ViewEndDate DATETIME
SET #ViewStartDate = '2015-01-01 00:00:00.000';
SET #ViewEndDate = '2015-02-25 00:00:00.000';
;WITH Dates([Date])
AS
(
SELECT #ViewStartDate
UNION ALL
SELECT DATEADD(DAY, 1,Date)
FROM Dates
WHERE DATEADD(DAY, 1,Date) <= #ViewEndDate
)
SELECT [Date], COUNT(*)
FROM Dates
LEFT JOIN PersonData ON Dates.Date >= PersonData.StartDate
AND Dates.Date <= PersonData.EndDate
GROUP By [Date]
Replace the PersonData with your table name
If startdate and enddate columns can be null, then you need to add
addditional conditions to the join
It assumes one person has only one record in the same date range
You could do this by creating data where every start date is a +1 event and end date is -1 and then calculate a running total on top of that.
For example if your data is something like this
PersonId StartDate EndDate
1 20150101 20150201
2 20150102 20150115
3 20150101
You first create a data set that looks like this:
EventDate ChangeValue
20150101 +2
20150102 +1
20150115 -1
20150201 -1
And if you use running total, you'll get this:
EventDate Total
2015-01-01 2
2015-01-02 3
2015-01-15 2
2015-02-01 1
You can get it with something like this:
select
p.eventdate,
sum(p.changevalue) over (order by p.eventdate asc) as total
from
(
select startdate as eventdate, sum(1) as changevalue from personnel group by startdate
union all
select enddate, sum(-1) from personnel where enddate is not null group by enddate
) p
order by p.eventdate asc
Having window function with sum() requires SQL Server 2012. If you're using older version, you can check other options for running totals.
My example in SQL Fiddle
If you have dates that don't have any events and you need to show those too, then the best option is probably to create a separate table of dates for the whole range you'll ever need, for example 1.1.2000 - 31.12.2099.
-- Edit --
To get count for a specific day, it's possible use the same logic, but just sum everything up to that day:
declare #eventdate date
set #eventdate = '20150117'
select
sum(p.changevalue)
from
(
select startdate as eventdate, 1 as changevalue from personnel
where startdate <= #eventdate
union all
select enddate, -1 from personnel
where enddate < #eventdate
) p
Hopefully this is ok, can't test since SQL Fiddle seems to be unavailable.

limit the number of months in ssrs

I have a dataset, output of a sql query which gives me the below output :
Year Month TotalSales TotalProducts
2013 1 23233 45
2013 2 3344 43
2013 3 232 11
2013 4 2232 23
I am trying to represent the above dataset in a table and a bar graph using SSRS.
Is there a way I can limit the months to the last three months on SSRS ??
I am using the current month as a parameter in SSRS.
So example : I choose month 4 as a parameter , i would like to see the results only for the months 4,3 & 2 in the bar chart and the table
Is there a way I can do it ?
The actual query looks something like this :
SELECT ISNULL(x.[Month],z.[Month]) AS [Month],
ISNULL(x.Sum_Stores, 0) - ISNULL(y.Sum_SalesStores, 0) AS Difference , ISNULL(Sum_onetonine, 0) as EcontractsbetweenOneandNine........................
FROM
(
SELECT [Month], Sum(Stores) AS Sum_Stores
FROM SUM_XXX
WHERE [Year] = '2013' and Name = 'Pro'
GROUP BY [Month]
) AS x
FULL OUTER JOIN
(
SELECT [Month], Sum(tracts) AS Sum_SalesStores
FROM SUM_yyy
WHERE [Year] = '2013' and Name = 'Pro'
GROUP BY [Month]
) AS y ON x.[Month] = y.[Month]
............................
declare #ParamDate DateTime =null; -- Pass the required date in datetime format
-- as per your example pass #ParamDate as '20130401'
SET #ParamDate = getdate(); --setting the current date for demo purpose
SELECT ISNULL(x.[Month],z.[Month]) AS [Month],
ISNULL(x.Sum_Stores, 0) - ISNULL(y.Sum_SalesStores, 0) AS Difference , ISNULL(Sum_onetonine, 0) as EcontractsbetweenOneandNine........................
FROM
(
SELECT [Month], Sum(Stores) AS Sum_Stores
FROM SUM_XXX
WHERE ([Year] = year(#ParamDate) or [Year] = year(dateadd(m,-2,#ParamDate)) )
and ([Month] = month(#ParamDate) or [Month] = month(dateadd(m,-2,#ParamDate)) )
and Name = 'Pro'
GROUP BY [Month]
) AS x
FULL OUTER JOIN
(
SELECT [Month], Sum(tracts) AS Sum_SalesStores
FROM SUM_yyy
WHERE ([Year] = year(#ParamDate) or [Year] = year(dateadd(m,-2,#ParamDate)) )
and ([Month] = month(#ParamDate) or [Month] = month(dateadd(m,-2,#ParamDate)) )
and Name = 'Pro'
GROUP BY [Month]
) AS y ON x.[Month] = y.[Month]
............................
There are several ways to achieve this in SSRS.
Do you want to limit the results returned by the query if so use a query parameter link
Or you can apply a filter to the dataset / dataregion link
What you will essentially want to do would be (pseudo)
DataSet!Month.Value BETWEEN Parameter!Month.Value-2 AND Parameter!Month.Value
That doesn't take into considertion months 1 and 2 properly. Will update when I have a better solution then a convoluted if-else.
Edit: If you added a MonthStart field to the result set as a proper DATETIME and used MonthStart as values to the parameter then you could do
DataSet!Month.Value BETWEEN DATEADD (MONTH, -2, Parameter!Month.Value) AND Parameter!Month.Value

Checking a range of dates in SQL

Following on from a question I put on yesterday, I need to return a range of "available" dates for a laptop rollout "booking system". I want to populate a table of possible available dates a user can book a slot on by checking for each date the total possible number of slots, and subtracting the number of slots already booked.
The logic is as follows:
A technician can build 3 laptops per day.
On any day there may be 1, 2 or 3 technicians available.
A table holds the bookings made
I don't want a table of all possible dates, I want to calculate it on the fly
Relevant tables are:
tl_sb_slotBooking
This contains the bookings already made
tl_sb_availabilityPeriods
This is used to calculate the total number of available slots on a given day
I can bring back a list of dates with a fixed maximum number (in this case 3) of slots:
DECLARE #startDate DATE
DECLARE #endDate DATE
SET #startDate = GETDATE()
SET #endDate = DATEADD(m,3,#startDate)
;
WITH dates(Date) AS
(
SELECT #startdate as Date
UNION ALL
SELECT DATEADD(d,1,[Date])
FROM dates
WHERE DATE < #enddate
)
SELECT Date
FROM dates
EXCEPT
SELECT date
FROM tl_sb_booking
GROUP BY date
HAVING COUNT(date) >= 3
However, the maximum won't always be 3, it changes for each day.
I can find the maximum possible slots for a given day:
DECLARE #myDate DATETIME = '2013-06-22'
SELECT SUM(laptopsPerDay) AS totalSlots
FROM tl_sb_technicianAvailability
WHERE startDate <= #myDate AND endDate >= #myDate
AND availabiltyStateID=3
it will bring back 6 as the total number of slots available for 2013-06-22. (The availabilityStateID field is used to store available/unavailable etc.)
So, the bit I am stuck on is combining the two.
What I want is for each possible date, if the number of slots already booked is less than the number of possible slots for that day, add it to the table being returned (otherwise don't).
Firstly, althought you are only generating a small list, using a CTE to generate a sequential list performs terribly and is best avoided.
For the sake of this I will use the system table Master..spt_values for a sequential list of numbers, but if you are worried about using undocumented system tables then there are other methods in the link above.
The first thing I would do is split the technician availability dates into a row per day, this will allow for technicians who are available for only part of the peiod required (e.g. from your sceen shot of the table if you wanted to query from 18th June to 26th June none of the technicians show would appear as available using the query you have posted):
SELECT Date = DATEADD(DAY, spt.Number, ta.StartDate),
ta.TechnicianID,
ta.LapTopsPerDay
FROM tl_sb_technicianAvailability ta
INNER JOIN Master..spt_values spt
ON spt.Type = 'P'
AND spt.Number BETWEEN 0 AND DATEDIFF(DAY, ta.startDate, ta.EndDate)
This would simply turn:
TechnicianID StartDate EndDate LapTopsPerDay
1 20130620 20130624 3
into
Date TechnicianID LapTopsPerDay
20130620 1 3
20130621 1 3
20130622 1 3
20130623 1 3
20130624 1 3
You can then limit this list to the date range required, and sum up the total laptops than can be done as this is not needed on a technicial level:
WITH ExplodedAvailability AS
( SELECT Date = DATEADD(DAY, spt.Number, ta.StartDate),
ta.TechnicianID,
ta.LapTopsPerDay
FROM tl_sb_technicianAvailability ta
INNER JOIN Master..spt_values spt
ON spt.Type = 'P'
AND spt.Number BETWEEN 0 AND DATEDIFF(DAY, ta.startDate, ta.EndDate)
)
SELECT Date, TotalLaptops = SUM(LapTopsPerDay)
FROM ExplodedAvailability
WHERE Date >= #StartDate
AND Date < #EndDate
GROUP BY Date;
Finally you can LEFT JOIN to the bookings table to get the available slots per day
WITH ExplodedAvailability AS
( SELECT Date = DATEADD(DAY, spt.Number, ta.StartDate),
ta.TechnicianID,
ta.LapTopsPerDay
FROM tl_sb_technicianAvailability ta
INNER JOIN Master..spt_values spt
ON spt.Type = 'P'
AND spt.Number BETWEEN 0 AND DATEDIFF(DAY, ta.startDate, ta.EndDate)
), Availability AS
( SELECT Date, TotalLaptops = SUM(LapTopsPerDay)
FROM ExplodedAvailability
WHERE Date >= #StartDate
AND Date < #EndDate
GROUP BY Date
), Bookings AS
( SELECT Date, SlotsBooked = COUNT(*)
FROM tl_sb_booking
GROUP BY Date
)
SELECT Availability.Date,
Availability.TotalLaptops,
RemainingSlots = Availability.TotalLaptops - ISNULL(Bookings.SlotsBooked, 0)
FROM Availability
LEFT JOIN Bookings
ON Bookings.Date = Availability.Date;
I think what you are after is to add a booking to the next available day, so the query to do this would be:
DECLARE #UserID INT = 1;
WITH ExplodedAvailability AS
( SELECT Date = DATEADD(DAY, spt.Number, ta.StartDate),
ta.TechnicianID,
ta.LapTopsPerDay
FROM tl_sb_technicianAvailability ta
INNER JOIN Master..spt_values spt
ON spt.Type = 'P'
AND spt.Number BETWEEN 0 AND DATEDIFF(DAY, ta.startDate, ta.EndDate)
), Availability AS
( SELECT Date, TotalLaptops = SUM(LapTopsPerDay)
FROM ExplodedAvailability
WHERE Date >= CAST(GETDATE() AS DATE)
GROUP BY Date
), Bookings AS
( SELECT Date, SlotsBooked = COUNT(*)
FROM tl_sb_booking
GROUP BY Date
)
INSERT tl_sb_slotBooking (UserID, Date)
SELECT #UserID, MIN(Availability.Date)
FROM Availability
LEFT JOIN Bookings
ON Bookings.Date = Availability.Date
WHERE Availability.TotalLaptops > ISNULL(Bookings.SlotsBooked, 0)
Should this be of use to anyone, this is the way I ultimately did it:
DECLARE #startDate DATE
DECLARE #endDate DATE
SET #startDate = GETDATE()
SET #endDate = DATEADD(m,3,#startDate)
;
WITH dates(currentDate) AS
(
SELECT #startdate as currentDate
UNION ALL
SELECT DATEADD(d,1,[currentDate])
FROM dates
WHERE currentDate < #enddate
)
SELECT currentDate
FROM dates
WHERE /* slots booked for date */
(
SELECT count([date])
FROM tl_sb_booking
where [date] = currentDate
)
<
/* total slots available */
(
SELECT SUM(laptopsPerDay) AS totalSlots
FROM tl_sb_technicianAvailability
WHERE startDate <= currentDate AND endDate >= currentDate
AND availabiltyStateID=3
)

SQL Server multiple date languages in same query

On a datawarehouse project with SSIS/SSAS, I have to generate my own time dimension because I've personal data to integrate with.
My problem is with SSAS because I also need to integrate translation. After reading the documentation, I've found a command to set language for the current session by using SET LANGUAGE ENGLISH but I'm not able to change language for different field of the query.
Is there a way to generate MONTH_NAME in French and also get MONTH_NAME_DE in German ?
Here is the script that I've found on Internet
WITH Mangal as
(
SELECT Cast ('1870-01-01' as DateTime) Date --Start Date
UNION ALL
SELECT Date + 1
FROM Mangal
WHERE Date + 1 < = '2015-12-31' --End date
)
SELECT
Row_Number() OVER (ORDER BY Date) as ID
, Date as DATE_TIME
, YEAR (date) as YEAR_NB
, MONTH (date) as MONTH_NB
, DAY (date) as DAY_NUMBER
, DateName (mm, date) as MONTH_NAME
, LEFT ( DateName (mm, date), 3) KMONTH_NAME
, DateName (dw, date) as DAY_NAME
, LEFT (DateName (dw, date), 3) as KDAY_NAME
, (SELECT TOP 1 FIELD
FROM TABLEXY
WHERE Date BETWEEN TABLEXY.DATE_FROM AND LEGISLATUR.DATE_TO
AND LANGAGE = 'FR'
) as PERSONAL_FIELD
, (SELECT TOP 1 FIELD
FROM TABLEXY
WHERE Date BETWEEN TABLEXY.DATE_FROM AND LEGISLATUR.DATE_TO
AND LANGAGE = 'DE'
) as PERSONAL_FIELD_DE
FROM Mangal
OPTION (MAXRECURSION 0)
SQL Server has a table containing names of Months and week days. However, they are stored as comma delimited values:
select
months,
shortmonths,
days
from
master.dbo.syslanguages
where
alias in ('English','French', 'German')
You might use this in your query.

Resources