Calculate Date difference between multiple rows SQL - sql

I need to calculate the date difference between multiple rows. The scenario is I have a vehicle that can do inspections throughout the month as well as when the vehicle is assigned to a different project. I want to calculate that how many days that a vehicle is assigned to the project per month or previous month. I have tried multiple ways and I can't get even closer. I am relatively new to stack overflow. Apologies if anything is missing. Please let me know if this can be done. Thank you.
All the columns are in one single table if that helps. Please let me know the query on how to achieve this
I am using SQL server 2017.
Original Data
Expected Output

I am not proud of this solution, but I think it works for you. My approach was to create a table of days and then look at which project the vehicle was assigned to each day. Finally, aggregate by month and year to get the results. I had to do this as a script since you can use aggregate functions in the definitions of recursive CTEs, but you may find a way to do this without needing a recursive CTE.
I created a table variable to import your data so I could write this. Note, I added an extra assignment to test assignments that spanned months.
DECLARE #Vehicles AS TABLE
(
[VehicleID] INT NOT NULL,
[Project] CHAR(2) NOT NULL,
[InspectionDate] DATE NOT NULL
);
INSERT INTO #Vehicles
(
[VehicleID],
[Project],
[InspectionDate]
)
VALUES
(1, 'P1', '2021-08-20'),
(1, 'P1', '2021-09-05'),
(1, 'P2', '2021-09-15'),
(1, 'P3', '2021-09-20'),
(1, 'P2', '2021-10-10'),
(1, 'P1', '2021-10-20'),
(1, 'P3', '2021-10-21'),
(1, 'P2', '2021-10-22'),
(1, 'P4', '2021-11-15'),
(1, 'P4', '2021-11-25'),
(1, 'P4', '2021-11-30'),
(1, 'P1', '2022-02-05');
DECLARE #StartDate AS DATE, #EndDate AS DATE;
SELECT #StartDate = MIN([InspectionDate]), #EndDate = MAX([InspectionDate])
FROM #Vehicles;
;WITH [seq]([n])
AS (SELECT 0 AS [n]
UNION ALL
SELECT [n] + 1
FROM [seq]
WHERE [n] < DATEDIFF(DAY, #StartDate, #EndDate)),
[days]
AS (SELECT DATEADD(DAY, [n], #StartDate) AS [d]
FROM [seq]),
[inspections]
AS (SELECT [VehicleID],
[Project],
[InspectionDate],
LEAD([InspectionDate], 1) OVER (PARTITION BY [VehicleID]
ORDER BY [InspectionDate]
) AS [NextInspectionDate]
FROM #Vehicles),
[assignmentsByDay]
AS (SELECT [d].[d], [i].[VehicleID], [i].[Project]
FROM [days] AS [d]
INNER JOIN [inspections] AS [i]
ON [d].[d] >= [i].[InspectionDate]
AND [d] < [i].[NextInspectionDate])
SELECT [assignmentsByDay].[VehicleID],
[assignmentsByDay].[Project],
MONTH([assignmentsByDay].[d]) AS [month],
YEAR([assignmentsByDay].[d]) AS [year],
COUNT(*) AS [daysAssigned]
FROM [assignmentsByDay]
GROUP BY [assignmentsByDay].[VehicleID],
[assignmentsByDay].[Project],
MONTH([assignmentsByDay].[d]),
YEAR([assignmentsByDay].[d])
ORDER BY [year], [month], [assignmentsByDay].[VehicleID], [assignmentsByDay].[Project]
OPTION(MAXRECURSION 0);
And the output is:
VehicleID
Project
month
year
daysAssigned
1
P1
8
2021
12
1
P1
9
2021
14
1
P2
9
2021
5
1
P3
9
2021
11
1
P1
10
2021
1
1
P2
10
2021
20
1
P3
10
2021
10
1
P2
11
2021
14
1
P4
11
2021
16
1
P4
12
2021
31
1
P4
1
2022
31
1
P4
2
2022
4

I think you are looking for this:
select vehicleId
, Project
, month(inspectiondate) month
, year(inspectiondate) year
, datediff(day , min(inspectiondate), case when max(inspectiondate) = min(inspectiondate) then eomonth(min(inspectiondate)) else max(inspectiondate) end) days
from Vehicles
group by vehicleId, Project , month(inspectiondate), year(inspectiondate)
This query in for each month/year for each specific vehicle in a project in that month/year , you get the max and min inspection date and calculate the difference.
db<>fiddle here

Related

Count records using Date filter related tables SQL Server

I'm trying to calculates the number of reports (report_user table) per user between two date (Calendar table) and by day worked (agenda_user table).
Here is the diagram of my tables:
Calendar table :
DATE Year Month
---------------------------------
2020-01-01 2020 1
2020-01-02 2020 1
2020-01-03 2020 1
2020-01-04 2020 1
AGENDA_USER table :
ID_USER DATE Value
---------------------------------
1 2020-01-01 1
2 2020-01-01 1
1 2020-01-02 0
2 2020-01-02 1
User table :
ID_USER Name
-------------------------
1 Jack
2 Robert
Report_Result table :
ID_USER Date Result
-----------------------------------
1 2020-01-01 good
1 2020-01-01 good
2 2020-01-01 bad
2 2020-01-01 good
2 2020-01-02 good
2 2020-01-02 good
Result I'm trying to find with an SQL query
ID_USER Date Number of report Day work report/work
---------------------------------------------------------------------------
1 2020-01-01 2 1 2/1 = 2
2 2020-01-01 2 1 1
1 2020-01-02 0 0 0
2 2020-01-02 2 1 2
SELECT
REPORT_USER.ID_USER,
COUNT(ID_USER) AS result
FROM [DB].[dbo].REPORT_USER AS report,
JOIN [DB].[dbo].[USER] AS [USER]
ON [USER].ID_USER = report.ID_USER
JOIN [DB].[dbo].AGENDA_USER AS agenda
ON agenda.ID_USER = report.ID_USER
WHERE CAST(agenda.[Date] AS DATE) >= '2020-09-01'
AND CAST(agenda.[Date] AS DATE) <= '2021-07-28'
AND [USER].ID_user = 1167
GROUP BY
report.ID_VENDEUR;
I'm not entirely sure I understand your problem, but I think I'm reasonably close so here is a start, point out my invalid assumptions and we can refine. More data, particularly in Agenda and Reports would really help. An explanation is below (plus see the comment in the code).
The overall flow is to generate a list of days/people you want to report on (cteUserDays), generate a list of how many reports each user generated on each date (cteReps), generate a list of who worked on what days (cteWork), and then JOIN all 3 parts together using a LEFT OUTER JOIN so the report covers all workers on all days.
EDIT: Add cteRepRaw where DATETIME is converted to DATE and "bad" reports are filtered out. Grouping and counting still happens in cteReps, but joining to cteUserDays is not there because it was adding 1 to count if there was a NULL.
DECLARE #Cal TABLE (CalDate DATETIME, CalYear int, CalMonth int)
DECLARE #Agenda TABLE (UserID int, CalDate DATE, AgendaVal int)
DECLARE #User TABLE (UserID int, UserName nvarchar(50))
DECLARE #Reps TABLE (UserID int, CalDate DATETIME, RepResult nvarchar(50))
INSERT INTO #Cal(CalDate, CalYear, CalMonth)
VALUES ('2020-01-01', 2020, 1), ('2020-01-02', 2020, 1), ('2020-01-03', 2020, 1), ('2020-01-04', 2020, 1)
INSERT INTO #Agenda(UserID, CalDate, AgendaVal)
VALUES (1, '2020-01-01', 1), (2, '2020-01-01', 1), (1, '2020-01-02', 0), (2, '2020-01-02', 1)
INSERT INTO #User (UserID , UserName )
VALUES (1, 'Jack'), (2, 'Robert')
INSERT INTO #Reps (UserID , CalDate , RepResult )
VALUES (1, '2020-01-01', 'good'), (1, '2020-01-01', 'good')
, (2, '2020-01-01', 'bad'), (2, '2020-01-01', 'good')
, (2, '2020-01-02', 'good'), (2, '2020-01-02', 'good')
; with cteUserDays as (
--First, you want zeros in your table where no reports are, so build a table for that
SELECT CONVERT(DATE, D.CalDate) as CalDate --EDIT add CONVERT here
, U.UserID FROM #Cal as D CROSS JOIN #User as U
WHERE D.CalDate >= '2020-01-01' AND D.CalDate <= '2021-07-28'
--EDIT Watch the <= date here, a DATE is < DATETIME with hours of the same day
), cteRepRaw as (--EDIT Add this CTE to convert DATETIME to DATE so we can group on it
--Map the DateTime to a DATE type so we can group reports from any time of day
SELECT R.UserID
, CONVERT(DATE, R.CalDate) as CalDate --EDIT add CONVERT here
, R.RepResult
FROM #Reps as R
WHERE R.RepResult='good' --EDIT Add this test to only count good ones
), cteReps as (
--Get the sum of all reports for a given user on a given day, though some might be missing (fill 0)
SELECT R.UserID , R.CalDate , COUNT(*) as Reports --SUM(COALESCE(R.RepResult, 0)) as Reports
FROM cteRepRaw as R--cteUserDays as D
--Some days may have no reports for that worker, so use a LEFT OUTER JOIN
--LEFT OUTER JOIN cteRepRaw as R on D.CalDate = R.CalDate AND D.UserID = R.UserID
GROUP BY R.UserID , R.CalDate
) , cteWork as (
--Unclear what values "value" in Agenda can take, but assuming it's some kind of work
-- unit, like "hours worked" or "shifts" so add them up
SELECT A.UserID , A.CalDate, SUM(A.AgendaVal) as DayWork FROM #Agenda as A
WHERE A.CalDate >= '2020-01-01' AND A.CalDate <= '2021-07-28'
GROUP BY A.CalDate, A.UserID
)
SELECT D.UserID , D.CalDate, COALESCE(R.Reports, 0) as Reports, W.DayWork
--NOTE: While it's probably a mistake to credit a report to a day a worker had
--no shifts, it could happen and would throw an error so check
, CASE WHEN W.DayWork > 0 THEN R.Reports / W.DayWork ELSE 0 END as RepPerWork
FROM cteUserDays as D
LEFT OUTER JOIN cteReps as R on D.CalDate=R.CalDate AND R.UserID = D.UserID
LEFT OUTER JOIN cteWork as W on D.UserID = W.UserID AND D.CalDate = W.CalDate
ORDER BY CalDate , UserID
First, as per the comments in your OP "Agenda" represents when the user is working, you don't say how it's structured so I'll assume it can have multiple entries for a given person on a given day (i.e. a 4 hour shift and an 8 hour shift) so I'll add them up to get total work (cteWork). I also assume that if somebody didn't work, you can't have a report for them. I check for this, but normally I'd expect your data validator to pre-screen those out.
Second, I'll assume reports are 1 per record, and a given user can have multiple per day. You have that in your given, but it's important to this solution so I'm restating in case somebody else reads this later.
Third, I assume you want all days reported for all users, I assure this by generating a CROSS join between users and days (cteUserDays)

Returning Records by Week

I'm trying to come up with a way to return a result set from the below data without a loop that shows the number of records by Team for a particular date range by week.
I've got a Date table (https://www.mssqltips.com/sqlservertip/4054/creating-a-date-dimension-or-calendar-table-in-sql-server/) that has every day/week/year referenced, but not sure how to connect it up.
Create Table #Team
(
TeamID int,
TeamName varchar(20)
)
insert into #Team
(
TeamID,
TeamName
)
select
1,
'Team 1'
union all
select
2,
'Team 2'
union all
select
3,
'Team 3'
union all
select
4,
'Team 4'
Create Table #Entries
(
EntryID int,
DateCreated datetime,
TeamID int
)
insert into #Entries
(
EntryID,
DateCreated,
TeamID
)
select
1,
'2 Nov 2020',
1
union all
select
2,
'4 Nov 2020',
2
I've got this query:
select
T.TeamName,
WeekOfYear,
WeekOfMonth,
count(*) as Count(*)
from
#Team T
Left Join #Entries E on
T.TeamID = E.TeamID
Inner Join DimDate D on
cast(E.DateCreated as date) = D.[Date]
group by
T.TeamName,
WeekOfYear,
WeekOfMonth
Where it fails is:
It doesn't include the teams with 0 results
I need to be able to show results for multiple weeks through a date range. In the above example, they would be 0.
I think the trick is to first generate all the rows you need, then LEFT JOIN those onto their results to get what you want.
Note that in your query, you are pulling out WeekOfYear and WeekOfMonth, but you probably also want to pull out Year in case the data crosses years or goes for multiple years.
For the date range
I have two variables #RangeStart and #RangeEnd- both dates - to do filtering
I create a table (probably incorrect) to model the date dimension table
CREATE TABLE #DimDate ([Date] date, WeekOfYear int, WeekOfMonth int, y_year int)
INSERT INTO #DimDate ([Date], WeekOfYear, WeekOfMonth, y_year) VALUES
('20201029', 35, 4, 2020),
('20201030', 35, 4, 2020),
('20201031', 35, 4, 2020),
('20201101', 36, 1, 2020),
('20201102', 36, 1, 2020),
('20201103', 36, 1, 2020),
('20201104', 36, 1, 2020);
-- Note that I called the year 'y_year' - will need to be changed
-- to your value (or converted to YEAR([date]) function)
DECLARE #RangeStart date = '20201030';
DECLARE #RangeEnd date = '20201102';
WITH AllTeamDates AS
(SELECT T.TeamId,
D.[Date],
D.WeekOfMonth,
D.WeekOfYear,
D.y_year
FROM #Team T
CROSS JOIN #DimDate D
WHERE D.[Date] BETWEEN #RangeStart AND #RangeEnd
)
SELECT ATD.y_year,
ATD.WeekOfYear,
ATD.WeekOfMonth,
ATD.TeamID,
COUNT(E.EntryID) AS NumEntries
FROM AllTeamDates ATD
LEFT OUTER JOIN #Entries E
ON ATD.TeamID = E.TeamID AND ATD.Date = E.DateCreated
GROUP BY ATD.y_year,
ATD.WeekOfYear,
ATD.WeekOfMonth,
ATD.TeamID;
Results for the above, with your data and my date table and range dates applied (noting that the date range I selected gets the first value in #Entries for 2 Nov, but doesn't get the second for 4 Nov).
y_year WeekOfYear WeekOfMonth TeamID NumEntries
2020 35 4 1 0
2020 35 4 2 0
2020 35 4 3 0
2020 35 4 4 0
2020 36 1 1 1
2020 36 1 2 0
2020 36 1 3 0
2020 36 1 4 0
Note that in this case I am creating all possible dates, then grouping to get week-by-week at the very end. It is possible to also do this by grouping into week-by-week data as soon as possible (e.g., the CTE will return data by week instead of day, then the outer part of the LEFT JOIN also then needs to be grouped into weeks first).
WITH AllTeamWeeks AS
(SELECT T.TeamId, D.WeekOfMonth, D.WeekOfYear, D.y_year
FROM #Team T
CROSS JOIN #DimDate D
WHERE D.[Date] BETWEEN #RangeStart AND #RangeEnd
GROUP BY T.TeamId, D.WeekOfMonth, D.WeekOfYear, D.y_year
),
AllEntries AS
(SELECT E.TeamId, D.WeekOfMonth, D.WeekOfYear, D.y_year,
COUNT(E.EntryID) AS NumEntries
FROM #Entries E
INNER JOIN #DimDate D ON E.DateCreated = D.Date
WHERE E.[DateCreated] BETWEEN #RangeStart AND #RangeEnd
GROUP BY E.TeamId, D.WeekOfMonth, D.WeekOfYear, D.y_year
)
SELECT ATW.y_year,
ATW.WeekOfYear,
ATW.WeekOfMonth,
ATW.TeamID,
ISNULL(AE.NumEntries,0) AS NumEntries
FROM AllTeamWeeks ATW
LEFT OUTER JOIN AllEntries AE
ON ATW.TeamID = AE.TeamID
AND ATW.WeekOfMonth = AE.WeekOfMonth
AND ATW.WeekOfYear = AE.WeekOfYear
AND ATW.y_year = AE.y_year;
This gives the same results, and possibly provides a performance benefit, but is more complex and you'd probably need to ensure that SQL Server is getting accurate estimates/etc when doing the multiple GROUP BYs.
As such I wouldn't use it unless there is a performance issue with the first one - and if there was, I'd also try turning the CTE into a temporary table first, then joining that to #Entries.

Need to add 3 months to each value within a column, based on the 1st '3 Months' calculated off the Admission Date column in T-SQL

I have 14K records table as the following (example of the data related to one particular client_id = 1002):
(my date format is mm/dd/yyyy, months come first)
ClientsEpisodes:
client_id adm_date disch_date
1002 3/11/2005 5/2/2005
1002 8/30/2005 2/16/2007
1002 3/16/2017 NULL
In SQL Server (T-SQL) - I need to calculate + 3 months date into the new column [3Month Date], where the 1st "+ 3 months" value will be calculated off my existing [adm_date] column. Then + 3 more months should be added to the value in [3Months Date], then the next 3 months should be added to the next value in the [3Months Date] column, and so on..., until [3MonthsDate] <= [disch_date]. When [3Months Date] is more than [disch_date] then the data shouldn't be populated. If my [disch_date] IS NULL then the condition should be
[3Months Date] <= current date (whatever it is) from GETDATE() function.
Here is what I expect to see as a result:
(I highlighted my dates offsets with different colors, for a better view)
Below, I'll clarify with more detailed explanation, about each populated (or not populated) data set:
My first [adm_date] from ClientsEpisode table was 3/11/2005.
Adding 3 months:
3/11/2005 + 3 months = 6/11/2005 - falls AFTER the initial [disch_date] (5/2/2005) - not populated
Next [adm_date] from ClientEpisode is 8/3/2005 + 3 Months = 11/30/2005;
then + 3 months must be added to 11/30/2005 = 2/30/2006;
then 2/30/2006 + 3 months = 5/30/2006;
then 5/30/2006 + 3 months = 8/30/2006;
then 8/30/2006 + 3 months = 11/30/2006;
then 11/30/2006 + 3 months = 3/2/2007 - falls AFTER my [disch_date]
(2/16/2007) - not populated
the same algorithm for the next [adm_date] - [disch_date] sets 11/5/2007-2/7/2009 (in dark blue).
then, where [adm_date] = 3/16/17, I have [disch_date] IS NULL, so, the algorithm applies until
[3 Months Date] <= current date (10/15/2020 in this case)
You can use recursive common expression. Below is an example. Note, that you can change the DATEADD part with other (for example add 90 days if you want) - it's a matter of bussness logic.
DECLARE #DataSource TABLE
(
[client_id] INT
,[adm_date] DATE
,[disch_date] DATE
);
INSERT INTO #DataSource ([client_id], [adm_date], [disch_date])
VALUES (1002, '3/11/2005 ', '5/2/2005')
,(1002, '8/30/2005 ', '2/16/2007')
,(1002, '3/16/2017 ', NULL);
WITH DataSource AS
(
SELECT ROW_NUMBER() OVER(ORDER BY [client_id]) AS [row_id]
,[client_id]
,[adm_date]
,DATEADD(MONTH, 3, [adm_date]) AS [3Month Date]
,ISNULL([disch_date], GETUTCDATE()) AS [disch_date]
FROM #DataSource
WHERE DATEADD(MONTH, 3, [adm_date]) <= ISNULL([disch_date], GETUTCDATE())
),
RecursiveDataSource AS
(
SELECT [row_id]
,[client_id]
,[adm_date]
,[3Month Date]
,[disch_date]
,0 AS [level]
FROM DataSource
UNION ALL
SELECT DS.[row_id]
,DS.[client_id]
,DS.[adm_date]
,DATEADD(MONTH, 3, RDS.[3Month Date])
,DS.[disch_date]
,[level] + 1
FROM RecursiveDataSource RDS
INNER JOIN DataSource DS
ON RDS.[row_id] = DS.[row_id]
AND DATEADD(MONTH, 3, RDS.[3Month Date]) < DS.[disch_date]
)
SELECT *
FROM RecursiveDataSource
ORDER BY [row_id]
,[level];
This question already has an accepted answer, but you say in the comments for that, that you have performance problems. Try this instead - it's also a lot simpler.
A recursive CTE is really useful if the value of the next row depends on the value of the previous row.
Here, we don't need the answer to the previous row - we just add n x 3 months (e.g., 3 months, 6 months, 9 months) and filter the rows you want to keep.
Therefore, instead of doing a recursive CTE, just do it via set logic.
Here's some data setup:
CREATE TABLE #Datasource (client_id int, adm_date date, disch_date date);
INSERT INTO #Datasource (client_id, adm_date, disch_date) VALUES
(1002, '20050311', '20050502'),
(1002, '20050830', '20070216'),
(1002, '20170316', NULL),
(1002, '20071105', '20090207');
And here's the simple SELECT
WITH DataSourceMod AS
(SELECT client_id, adm_date, disch_date, ISNULL(disch_date, getdate()) AS disc_date_mod
FROM #Datasource
),
Nums_One_to_OneHundred AS
(SELECT a * 10 + b AS n
FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) A(a)
CROSS JOIN (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) B(b)
)
SELECT ds.client_id, ds.adm_date, ds.disch_date, DATEADD(month, 3*Nums.n, ds.adm_date) AS ThreeMonthDate
FROM DataSourceMod ds
CROSS JOIN Nums_One_to_OneHundred Nums
WHERE DATEADD(month, 3* Nums.n, ds.adm_date) <= ds.disc_date_mod
ORDER BY ds.client_id, ds.adm_date;
This works by
Calculating the effective discharge date (the specified date, or today)
Calculating all possible rows for up to 300 months in the future (the table One_to_OneHundred .. um.. has all the values from 1 to 100, then multiplied by 3.)
Only taking those that fulfil the date condition
You can further optimise this if desired, by limiting the number of 3 months you need to add. Here's a rough version.
WITH DataSourceMod AS
(SELECT client_id, adm_date, disch_date, ISNULL(disch_date, getdate()) AS disc_date_mod,
FLOOR(DATEDIFF(month, adm_date, ISNULL(disch_date, getdate())) / 3) + 1 AS nMax
FROM #Datasource
),
Nums_One_to_OneHundred AS
(SELECT a * 10 + b AS n
FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) A(a)
CROSS JOIN (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) B(b)
)
SELECT ds.client_id, ds.adm_date, ds.disch_date, DATEADD(month, 3*Nums.n, ds.adm_date) AS ThreeMonthDate
FROM DataSourceMod ds
INNER JOIN Nums_One_to_OneHundred Nums ON Nums.n <= ds.nMax
WHERE DATEADD(month, 3* Nums.n, ds.adm_date) <= ds.disc_date_mod
ORDER BY ds.client_id, ds.adm_date;

Calculate number of leaves taken by Employee in a month in SQL Server

How can I calculate number of leaves taken by Employee in a month in SQL Server?
Empid Leaveid Fromdate Todate No of days
100 L1 2008-05-10 2008-05-13 3
100 L2 2008-05-20 2008-05-21 1
100 L3 2008-05-25 2008-06-05 12
100 L4 2009-01-20 2009-01-22 2
100 L5 2009-02-14 2009-02-20 6
100 L6 2009-02-28 2009-02-28 1
Use SUM and GROUP BY.
SELECT Empid, SUM([No of Days]) AS Days
FROM leavetable
GROUP BY Empid
Read more here GROUP BY and here SUM.
This would give you:
Empid Days
100 25
Or if you mean count the amount of times an employee has been off use Count.
SELECT Empid, Count(Leaveid) AS LeaveTotal
FROM leavetable
GROUP BY Empid
Read more here COUNT.
This would give you:
Empid LeaveTotal
100 6
CREATE TABLE #EmpLeave (EmpId INT, LeaveID VARCHAR(5), FromDate Date, Todate Date, NoOfDays INT)
INSERT INTO #EmpLeave VALUES
(100, 'L1', '2008-05-10', '2008-05-13', 4),
(100, 'L2', '2008-05-20', '2008-05-21', 2),
(100, 'L3', '2008-05-25', '2008-06-05', 12),
(100, 'L4', '2009-01-20', '2009-01-22', 3),
(100, 'L5', '2009-02-14', '2009-02-20', 7),
(100, 'L6', '2009-02-28', '2009-02-28', 1)
;With CTE_leave AS (
select * from #EmpLeave where Month(FromDate) <> Month(ToDate) )
SELECT a.EMPID,DATENAME(MONTH,FromDate ) Month ,SUM(a.LeaveCount) LeaveTaken
FROM
(
SELECT C.EMPID,C.LeaveID, C.FromDate, DATEDIFF(DD,C.FromDate,EOMonth(FromDate) ) + 1 LeaveCount
FROM CTE_Leave C
UNION
SELECT C.EMPID, C.LeaveID, DATEADD(DD,1, EOMonth(FromDate)) fromDate, DATEDIFF(DD,DATEADD(DD,1, EOMonth(FromDate)), C.ToDate ) + 1 LeaveCount
FROM CTE_Leave C
UNION
SELECT EMPID, LeaveID, FromDate, DATEDIFF(DD, FromDate, Todate) + 1 LeaveCount FROM #EmpLeave where Month(FromDate) = Month(ToDate)
) a
group by a.EMPID, DATENAME(MONTH,FromDate )
#Anchalose find above possible solution. Let us know solves your query.
Please do post schema details along with the desired result also while posting your query, it will help us understand the problem better.
#Matt, please refrain from using cuss words and try to post the solution as per the requirement.

How do I fetch data grouped by month and year including the period with no data?

Relatively new to SQL and I am stumped on this little issue. This doesn't seem to be very difficult to do, but I just can't seem to figure it out.
I am trying to get a count of transactions from a table, but I can't seem to get sql to get me to show all of the months instead of only the months and the year that the transactions occured in.
Here is the query:
SELECT YEAR(dbo.countproject.trans_date) AS [TransYear]
, MONTH (dbo.countproject.trans_date) AS [TransMonth]
, COUNT(Id) AS TransNum
FROM dbo.countproject
WHERE dbo.countproject.make_name = 'Honda'
AND dbo.countproject.model_name = 'Civic'
AND dbo.countproject.type = 'Sale'
AND dbo.countproject.trans_type LIKE '%%EU'
AND dbo.countproject.mfr = '2000'
GROUP BY YEAR(dbo.countproject.trans_date)
, MONTH(dbo.countproject.trans_date)
ORDER BY YEAR(dbo.countproject.trans_date)
The query returns the following result set:
| TransYear | TransMonth | TransNum |
|-----------|------------|----------|
| 2004 | 1 | 5 |
| 2004 | 3 | 1 |
| 2005 | 4 | 2 |
and so forth....
I am trying to get it to show all the months and years even if the value is NULL.
I tried creating a new table which will have the year and the month as columns to get it to join somehow, but I am lost.
Any help would be appreciated.
If you are using SQL Server 2005 or above, you could use Common Table Expressions (CTE) to get the desired result. Below example shows how you can fetch the results as you had described in the question.
Click here to view the demo in SQL Fiddle.
Description:
Create and insert statements create the table and populates with some sample data. I have created the table based on the query provided in the question.
The statement within the WITH clause is executing a recursive expression. In this case the SELECT above the UNION ALL fetches the minimum and maximum dates available in the table dbo.countproject
Once the minimum date is fetched, the second SELECT statement after the UNION ALL increments the date in 1 month intervals until the recursive expression reaches the maximum date available in the table.
The recursive CTE has produced all the available dates possible. This output is available in the table named alltransactions.
We have to join this CTE output alltransactions with the actual table countproject using LEFT OUTER JOIN since we want to show all years and months even if there are no transactions.
The tables alltransactions and countproject are joined on the year and month parts of the date. The query then applies the necessary filters in the WHERE clause and then groups the data by year and month before ordering it by year and month.
You can notice from the sample data that the earliest date in the table is 2004-07-01 and the latest date is 2005-12-01. Hence the output shows from year 2004 / month 07 till year 2005 / month 12.
Hope that helps.
Script:
CREATE TABLE dbo.countproject
(
id INT NOT NULL IDENTITY
, trans_date DATETIME NOT NULL
, make_name VARCHAR(20) NOT NULL
, model_name VARCHAR(20) NOT NULL
, type VARCHAR(20) NOT NULL
, trans_type VARCHAR(20) NOT NULL
, mfr INT NOT NULL
);
INSERT INTO dbo.countproject (trans_date, make_name, model_name, type, trans_type, mfr) VALUES
('1900-01-01', 'Honda', 'Civic', 'Sale', 'EU', 2000),
('1900-01-01', 'Toyota', 'Corolla', 'Sale', 'EU', 2000),
('2004-07-01', 'Nissan', 'Altima', 'Sale', 'EU', 2000),
('2005-12-01', 'Toyota', 'Camry', 'Sale', 'EU', 2000),
('2004-04-01', 'Ford', 'Focus', 'Sale', 'EU', 2000),
('2005-08-01', 'Honda', 'Civic', 'Sale', 'EU', 2000),
('2005-11-01', 'Toyota', 'Camry', 'Sale', 'EU', 2000),
('2004-08-01', 'Toyota', 'Corolla', 'Sale', 'EU', 2000),
('2005-12-01', 'Honda', 'Civic', 'Sale', 'EU', 2000),
('2004-07-01', 'Honda', 'Civic', 'Sale', 'EU', 2000),
('2004-11-01', 'Honda', 'Civic', 'Sale', 'EU', 2000),
('2005-08-01', 'Honda', 'Civic', 'Sale', 'EU', 2000);
;WITH alltransactions
AS
(
SELECT MIN(trans_date) AS continuousdate
, MAX(trans_date) AS maximumdate
FROM dbo.countproject
WHERE trans_date <> '1900-01-01'
UNION ALL
SELECT DATEADD(MONTH, 1, continuousdate) AS continuousdate
, maximumdate
FROM alltransactions
WHERE DATEADD(MONTH, 1, continuousdate) <= maximumdate
)
SELECT YEAR(at.continuousdate) AS [Year]
, MONTH(at.continuousdate) AS [Month]
, COUNT(cp.trans_date) AS [Count]
FROM alltransactions at
LEFT OUTER JOIN countproject cp
ON YEAR(at.continuousdate) = YEAR(cp.trans_date)
AND MONTH(at.continuousdate) = MONTH(cp.trans_date)
AND cp.make_name = 'Honda'
and cp.model_name = 'Civic'
and cp.type = 'Sale'
and cp.trans_type LIKE '%EU'
and cp.mfr = '2000'
GROUP BY YEAR(at.continuousdate)
, MONTH(at.continuousdate)
ORDER BY [Year]
, [Month];
Output:
Year Month Count
----- ------ -----
2004 4 0
2004 5 0
2004 6 0
2004 7 1
2004 8 0
2004 9 0
2004 10 0
2004 11 1
2004 12 0
2005 1 0
2005 2 0
2005 3 0
2005 4 1
2005 5 0
2005 6 0
2005 7 0
2005 8 2
2005 9 0
2005 10 0
2005 11 0
2005 12 1
You have to use an LEFT or RIGHT OUTER JOIN!
Here is an easy sample: http://www.w3schools.com/sql/sql_join_left.asp
You should get it done by yourself.
Greetings
Alas, the SQL statement can only return the data in the table. If you want all months, you need either a table with the year/month combinations you are intererested in or, preferably, a calendar table with all days and information about them.
With a calendar table, your query have a from clause that looked like:
from
(
select distinct year(date) as yr, month(date) as mon
from calendar c
where date between <earliest> and <latest>
) c
left outer join CountTable ct
on c.yr = year(ct.trans_date)
and c.mon = month(ct.trans_date)