SQL Server 2008 Count(Distinct CASE? - sql

SELECT
ScheduleDays = COUNT(DISTINCT(CAST(datediff(d, 0, a.ApptStart) AS datetime)))
FROM
Appointments a
WHERE
ApptKind = 1 AND
--filter on current month
a.ApptStart >= ISNULL(DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0),'1/1/1900') AND
a.ApptStart < ISNULL(DATEADD(month, DATEDIFF(month, 0, GETDATE())+1, 0),'1/1/3000')AND
--filter all days that aren't Friday, and then give you all Fridays that have an hour > 12.
DATENAME(weekday, a.ApptStart) <> 'Friday' and DATEPART(hour, a.ApptStart) > 12 AND
--Filter on doctor
a.ResourceID in (201)
This query will look through appointment start times and not count Fridays as our Docs only work a half day on Fridays. I was told that we do want to count them, but only as half days (first time around I was told to exclude them lol).
Could someone please help me with a Case statement that will count Fridays that do not have an appointment after 12noon, as half a day? I believe it will have to go in the ScheduleDays=COUNT(DISTINCT(CAST(datediff(d,0,a.ApptStart) as datetime))). Perhaps we can put the Friday and after 12 filters in there instead of in the where clause if we are going to use case anyways. ScheduleDays=COUNT(DISTINCT CASE WHEN etc. I really appreciate the help.

You can't really count half things using count, so that is not the way to go. But, you can do it with arithmetic. I think something like this:
select (count(distinct (case when DATENAME(weekday, a.ApptStart) <> 'Friday'
then cast(a.apptstart as date)
end)
) +
0.5 * count(distinct (case when DATENAME(weekday, a.ApptStart) = 'Friday'
then cast(a.apptstart as date)
end)
)
) as ScheduleDays
If the docs only work on Fridays for half a day, I don't think you need to check for the time of the appointment. Of course, you can if you like by adding it into the second count.
Note that to count days, I used the simpler syntax of casting the datetime to a date.
EDIT:
With the hour check:
select (count(distinct (case when DATENAME(weekday, a.ApptStart) <> 'Friday'
then cast(a.apptstart as date)
end)
) +
0.5 * count(distinct (case when DATENAME(weekday, a.ApptStart) = 'Friday' and DATEPART(hour, a.ApptStart) <= 12
then cast(a.apptstart as date)
end)
)
) as ScheduleDays

Related

SQL Server Group a promotion by last 24 hours, last week and last month and sort by week descending

I'm trying to look at how successful different promotions have been in the last 24 hours, week and month. To get the amount by promotion for the last 24 hours I've used this code but I don't understand how to get another two columns for the last week and the last month. And then finally I want to order it by the amount in the last week descending. I want to be able to run this query at any point during the month. Please help me.
SELECT Promotion
, Sum(Amount) AS Last_24
FROM dbo.CustomerPayment
WHERE Started >= DATEADD(day, - 1, GETDATE())
GROUP
BY Promotion
You can do it in a single query :
SELECT Promotion
, Sum(CASE WHEN Started >= DATEADD(day, -1, GETDATE()) THEN Amount ELSE 0 END) AS Last_24
, Sum(CASE WHEN Started >= DATEADD(day, -7, GETDATE()) THEN Amount ELSE 0 END) AS Last_Week
, Sum(Amount) AS Last_Month
FROM dbo.CustomerPayment
WHERE Started >= DATEADD(day, - 31, GETDATE())
GROUP
BY Promotion
ORDER BY Last_Week DESC
Note that this part :
WHERE Started >= DATEADD(day, - 31, GETDATE())
as te be clarified following your own interpretation of "Last Month" concept.
Use conditional aggregation -- that is, move the conditions to the select:
SELECT Promotion,
SUM(case when Started >= DATEADD(day, - 1, GETDATE()) then Amount end) AS Last_1_day,
SUM(case when Started >= DATEADD(day, - 7, GETDATE()) then Amount end) AS Last_7_day,
. . .
FROM dbo.CustomerPayment
GROUP BY Promotion;
One possible issue, though. GETDATE() -- despite its name -- returns a time component to the date. I suspect that you might actually want to treat this as a date, not a datetime:
SELECT Promotion,
SUM(case when Started >= DATEADD(day, - 1, CONVERT(DATE, GETDATE())) then Amount end) AS Last_1_day,
SUM(case when Started >= DATEADD(day, - 7, CONVERT(DATE, GETDATE())) then Amount end) AS Last_7_day,
. . .
FROM dbo.CustomerPayment
GROUP BY Promotion;
I was looking for month / week. Not 7 / 30 days.
If you wish those, just use variables to have that query readable.
declare #monthstart date,
#weekstart date
;
select #monthstart=datefromparts(year(current_timestamp),month(current_timestamp),1)
select cast(DATEADD(d,1-DATEPART(WEEKDAY,current_timestamp),CURRENT_TIMESTAMP) as date) as Sunday,
cast(DATEADD(d,2-case when DATEPART(WEEKDAY,current_timestamp)=1 then 8 else DATEPART(WEEKDAY,current_timestamp) end,CURRENT_TIMESTAMP) as date) as Monday
;

Can anyone explain this sql query clearly

Query will exclude weekends. In the query:
What is the * 2 doing in (DATEDIFF(wk, Startdate,Enddate) * 2)
Here Saturday how it will be minus (Saturday not mentioned)
(CASE WHEN DATENAME(dw, Startdate) = 'Sunday' THEN 1 ELSE 0 END)
Here how Sunday will minus (Sunday not mentioned)
(CASE WHEN DATENAME(dw, Enddate) = 'Saturday' THEN 1 ELSE 0 END)
In date diff(wk,startdate,enddate)*2) here how weekends finding and excluding exactly.
I am thinking like( 20 days-(20/7=2.6*2=5.8(Round to 6)-0-0) briefly(20-6-0-0= 14 days) is correct. but how it exactly identifying sat and sun.
I am correct or not. My question is why multiplying with 2 why can't with 3 or 4?
Query is working correctly. I am missing things in the query to understand.
Please see the query and explain clearly.
SELECT
Startdate, Enddate,
(DATEDIFF(dd, Startdate, Enddate)) -
(DATEDIFF(wk, Startdate, Enddate) * 2) -
(CASE WHEN DATENAME(dw, Startdate) = 'Sunday' THEN 1 ELSE 0 END) -
(CASE WHEN DATENAME(dw, Enddate) = 'Saturday' THEN 1 ELSE 0 END) AS workingdays
FROM
EMP_Table;
Line by line:
First get the raw number of days between the dates:
(DATEDIFF(dd, Startdate,Enddate))
Subtract the number of weekend days, which will be the number of weeks times 2
-(DATEDIFF(wk, Startdate,Enddate )*2)
Adjust for the case where the start date is a Sunday
-(CASE WHEN DATENAME(dw, Startdate) = 'Sunday' THEN 1 ELSE 0 END)
Adjust for the case where the end date is a Saturday
-(CASE WHEN DATENAME(dw, Enddate) = 'Saturday' THEN 1 ELSE 0 END)
As pr DATEDIFF() will return the difference between two date by given interval,
and
DATENAME() will return specific part of the date which here dw will return weekday it may any day of week like Sunday, Monday etc
Now Execution of Query:
For example:
Startdate='2019/10/6' and Enddate='2019/10/12'
DATEDIFF(dd, Startdate, Enddate) will give 6
DATEDIFF(wk, Startdate, Enddate) will give 0 and there are 2 days of weekend so 0*2=0
CASE WHEN DATENAME(dw, Startdate) = 'Sunday' THEN 1 ELSE 0 END will give 1 because it's a Sunday and same as for Enddate it's a 1 due to Saturday
So final equation is workingdays=(6-0-1-1) so it give result as workingdays=4

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 2008 Count(Distinct?

ApptStart
2005-02-18 10:00:00.000
2005-02-18 13:00:00.000
2005-02-18 11:00:00.000
2005-02-21 09:00:00.000
2005-02-18 15:30:00.000
2005-02-18 14:30:00.000
.
.
.
I have a column in our database that looks like the above. I want to count appointments for the month for a given doc. On Fridays most of them do a half day. So I do not want to count Fridays with appointments only in the morning. If the appointment is in the afternoon, after 12:00:00.000 I want to include that day in the distinct count.
So far I have:
SELECT
ScheduleDays = count(distinct CONVERT(datetime, convert(char(12), a.ApptStart, 1)))
FROM Appointments a
WHERE
ApptKind = 1 AND
--filter on current month
a.ApptStart >= ISNULL(DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0),'1/1/1900') AND
a.ApptStart < ISNULL(DATEADD(month, DATEDIFF(month, 0, GETDATE())+1, 0),'1/1/3000') AND
--Filter on doctor
a.ResourceID in (201)
This worked when I counted every day with appointments on it, but like I said I need to exclude those half days.So I was thinking about only looking at the last right chars of the ApptStart and comparing it x > noon in a case inside the distinct count...
I tried the following, but it did not work:
ScheduleDays = count(distinct case when (Right(a.ApptStart, 12)) > '12:00:00:000' then 1 else 0 END)
Thanks in advance!
EDIT
I tried:
SELECT
ScheduleDays=COUNT(DISTINCT(CAST(datediff(d,0,a.ApptStart) as datetime)))
FROM Appointments a
WHERE
ApptKind = 1 AND
--filter on current month
a.ApptStart >= ISNULL(DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0),'1/1/1900') AND
a.ApptStart < ISNULL(DATEADD(month, DATEDIFF(month, 0, GETDATE())+1, 0),'1/1/3000')AND
--filter all days that aren't Friday, and then give you all Fridays with an hour > 12.
DATENAME(weekday, a.ApptStart) <> 'Friday' OR DATEPART(hour, a.ApptStart) > 12 AND
--Filter on doctor
a.ResourceID in (201)
for 1808 as the count
You probably want to treat dates as dates, rather than strings. You can determine whether a particular timestamp is on a Friday -- or the hour of a timestamp -- using DATEPART, without having to CONVERT it into a CHAR:
datename(weekday, timestamp_value) -- returns Friday
datepart(weekday, timestamp_value) -- returns either 5 or 6, depending on the value of SET DATEFIRST. (Get day of week in SQL 2005/2008)
datepart(hour, timestamp_value) -- returns hour part
Using these, you can test whether a timestamp is on Friday at or after noon by checking if datepart(weekday, timestamp_value) = 6 and datepart(hour, timestamp_value) >= 12.
bendataclear pointed out that you're using distinct on a case statement which can only ever return 0 or 1, so your total will only ever be 0, 1, or 2. If you're trying to determine which days the doctors worked more than half a day, you'll need to select distinct dates --
SELECT COUNT(DISTINCT(CAST(datediff(d,0,timestamp_value) as datetime)))
FROM table_name
WHERE DATENAME(weekday, timestamp_value) <> 'Friday' OR DATEPART(hour, timestamp_value) > 12
AND the rest of your filters here
The WHERE clause there will give you all days that aren't Friday, and then give you all Fridays that have an hour > 12.

SQL - Display running count of appointments grouped by time frame

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.