I have a scenario to display data from 2 tables combined. First table Named 'DayTable' consists of daily plan and actual. Second table named 'MonthTable' consists of Monthly plan and actual. I need to display last 6months data and the current month daily data. So i wrote the query like below i for the expected output
Declare #startdate date = CONVERT(DATE, DATEADD(dd, -DAY(DATEADD(MONTH, 0, GETDATE())) + 1, DATEADD(MONTH, 0, GETDATE())))
Declare #endDate date = DATEADD(DAY, -DAY(DATEADD(MONTH, 1, GETDATE())), DATEADD(MONTH, 1, GETDATE()))
CREATE TABLE #TEMP
(
PlanDate NVARCHAR(100),
[PastTrend - Plan] INT,
[PastTrend - Actual] INT,
[Current - Plan] INT,
[Current - Actual] INT,
)
;With cte
as
(
Select #startdate sDate
Union All
Select DATEADD(day,1,sDate) From cte where DATEADD(day,1,sDate) <= #endDate
)
INSERT INTO #TEMP
SELECT
REPLACE(CONVERT(CHAR(6), A.sDate, 106),' ',' - ') PlanDate
,NULL AS [PastTrend - Plan]
,NULL AS [PastTrend - Actual]
,SUM(B.PlanQuantity) AS [Current - Plan]
,SUM(B.Actual) AS [Current - Actual]
FROM cte A
LEFT OUTER JOIN DayTable B
ON A.sDate = CONVERT(DATE,B.PlanDate)
GROUP BY A.sDate
--ORDER BY A.sDate
SELECT
*
FROM
(
SELECT
CONVERT(CHAR(3), datename(month,PlanMonth)) + ' ' + RIGHT(CONVERT(VARCHAR(4), YEAR(PlanMonth)), 2) AS PlanDate
,SUM(PlanQuantity) AS [PastTrend - Plan]
,SUM(Actual) AS [PastTrend - Actual]
,NULL AS [Current - Plan]
,NULL AS [Current - Actual]
FROM
MonthTable
WHERE CONVERT(DATE, PlanMonth) >= CONVERT(DATE, DATEADD(dd, -DAY(DATEADD(MONTH, 0, GETDATE())) + 1, DATEADD(MONTH, -6, GETDATE())))
group by PlanMonth
UNION ALL
SELECT
PlanDate
,[PastTrend - Plan]
,[PastTrend - Actual]
,[Current - Plan]
,[Current - Actual]
FROM
#TEMP
) T1
DROP TABLE #TEMP
My Output is like
Now i am thining to avoid temp table concept because if any failure after create temp table it will not drop. So rewrite the query like below
Declare #startdate date = CONVERT(DATE, DATEADD(dd, -DAY(DATEADD(MONTH, 0, GETDATE())) + 1, DATEADD(MONTH, 0, GETDATE())))
Declare #endDate date = DATEADD(DAY, -DAY(DATEADD(MONTH, 1, GETDATE())), DATEADD(MONTH, 1, GETDATE()))
;With cte
as
(
Select #startdate sDate
Union All
Select DATEADD(day,1,sDate) From cte where DATEADD(day,1,sDate) <= #endDate
)
SELECT
A.sDate AS OriginalDate
,REPLACE(CONVERT(CHAR(6), A.sDate, 106),' ',' - ') PlanDate
,NULL AS [PastTrend - Plan]
,NULL AS [PastTrend - Actual]
,SUM(B.PlanQuantity) AS [Current - Plan]
,SUM(B.Actual) AS [Current - Actual]
FROM cte A
LEFT OUTER JOIN DayTable B
ON A.sDate = CONVERT(DATE,B.PlanDate)
GROUP BY A.sDate
UNION ALL
SELECT
PlanMonth AS OriginalDate
,CONVERT(CHAR(3), datename(month,PlanMonth)) + ' ' + RIGHT(CONVERT(VARCHAR(4), YEAR(PlanMonth)), 2) AS PlanDate
,SUM(PlanQuantity) AS [PastTrend - Plan]
,SUM(Actual) AS [PastTrend - Actual]
,NULL AS [Current - Plan]
,NULL AS [Current - Actual]
FROM
MonthTable
WHERE CONVERT(DATE, PlanMonth) >= CONVERT(DATE, DATEADD(dd, -DAY(DATEADD(MONTH, 0, GETDATE())) + 1, DATEADD(MONTH, -6, GETDATE())))
group by PlanMonth
ORDER BY OriginalDate
But here i have problem. In the output i dont need OriginalDate. How to avoid this. For this i can wrap the union output to a select query but how can i got error in cte. Please guid me. Also suggest which method is best one
I finished the query. ACtually i just wrap the query as outer with out the cte. Cte must be the top in the query. The final query is
Declare #startdate date = CONVERT(DATE, DATEADD(dd, -DAY(DATEADD(MONTH, 0, GETDATE())) + 1, DATEADD(MONTH, 0, GETDATE())))
Declare #endDate date = DATEADD(DAY, -DAY(DATEADD(MONTH, 1, GETDATE())), DATEADD(MONTH, 1, GETDATE()))
;With cte
as
(
Select #startdate sDate
Union All
Select DATEADD(day,1,sDate) From cte where DATEADD(day,1,sDate) <= #endDate
)
SELECT
T1.PlanDate
,T1.[PastTrend - Plan]
,T1.[PastTrend - Actual]
,T1.[Current - Plan]
,T1.[Current - Actual]
FROM
(
SELECT
A.sDate AS OriginalDate
,REPLACE(CONVERT(CHAR(6), A.sDate, 106),' ',' - ') PlanDate
,NULL AS [PastTrend - Plan]
,NULL AS [PastTrend - Actual]
,SUM(B.PlanQuantity) AS [Current - Plan]
,SUM(B.Actual) AS [Current - Actual]
FROM cte A
LEFT OUTER JOIN DayTable B
ON A.sDate = CONVERT(DATE,B.PlanDate)
GROUP BY A.sDate
UNION ALL
SELECT
PlanMonth AS OriginalDate
,CONVERT(CHAR(3), datename(month,PlanMonth)) + ' ' + RIGHT(CONVERT(VARCHAR(4), YEAR(PlanMonth)), 2) AS PlanDate
,SUM(PlanQuantity) AS [PastTrend - Plan]
,SUM(Actual) AS [PastTrend - Actual]
,NULL AS [Current - Plan]
,NULL AS [Current - Actual]
FROM
MonthTable
WHERE CONVERT(DATE, PlanMonth) >= CONVERT(DATE, DATEADD(dd, -DAY(DATEADD(MONTH, 0, GETDATE())) + 1, DATEADD(MONTH, -6, GETDATE())))
group by PlanMonth
) T1
ORDER BY T1.OriginalDate
But i need to know the peroformance. When i execute this query with actual execution plan Query Cost (Relative to the batch) : 100%
When i execute the first method using temp the query cost is 90%. Can anyone guid for this
Related
I'm trying to write a stored procedure which groups up rows based on their month and return a sum of all items if they exist and 0 if they don't.
For the date part of the query, what I am trying to get is today's date - extract the month and go back 5 months to gather any data if it exists.
At this stage, the query runs fine as is but I'm wondering if there's any way to optimise this as it looks like I'm running the same set of data over and over again and also it's hard coded to an extent.
The dataset I am trying to achieve is as follows:
Month TotalAmount TotalCount
-----------------------------------
2017-11 0 0
2017-12 200.00 2
2018-01 300.00 3
2018-02 0 0
2018-03 300.00 3
2018-04 100.00 1
Using the following query below, I was able to achieve what I want but as you can see, it's hard coding back the past 5 months so if I wanted to go back 12 months, I'd have to add in more code.
DECLARE #5MonthAgo date = CAST(DATEADD(MONTH, -5, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -5, GETDATE())) AS DATE)
DECLARE #4MonthAgo date = CAST(DATEADD(MONTH, -4, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -4, GETDATE())) AS DATE)
DECLARE #3MonthAgo date = CAST(DATEADD(MONTH, -3, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -3, GETDATE())) AS DATE)
DECLARE #2MonthAgo date = CAST(DATEADD(MONTH, -2, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -2, GETDATE())) AS DATE)
DECLARE #1MonthAgo date = CAST(DATEADD(MONTH, -1, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -1, GETDATE())) AS DATE)
DECLARE #CurrentMonth date = CAST(GETDATE() + 1 - DATEPART(DAY, GETDATE()) AS DATE)
-- Table to return grouped and sum data
DECLARE #StatsTable TABLE ([Month] DATE,
[Total Amount] DECIMAL(18,2),
[Total Count] INT
)
-- Temporary table to hold onto data batch - so table isn't used later on
DECLARE #TempGenTable TABLE ([Id] INT,
[Date] DATETIME,
[Lines] INT NULL,
[Amount] DECIMAL(18, 2) NULL
)
INSERT INTO #TempGenTable
SELECT
Id, Date, Lines, Amount
FROM
TallyTable
WHERE
Date >= #5MonthAgo
INSERT INTO #StatsTable
SELECT
#5MonthAgo,
COALESCE((SELECT SUM(Amount)
FROM #TempGenTable
WHERE Date >= #5MonthAgo AND Date < #4MonthAgo
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0),
COALESCE((SELECT COUNT(Id)
FROM #TempGenTable
WHERE Date >= #5MonthAgo AND Date < #4MonthAgo
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0)
UNION
SELECT
#4MonthAgo,
COALESCE((SELECT SUM(Amount)
FROM #TempGenTable
WHERE Date >= #4MonthAgo AND Date < #3MonthAgo
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0),
COALESCE((SELECT COUNT(Id)
FROM #TempGenTable
WHERE Date >= #4MonthAgo AND Date < #3MonthAgo
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0)
...
Is there an easier way to be able to get the above data with more flexibility in the number of months?
Is it better to just have the query pass in a month variable and it checks just the current month and have a loop within the controller to go back x number of months?
I would generate the data using a recursive CTE and then use left join:
with months as (
select datefromparts(year(getdate()), month(getdate()), 1) as month_start, 5 as n
union all
select dateadd(month, -1, month_start), n - 1
from months
where n > 0
)
select m.month_start, count(s.id), sum(s.amount)
from months m left join
#StatsTable s
on m.month_start = s.month
group by m.month_start
order by m.month_start;
You haven't provided sample data, so I'm not sure what s.month looks like. You might want the join condition to be:
on s.month >= m.month_start and s.month < dateadd(month, 1, m.month_start)
Below is a set-based method to generate the needed monthly periods:
--sample data
CREATE TABLE dbo.TallyTable (
Id int
, Date datetime
, Lines int
, Amount decimal(18, 2)
);
INSERT INTO dbo.TallyTable
VALUES
(1, '2017-12-05', 1, 50.00)
,(2, '2017-12-06', 1, 150.00)
,(3, '2018-01-10', 1, 100.00)
,(4, '2018-01-11', 1, 100.00)
,(5, '2018-01-12', 1, 100.00)
,(6, '2018-03-15', 1, 225.00)
,(7, '2018-03-15', 1, 25.00)
,(8, '2018-03-15', 1, 50.00)
,(9, '2018-04-20', 1, 100.00);
GO
DECLARE #Months int = 5; --number of historical months
WITH
t10 AS (SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t(n))
,t100 AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) - 1 AS num FROM t10 AS a CROSS JOIN t10 AS b)
, periods AS (SELECT
CONVERT(varchar(7), DATEADD(month, DATEDIFF(month, '', GETDATE()) - num, ''),121) AS Month
, DATEADD(month, DATEDIFF(month, '', CAST(GETDATE() AS date)) - num, '') AS PeriodStart
, DATEADD(month, DATEDIFF(month, '', CAST(GETDATE() AS date)) - num + 1, '') AS NextPeriodStart
FROM t100
WHERE num <= #Months
)
SELECT periods.Month, COALESCE(SUM(Amount), 0) AS TotalAmount, COALESCE(COUNT(ID), 0) AS TotalCount
FROM periods
LEFT JOIN dbo.TallyTable ON
TallyTable.Date >= PeriodStart
AND TallyTable.Date < NextPeriodStart
GROUP BY periods.Month
ORDER BY periods.Month;
we need to get week start and end dates for the month output shown as below:
Week# StartDate EndDate
Week 1 2017-03-01 2017-03-04
Week 2 2017-03-05 2017-03-11
Week 3 2017-03-12 2017-03-18
Week 4 2017-03-19 2017-03-25
Week 5 2017-03-26 2017-03-31
This should work:
declare #first_day_of_month date = '20170301'
declare #days_in_month int = datediff(day, #first_day_of_month, dateadd(month, 1, #first_day_of_month))
;with x as (
select datepart(week, dateadd(day, n-1, #first_day_of_month))+1 wk, dateadd(day, n-1, #first_day_of_month) dy
from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10), (11),(12),(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23),(24),(25),(26),(27),(28),(29),(30), (31)) as numbers(n)
where n <= #days_in_month
)
select wk - datepart(week, #first_day_of_month) as [Week#], min(dy) as StartDate, max(dy) as EndDate
from x
group by wk
order by wk
If you have Numbers table in the database, you can get rid of the values.
How about this:
Declare #StartDate Date = '2017-03-01'
;With Nums
AS
(
SELECT *
FROM (VALUES (0),(1),(2),(3),(4)) Nums(n)
),
StartDate
AS
(
SELECT #StartDate As StartDate, DatePart(dw,#StartDate) As DayWeekNumber
)
SELECT DATEADD(Week, n, CASE WHEN n = 0 THEN StartDate ELSE DATEADD(Day, DayWeekNumber * -1 + 1, StartDate) END) As StartDate,
CASE WHEN DATEPART(Month, DATEADD(Week, n+1, DATEADD(Day, DayWeekNumber * -1 , StartDate))) = DATEPART(Month, #StartDate)
THEN DATEADD(Week, n+1, DATEADD(Day, DayWeekNumber * -1 , StartDate))
ELSE EOMONTH(#StartDate) END As EndDate
FROM Nums
CROSS JOIN StartDate
Option 1: Non-UDF version
Declare #Date1 date = '2017-03-01'
Declare #Date2 date = '2017-03-31'
Select [Week#]
,StartDate = min(D)
,EndDate = max(D)
From (
Select *,[Week#] = concat('Week ',Dense_Rank() over (Partition By Year(D),Month(D) Order By DatePart(WK,D)))
From (Select Top (DateDiff(DD,#Date1,#Date2)+1) D=DateAdd(DD,-1+Row_Number() Over (Order By Number),#Date1) From master..spt_values ) A1
) A
Group By [Week#],Year(D),Month(D)
Order By 2
Option 2: TFV Version
I'll often use a TVF to create dynamic date/time ranges. A tally/calendar table would do the trick as well, but the UDF offers some dynamic options. You supply the Date Range, DatePart and Increment
Declare #Date1 date = '2017-03-01'
Declare #Date2 date = '2017-03-31'
Select [Week#]
,StartDate = min(RetVal)
,EndDate = max(RetVal)
From (
Select *,[Week#] = concat('Week ',Dense_Rank() over (Partition By Year(RetVal),Month(RetVal) Order By DatePart(WK,RetVal)))
From [dbo].[udf-Range-Date](#Date1,#Date2,'DD',1)
) A
Group By [Week#],Year(RetVal),Month(RetVal)
Order By 2
Both would Return
Week# StartDate EndDate
Week 1 2017-03-01 2017-03-04
Week 2 2017-03-05 2017-03-11
Week 3 2017-03-12 2017-03-18
Week 4 2017-03-19 2017-03-25
Week 5 2017-03-26 2017-03-31
The UDF if interested
CREATE FUNCTION [dbo].[udf-Range-Date] (#R1 datetime,#R2 datetime,#Part varchar(10),#Incr int)
Returns Table
Return (
with cte0(M) As (Select 1+Case #Part When 'YY' then DateDiff(YY,#R1,#R2)/#Incr When 'QQ' then DateDiff(QQ,#R1,#R2)/#Incr When 'MM' then DateDiff(MM,#R1,#R2)/#Incr When 'WK' then DateDiff(WK,#R1,#R2)/#Incr When 'DD' then DateDiff(DD,#R1,#R2)/#Incr When 'HH' then DateDiff(HH,#R1,#R2)/#Incr When 'MI' then DateDiff(MI,#R1,#R2)/#Incr When 'SS' then DateDiff(SS,#R1,#R2)/#Incr End),
cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (Select M from cte0) Row_Number() over (Order By (Select NULL)) From cte1 a, cte1 b, cte1 c, cte1 d, cte1 e, cte1 f, cte1 g, cte1 h ),
cte3(N,D) As (Select 0,#R1 Union All Select N,Case #Part When 'YY' then DateAdd(YY, N*#Incr, #R1) When 'QQ' then DateAdd(QQ, N*#Incr, #R1) When 'MM' then DateAdd(MM, N*#Incr, #R1) When 'WK' then DateAdd(WK, N*#Incr, #R1) When 'DD' then DateAdd(DD, N*#Incr, #R1) When 'HH' then DateAdd(HH, N*#Incr, #R1) When 'MI' then DateAdd(MI, N*#Incr, #R1) When 'SS' then DateAdd(SS, N*#Incr, #R1) End From cte2 )
Select RetSeq = N+1
,RetVal = D
From cte3,cte0
Where D<=#R2
)
/*
Max 100 million observations -- Date Parts YY QQ MM WK DD HH MI SS
Syntax:
Select * from [dbo].[udf-Range-Date]('2016-10-01','2020-10-01','YY',1)
Select * from [dbo].[udf-Range-Date]('2016-01-01','2017-01-01','MM',1)
*/
For a one-off use, this would work:
rextester: http://rextester.com/WCMZRW61517
declare #FromDate date = '20170301';
with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, d as (
select DateValue=convert(date,dateadd(day
, row_number() over (order by (select 1)) -1, #FromDate))
from n as deka
cross join n as hecto
)
select
[Week#] = dense_rank() over (
partition by dateadd(month, datediff(month, 0, DateValue) , 0)
order by convert(tinyint,datepart(week,DateValue))
)
, StartDate = min(DateValue)
, EndDate = max(DateValue)
from d
where dateadd(month, datediff(month, 0, DateValue) , 0) = #FromDate
group by
dateadd(month, datediff(month, 0, DateValue) , 0)
, convert(tinyint,datepart(week,DateValue))
If you want to have a calendar table you can reference as needed, something like this would work:
if object_id('dbo.Calendar') is not null drop table dbo.Calendar;
create table dbo.Calendar (
[Date] date not null
, [Year] smallint not null
, [Month] tinyint not null
, MonthStart date not null
, MonthEnd date not null
, [Week] tinyint not null
, MonthWeek tinyint not null
, MonthWeekStart date not null
, MonthWeekEnd date not null
, constraint pk_Calendar primary key clustered (date)
);
declare #FromDate date = '20170101';
declare #ThruDate date = '20171231';
with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, d as (
select DateValue=convert(date,dateadd(day
, row_number() over (order by (select 1)) -1, #fromdate))
from n as deka
cross join n as hecto
cross join n as kilo /* 2.73 years */
cross join n as [tenK] /* 27.3 years */
--cross join n as [hundredk] /* 273 years */
)
insert into dbo.Calendar
([Date], [Year], [Month],MonthStart, MonthEnd, [Week]
, MonthWeek, MonthWeekStart, MonthWeekEnd)
select top (datediff(day, #FromDate, #ThruDate)+1)
[Date] = DateValue
, [Year] = convert(smallint,datepart(year,DateValue))
, [Month] = convert(tinyint,datepart(month,DateValue))
, MonthStart = dateadd(month, datediff(month, 0, DateValue) , 0)
, MonthEnd = convert(date,dateadd(day,-1
,dateadd(Month,datediff(Month,0,DateValue) +1,0) ) )
, [Week] = convert(tinyint,datepart(week,DateValue))
, MonthWeek = dense_rank() over (
partition by dateadd(month, datediff(month, 0, DateValue) , 0)
order by convert(tinyint,datepart(week,DateValue))
)
, MonthWeekStart = min(DateValue) over (
partition by dateadd(month, datediff(month, 0, DateValue) , 0)
, convert(tinyint,datepart(week,DateValue))
)
, MonthWeekEnd = max(DateValue) over (
partition by dateadd(month, datediff(month, 0, DateValue) , 0)
, convert(tinyint,datepart(week,DateValue))
)
from d
order by DateValue;
select distinct
MonthWeek
, MonthWeekStart
, MonthWeekEnd
from dbo.Calendar
where MonthStart = '20170301'
Calendar and Numbers Tables
Generate a set or sequence without loops - Aaron Bertrand
Creating a Date Table/Dimension in SQL Server 2008 - David Stein
Calendar Tables - Why You Need One - David Stein
Creating a date dimension or calendar table in SQL Server - Aaron Bertrand
TSQL Function to Determine Holidays in SQL Server - Tim Cullen
F_TABLE_DATE - Michael Valentine Jones
I'm wondering if someone might be able to troubleshoot my query. I have a simple table that has project savings per month. There are always 12 consecutive months worth or savings, but the first month can vary (e.g.: start from January for 12 months, start from March for 12 months, etc).
I need a report that gets me all savings (by month) for a given year. This means that for some project savings, if the start month is not January, then some of that project savings will fall in a different report year.
So I need a query that will return all months for the current report year, and have zero haves for where a project doesn't have saving values for that month.
I have some projects starting in July, and I'm only getting back those 6 months with their value. That is, the left join back to the date WITH is not outer joining properly. Can someone tell me where I'm going wrong please?
See code below:
DECLARE #MonthEndSnapshot SMALLDATETIME;
SELECT #MonthEndSnapshot = getdate()
DECLARE #StartDate SMALLDATETIME, #EndDate SMALLDATETIME;
SELECT #StartDate = FORMAT(#MonthEndSnapshot, 'yyyy') + '0101', #EndDate = FORMAT(#MonthEndSnapshot, 'yyyy') + '1231';
;WITH d(d) AS
(
SELECT
DATEADD(MONTH, n, DATEADD(MONTH, DATEDIFF(MONTH, 0, #StartDate), 0))
FROM
(SELECT TOP
(DATEDIFF(MONTH, #StartDate, #EndDate) + 1)
n = ROW_NUMBER() OVER (ORDER BY [object_id]) - 1
FROM
sys.all_objects
ORDER BY [object_id]) AS n
)
select
left(datename(month, d.d), 3) as xAxisValueMon,
datepart(mm, d.d) as xAxisValue,
a.ProjectId as ProjectId,
ISNULL(SUM(a.Saving), 0) as yAxisValue
from
d
LEFT OUTER JOIN
(SELECT
mes.ProjectId, mes.Saving, mes.SavingMonth
FROM
dbo.sf_SnapshotMonthEndSaving() mes) AS a ON d.d = a.SavingMonth
group by
a.ProjectId, datename(month, d.d), datepart(mm, d.d)
order by
a.ProjectId, datepart(mm, d.d)
The WITH d(d) part works, and returns 12 month dates (1st month from Jan to Dec).
I also tried the following structure as the query:
;WITH d(d) AS
(
SELECT DATEADD(MONTH, n, DATEADD(MONTH, DATEDIFF(MONTH, 0, #StartDate), 0))
FROM ( SELECT TOP (DATEDIFF(MONTH, #StartDate, #EndDate) + 1)
n = ROW_NUMBER() OVER (ORDER BY [object_id]) - 1
FROM sys.all_objects ORDER BY [object_id] ) AS n
)
select left(datename(month, d.d), 3) as xAxisValueMon,
datepart(mm, d.d) as xAxisValue,
mes.ProjectId as ProjectId,
ISNULL(SUM(mes.Saving), 0) as yAxisValue
from d LEFT OUTER JOIN
dbo.sf_SnapshotMonthEndSaving() mes
ON d.d = mes.SavingMonth
group by mes.ProjectId, datename(month, d.d), datepart(mm, d.d)
order by mes.ProjectId, datepart(mm, d.d)
But same results. The MonthEndSaving table is as follows:
CREATE TABLE [dbo].[MonthEndSaving]
(
[MonthEndSavingId] [int] IDENTITY(1,1) NOT NULL,
[MonthEndSnapshot] [datetime] NOT NULL,
[ProjectId] [int] NOT NULL,
[SavingMonth] [datetime] NOT NULL,
[Saving] [money] NOT NULL,
[DateCreated] [datetime] NOT NULL,
PRIMARY KEY CLUSTERED (MonthEndSavingId)
)
GO
ALTER TABLE MonthEndSaving
ADD CONSTRAINT [ProjectMonthEndSaving]
FOREIGN KEY (ProjectId) REFERENCES [dbo].[Project](ProjectId)
GO
Dang, Laughing Vergil seems to be a faster typist =)
Anyway, the idea is pretty much the same. Your 'error' was that you join each month to ALL the projects in dbo.sf_SnapshotMonthEndSaving(). If one fits, it gets returned for that one only, if two fit, it will show those two etc... but it will NOT repeat for EVERY project. This should.
DECLARE #StartDate datetime = '1 jan 2016',
#EndDate datetime = '1 dec 2016'
;WITH d(FirstDayOfMonth) AS
(
SELECT DATEADD(MONTH, n, DATEADD(MONTH, DATEDIFF(MONTH, 0, #StartDate), 0))
FROM ( SELECT TOP (DATEDIFF(MONTH, #StartDate, #EndDate) + 1)
n = ROW_NUMBER() OVER (ORDER BY [object_id]) - 1
FROM sys.all_objects ORDER BY [object_id] ) AS n
),
RelevantProjects AS
(
SELECT DISTINCT ProjectId
FROM dbo.sf_SnapshotMonthEndSaving() mes
WHERE mes.SavingMonth BETWEEN #StartDate AND #EndDate -- you could also join to d but I think this is faster
),
ProjectsAndDates AS
(
SELECT ProjectID,
FirstDayOfMonth
FROM d
CROSS JOIN RelevantProjects
)
select left(datename(month, d.FirstDayOfMonth), 3) as xAxisValueMon,
datepart(mm, d.FirstDayOfMonth) as xAxisValue,
d.ProjectId as ProjectId,
ISNULL(SUM(mes.Saving), 0) as yAxisValue
from ProjectsAndDates d
LEFT OUTER JOIN [MonthEndSaving] mes -- dbo.sf_SnapshotMonthEndSaving() mes
ON mes.SavingMonth = d.FirstDayOfMonth
AND mes.Project_id = d.ProjectID
group by d.ProjectId, datename(month, d.FirstDayOfMonth), datepart(mm, d.FirstDayOfMonth)
order by d.ProjectId, datepart(mm, d.FirstDayOfMonth)
This code should do what you need:
DECLARE #MonthEndSnapshot SMALLDATETIME;
SELECT #MonthEndSnapshot = getdate()
DECLARE #StartDate SMALLDATETIME, #EndDate SMALLDATETIME;
SELECT #StartDate = FORMAT(#MonthEndSnapshot, 'yyyy') + '0101', #EndDate = FORMAT(#MonthEndSnapshot, 'yyyy') + '1231';
;WITH d(d) AS
(
SELECT DATEADD(MONTH, n, DATEADD(MONTH, DATEDIFF(MONTH, 0, #StartDate), 0))
FROM ( SELECT TOP (DATEDIFF(MONTH, #StartDate, #EndDate) + 1)
n = ROW_NUMBER() OVER (ORDER BY [object_id]) - 1
FROM sys.all_objects ORDER BY [object_id] ) AS n
)
select left(datename(month, d.d), 3) as xAxisValueMon,
datepart(mm, d.d) as xAxisValue,
prj.ProjectId as ProjectId,
ISNULL(SUM(a.Saving), 0) as yAxisValue
from d
CROSS JOIN
(
SELECT DISTINCT mes.ProjectId
FROM dbo.sf_SnapshotMonthEndSaving() mes
) as prj
LEFT OUTER JOIN
(
SELECT mes.ProjectId, mes.Saving, mes.SavingMonth
FROM dbo.sf_SnapshotMonthEndSaving() mes
) as a
ON d.d = a.SavingMonth
AND prj.ProjectID = a.ProjectID
group by prj.ProjectId, datename(month, d.d), datepart(mm, d.d)
order by prj.ProjectId, datepart(mm, d.d)
I'm trying to make a report that generates a calendar for each month where the user can specify a range of months and years. My matrix has a column grouping on Weekday and the row grouping is on WeekNumber (see query results below).
The issue I'm stuck on is that the day is starting in the wrong place as shown below:
This is because my query only takes in the days from that particular month and not the few days from the month before and after that would normally be grayed out on a calendar:
Although I suspect this problem may also be solvable in the matrix itself I'm not sure if solving it in the query or solving it in the matrix would be better than the other.
My current query:
SELECT t1.[TimeSheetLineID] ,
t1.[TimeSheetID] ,
t1.[JobID] ,
t1.[TimeSheetDate] ,
t1.[TimeSheetCreatedOn] ,
t1.[TimeSheetLastModifiedOn] ,
t1.[StartTime] ,
t1.[EndTime] ,
t1.[TotalTime] ,
t1.[EmployeeName] ,
t2.DateFull ,
t2.[FullYear] ,
t2.[WeekNumber] ,
t2.[WeekDay] ,
t2.[WeekDayName] ,
t2.[MonthDay] ,
t2.[MonthName] ,
t2.[MonthNumber]
FROM dbo.DateLookup t2
LEFT JOIN [dbo].[FactTimeSheets] t1
ON t1.TimeSheetDate = t2.DateFull
AND (t1.JobID = #jobNumber)
WHERE (t2.FullYear = #year)
AND (t2.MonthNumber BETWEEN #startMonth AND #endMonth)
ORDER BY FullYear, MonthNumber, MonthDay, WeekDay
Current query result (notice WeekDay starts with 2 for the month of Aug):
Based off this article.
--UPDATE--
I'm using the CTE in my query below to get the extra dates I need to fill out this calendar. I tested it on the Date table and will update my answer tomorrow with the final query:
DECLARE #year INT ,
#startMonth DATE ,
#endMonth DATE;
SET #year = '2016';
SET #startMonth = '2016-08-01';
SET #endMonth = '2016-08-31';
DECLARE #StartDate DATE ,
#EndDate DATE;
SET #StartDate = DATEADD(s, 0, DATEADD(mm, DATEDIFF(m, 0, #startMonth), 0));
SET #StartDate = DATEADD(DAY, -DATEPART(WEEKDAY, #StartDate) + 1, #StartDate);
--SELECT #StartDate
SET #EndDate = DATEADD(s, -1, DATEADD(mm, DATEDIFF(m, 0, #startMonth) + 1, 0));
SET #EndDate = DATEADD(DAY, 7 - DATEPART(WEEKDAY, #EndDate), #EndDate)
--SELECT #EndDate;
;
WITH Dates ( [Date] )
AS (
--Select First day in range
SELECT CONVERT(DATETIME, #StartDate) AS [Date]
UNION ALL
--Add a record for every day in the range
SELECT DATEADD(DAY, 1, [Date])
FROM Dates
WHERE Date < CONVERT(DATETIME, #EndDate)
),
Events
AS ( SELECT [FullYear] ,
[DateFull] ,
[WeekNumber] ,
[WeekDay] ,
[WeekDayName] ,
[MonthDay] ,
[MonthName] ,
[MonthNumber]
FROM [dbo].[DateLookup]
)
SELECT e.[FullYear] ,
e.[DateFull] ,
d.[Date] ,
e.[WeekNumber] ,
e.[WeekDay] ,
e.[WeekDayName] ,
e.[MonthDay] ,
e.[MonthName] ,
e.[MonthNumber]
FROM Dates d
LEFT JOIN Events e ON d.[Date] = CAST(e.DateFull AS DATE)
GROUP BY FullYear ,
MonthNumber ,
DateFull ,
d.[Date] ,
WeekNumber ,
MonthName ,
WeekDayName ,
MonthDay ,
WeekDay
ORDER BY Date;
--FINAL UPDATED QUERY--
I hope this helps someone in the future.
SET #startDate = DATEADD(mm, DATEDIFF(m, 0, CAST(CAST(#year AS varchar) + '-' + CAST(#startMonth AS varchar) + '-01' AS DATETIME)), 0);
SET #startDate = DATEADD(DAY, -DATEPART(WEEKDAY, #startDate) + 1, #startDate);
SET #endDate = DATEADD(mm, DATEDIFF(m, 0, CAST(CAST(#year AS varchar) + '-' + CAST(#endMonth AS varchar) + '-01' AS DATETIME)) + 1, 0);
SET #endDate = DATEADD(DAY, 7 - DATEPART(WEEKDAY, #endDate), #endDate);
WITH Dates ( [Date] )
AS ( SELECT CONVERT(DATETIME, #startDate) AS [Date]
UNION ALL
SELECT DATEADD(DAY, 1, [Date])
FROM Dates
WHERE Date < CONVERT(DATETIME, #endDate)
),
Events
AS ( SELECT t1.[TimeSheetLineID] ,
t1.[TimeSheetID] ,
t1.[JobID] ,
t1.[TimeSheetDate] ,
t1.[TimeSheetCreatedOn] ,
t1.[TimeSheetLastModifiedOn] ,
t1.[StartTime] ,
t1.[EndTime] ,
t1.[TotalTime] ,
t1.[EmployeeName] ,
t1.[CostCategory] ,
t2.[DateFull] ,
t2.[FullYear] ,
t2.[WeekNumber] ,
t2.[WeekDay] ,
t2.[WeekDayName] ,
t2.[MonthDay] ,
t2.[MonthName] ,
t2.[MonthNumber]
FROM dbo.DateLookup t2
LEFT JOIN [dbo].[FactTimeSheets] t1 ON t1.TimeSheetDate = t2.DateFull
AND ( t1.JobID = #jobNumber )
WHERE ( t2.FullYear = #year )
)
SELECT e.[TimeSheetLineID] ,
e.[TimeSheetID] ,
e.[JobID] ,
e.[TimeSheetDate] ,
e.[TimeSheetCreatedOn] ,
e.[TimeSheetLastModifiedOn] ,
e.[StartTime] ,
e.[EndTime] ,
e.[TotalTime] ,
e.[EmployeeName] ,
e.[CostCategory] ,
e.[DateFull] ,
d.[Date] ,
e.[FullYear] ,
e.[WeekNumber] ,
e.[WeekDay] ,
e.[WeekDayName] ,
e.[MonthDay] ,
e.[MonthName] ,
e.[MonthNumber]
FROM Dates d
LEFT JOIN Events e ON d.[Date] = CAST(e.DateFull AS DATE)
ORDER BY Date;
You can group the dates in a matrix to get the layout right.
Insert a matrix. Group and sort the columns by weekday (number). Group and sort the rows by week number. It should look like this:
Now when you run it it will come out like this:
Final answer:
SET #startDate = DATEADD(mm, DATEDIFF(m, 0, CAST(CAST(#year AS varchar) + '-' + CAST(#startMonth AS varchar) + '-01' AS DATETIME)), 0);
SET #startDate = DATEADD(DAY, -DATEPART(WEEKDAY, #startDate) + 1, #startDate);
SET #endDate = DATEADD(mm, DATEDIFF(m, 0, CAST(CAST(#year AS varchar) + '-' + CAST(#endMonth AS varchar) + '-01' AS DATETIME)) + 1, 0);
SET #endDate = DATEADD(DAY, 7 - DATEPART(WEEKDAY, #endDate), #endDate);
WITH Dates ( [Date] )
AS ( SELECT CONVERT(DATETIME, #startDate) AS [Date]
UNION ALL
SELECT DATEADD(DAY, 1, [Date])
FROM Dates
WHERE Date < CONVERT(DATETIME, #endDate)
),
Events
AS ( SELECT t1.[TimeSheetLineID] ,
t1.[TimeSheetID] ,
t1.[JobID] ,
t1.[TimeSheetDate] ,
t1.[TimeSheetCreatedOn] ,
t1.[TimeSheetLastModifiedOn] ,
t1.[StartTime] ,
t1.[EndTime] ,
t1.[TotalTime] ,
t1.[EmployeeName] ,
t1.[CostCategory] ,
t2.[DateFull] ,
t2.[FullYear] ,
t2.[WeekNumber] ,
t2.[WeekDay] ,
t2.[WeekDayName] ,
t2.[MonthDay] ,
t2.[MonthName] ,
t2.[MonthNumber]
FROM dbo.DateLookup t2
LEFT JOIN [dbo].[FactTimeSheets] t1 ON t1.TimeSheetDate = t2.DateFull
AND ( t1.JobID = #jobNumber )
WHERE ( t2.FullYear = #year )
)
SELECT e.[TimeSheetLineID] ,
e.[TimeSheetID] ,
e.[JobID] ,
e.[TimeSheetDate] ,
e.[TimeSheetCreatedOn] ,
e.[TimeSheetLastModifiedOn] ,
e.[StartTime] ,
e.[EndTime] ,
e.[TotalTime] ,
e.[EmployeeName] ,
e.[CostCategory] ,
e.[DateFull] ,
d.[Date] ,
e.[FullYear] ,
e.[WeekNumber] ,
e.[WeekDay] ,
e.[WeekDayName] ,
e.[MonthDay] ,
e.[MonthName] ,
e.[MonthNumber]
FROM Dates d
LEFT JOIN Events e ON d.[Date] = CAST(e.DateFull AS DATE)
ORDER BY Date;
August 1st is Monday so the weekday should be 1. But it's 2 in your table
I am almost a newbie to writing SQL queries.
In the context of SQL Server, how to get list of 2nd and 4th Saturday dates
in the year 2016?
Done as a derived table simply to show the logic but you can reduce if you prefer:
select *
from (
select d2016,
datename( weekday, d2016 ) as wkdy,
row_number( ) over ( partition by datepart( month, d2016 ), datename( weekday, d2016 ) order by d2016 ) as rn_dy_mth
from (
select dateadd( day, rn, cast( '2016-01-01' as date ) ) as d2016
from (
select row_number() over( order by object_id ) - 1 as rn
from sys.columns
) as rn
) as dy
) as dy_mth
where rn_dy_mth in ( 2, 4 )
and wkdy = 'Saturday'
order by d2016
--DEFINE LIMITS FOR DAY
DECLARE #TODATE DATETIME, #FROMDATE DATETIME
SET #FROMDATE ='2010-01-01'
SET #TODATE = '2017-12-31'
;WITH DATESEQUENCE( [DATE] ) AS
(
SELECT #FROMDATE AS [DATE]
UNION ALL
SELECT DATEADD(DAY, 1, [DATE])
FROM DATESEQUENCE
WHERE DATE < #TODATE
)
, DATESATURDAY AS
(SELECT CAST(CAST(YEAR([DATE]) AS VARCHAR)+
(CASE WHEN DATEPART(M,[DATE])<=9 THEN '0'+CAST(DATEPART(M,[DATE]) AS VARCHAR)
ELSE CAST(DATEPART(M,[DATE]) AS VARCHAR) END ) AS NUMERIC) AS MONTH_ID
,CONVERT(VARCHAR,[DATE],106) AS DAY_DESC
,UPPER(DATENAME(DW,[DATE]))AS DAY_NAME
FROM DATESEQUENCE )
,SECOND_FOURTH_SATURDAY AS
(SELECT *
,ROW_NUMBER() OVER (PARTITION BY MONTH_ID ORDER BY DAY_NAME) FALL_IN
FROM DATESATURDAY
WHERE DAY_NAME='SATURDAY')
SELECT * FROM SECOND_FOURTH_SATURDAY
WHERE FALL_IN IN(2,4)
OPTION (MAXRECURSION 10000)
You can get any Saturday of a month using the Following Query in SQL.
Here I'm Getting on Current Date, You can set your own selected date to get a Specific month Saturday
select DATEADD(dd, (14 - ##DATEFIRST - DATEPART(dw, DATEADD(MONTH, DATEDIFF(mm, 0,getdate()), 0))) % 7, DATEADD(MONTH, DATEDIFF(mm, 0, getdate()), 0)) as FirstSaturday,
DATEADD(dd,7,DATEADD(dd, (14 - ##DATEFIRST - DATEPART(dw, DATEADD(MONTH, DATEDIFF(mm, 0, getdate()), 0))) % 7, DATEADD(MONTH, DATEDIFF(mm, 0, getdate()), 0))) as SecondSaturday,
DATEADD(dd,14,DATEADD(dd, (14 - ##DATEFIRST - DATEPART(dw, DATEADD(MONTH, DATEDIFF(mm, 0, getdate()), 0))) % 7, DATEADD(MONTH, DATEDIFF(mm, 0, getdate()), 0))) as ThirdSaturday,
DATEADD(dd,21,DATEADD(dd, (14 - ##DATEFIRST - DATEPART(dw, DATEADD(MONTH, DATEDIFF(mm, 0, getdate()), 0))) % 7, DATEADD(MONTH, DATEDIFF(mm, 0, getdate()), 0))) as LastSaturday