SQL Query to Roll Up Data from the Last 7 Days - sql

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

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 how to count census points occurring between date records

I’m using MS-SQL-2008 R2 trying to write a script that calculates the Number of Hospital Beds occupied on any given day, at 2 census points: midnight, and 09:00.
I’m working from a data set of patient Ward Stays. Basically, each row in the table is a record of an individual patient's stay on a single ward, and records the date/time the patient is admitted onto the ward, and the date/time the patient leaves the ward.
A sample of this table is below:
Ward_Stay_Primary_Key | Ward_Start_Date_Time | Ward_End_Date_Time
1 | 2017-09-03 15:04:00.000 | 2017-09-27 16:55:00.000
2 | 2017-09-04 18:08:00.000 | 2017-09-06 18:00:00.000
3 | 2017-09-04 13:00:00.000 | 2017-09-04 22:00:00.000
4 | 2017-09-04 20:54:00.000 | 2017-09-08 14:30:00.000
5 | 2017-09-04 20:52:00.000 | 2017-09-13 11:50:00.000
6 | 2017-09-05 13:32:00.000 | 2017-09-11 14:49:00.000
7 | 2017-09-05 13:17:00.000 | 2017-09-12 21:00:00.000
8 | 2017-09-05 23:11:00.000 | 2017-09-06 17:38:00.000
9 | 2017-09-05 11:35:00.000 | 2017-09-14 16:12:00.000
10 | 2017-09-05 14:05:00.000 | 2017-09-11 16:30:00.000
The key thing to note here is that a patient’s Ward Stay can span any length of time, from a few hours to many days.
The following code enables me to calculate the number of beds at both census points for any given day, by specifying the date in the case statement:
SELECT
'05/09/2017' [Date]
,SUM(case when Ward_Start_Date_Time <= '05/09/2017 00:00:00.000' AND (Ward_End_Date_Time >= '05/09/2017 00:00:00.000' OR Ward_End_Date_Time IS NULL)then 1 else 0 end)[No. Beds Occupied at 00:00]
,SUM(case when Ward_Start_Date_Time <= '05/09/2017 09:00:00.000' AND (Ward_End_Date_Time >= '05/09/2017 09:00:00.000' OR Ward_End_Date_Time IS NULL)then 1 else 0 end)[No. Beds Occupied at 09:00]
FROM
WardStaysTable
And, based on the sample 10 records above, generates this output:
Date | No. Beds Occupied at 00:00 | No. Beds Occupied at 09:00
05/09/2017 | 4 | 4
To perform this for any number of days is obviously onerous, so what I’m looking to create is a query where I can specify a start/end date parameter (e.g. 1st-5th Sept), and for the query to then evaluate the Ward_Start_Date_Time and Ward_End_Date_Time variables for each record, and – grouping by the dates defined in the date parameter – count each time the 00:00:00.000 and 09:00:00.000 census points fall between these 2 variables, to give an output something along these lines (based on the above 10 records):
Date | No. Beds Occupied at 00:00 | No. Beds Occupied at 09:00
01/09/2017 | 0 | 0
02/09/2017 | 0 | 0
03/09/2017 | 0 | 0
04/09/2017 | 1 | 1
05/09/2017 | 4 | 4
I’ve approached this (perhaps naively) thinking that if I use a cte to create a table of dates (defined by the input parameters), along with associated midnight and 9am census date/time points, then I could use these variables to group and evaluate the dataset.
So, this code generates the grouping dates and census date/time points:
DECLARE
#StartDate DATE = '01/09/2017'
,#EndDate DATE = '05/09/2017'
,#0900 INT = 540
SELECT
DATEADD(DAY, nbr - 1, #StartDate) [Date]
,CONVERT(DATETIME,(DATEADD(DAY, nbr - 1, #StartDate))) [MidnightDate]
,DATEADD(mi, #0900,(CONVERT(DATETIME,(DATEADD(DAY, nbr - 1, #StartDate))))) [0900Date]
FROM
(
SELECT
ROW_NUMBER() OVER ( ORDER BY c.object_id ) AS nbr
FROM sys.columns c
) nbrs
WHERE nbr - 1 <= DATEDIFF(DAY, #StartDate, #EndDate)
The stumbling block I’ve hit is how to join the cte to the WardStays dataset, because there’s no appropriate key… I’ve tried a few iterations of using a subquery to make this work, but either I’m taking the wrong approach or I’m getting my syntax in a mess.
In simple terms, the logic I’m trying to create to get the output is something like:
SELECT
[Date]
,SUM (case when WST.Ward_Start_Date_Time <= [MidnightDate] AND (WST.Ward_End_Date_Time >= [MidnightDate] OR WST.Ward_End_Date_Time IS NULL then 1 else 0 end) [No. Beds Occupied at 00:00]
,SUM (case when WST.Ward_Start_Date_Time <= [0900Date] AND (WST.Ward_End_Date_Time >= [0900Date] OR WST.Ward_End_Date_Time IS NULL then 1 else 0 end) [No. Beds Occupied at 09:00]
FROM WardStaysTable WST
GROUP BY [Date]
Is the above somehow possible, or am I barking up the wrong tree and need to take a different approach altogether? Appreciate any advice.
I would expect something like this:
WITH dates as (
SELECT CAST(#StartDate as DATETIME) as dte
UNION ALL
SELECT DATEADD(DAY, 1, dte)
FROM dates
WHERE dte < #EndDate
)
SELECT dates.dte [Date],
SUM(CASE WHEN Ward_Start_Date_Time <= dte AND
Ward_END_Date_Time >= dte
THEN 1 ELSE 0
END) as num_beds_0000,
SUM(CASE WHEN Ward_Start_Date_Time <= dte + CAST('09:00' as DATETIME) AND
Ward_END_Date_Time >= dte + CAST('09:00' as DATETIME)
THEN 1 ELSE 0
END) as num_beds_0900
FROM dates LEFT JOIN
WardStaysTable wt
ON wt.Ward_Start_Date_Time <= DATEADD(day, 1, dates.dte) AND
wt.Ward_END_Date_Time >= dates.dte
GROUP BY dates.dte
ORDER BY dates.dte;
The cte is just creating the list of dates.
What a cool exercise. Here is what I came up with:
CREATE TABLE #tmp (ID int, StartDte datetime, EndDte datetime)
INSERT INTO #tmp values(1,'2017-09-03 15:04:00.000','2017-09-27 06:55:00.000')
INSERT INTO #tmp values(2,'2017-09-04 08:08:00.000','2017-09-06 18:00:00.000')
INSERT INTO #tmp values(3,'2017-09-04 13:00:00.000','2017-09-04 22:00:00.000')
INSERT INTO #tmp values(4,'2017-09-04 20:54:00.000','2017-09-08 14:30:00.000')
INSERT INTO #tmp values(5,'2017-09-04 20:52:00.000','2017-09-13 11:50:00.000')
INSERT INTO #tmp values(6,'2017-09-05 13:32:00.000','2017-09-11 14:49:00.000')
INSERT INTO #tmp values(7,'2017-09-05 13:17:00.000','2017-09-12 21:00:00.000')
INSERT INTO #tmp values(8,'2017-09-05 23:11:00.000','2017-09-06 07:38:00.000')
INSERT INTO #tmp values(9,'2017-09-05 11:35:00.000','2017-09-14 16:12:00.000')
INSERT INTO #tmp values(10,'2017-09-05 14:05:00.000','2017-09-11 16:30:00.000')
DECLARE
#StartDate DATE = '09/01/2017'
,#EndDate DATE = '10/01/2017'
, #nHours INT = 9
;WITH d(OrderDate) AS
(
SELECT DATEADD(DAY, n-1, #StartDate)
FROM (SELECT TOP (DATEDIFF(DAY, #StartDate, #EndDate) + 1)
ROW_NUMBER() OVER (ORDER BY [object_id]) FROM sys.all_objects) AS x(n)
)
, CTE AS(
select OrderDate, t2.*
from #tmp t2
cross apply(select orderdate from d ) d
where StartDte >= #StartDate and EndDte <= #EndDate)
select OrderDate,
SUM(CASE WHEN OrderDate >= StartDte and OrderDate <= EndDte THEN 1 ELSE 0 END) [No. Beds Occupied at 00:00],
SUM(CASE WHEN StartDTE <= DateAdd(hour,#nHours,CAST(OrderDate as datetime)) and DateAdd(hour,#nHours,CAST(OrderDate as datetime)) <= EndDte THEN 1 ELSE 0 END) [No. Beds Occupied at 09:00]
from CTE
GROUP BY OrderDate
This should allow you to check for any hour of the day using the #nHours parameter if you so choose. If you only want to see records that actually fall within your date range then you can filter the cross apply on start and end dates.

SQL - Conditional column selection in join

I am not sure if this scenario can be achieved using TSQL. I have a table called WorkingDays, which have this info
ID | EmployeeId | Monday | Tuesday | Wednesday | Thursday | Friday
----------------------------------------------------------------------
1 | 1 | 2 | 2 | 3 | 6 | 5
2 | 2 | 1 | 7 | 5 | 2 | 3
The days columns store Ids of WorkingSchedule table, which has this columns:
ID int Primary Key
StartTime time
EndTime time
So what I need id get the StartTime and EndTime of an employee depending on the current date.
What I need to get from query is the start and end time depending on the day. The day I want to filter is de current date (using getdate() function)
So need to select the correct day column name to make the join.
How can I achieve this scenario?
The dynamic sql version:
declare #sql nvarchar(max) ='
select
t.EmployeeId
, StarTime = max(case when t.rn=1 then '+quotename(datename(weekday,getdate()))+' end)
, EndTime = max(case when t.rn=2 then '+quotename(datename(weekday,getdate()))+' end)
from (
select *
, rn = row_number() over (partition by t.EmployeeId order by t.Id)
from t
) t
group by t.EmployeeId;'
exec sp_executesql #sql;
rextester demo: http://rextester.com/WNH34961
returns:
+------------+----------+---------+
| EmployeeId | StarTime | EndTime |
+------------+----------+---------+
| 1 | 5 | 3 |
+------------+----------+---------+
Depending on how you want the output, here are two other ways that do not use dynamic sql:
Both use cross apply() to unpivot the data, and WorkDay = datename(weekday,getdate()) to get the current WorkDay column.
For one row output we add some conditional aggregation:
/* one row per employeeId */
select
t.EmployeeId
, x.WorkDay
, StarTime = max(case when t.rn=1 then x.Time end)
, EndTime = max(case when t.rn=2 then x.Time end)
from (
select *
, rn = row_number() over (partition by t.EmployeeId order by t.Id)
from t
) t
cross apply (values
('Monday',Monday),('Tuesday',Tuesday),('Wednesday',Wednesday)
,('Thursday',Thursday),('Friday',Friday)
) x (WorkDay,Time)
where WorkDay = datename(weekday,getdate())
group by t.EmployeeId, x.WorkDay
returns:
+------------+---------+----------+---------+
| EmployeeId | WorkDay | StarTime | EndTime |
+------------+---------+----------+---------+
| 1 | Friday | 5 | 3 |
+------------+---------+----------+---------+
If you want the output on two rows, like your current output:
/* two rows per employeeId */
select
t.Id
, t.EmployeeId
, x.WorkDay
, t.StartEnd
, x.Time
from (
select *
, StartEnd = case
when row_number() over (partition by t.EmployeeId order by t.Id) = 1
then 'StartTime'
else 'EndTime'
end
from t
) t
cross apply (values
('Monday',Monday),('Tuesday',Tuesday),('Wednesday',Wednesday)
,('Thursday',Thursday),('Friday',Friday)
) x (WorkDay,Time)
where WorkDay = datename(weekday,getdate());
returns:
+----+------------+---------+-----------+------+
| Id | EmployeeId | WorkDay | StartEnd | Time |
+----+------------+---------+-----------+------+
| 1 | 1 | Friday | StartTime | 5 |
| 2 | 1 | Friday | EndTime | 3 |
+----+------------+---------+-----------+------+
select wd.Employee, ws.StartTime, ws.EndTime
from WorkingDays wd
join WorkingSchedule ws on ws.Id = case datename(weekday, getdate())
when 'Monday' then ws.Monday
when 'Tuesday' then ws.Tuesday
when 'Wednesday' then ws.Wednesday
when 'Thursday' then ws.Thursday
when 'Friday' then ws.Friday
else 0
end
Hint: datename(weekday, getdate()) returns you the weekday name in your current locale! This might be better:
select wd.Employee, ws.StartTime, ws.EndTime
from WorkingDays wd
join WorkingSchedule ws on ws.Id = case datepart(weekday, getdate())
when 1 then wd.Monday
when 2 then wd.Tuesday
when 3 then wd.Wednesday
when 4 then wd.Thursday
when 5 then wd.Friday
else 0
end
But then you have to check which day is the first of week (0, 1), depending on your settings.

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

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.

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