SQL - Display running count of appointments grouped by time frame - sql

I have a table
tblAppointment {
App_ID,
App_Date,
User_ID }
I currently have a statement that returns number of appointments grouped by year and month
SELECT
YEAR(App_Date) AS Year,
MONTH(App_Date) AS Month,
count(*) AS "No of Appointments"
FROM
tblapplication
GROUP BY
YEAR(App_Date),
MONTH(App_Date)
Im not sure how to write a select statement to return it with headings {time frame, No of applications}, and then have data in
row 1: time frame = week thus far, no of app = x.
row 2: time frame = month thus far, no of app = y.
ro3 3: time frame = year so far, no of app - z.
I would like to know how many appointments there are for 1. the current week, 2. the current month. 3. the current year, And have each result in its own row.
Any help in the right direction would be greatly appreciated. The actual problem is much greater than this but believe I have simplified it to the crux of the matter for now.

If you're okay with the results on one row, it's relatively easy:
select count(case when datepart(wk, App_Date) = datepart(wk, getdate())
then 1 end) as WeekSofFar
, count(case when datepart(m, App_Date) = datepart(m, getdate())
then 1 end) as MonthSofFar
, count(*) as YearSoFar
from tblApplications
where datepart(y, App_Date) = datepart(y, getdate())
If the separate rows are a must-have, try something like:
select 'WeekSoFar' as Period
, (
select count(*)
from tblApplications
where datepart(y, App_Date) = datepart(y, getdate())
and datepart(wk, App_Date) = datepart(wk, getdate())
) as NumberOfApps
union all
select 'MonthSoFar'
, (
select count(*)
from tblApplications
where datepart(y, App_Date) = datepart(y, getdate())
and datepart(m, App_Date) = datepart(m, getdate())
)
union all
select 'YearSoFar'
, (
select count(*)
from tblApplications
where datepart(y, App_Date) = datepart(y, getdate())
)

I assumed SQL 2008 as you didn't specify.
SELECT
X.TimePeriod,
Count(
CASE X.Which
WHEN 3 THEN
CASE
WHEN App_Date > DateAdd(Day, -DatePart(Weekday, GetDate()), GetDate())
THEN 1
END
WHEN 2 THEN CASE WHEN Month(App_Date) = Month(GetDate()) THEN 1 END
ELSE 1 END
) [No of Appointments]
FROM
tblApplication
CROSS JOIN (
VALUES (1, 'Year to date'), (2, 'Month to date'), (3, 'Week to date')
) X (Which, TimePeriod)
WHERE
App_Date < Convert(date, GetDate() + 1)
AND App_Date >= DateAdd(Year, DateDiff(Year, '19000101', App_Date), '19000101')
GROUP BY
X.Which,
X.TimePeriod
ORDER BY
X.Which
If you have a lot of data in your table and an index on App_Date, this query will perform hugely better than one using date functions on the entire table without filtering.
I also have an embedded assumption that your App_Date values have no time portion (they are all set to 12am). If this is not true please let me know so I can modify case 3 to be correct.
If anyone wants to try the code here's some setup:
CREATE TABLE tblApplication (
App_Date datetime
)
INSERT tblApplication
VALUES
('2/1/2012'), ('2/5/2012'), ('2/10/2012'), ('1/2/2012'), ('1/9/2012'),
('1/15/2012'), ('1/28/2012'), ('12/1/2012'), ('12/5/2012'), ('12/10/2012'),
('11/2/2012'), ('11/9/2012'), ('11/15/2012'), ('11/28/2012')
Sorry about not using 'YYYYMMDD', I wasn't thinking when I typed it out.

Related

Roll weekend counts into monday counts

