limit the number of months in ssrs - sql-server

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

Related

Comparing SQL date ranges across multiple nullable date columns

I have a calendar type check I'm trying to do on SQL Server. For each month of the year, I need to check if the employee was hired or not. There can be an original hire date, a rehire date, a termination date, and the last termination date; other than the original hire date, which will always have a value, all of these date fields can be null.
Given the following data:
EmpID OrigHireDate TermDate LastTermDate RehireDate
42 2017-09-25 NULL 2019-03-26 2019-10-30
What I am trying to achieve is the following result for each month for last year (i.e. 2019) and having no luck in coming up with the right statement. Assume I already have a table containing each month's number along with the start/end date of the month that I can use to compare the date ranges.
EmpID Month EmployeeDuring
42 1 True
42 2 True
42 3 True
42 4 False
42 5 False
42 6 False
42 7 False
42 8 False
42 9 False
42 10 True
42 11 True
42 12 True
The following works. May need some minor adjustments to handle all possible combinations of EmpID, OrigHireDate, TermDate, LastTermDate, RehireDate
I apologize for posting mostly code. Will add more explanation and or comments tomorrow.
DECLARE #EmpID int, #OrigHireDate date, #TermDate date, #LastTermDate date, #RehireDate date
DECLARE #year int
SET #year = 2019
SET #EmpID = 42
SET #OrigHireDate = '2017-09-25'
SET #TermDate = NULL
SET #LastTermDate = '2019-03-26'
SET #RehireDate = '2019-10-30'
SET #OrigHireDate = DATEADD(day,-DAY(#OrigHireDate)+1, #OrigHireDate)
SET #LastTermDate = DATEADD(day,-DAY(ISNULL(#LastTermDate,GETDATE()))+1, #LastTermDate)
SET #RehireDate = DATEADD(day,-DAY(#RehireDate)+1, #RehireDate)
SET #TermDate = DATEADD(day,-DAY(ISNULL(#TermDate,GETDATE()))+1, #TermDate)
;WITH CTE_DATES_ORIGINAL([Date],[Level])
AS
(
SELECT #OrigHireDate AS [DATE],
1 AS [Level]
UNION ALL
SELECT
DATEADD(MONTH,1, [DATE] ) , [Level] + 1
FROM CTE_DATES_ORIGINAL
WHERE [DATE] < ISNULL(#LastTermDate,GETDATE())
),
CTE_DATES_REHIRE([Date],[Level])
AS
(
SELECT #RehireDate AS [DATE],
1 AS [Level]
UNION ALL
SELECT
DATEADD(MONTH,1, [DATE] ) , [Level] + 1
FROM CTE_DATES_REHIRE
WHERE [DATE] < ISNULL(#TermDate,GETDATE())
),
CTE_DATES_YEAR(m) AS
(
SELECT 1
UNION ALL
SELECT m+1
FROM CTE_DATES_YEAR
WHERE m < 12
)
SELECT #EmpID AS EmpID, m AS [Month], ISNULL(EmployeeDuring.EmployeeDuring,0) AS EmployeeDuring
FROM CTE_DATES_YEAR y
LEFT OUTER JOIN
(
SELECT
[Date], 1 AS EmployeeDuring
FROM
CTE_DATES_ORIGINAL
UNION
SELECT
[Date] , 1 AS EmployeeDuring
FROM
CTE_DATES_REHIRE
) employeeDuring
ON DATEADD(month,m-1, CAST(CAST(#year AS CHAR(4)) + '-1-1' AS DATE)) = employeeDuring.[Date]
ORDER BY m
OPTION (MAXRECURSION 5000)

How to dynamically allocate all months based to a column on condition in SQL Server?

I have a table in which I am storing the Product Name and it's Renewal Date along with the payment plan (Monthly/Quarterly/Yearly). Now if the payment plan of the product is Yearly or Monthly it will display the get the month of Renewal and show the Rate/Amount against that month but if the payment plan is Monthly it should display the Rate/Amount in front of each month as shown below.
For example if a product named ABC has payment plan of Yearly, subscription rate of 276 and Renewal Date 2019-12-01 and there is another product XYZ with payment plan of Monthly, subscription rate of 17 and Renewal Date 2019-08-15 then the result set I want should something like this
ProductName RenewalMonth Rate
------------------------------------
ABC December 276
XYZ January 17
XYZ February 17
XYZ March 17
XYZ April 17
XYZ May 17
XYZ June 17
XYZ July 17
XYZ August 17
XYZ September 17
XYZ October 17
XYZ November 17
XYZ December 17
Here is the query which I have wrote which is returning data that's present in the database fine but not the months other than December for Product XYZ. Keeping in mind this should only display same rate for all other months provided in the desired dates where Payment Plan is 'Monthly', for other payment plans it should show rate in front of given month as present in the database.
Sample data is as follows:
CREATE TABLE ItemDefinition
(
ID INT NOT NULL,
ProductName VARCHAR(50),
PaymentPlan VARCHAR(50),
RenewalDate DATETIME,
UnitRate NUMERIC(18, 0)
);
INSERT INTO ItemDefinition
VALUES (1, 'ABC', 'Yearly', '2019-12-01', 276),
(1, 'XYZ', 'Monthly', '2019-08-15', 17);
And the query I am writing is
SELECT
ProductName, SUM(UnitRate) Rate,
DATENAME(MONTH , DATEADD(MONTH , MONTH(RenewalDate)-1 , '1900-01-01')) RenewalMonth
FROM
ItemDefinition
WHERE
MONTH(RenewalDate) IS NOT NULL
AND RenewalDate BETWEEN #dtStart AND #dtEnd
GROUP BY
ProductName, MONTH(RenewalDate)
ORDER BY
MONTH(RenewalDate)
It might be something like this:
DECLARE #DateBeg DATE = '2019-01-01'
,#DateEnd DAte = '2020-12-01';
WITH Ranges AS
(
SELECT *
,#DateBeg AS [DateBeg]
,#DateEnd AS [DateEnd]
FROM ItemDefinition DS
)
SELECT *
,DATENAME(MONTH ,ISNULL([GeneratedDate], [RenewalDate])) AS RenewalMonth
FROM Ranges
OUTER APPLY
(
SELECT DATEADD(MONTH, [number], [DateBeg])
FROM
(
select number
from master.dbo.spt_values
where [type] = 'P'
) numbers
WHERE DATEADD(MONTH, [number], [DateBeg]) < [DateEnd]
AND [PaymentPlan] = 'Monthly'
) AutoDates ([GeneratedDate]);
You can change the DateEnd parameter to something less and you will see how less months are generated.
The idea is to have start and end date for each row and depending on it to generate your months.
To get the records for the years use the following:
WITH Ranges AS
(
SELECT *
,#DateBeg AS [DateBeg]
,#DateEnd AS [DateEnd]
FROM ItemDefinition DS
)
SELECT *
,DATENAME(MONTH ,ISNULL([GeneratedDate], [RenewalDate])) AS RenewalMonth
,IIF([PaymentPlan] = 'Monthly', [UnitRate], IIF(CONVERT(VARCHAR(7), [RenewalDate], 121) = CONVERT(VARCHAR(7), [GeneratedDate], 121), [UnitRate], NULL))
FROM Ranges
OUTER APPLY
(
SELECT DATEADD(MONTH, [number], [DateBeg])
FROM
(
select number
from master.dbo.spt_values
where [type] = 'P'
) numbers
WHERE DATEADD(MONTH, [number], [DateBeg]) < [DateEnd]
) AutoDates ([GeneratedDate]);
or the following to get the year rate for the first record:
DECLARE #DateBeg DATE = '2019-01-01'
,#DateEnd DAte = '2020-12-01';
WITH Ranges AS
(
SELECT *
,#DateBeg AS [DateBeg]
,#DateEnd AS [DateEnd]
FROM ItemDefinition DS
)
SELECT *
,DATENAME(MONTH ,ISNULL([GeneratedDate], [RenewalDate])) AS RenewalMonth
,IIF([PaymentPlan] = 'Monthly', [UnitRate], IIF([number] = 0, [UnitRate], NULL))
FROM Ranges
OUTER APPLY
(
SELECT DATEADD(MONTH, [number], [DateBeg])
,[number]
FROM
(
select number
from master.dbo.spt_values
where [type] = 'P'
) numbers
WHERE DATEADD(MONTH, [number], [DateBeg]) < [DateEnd]
) AutoDates ([GeneratedDate], [number]);
My advice is to introduce an additional table that will have a single record for a Yearly plan and 12 records for Monthly plan. For example:
create table PaymentPlanInterval(
PaymentPlan VARCHAR(50),
Interval varchar(50)
)
And perhaps this table may contain 2 records for Semi-annual payment plan and 4 records for quartely plan.
In order to get your desired result you should be joining your ItemDefinition table with PaymentPlanInterval. Voila.

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

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;

SQL query with function

I have a table of data which i am using a count statement to get the amount of records for the submission date
example
AuditId Date Crew Shift Cast ObservedBy 2ndObserver AuditType Product
16 2017-06-27 3 Day B1974, B1975 Glen Mason NULL Identification Billet
20 2017-06-29 1 Day 9879 Corey Lundy NULL Identification Billet
21 2017-06-29 4 Day T9627, T9625 Joshua Dwyer NULL ShippingPad Tee
22 2017-06-29 4 Day NULL Joshua Dwyer NULL Identification Billet
23 2017-06-29 4 Day S9874 Joshua Dwyer NULL ShippingPad Slab
24 2017-06-29 4 Day Bay 40 Joshua Dwyer NULL Identification Billet
Basically I am using the following code to get my results
SELECT YEAR([Date]) as YEAR, CAST([Date] as nvarchar(25)) AS [Date], COUNT(*) as "Audit Count"
FROM AuditResults
where AuditType = 'Identification' AND Product = 'Billet'
group by Date
this returns example
YEAR Date Audit Count
2017 2017-06-27 1
2017 2017-06-29 3
Now I want to be able to retrieve all dates even if blank
so I would like the return to be
YEAR Date Audit Count
2017 2017-06-27 1
2017 2017-06-28 0
2017 2017-06-29 3
I have the following function I am trying to use:
ALTER FUNCTION [dbo].[fnGetDatesInRange]
(
#FromDate datetime,
#ToDate datetime
)
RETURNS #DateList TABLE (Dt date)
AS
BEGIN
DECLARE #TotalDays int, #DaysCount int
SET #TotalDays = DATEDIFF(dd,#FromDate,#ToDate)
SET #DaysCount = 0
WHILE #TotalDays >= #DaysCount
BEGIN
INSERT INTO #DateList
SELECT (#ToDate - #DaysCount) AS DAT
SET #DaysCount = #DaysCount + 1
END
RETURN
END
How do I use my select statement with this function? or is there a better way?
cheers
Try this;
ALTER FUNCTION [dbo].[fnGetDatesInRange]
(
#FromDate datetime,
#ToDate datetime
)
RETURNS #YourData TABLE ([Year] int, DateText nvarchar(25),[Audit Count] int)
AS
begin
insert into #YourData
SELECT
YEAR(allDates.[Date]) as YEAR,
CAST(allDates.[Date] as nvarchar(25)) AS [Date],
COUNT(r.Product) as "Audit Count"
from
(
SELECT
[date]=convert(datetime, CONVERT(float,d.Seq))
FROM
(
select top 100000 row_number() over(partition by 1 order by A.name) as Seq
from syscolumns A, syscolumns B
)d
)allDates
left join
AuditResults r on r.[Date]=allDates.[date] and r.AuditType = 'Identification' AND r.Product = 'Billet'
where
allDates.[Date]>=#FromDate and allDates.[Date]<=#ToDate
group by
allDates.[Date]
return
end
The key is the 'allDates' section ;
SELECT
[date]=convert(datetime, CONVERT(float,d.Seq))
FROM
(
select top 100000 row_number() over(partition by 1 order by A.name) as Seq
from syscolumns A, syscolumns B
)d
This will return all dates between 1900 and 2173 (in this example). Limit that as you need but a nice option. A ton of different ways to approach this clearly
you have to create another table calendar as (Mysql)- idea is the same on all RDBMS-
CREATE TABLE `calendar` (
`dt` DATE NOT NULL,
UNIQUE INDEX `calendar_dt_unique` (`dt`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
;
and fill with date data.
more details

Set based solution for processing rows in a SQL table

Can someone steer me in the right direction for solving this issue with a set-based solution versus cursor-based?
Given a table with the following rows:
Date Value
2013-11-01 12
2013-11-12 15
2013-11-21 13
2013-12-01 0
I need a query that will give me a row for each date between 2013-11-1 and 2013-12-1, as follows:
2013-11-01 12
2013-11-02 12
2013-11-03 12
...
2013-11-12 15
2013-11-13 15
2013-11-14 15
...
2013-11-21 13
2013-11-21 13
...
2013-11-30 13
2013-11-31 13
Any advice and/or direction will be appreciated.
The first thing that came to my mind was to fill in the missing dates by looking at the day of the year. You can do this by joining to the spt_values table in the master DB and adding the number to the first day of the year.
DECLARE #Table AS TABLE(ADate Date, ANumber Int);
INSERT INTO #Table
VALUES
('2013-11-01',12),
('2013-11-12',15),
('2013-11-21',13),
('2013-12-01',0);
SELECT
DateAdd(D, v.number, MinDate) Date
FROM (SELECT number FROM master.dbo.spt_values WHERE name IS NULL) v
INNER JOIN (
SELECT
Min(ADate) MinDate
,DateDiff(D, Min(ADate), Max(ADate)) DaysInSpan
,Year(Min(ADate)) StartYear
FROM #Table
) dates ON v.number BETWEEN 0 AND DaysInSpan - 1
Next I would wrap that to make a derived table, and add a subquery to get the most recent number. Your end result may look something like:
DECLARE #Table AS TABLE(ADate Date, ANumber Int);
INSERT INTO #Table
VALUES
('2013-11-01',12),
('2013-11-12',15),
('2013-11-21',13),
('2013-12-01',0);
-- Uncomment the following line to see how it behaves when the date range spans a year end
--UPDATE #Table SET ADate = DateAdd(d, 45, ADate)
SELECT
AllDates.Date
,(SELECT TOP 1 ANumber FROM #Table t WHERE t.ADate <= AllDates.Date ORDER BY ADate DESC)
FROM (
SELECT
DateAdd(D, v.number, MinDate) Date
FROM
(SELECT number FROM master.dbo.spt_values WHERE name IS NULL) v
INNER JOIN (
SELECT
Min(ADate) MinDate
,DateDiff(D, Min(ADate), Max(ADate)) DaysInSpan
,Year(Min(ADate)) StartYear
FROM #Table
) dates ON v.number BETWEEN 0 AND DaysInSpan - 1
) AllDates
Another solution, not sure how it compares to the two already posted performance wise but it's a bit more concise:
Uses a numbers table:
Linky
Query:
DECLARE #SDATE DATETIME
DECLARE #EDATE DATETIME
DECLARE #DAYS INT
SET #SDATE = '2013-11-01'
SET #EDATE = '2013-11-29'
SET #DAYS = DATEDIFF(DAY,#SDATE, #EDATE)
SELECT Num, DATEADD(DAY,N.Num,#SDATE), SUB.[Value]
FROM Numbers N
LEFT JOIN MyTable M ON DATEADD(DAY,N.Num,#SDATE) = M.[Date]
CROSS APPLY (SELECT TOP 1 [Value]
FROM MyTable M2
WHERE [Date] <= DATEADD(DAY,N.Num,#SDATE)
ORDER BY [Date] DESC) SUB
WHERE N.Num <= #DAYS
--
SQL Fiddle
It's possible, but neither pretty nor very performant at scale:
In addition to your_table, you'll need to create a second table/view dates containing every date you'd ever like to appear in the output of this query. For your example it would need to contain at least 2013-11-01 through 2013-12-01.
SELECT m.date, y.value
FROM your_table y
INNER JOIN (
SELECT md.date, MAX(my.date) AS max_date
FROM dates md
INNER JOIN your_table my ON md.date >= my.date
GROUP BY md.date
) m
ON y.date = m.max_date

Resources