T-SQL Query to count the occurrences of different conditions in table - sql

DBMS: SQL Server 2008
I have a table with the below structure, which represents tender applications by a particular vendor in different companies and the status of their application. Decision R = Reject, A = Accept.
---------------------------------------------------------------
| ID | Company | ApplicationDate | Decision | DecisionDate |
---------------------------------------------------------------
| 1 | ABC | 15/03/2011 | A | 17/04/2011 |
| 2 | ABC | 23/05/2012 | R | 01/03/2014 |
| 3 | XYZ | 14/07/2012 | R | 20/07/2012 |
| 4 | ABC | 18/01/2013 | A | 24/02/2013 |
| 5 | XYZ | 12/08/2013 | R | 11/09/2013 |
| 6 | ABC | 30/09/2013 | R | 14/10/2013 |
| 7 | ABC | 08/01/2014 | A | 08/06/2014 |
| 8 | ABC | 10/05/2014 | A | 19/05/2014 |
---------------------------------------------------------------
*Dates are in time-stamp format. Dates in the example table (dd/mm/yyyy) are for representation purpose only.
What I need to mine from this simple database is,
Number of tenders applied in the last 12 months - assuming 11/07/2014 as the current date.
Number of tenders rejected in the last 12 months.
Time in months since the last tender application.
Time in months since the last tender rejection.
Number of tenders applied in ABC in the last 12 months.
Number of tenders rejected in ABC in the last 12 months.
Time in months since the last tender application to ABC.
Time in months since the last tender rejection by ABC.
So based on the given table data, the statistics would be,
Four. (IDs 5, 6, 7 and 8 have application date with in 12 months of today)
Three (IDs 2, 5 and 6 have decision date with in 12 months and decision is R)
Two (10/05/2014 till today)
Four (ID 2's rejection was on 01/03/2014)
Three (IDs 6, 7 and 8)
Two (IDs 2 and 6)
Two (10/05/2014 till today)
Four (ID 2's rejection was on 01/03/2014)
Is there a way to get these stats using a single query on the table (possibly by using Sum with case)?
What I have so far is as below.
SELECT
SUM(CASE WHEN DATEDIFF(MM, ApplicationDate, GETDATE()) <= 12 THEN 1 ELSE 0 END) 'Total Tenders',
SUM(CASE WHEN DATEDIFF(MM, DecisionDate, GETDATE()) <= 12 AND DECISION = R THEN 1 ELSE 0 END) 'Total Rejects'
SUM(CASE WHEN DATEDIFF(MM, ApplicationDate, GETDATE()) <= 12 AND Company = 'ABC' THEN 1 ELSE 0 END) 'Total Tenders To ABC',
SUM(CASE WHEN DATEDIFF(MM, DecisionDate, GETDATE()) <= 12 AND DECISION = R AND Company = 'ABC' THEN 1 ELSE 0 END) 'Total Rejects By ABC'
FROM TenderTable;
That gives me 1, 2, 5 and 6 of the required stats.

You can use WITH ROLLUP
SET DATEFORMAT 'dmy'
DECLARE #tbl TABLE (ID INT, Company VARCHAR(3), ApplicationDate DATE, Decision CHAR(1), DecisionDate DATE)
INSERT INTO #tbl
(ID, Company, ApplicationDate, Decision, DecisionDate)
VALUES
(1,'ABC','15/03/2011','A','17/04/2011'),
(2,'ABC','23/05/2012','R','01/03/2014'),
(3,'XYZ','14/07/2012','R','20/07/2012'),
(4,'ABC','18/01/2013','A','24/02/2013'),
(5,'XYZ','12/08/2013','R','11/09/2013'),
(6,'ABC','30/09/2013','R','14/10/2013'),
(7,'ABC','08/01/2014','A','08/06/2014'),
(8,'ABC','10/05/2014','A','19/05/2014')
SELECT
Company = CASE WHEN (GROUPING(Company) = 1) THEN 'ALL' ELSE ISNULL(Company, 'UNKNOWN') END,
TendersApplied = SUM(CASE WHEN ApplicationDate >= DATEADD(M, -12, CAST(GETDATE() AS DATE)) THEN 1 END),
TendersRejected = SUM(CASE WHEN DecisionDate >= DATEADD(M, -12, CAST(GETDATE() AS DATE)) AND Decision = 'R' THEN 1 END),
MonthsSinceLastTenderApplication = DATEDIFF(M, MAX(ApplicationDate), GETDATE()),
MonthsSinceLastTenderRejection = DATEDIFF(M, MAX(CASE WHEN Decision = 'R' THEN DecisionDate END), GETDATE())
FROM #tbl
GROUP BY Company
WITH ROLLUP
HAVING GROUPING(Company) = 1
OR Company = 'ABC'
ORDER BY GROUPING(Company), Company
Which produces
Company TendersApplied TendersRejected MonthsSinceLastTenderApplication MonthsSinceLastTenderRejection
------- -------------- --------------- -------------------------------- ------------------------------
ABC 3 2 2 4
ALL 4 3 2 4
Edit by Questioner:
Modification to the query above satisfies the requirements.
SELECT
TendersApplied = SUM(CASE WHEN ApplicationDate >= DATEADD(M, -12, CAST(GETDATE() AS DATE)) THEN 1 END),
TendersRejected = SUM(CASE WHEN DecisionDate >= DATEADD(M, -12, CAST(GETDATE() AS DATE)) AND Decision = 'R' THEN 1 END),
MonthsSinceLastTenderApplication = DATEDIFF(M, MAX(ApplicationDate), GETDATE()),
MonthsSinceLastTenderRejection = DATEDIFF(M, MAX(CASE WHEN Decision = 'R' THEN DecisionDate END), GETDATE()),
TendersAppliedABC = SUM(CASE WHEN Company = 'ABC' AND ApplicationDate >= DATEADD(M, -12, CAST(GETDATE() AS DATE)) THEN 1 END),
TendersRejectedABC = SUM(CASE WHEN Company = 'ABC' AND DecisionDate >= DATEADD(M, -12, CAST(GETDATE() AS DATE)) AND Decision = 'R' THEN 1 END),
MonthsSinceLastTenderApplicationABC = DATEDIFF(M, MAX(CASE WHEN Company = 'ABC' THEN ApplicationDate END), GETDATE()),
MonthsSinceLastTenderRejectionABC = DATEDIFF(M, MAX(CASE WHEN Company = 'ABC' AND Decision = 'R' THEN DecisionDate END), GETDATE())
FROM #tbl

You may be overlooking an simple way that is not obvious if you've not seen it before.
For example, I have a Table Named StateCounty that has State and County data
select count(*) as TotalCounties
, (select count(*) from StateCounty where StateCode = 'AK') as AlaskaCounties
, (select count(*) from StateCounty where StateCode = 'TX') as TexasCounties
, (select count(*) from StateCounty where County = 'Marion') as CountiesNamedMarion
, (select count(*) from StateCounty where County = 'Washington') as CountiesNamedWashington
from StateCounty
Yields the output
TotalCounties AlaskaCounties TexasCounties CountiesNamedMarion CountiesNamedWashington
------------- -------------- ------------- ------------------- -----------------------
3131 17 254 17 31
(1 row(s) affected)
I believe you can write the query you want yourself now.

Related

TSQL Check if Month and Year fields are expired

i have a table with Month and Year fields as integer, eg:
Month | Year
------------
10 | 17
------------
11 | 17
------------
12 | 17
------------
1 | 18
------------
(Year 17 is for 2017 and Year 18 is for 2018)
I want add into a query a calculated field for check if the date is expired
SELECT [Year], [Month],
CASE WHEN
([Year]+2000) < DATEPART(Year, GetDate()) OR
(([Year]+2000) = DATEPART(Year, GetDate()) AND [Month] < DATEPART(Month, GetDate()))
THEN 1 ELSE 0 END AS IsExpired
FROM test
the output is
Month | Year | IsExpired
------------------------
10 | 17 | 1
------------------------
11 | 17 | 1
------------------------
12 | 17 | 1
------------------------
1 | 18 | 1
------------------------
the expected output is (because current GetDate() is 2017-11-29):
Month | Year | IsExpired
------------------------
10 | 17 | 1
------------------------
11 | 17 | 0
------------------------
12 | 17 | 0
------------------------
1 | 18 | 0
------------------------
see live on http://sqlfiddle.com/#!6/8c807/2
what i'm doing wrong?
Convert your values to dates:
WITH IntDates AS (
SELECT *
FROM (VALUES (10,17),(11,17),(12,17),(1,18)) AS D ([Month], [Year])),
Dates AS(
SELECT *,
DATEADD(YEAR, [Year], DATEADD(MONTH, [Month], '20000101')) AS DateValue
FROM IntDates)
SELECT *,
CASE WHEN DateValue < GETDATE() THEN 1 ELSE 0 END AS Expired
FROM Dates;
If you were using the date datatype this becomes a lot simpler.
create table test2
(
ExpirationDate date
)
--have to do a bunch of string manipulation to turn this into viable dates.
--and this of course is after switching the columns posted in your sql fiddle.
insert test2
select convert(char(4), [Year] + 2000) + right('0' + convert(varchar(2), [Month]), 2) + '01'
from Test
select case when ExpirationDate < dateadd(month, datediff(month, 0, getdate()), 0) --get beginning of the current month
then 1 else 0 end
, ExpirationDate
from test2

SQL Query to Roll Up Data from the Last 7 Days

Let's assume I have a table structure like this.
CheckIn
- int checkInId pk
- int companyPositionId
- Date checkInDate
Let's say I want to get a count of all check ins for the last 7 days from a given date. What would be the best way to do this? Right now I'm making a query for each of the 7 dates AND the company positions. Currently this is too slow because there could be many companyPositions * 7 days. How can I roll this up into one query?
Would it be easiest to generate the last 7 days dates and construct a long query? Could I then group count by a date range for each of the 7 days?
An ideal result back could look like:
companyPositionId, date1Count, date2Count, date3Count, date4Count, date5Count, date6Count.
Example Data:
checkInId | companyPositionId | checkInDate
1 | 1 | 1970-01-01
2 | 1 | 1970-01-02
3 | 1 | 1970-01-03
4 | 1 | 1970-01-04
5 | 1 | 1970-01-05
6 | 1 | 1970-01-06
7 | 1 | 1970-01-07
8 | 2 | 1970-01-01
9 | 2 | 1970-01-02
10 | 2 | 1970-01-03
11 | 2 | 1970-01-04
12 | 2 | 1970-01-05
13 | 2 | 1970-01-06
14 | 2 | 1970-01-07*
15 | 2 | 1970-01-07*
My current query is this:
SELECT * FROM CheckIn
WHERE (startDate) <= (inputDate)
AND (inputDate) <= (endDate)
AND companyPositionId = (companyPositionId);
I then loop through each startDate/endDate that is generated from the beginning of the day and end of that day. And then each of the companyPositionId's.
Ideal result:
companyPositionId | date1Count | date2Count | date3Count | date4Count | date5Count | date6Count | date7Count
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
2 | 1 | 1 | 1 | 1 | 1 | 1 | 2
You can do this with the PIVOT command, or with conditional SUMs:
DECLARE #my_date DATE = CAST(GETDATE() AS DATE)
SELECT
companyPositionId,
SUM(CASE WHEN CAST(checkInDate AS DATE) = DATEADD(DAY, -7, #my_date) THEN 1 ELSE 0 END) AS date1Count,
SUM(CASE WHEN CAST(checkInDate AS DATE) = DATEADD(DAY, -6, #my_date) THEN 1 ELSE 0 END) AS date2Count,
SUM(CASE WHEN CAST(checkInDate AS DATE) = DATEADD(DAY, -5, #my_date) THEN 1 ELSE 0 END) AS date3Count,
SUM(CASE WHEN CAST(checkInDate AS DATE) = DATEADD(DAY, -4, #my_date) THEN 1 ELSE 0 END) AS date4Count,
SUM(CASE WHEN CAST(checkInDate AS DATE) = DATEADD(DAY, -3, #my_date) THEN 1 ELSE 0 END) AS date5Count,
SUM(CASE WHEN CAST(checkInDate AS DATE) = DATEADD(DAY, -2, #my_date) THEN 1 ELSE 0 END) AS date6Count,
SUM(CASE WHEN CAST(checkInDate AS DATE) = DATEADD(DAY, -1, #my_date) THEN 1 ELSE 0 END) AS date7Count
FROM
CheckIn
WHERE
checkInDate BETWEEN DATEADD(DAY, -7, GETDATE()) AND DATEADD(DAY, -1, GETDATE())
GROUP BY
companyPositionId
If your checkInDate has a time component then you'll need to account for that.
I declared the #my_date variable just to avoid having to repeat that expression in the query a bunch of times, but you could replace the variable with that expression and it would work as well. You could also use BETWEEN which might have better performance since the optimizer could then potentially use an index on your checkInDate. Just calculate midnight/11:59:59 of each of the days instead of looking for equality.
Look at
Declare #d date= '2016-05-05'; -- parameter, we need this day and six previous
Select
companyPositionId
,date1Count = count(case checkInDate when #d then checkInId end)
,date2Count = count(case checkInDate when dateadd(d,-1,#d) then checkInId end)
--...
from checkIn
where checkInDate between dateadd(d,-6,#d) and #d
group by companyPositionId
order by companyPositionId;
Here's a quick pivot example assuming you're looking for the previous 7 days of the #inputDate. This should work even with the time component since it goes off a DATEDIFF with days only.
DECLARE #InputDate DATE = '1/8/1970'
;WITH CTE AS (
SELECT *, DATEDIFF(DD, checkInDate, #InputDate) AS DaysAgo FROM #CheckIns
)
SELECT
companyPositionId,
SUM([7]) AS date1Count,
SUM([6]) AS date2Count,
SUM([5]) AS date3Count,
SUM([4]) AS date4Count,
SUM([3]) AS date5Count,
SUM([2]) AS date6Count,
SUM([1]) AS date7Count -- Reversed columns since you wanted 7 to be most recent day
FROM CTE
PIVOT (COUNT(checkInId) FOR DaysAgo IN ([1], [2], [3], [4], [5], [6], [7])) PVT
GROUP BY
companyPositionId
This gives the following results matching your desired output:
companyPositionId date1Count date2Count date3Count date4Count date5Count date6Count date7Count
----------------- ----------- ----------- ----------- ----------- ----------- ----------- -----------
1 1 1 1 1 1 1 1
2 1 1 1 1 1 1 2
Create Data:
CREATE TABLE #checkin (
checkInId INT IDENTITY(1, 1),
companyPositionId int,
checkInDate DATE
)
DECLARE #counter INT = 100
WHILE #counter > 0
BEGIN
INSERT INTO #checkin
( companyPositionId, checkInDate )
VALUES ( RAND() * 10 + 1, -- 10 possible companies?
DATEADD(day, RAND()*-7, GETDATE()) -- random days in the last 2 weeks
)
SET #counter = #counter - 1
END
Logic starts here:
DECLARE
#now DATETIME = GETDATE(),
#days INT = -7 -- Our interval of interest
This logic is partially borrowed from Bogdan Sahlean, his results were getting all '1' for me, so I changed the dayNum calculation
SELECT *
FROM (
SELECT companyPositionId, checkInId, 1 + CAST(DATEDIFF(DAY,checkInDate, #now) AS INT) AS DayNum
FROM #CheckIn
WHERE checkInDate BETWEEN CAST(DATEADD(DAY, #days, #now) AS DATE) AND #now
) AS ps -- Pivot source
PIVOT ( COUNT(checkInId) FOR DayNum IN ([1], [2], [3], [4], [5], [6], [7]) ) AS p
DROP TABLE #checkin

SQL query to fetch data based on date range

i want to fetch data for below scenario
input: (Let say today is: 1-Mar-2015)
LicenseNo LicenseEndDate LicenseType Amount
1 1-Apr-2015 AB 100
2 5-Apr-2015 AB 150
3 7-Apr-2015 BC 200
4 10-July-2015 AB 120
5 10-july-2015 BC 140
Expected O/P
AB BC
Between 0-3 months 250 200
Between 3-6 months 120 140
this may increase
SELECT 'Between 0-3 months',
SUM(Case when l.LicenseType='AB' then l.Amount End),
SUM(Case when l.LicenseType='BC' then l.Amount End)
FROM licence l
WHERE l.LicenceEndDate BETWEEN #inputDate AND DATEADD (month , 3 , #inputDate)
UNION
SELECT 'Between 3-6 months',
SUM(Case when l.LicenseType='AB' then l.Amount End),
SUM(Case when l.LicenseType='BC' then l.Amount End)
FROM licence l
WHERE l.LicenceEndDate BETWEEN DATEADD (month , 3 , #inputDate) AND DATEADD (month , 6 , #inputDate)
Union of two queries for the two interval.
Or you can create a temporary table based on your input date like this
| ID | DESCRIPTION | DATA_MIN | DATA_MAX |
| 1 | Between 0-3 months | #input | #input + 3|
| 2 | Between 3-6 months | #input +3| #input + 6|
And use that for your join
Having the derived table in this solution just makes grouping easier in the outer select, it saves us from repeating ourselves too much. The SUM(case...end) structure is one way of pivoting our results, you could look at the pivot operator but I think it is overkill for this scenario. I also added a few other cases even though your supplied data doesn't use them because I figure they are likely. you can always add a where clause if you are looking at specific groups and this is facilitated by the derived table as well.
I have used GETDATE() but you can substitute a date variable for that if it suits better.
declare #t as table
(
LicenseNo int,
LicenseEndDate datetime,
LicenseType varchar(2),
Amount numeric(10,2)
)
insert into #t
values
(1,'1-Apr-2015','AB',100),
(2,'5-Apr-2015','AB',150),
(3,'7-Apr-2015','BC',200),
(4,'10-July-2015','AB',120),
(5,'10-july-2015','BC',140)
declare #comparison_date as datetime = getdate()
select
case ExpGrp
when 0 then 'Expired'
when 1 then 'Expires today'
when 2 then 'Expires in 0-3 months'
when 3 then 'Expires in 3-6 months'
when 4 then 'Not due to expire'
else 'Something went wrong'
end as Descrip,
sum(case when LicenseType = 'AB'
then Amount
else 0
end) as AB,
sum(case when LicenseType = 'BC'
then Amount
else 0
end) as BC
from
(select *,
case
when LicenseEndDate < #comparison_date
then 0
when LicenseEndDate = #comparison_date
then 1
when LicenseEndDate > #comparison_date and LicenseEndDate <= dateadd(MONTH,3,#comparison_date)
then 2
when LicenseEndDate > dateadd(MONTH,3,#comparison_date) and LicenseEndDate <= dateadd(MONTH,6,#comparison_date)
then 3
else 4
end as ExpGrp
from #t) t
group by t.ExpGrp

How to get the date for the previous month which are closed in the current month

Iam using SSRS Report builder application to create BI Report for my System which is tracking the numbers of incidents logged and closed based on each month.
the below is the table which i need to create the query
Month Logged Received Closed Remaining
January 200 220 150 70
February 150 220 200 20
March 110 130 100 30
April 200 230 200 30
and each column define as follow:
Logged= Open Incident in the Current Month for example open from 1/1/2014 to 31/1/2014 (Contain only the current month data )
Received = Logged incident+ the remaining from the previous months which are still open not close for example the month febreuary will be 150 for the current moth+70 from previous month remaining will give me total 220 which is received.
Closed= incident which are opened in the current month and closed in the current month + the remaining from the previous month which closed in this month
Remaining= Received – closed
the code which i used is not giving me the close incident for the previous months also its only giving me which were closed in the current month
the below is the code which i used for my query:
SELECT group_id, YEAR(Opendate) AS Year, MONTH(Opendate) AS Month,
COUNT(CASE WHEN Month(Closedate) = Month(Opendate)
AND Month(closedate)> Month (opendate) THEN 1 ELSE NULL END) AS closed,
COUNT(*) AS Logged,
FROM Incidents
WHERE (Opendate >= #YearStart) AND (Opendate <= #YearEnd)
GROUP BY YEAR(Opendate), MONTH(Opendate), group_id
ORDER BY Year, Month,group_id
Logged is working fine the closed, Received and remaining i am stuck on it.
I tried to use Union and got the Logged and Closed Data
Select count(*) logged,year(opendate) as year1,MONTH(opendate) as
month1,'Logged' as status1
From Incidents
where opendate is not null
GROUP BY year(opendate),MONTH(opendate)
UNION
Select count(*) closed,year(Closedate) as year1,MONTH(Closedate) as
month1,'All_Closed' as status1
From Incidents
where Closedate is not null
GROUP BY year(Closedate),MONTH(Closedate)
UNION
Select count(*) Remaining,year(opendate) as year1,MONTH(opendate) as
month1,'Current_Month_Not_Closed' as status1
From Incidents
where Month(Closedate) > MONTH(Opendate)
GROUP BY year(opendate),MONTH(opendate)
UNION
Select count(*) Month_Closed,year(opendate) as year1,MONTH(opendate) as
month1,'Current_Month_Close' as status1
From Incidents
where MONTH(Closedate) = MONTH(Opendate)
GROUP BY year(opendate),MONTH(opendate)
order by year1,month1
the data which I received are as follow:
logged | year1 | month1 | status1
-------+-------+--------+-------------------------
1093 | 2014 | 1 | Logged
1089 | 2014 | 1 | All_Closed
997 | 2014 | 1 | Current_Month_Close
96 | 2014 | 1 | Current_Month_Not_Closed
1176 | 2014 | 2 | Logged
1176 | 2014 | 2 | All_Closed
91 | 2014 | 2 | Current_Month_Not_Closed
1085 | 2014 | 2 | Current_Month_Close
1340 | 2014 | 3 | Logged
1327 | 2014 | 3 | All_Closed
107 | 2014 | 3 | Current_Month_Not_Closed
1232 | 2014 | 3 | Current_Month_Close
116 | 2014 | 4 | Current_Month_Not_Closed
1320 | 2014 | 4 | Current_Month_Close
1424 | 2014 | 4 | All_Closed
1441 | 2014 | 4 | Logged
1167 | 2014 | 5 | Current_Month_Close
105 | 2014 | 5 | Current_Month_Not_Closed
1277 | 2014 | 5 | Logged
1283 | 2014 | 5 | All_Closed
To have a reliable data a calendar table as anchor can help, and is needed in case the tickets can be alive for months from their opening date or there can be a month without ticket created.
For example with the fake data
CREATE TABLE Incidents (
id int identity(1, 1)
, group_id nvarchar(100)
, Opendate Datetime
, Closedate Datetime
)
INSERT INTO Incidents
VALUES ('Service Desk', '20140107', '20140120')
, ('Service Desk', '20140117', '20140123')
, ('Service Desk', '20140127', '20140313')
, ('Service Desk', '20140310', '')
-- from an OP comment the open tickets have the Closedate '' (1900-01-01)
without a calendar table (or a temp, or a CTE) there is no way to add february in the resultset, even if the third record is both "Received" and "Remaining" in that month.
To create a calendar there are several way, in this case we need the some information about months but nothing about the days, so those are not generated.
declare #YearStart date = '20140101'
declare #YearEnd date = '20140430'
;WITH D(N) AS (
SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7
UNION ALL SELECT 8 UNION ALL SELECT 9
)
SELECT EOM
= DATEADD(D, -1, DATEADD(M, u.N + 10 * t.N + 1
, DATEADD(Y, DATEDIFF(Y, 0, #YearStart), 0)))
, pMonth = u.N + 10 * t.N
FROM D u
CROSS JOIN D t
WHERE u.N + 10 * t.N <= DATEDIFF(M, #YearStart, #YearEnd)
Here EOM is the date of the end of the month, it'll be used to check if the incidents are closed in the month and pMonth is the progressive month starting from #YearStart.
Now we need to prepare the data in the incident table to be used
SELECT ID
, OpenDate
, Closedate = COALESCE(NULLIF(Closedate, ''), '99991231')
, pOpenDate = DATEDIFF(M, #YearStart, OpenDate)
, pClosedate = DATEDIFF(M, #YearStart
, COALESCE(NULLIF(Closedate, ''), '99991231'))
FROM Incidents
the Closedate need to always have a value higher than the OpenDate, for this is used the constant date 9999-12-31, pOpenDate and pClosedate, as pMonth before, are the progressive month starting from #YearStart respectively of OpenDate and Closedate.
Putting those togheter it's possible to create the main query
declare #YearStart date = '20140101'
declare #YearEnd date = '20140430'
;WITH D(N) AS (
SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7
UNION ALL SELECT 8 UNION ALL SELECT 9
), CM AS (
SELECT EOM
= DATEADD(D, -1, DATEADD(M, u.N + 10 * t.N + 1
, DATEADD(Y, DATEDIFF(Y, 0, #YearStart), 0)))
, pMonth = u.N + 10 * t.N
FROM D u
CROSS JOIN D t
WHERE u.N + 10 * t.N <= DATEDIFF(M, #YearStart, #YearEnd)
), I AS (
SELECT ID
, OpenDate
, Closedate = COALESCE(NULLIF(Closedate, ''), '99991231')
, pOpenDate = DATEDIFF(M, #YearStart, OpenDate)
, pClosedate = DATEDIFF(M, #YearStart
, COALESCE(NULLIF(Closedate, ''), '99991231'))
FROM Incidents
)
SELECT MONTH(CM.EOM) [Month]
, Logged = SUM(CASE WHEN pOpenDate = pMonth
THEN 1
ELSE 0
END)
, Received = Count(i.id)
, Closed = SUM(CASE WHEN pClosedate = pMonth
AND i.Closedate < CM.EOM
THEN 1
ELSE 0
END)
, Remaining = SUM(CASE WHEN i.Closedate > CM.EOM
THEN 1
ELSE 0
END)
FROM CM
INNER JOIN I ON CM.pMonth
BETWEEN i.pOpenDate AND i.pClosedate
WHERE CM.EOM <= #YearEnd
GROUP BY CM.EOM
ORDER BY CM.EOM
SQLFiddle Demo
using a JOIN to get the month from the calendar table between #YearStart and #YearEnd and all the incident alive in the month. Their attribute are calculated with the CASE logic, in case of Received if a ticket is alive it's received so no logic is needed.
All the CASE can be transformed in BIT logic
declare #YearStart date = '20140101'
declare #YearEnd date = '20140430'
;WITH D(N) AS (
SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7
UNION ALL SELECT 8 UNION ALL SELECT 9
), CM AS (
SELECT EOM
= DATEADD(D, -1, DATEADD(M, u.N + 10 * t.N + 1
, DATEADD(Y, DATEDIFF(Y, 0, #YearStart), 0)))
, pMonth = u.N + 10 * t.N
FROM D u
CROSS JOIN D t
WHERE u.N + 10 * t.N <= DATEDIFF(M, #YearStart, #YearEnd)
), I AS (
SELECT ID
, OpenDate
, Closedate = COALESCE(NULLIF(Closedate, ''), '99991231')
, pOpenDate = DATEDIFF(M, #YearStart, OpenDate)
, pClosedate = DATEDIFF(M, #YearStart
, COALESCE(NULLIF(Closedate, ''), '99991231'))
FROM Incidents
)
SELECT MONTH(CM.EOM) [Month]
, Logged = SUM(1 - CAST(pOpenDate - pMonth AS BIT))
, Received = Count(i.id)
, Closed = SUM(1 - CAST(pClosedate - pMonth AS BIT))
, Remaining = SUM(0 + CAST(i.pClosedate / (CM.pMonth + 1) AS BIT))
FROM CM
INNER JOIN I ON CM.pMonth
BETWEEN i.pOpenDate AND i.pClosedate
WHERE CM.EOM <= #YearEnd
GROUP BY CM.EOM
ORDER BY CM.EOM;
SQLFiddle Demo
The bit logic is base on how the CAST to BIT works:
0 go to 0
everything else go to 1
based on that (with A and B integer):
1 - CAST(A - B AS BIT) is 1 when A = B
CAST(A / (B + 1) AS BIT) is 1 when A > B (the 0 + is to force an implicit cast to INT as BIT cannot be SUMmed)
Received would be the number of tickets that were opened before the end of the month, and not closed before the start of the month.
count(case when OpenDate <= #EndOfMonth and
(#StartOfMonth >= CloseDate or CloseDate is null) then 1 end)
as Received
Closed is straightforward:
count(case when CloseDate between #StartOfMonth and #EndOfMonth
then 1 end) as Closed
You should be able to figure out how to calculate the start and end of a month using Google.

Complex SQL query

I have a table that tracks emails sent from applications on my server. I would like to write a query that shows how many emails were sent by each application in a certain time period. Here is the table:
----------------------------------------------------------
| emailID | SentDT | ApplicationName |
----------------------------------------------------------
| 1 | 2011-08-04 14:43:31.080 | Term Form |
----------------------------------------------------------
| 2 | 2011-08-04 13:59:46.062 | Term Form |
----------------------------------------------------------
| 3 | 2011-08-03 10:38:15.015 | Request Form |
----------------------------------------------------------
| 4 | 2011-08-03 05:52:29.005 | Term Form |
----------------------------------------------------------
| 5 | 2011-08-01 19:58:31.094 | Recruiting Form |
----------------------------------------------------------
I would like to see number of emails sent Today, Last 24 hours, Last 7 days, This Month, Last Month, All time.
I know how to do each of these queries by themselves, but I have no clue how to do it in one trip to the database.
For example:
--------------------------------------------------------------
| ApplicationName | Today | Last24 | Last7days | ThisMonth |
--------------------------------------------------------------
| Term Form | 2 | 5 | 10 | 19 |
--------------------------------------------------------------
| Request Form | 9 | 18 | 36 | 75 |
--------------------------------------------------------------
| Recruiting Form | 15 | 35 | 100 | 250 |
--------------------------------------------------------------
I tried using a nested select for each subset of times, but I can't use a group by in the nested select. My query that doesn't produce results:
select COUNT(emailID), ApplicationName, (select COUNT(emailID) from emaillog where SentDT > '08/02/2011') as TwoDaysAgo
from emaillog
group by ApplicationName
order by ApplicationName
I think it's much easier to do all the date calculations up front, then you can refer to local variables with logical names instead of embedding all the datediff/case etc. calculations in the query logic.
Made a couple of assumptions here. (1) that no data in EmailLog is in the future (2) that by "Last 7 days" you mean today and the full 6 days preceding. I've also included a grand total - even though it's not listed in your desired output, it seems you were trying to get it with the COUNT() outside the subquery.
DECLARE #now SMALLDATETIME = SYSDATETIME();
DECLARE #today DATE = #now,
#24hrsago SMALLDATETIME = DATEADD(DAY, -1, #now);
DECLARE #7daysago DATE = DATEADD(DAY, -6, #today),
#ThisMonth DATE = DATEADD(DAY, 1-DATEPART(DAY, #today), #today);
--SELECT #now, #today, #24hrsago, #7daysago, #ThisMonth;
WITH d AS
(
SELECT ApplicationName, c = COUNT(*)
FROM EmailLog
GROUP BY ApplicationName
),
g AS
(
SELECT
ApplicationName,
[Today] = SUM(CASE WHEN SentDt >= #today THEN 1 ELSE 0 END),
[Last24] = SUM(CASE WHEN SentDt >= #24hrsago THEN 1 ELSE 0 END),
[Last7Days] = SUM(CASE WHEN SentDt >= #7daysago THEN 1 ELSE 0 END),
[ThisMonth] = SUM(CASE WHEN SentDt >= #ThisMonth THEN 1 ELSE 0 END)
FROM EmailLog
GROUP BY ApplicationName
)
SELECT d.ApplicationName,
Total = d.c,
[Today] = COALESCE(g.[Today], 0),
[Last24] = COALESCE(g.[Last24], 0),
[Last7days] = COALESCE(g.Last7days, 0),
[ThisMonth] = COALESCE(g.ThisMonth, 0)
FROM d LEFT OUTER JOIN g
ON d.ApplicationName = g.ApplicationName;
EDIT
If my assumption was wrong and you don't need the total count by application name, the query becomes much simpler:
DECLARE #now SMALLDATETIME = SYSDATETIME();
DECLARE #today DATE = #now,
#24hrsago SMALLDATETIME = DATEADD(DAY, -1, #now);
DECLARE #7daysago DATE = DATEADD(DAY, -6, #today),
#ThisMonth DATE = DATEADD(DAY, 1-DATEPART(DAY, #today), #today);
SELECT ApplicationName,
[Today] = SUM(CASE WHEN SentDt >= #today THEN 1 ELSE 0 END),
[Last24] = SUM(CASE WHEN SentDt >= #24hrsago THEN 1 ELSE 0 END),
[Last7Days] = SUM(CASE WHEN SentDt >= #7daysago THEN 1 ELSE 0 END),
[ThisMonth] = SUM(CASE WHEN SentDt >= #ThisMonth THEN 1 ELSE 0 END)
FROM EmailLog
GROUP BY ApplicationName;
Ordering optional of course.
try:
Select ApplicationName, COunt(*) numEmails
From table
where SentDT Between #startDateTime and #EndDateTime
Group By ApplicationName
NOTE: startDateTime and EndDateTime are oundary limits on records to be processed.
if you also want to establish buckets around specified datetiome ranges, you simply need to define those datetime range buckets in another group by expression (and output that same expression in the select clause ... as an example, say the datetime ranges are calendar months...
Select DateAdd(month, DateDiff(month, 0, SentDT), 0) CalMonth,
ApplicationName, Count(*) numEmails
From table
where SentDT Between #startDateTime and #EndDateTime
Group By DateAdd(month, DateDiff(month, 0, SentDT), 0),
ApplicationName
Something like this should do the trick
select
ApplicationName,
sum(case when daterange = 0 then cnt else 0 end) as Today,
sum(case when daterange = 1 then cnt else 0 end) as yesterday,
sum(case when daterange <=2 then cnt else 0 end) as Week,
sum(case when daterange <=3 then cnt else 0 end) as month,
sum(cnt) as AllTime
from
(select
ApplicationName,
case
when days = 0 then '0'
when days = 1 then '1'
when days <= 7 then '2'
when days <= 30 then '3'
else 4
end as
DateRange,
Count(emailid) cnt
from
(select ApplicationName, EmailID, datediff(dd, SentDT, getdate()) as Days
from
dbo.[YourTableGoesHere]
) as foo
Group by
ApplicationName,
case when days < 1 then '0'
when days = 1 then '1'
when days <= 7 then '2'
when days <= 30 then '3'
else 4
end) as bar
group by
ApplicationName