I have a query like this:
select date, count(*)
from inflow
where date >= dateadd(year, -2, getdate())
group by date
order by date
I need to exclude Saturday and Sunday dates, and instead add their counts into the following Monday. What would be the best way to do this? Do I need to exclude Saturday, Sunday, and Mondays, then add them on with a join to a different query? The query above is a simplified version, this is a relatively big query, so I need to keep efficiency in mind.
Well, this is a somewhat brute-force approach:
select date,
(case when datename(weekday, date) = 'Monday')
then cnt + cnt1 + cnt2
else cnt
end) as cnt
from (select date, count(*) as cnt,
lag(count(*), 1, 0) over (order by date) as prev_cnt,
lag(count(*), 2, 0) over (order by date) as prev_cnt2
from inflow
where date >= dateadd(year, -2, getdate())
group by date
) d
where datename(weekday, date) not in ('Saturday', 'Sunday')
order by date;
Note: This is assuming English-language settings so the datename() logic works.
An alternative method without subqueries;
select v.dte, count(*) as cnt
from inflow i cross apply
(values (case when datename(weekday, i.date) = 'Saturday'
then dateadd(day, 2, i.date)
when datename(weekday, i.date) = 'Sunday'
then dateadd(day, 1, 9.date)
else i.date
end)
) v.dte
where i.date >= dateadd(year, -2, getdate())
group by v.dte
order by date;
You state for performance, however without knowing the full picture it's quite hard to understand how to optimise the query.
While I've been working on this, I noticed Gordon Linoff's answer, however I'll continue to write my version up as well, we both following the same path, but get to the answer a little different.
WITH DateData (date, datetoapply)
AS
(
SELECT
[date],
CASE DATEPART(w, [date])
WHEN 5 THEN DATEADD(d, 2, [date])
WHEN 6 THEN DATEADD(d, 1, [date])
ELSE date
END as 'datetoapply'
FROM inflow
WHERE [date] >= dateadd(year, -2, getdate())
)
SELECT datetoapply, count(*)
FROM DateData
GROUP BY datetoapply
ORDER BY datetoapply
While I could not get Gordon's query working as expected, I can confirm that "DATEPART(w, [date])" performs much better than "DATENAME(weekday, [date])", which if replaced in the query above increases the server processing time from 87ms to 181ms based on a table populated with 10k rows in Azure.

SQL Server - Null row not Showing?

Is there a way how to add null values rows, replacing with 0?
Ideally,I want to show data of all Month, if there is no data then count column set to zero.
Currently SQL query is not Returning Empty Rows and I'm getting output like this...
I'm using the code below to get the result set below that:
SELECT
DATENAME(MONTH,A.Date) 'Month', COUNT(LateStatus) 'Count', 'Late' 'Type'
FROM
Attendance1 A
WHERE
A.EnrollId = '10000' AND A.Date BETWEEN '2016-01-01' AND '2016-08-31' AND LateStatus = 'L'
GROUP BY
DATENAME(MONTH,A.Date)
Use a recursive CTE (dynamically makes a calendar table):
With Months(mon) As
(Select dateAdd(month, datediff(month, 365, getdate()), 0)
Union All
Select DateAdd(month, 1, mon) from Months
Where mon < getdate())
Select DATENAME(MONTH, m.mon ) 'Month',
COUNT(LateStatus) 'Count', 'Late' 'Type'
FROM months m
left join Attendance1 a
on a.Date between m.mon and dateadd(month, 1, m.mon)
and a.EnrollId = '10000'
and a.LateStatus = 'L'
GROUP BY DATENAME(MONTH, m.Mon)
A more generic function, to generate a range of integers which can be used for any expression, is this:
create function dbo.NumberList (#start integer, #end integer)
returns table
as Return
(
With ints(aInt) As
(Select #start Union All
Select aInt + 1 From ints
Where aInt < #end)
Select aInt from ints
)
Once this function has been created, you can use it to dynamically create any range of values of any type:
For months from 8 months ago to 3 months ago
Select i.aInt, Dateadd(month, i.aInt,
DateAdd(month, datediff(month(0, getdate()), 0))
From dbo.NumberList(-8, -3) i
Assuming you have some data for all months, you can do this using conditional aggregation. That is, move the WHERE conditions to the SELECT using CASE:
SELECT DATENAME(MONTH,A.Date) as Mon,
SUM(CASE WHEN A.EnrollId = '10000' AND LateStatus = 'L'
THEN 1 ELSE 0
END) as cnt,
'Late' as 'Type'
FROM Attendance1 A
WHERE AND A.Date BETWEEN '2016-01-01' AND '2016-08-31'
GROUP BY DATENAME(MONTH, A.Date)
ORDER BY MIN(A.Date);
There are other methods, usually involving LEFT JOIN. But this is the simplest if it solves the problem.

SQL Query to show the same data but based on different dates in the same view

I have an SQL query that I use to show the # of work orders created by day within a designated date range. It works well when I query a single date range, such as 3/1/2016 to 3/31/2016.
SELECT DATEADD(dd, DATEDIFF(dd, 0, dateCreated), 0) the_date,
COUNT(*) work_order_count
FROM WorkOrder
where active = 1 and
dateCreated >= '3/1/2016' and
dateCreated <= '3/31/2016 23:59:59'
GROUP BY DATEADD(dd, DATEDIFF(dd, 0, dateCreated), 0)
order by 1;
I want to take this same query a step further so that I can query the same data but show results for multiple date ranges. My specific purpose is to show data side-by-side for previous year comparison. Ideally I want to show the # of work orders created for 3/1-3/31 in 2014, 2015 and 2016, but all within the same view/result.
Is this possible? I've looked into joins but they appear to be when you are using different tables, not the same one.
Just use conditional aggregation (or a pivot):
SELECT CAST(dateCreated as DATE) as the_date,
SUM(CASE WHEN YEAR(dateCreated) = 2014 THEN 1 ELSE 0 END) as cnt_2014,
SUM(CASE WHEN YEAR(dateCreated) = 2015 THEN 1 ELSE 0 END) as cnt_2015,
SUM(CASE WHEN YEAR(dateCreated) = 2016 THEN 1 ELSE 0 END) as cnt_2016
FROM WorkOrder
WHERE active = 1 and
MONTH(dateCreated) = 3 and
YEAR(dateCreated) in (2014, 2015, 2016)
GROUP BY CAST(dateCreated as DATE)
ORDER BY the_date;
Use pivot table:
;with cte1 as (
select dateCreated, datepart(year, dateCreated) as Y
from WorkOrder
where datepart(month, dateCreated) = 3 and datepart(day, dateCreated) between 5 and 25
)
SELECT *
FROM cte1
PIVOT
(
count(dateCreated)
FOR Y in ([2014], [2015], [2016])
) as pv
How about something like:
SELECT
SUM(
CASE
WHEN dateCreated BETWEEN '3/1/2014' AND '3/31/2014 23:59:59'
THEN 1
ELSE 0
END) AS March2014,
SUM(
CASE
WHEN dateCreated BETWEEN '3/1/2015' AND '3/31/2015 23:59:59'
THEN 1
ELSE 0
END) AS March2015,
SUM(
CASE
WHEN dateCreated BETWEEN '3/1/2016' AND '3/31/2016 23:59:59'
THEN 1
ELSE 0
END) AS March2016
FROM WorkOrder
WHERE active = 1;
SELECT datepart(dd, 'yyyy'), datepart(dd, 'MM'), datepart(dd, 'dd'),
COUNT(*) work_order_count
FROM WorkOrder
where active = 1
and dateCreated >= '3/1/2016'
and dateCreated < '4/1/2020'
GROUP BY datepart(dd, 'yyyy'), datepart(dd, 'MM'), datepart(dd, 'dd')
order by datepart(dd, 'yyyy'), datepart(dd, 'MM'), datepart(dd, 'dd');

How to add a column for just current month data to a multimonth query?

I have a simple query:-
SELECT *
FROM dbo.NGPCostPosition
That returns the below data:-
I want to try and display a total cost for just the current month while still having access to all other data so maybe a new column called current month that would only be populated by items that fall into that category?
What is the best way to do this?
All advice welcome and appreciated.
This should add an extra column to the query just showing current month total costs.
SELECT
*,
CASE
WHEN
DATEPART(MOTNH, TranDate) = DATEPART(MONTH, GETDATE()) AND
DATEPART(YEAR, TranDate) = DATEPART(YEAR, GETDATE())
THEN TotalCost
ELSE 0
END CurrentMonthCost
FROM dbo.NGPCostPosition
You could add a column to your query like so:
Select *,
CASE WHEN
datepart(mm, getdate()) == datepart(mm, TranDate)
and datepart(yy, getdate()) == datepart(yy, tranDate)
then TotalCost
else 0
end as CurrentMonthTotalCost
And then sum that up somewhere in your sheet. You could also use date formatting (I don't prefer these as they're a bit of a cipher as to what you're comparing):
Select *,
CASE
WHEN CONVERT(VARCHAR(7), GETDATE(), 111) == CONVERT(VARCHAR(7), TRANDATE, 111)
THEN TotalCost
ELSE 0
End as CurrentMonthTotalCost
try this:
SELECT doctype, projectNo, CostCat, [all other columns etc.],
(Select Sum(Quantity * UnitCost) From NGPCostPosition
Where transDate >= DateAdd(month,
datediff(month,0,p.Transdate), 0)
And transDate < DateAdd(month,
datediff(month,0, p.Transdate), 31)) MonthlyTotal
FROM NGPCostPosition p

What is the most efficient way to create an order count summary by hour, day, month in SQL Server 2005?

Given a table:
create table #orders (
orderid int,
orderdatetime datetime
)
What is the best way to write sql to output a report containing the count of orders from the current and previous 24 hours, total orders for the current day and previous 7 days, total orders for the current week and previous 4 weeks, and total orders for the month and previous 6 months?
I'm wondering if this can be efficiently rolled up into a single sql using analytical functions, or if 4 sql statements generating the 4 groups of data is the only (or best) way.
Also, given the hourly/day/week grouping, how would one do that in sql server? Datetimes seem to be a pain in the ass everytime I have to do something like this with them...
Ideas? Put into a SSAS cube and do it from there maybe?
SELECT DATEPART(month, orderdatetime), DATEPART(week, orderdatetime), DATEPART(day, orderdatetime), COUNT(*)
FROM #orders
GROUP BY
DATEPART(month, orderdatetime), DATEPART(week, orderdatetime), DATEPART(day, orderdatetime) WITH ROLLUP
This will group the COUNT's by day, week and month in a single query.
The week rollups will have a NULL in DATEPART(day, orderdatetime) column, the month rollups will have a NULL in both DATEPART(day, orderdatetime) and DATEPART(week, orderdatetime) columns.
To make it for every hour, day, week or month from the current without gaps, use CTE's:
WITH q_hours AS
(
SELECT 0 AS col_hour
UNION ALL
SELECT col_hour + 1
FROM q_hours
WHERE col_hour < 22
),
q_days AS
(
SELECT 0 AS col_day
UNION ALL
SELECT col_day + 1
FROM q_days
WHERE col_day < 31
),
q_months AS
(
SELECT 0 AS col_month
UNION ALL
SELECT col_month + 1
FROM q_months
WHERE col_month < 12
)
SELECT col_month, col_day, col_hour, COUNT(orderid)
FROM q_hours
CROSS JOIN
q_days
CROSS JOIN
q_months
LEFT JOIN
#orders
ON DATEDIFF(month, orderdatetime, GETDATE()) = col_month
AND DATEDIFF(day, orderdatetime, GETDATE()) % 31 = col_day
AND DATEDIFF(hour, orderdatetime, GETDATE()) % 24 = col_hour
GROUP BY
col_month, col_day, col_hour WITH ROLLUP
HAVING (
col_month = 0
AND col_day = 0
AND col_hour IS NOT NULL
) -- all hours within 24 hours from now
OR
(
col_month = 0
AND col_day <= 7
AND col_hour IS NULL
) -- all days within 7 days from now
OR
(
col_month <= 6
AND col_day IS NULL
AND col_hour IS NULL
) -- all months within 6 months from now
You could run the four selects from a "dummy table" or an "identity" table that consists of a single row.
You could have:
SELECT
(<query count of orders current/prev 24 hours>) as <name1>,
(<total orders current + 7 days>) as <name2>,
(<total orders current week + 4 weeks>) as <name3>,
(<total orders month + 6 months>) as <name4>
FROM
<IDENTITY table>;
Because you want different timeframes for each datepart type, using a single query with rollup probably won't give you what you want. I'd consider just unioning them all together similar to something like this...
SELECT DatePartValue = DATEPART(HH, orderdatetime),
Type = 'Hourly',
COUNT(*)
FROM #orders
WHERE orderdatetime > DATEADD(HH, -25, GETDATE())
GROUP BY DATEPART(HH, orderdatetime)
UNION
SELECT DATEPART(DD, orderdatetime),
Type = 'Daily',
COUNT(*)
FROM #orders
WHERE orderdatetime > DATEADD(DD, -8, GETDATE())
GROUP BY DATEPART(DD, orderdatetime)
UNION
SELECT DATEPART(WEEK, orderdatetime),
Type = 'Weekly',
COUNT(*)
FROM #orders
WHERE orderdatetime > DATEADD(WEEK, -5, GETDATE())
GROUP BY DATEPART(WEEK, orderdatetime)
ORDER BY Type, DatePartValue
UNION
SELECT DATEPART(MM, orderdatetime),
Type = 'Monthly',
COUNT(*)
FROM #orders
WHERE orderdatetime > DATEADD(MM, -7, GETDATE())
GROUP BY DATEPART(MM, orderdatetime)
ORDER BY Type, DatePartValue
For results in one row, something like this:
select
orders_day = sum(case when datediff(hour,orderdatetime,getdate()) < 24 then 1 else 0 end)
, orders_week = sum(case when datediff(day,orderdatetime,getdate()) < 7 then 1 else 0 end)
, orders_month = sum(case when datediff(week,orderdatetime,getdate()) < 4 then 1 else 0 end)
, orders_half = sum(case when datediff(month,orderdatetime,getdate()) < 6 then 1 else 0 end)
from #orders
You may want to fine tune the date criteria to get appropriate behavior.
For multiple rows, take the results above and transpose it with UNPIVOT or CASE .. CROSS JOIN.
SELECT
sum(case when orderdatetime between GetDate() - 1 and GetDate() then 1 else 0 end) as Current24Hours,
sum(case when orderdatetime between GetDate() - 2 and GetDate() - 1 then 1 else 0 end) as Previous24Hours,
sum(case when orderdatetime between GetDate() - 7 and GetDate() then 1 else 0 end) as Current7Days,
sum(case when orderdatetime between GetDate() - 14 and GetDate() - 7 then 1 else 0 end) as Previous7Days,
sum(case when DATEDIFF (m, OrderDate, #now) <= 1 then 1 else 0 end) as PreviousMonth,
sum(case when DATEDIFF (m, OrderDate, #now) <= 6 then 1 else 0 end) as PreviousSixMonths
FROM orders
I think you want grouping sets. I've understand that sql server supports grouping sets.
EDIT1: I've read that sql server 2005 doesn't support grouping sets but sql server 2008 does. Here an interesting read on a presumed but not existing difference between mapreduce and an rdbms like Oracle and Sql Server. Please read the comments too!! http://www.data-miners.com/blog/2008/01/mapreduce-and-sql-aggregations.html