Running a select statement for each case - sql

I'm a complete SQL novice. I want to display a simple grid.....
Sign up......>30.....30-60.....60-90.....>90.....Total
Feb...............4..........30........... 6 ......... 0 .......40
Mar ............. 0 .........11 ...........1 ..........4 .......16
Apr
May etc
Jun etc
Where total represents the total of number of customer sign ups per month, and where the likes of '30-60' represents the number sign ups on the date 30-60 days since today's date
The query I am using is as follows......
SELECT case MONTH(Datecreated)
when 2 then 'Febrary'
when 3 then 'March'
when 4 then 'April'
when 5 then 'May'
when 6 then 'June'
end as 'Signup Month',
(select count(*) from WS_USER_DETAILS where datediff(day, datecreated, GETUTCDATE()) < 30 ) as '<= 30 days',
(select count(*) from WS_USER_DETAILS where datediff(day, datecreated, GETUTCDATE()) between 30 and 60) as '<= 60 days',
(select count(*) from WS_USER_DETAILS where datediff(day, datecreated, GETUTCDATE()) between 60 and 90) as '<= 90 days',
(select count(*) from WS_USER_DETAILS where datediff(day, datecreated, GETUTCDATE()) >90) as '<= 120 days',
count(userid) AS 'Total' FROM WS_USER_DETAILS
group by MONTH(Datecreated)
The problem I have is....
I want to run the select statements for each month. However it populates all months. For example......
Sign up......>30.....30-60.....60-90.....>90.....Total
Feb...............0..........11........... 1 ......... 4 .......40
Mar ............. 0 .........11 ...........1 ..........4 .......16
If possible can someone advise me on how to run the select statements for each month? Thanks

First select the time span per record, then group and count. Be careful not to have overlapping ranges.
select
monthname,
isnull(sum(lessthan30),0) as lt30,
isnull(sum(between30and59),0) as btw30a59,
isnull(sum(between60and89),0) as btw60a89,
isnull(sum(between90and119),0) as btw90a119,
isnull(sum(morethan119),0) as mt119,
isnull(sum(lessthan30),0) + isnull(sum(between30and59),0) + isnull(sum(between60and89),0) + isnull(sum(between90and119),0) + isnull(sum(morethan119),0) as total
from
(
select
month(datecreated) as mon,
datename(month,datecreated) as monthname,
case when datediff(day, datecreated, GETUTCDATE()) < 30 then 1 else 0 end as lessthan30,
case when datediff(day, datecreated, GETUTCDATE()) between 30 and 59 then 1 else 0 end as between30and59,
case when datediff(day, datecreated, GETUTCDATE()) between 60 and 89 then 1 else 0 end as between60and89,
case when datediff(day, datecreated, GETUTCDATE()) between 90 and 119 then 1 else 0 end as between90and119,
case when datediff(day, datecreated, GETUTCDATE()) >= 120 then 1 else 0 end as morethan119
from WS_USER_DETAILS
) as dummy
group by monthname, mon
order by mon;

I think something like this would fit your needs better. You can use Sum Function and a case statement to work like a count if.
SELECT
DATENAME(MM,Datecreated), --This Function automatically gets the name of the month
--Here is where you can use case statements like a count if.
SUM(CASE WHEN DATEDIFF(dd, datecreated, GETUTCDATE()) < 30 THEN 1 ELSE 0 END) '<= 30 days',
SUM(CASE WHEN DATEDIFF(dd, datecreated, GETUTCDATE()) < 60 THEN 1 ELSE 0 END) '<= 60 days',
SUM(CASE WHEN DATEDIFF(dd, datecreated, GETUTCDATE()) < 90 THEN 1 ELSE 0 END) '<= 90 days',
SUM(CASE WHEN DATEDIFF(dd, datecreated, GETUTCDATE()) < 120 THEN 1 ELSE 0 END) '<= 120 days'
FROM WS_USER_DETAILS
GROUP BY Datepart(MM, Datecreated)
Sources:
http://msdn.microsoft.com/en-us/library/ms189794.aspx
http://msdn.microsoft.com/en-us/library/ms174420.aspx
http://msdn.microsoft.com/en-us/library/ms174420.aspx

Based on #ThorstenKettner but adding performance by removing function calls and adding sergability
declare #today date
declare #_30 date
declare #_31 date
declare #_59 date
declare #_60 date
declare #_89 date
declare #_90 date
declare #_119 date
declare #_120 date
set #today = GETUTCDATE()
set #_30 = datediff(day, #today, 30)
set #_31 = datediff(day, #today, 31)
set #_59 = datediff(day, #today, 59)
set #_60 = datediff(day, #today, 60)
set #_89 = datediff(day, #today, 89)
set #_90 = datediff(day, #today, 90)
set #_119 = datediff(day, #today, 119)
set #_120 = datediff(day, #today, 120)
select
monthname,
isnull(sum(lessthan30),0) as lt30,
isnull(sum(between30and59),0) as btw30a59,
isnull(sum(between60and89),0) as btw60a89,
isnull(sum(between90and119),0) as btw90a119,
isnull(sum(morethan119),0) as mt119,
isnull(sum(lessthan30),0) + isnull(sum(between30and59),0) + isnull(sum(between60and89),0) + isnull(sum(between90and119),0) + isnull(sum(morethan119),0) as total
from
(
select
month(datecreated) as mon,
datename(month,datecreated) as monthname,
case when datecreated < #_30 then 1 else 0 end as lessthan30,
case when datecreated between #_30 and #_59 then 1 else 0 end as between30and59,
case when datecreated between #_60 and #_89 then 1 else 0 end as between60and89,
case when datecreated between #_90 and #_119 then 1 else 0 end as between90and119,
case when datecreated >= #_120 then 1 else 0 end as morethan119
from WS_USER_DETAILS
)as dummy
group by monthname, mon
order by mon;

Related

simplify a SQL case statement in a case expression

How would I simplify this case statement in T-SQL? It provides the desired result, but it's very unwieldy and hard to read. I have to use the inner case statement to convert a Julian date (aka 6 digit number) into a regular date format.
Basically i'm doing a datediff( getdate(), case statement). Getdate() just returns the time now (ie. 2/27/2020) and the case statement converts a julian date (ie. 123456) into a normal date (ie, 1/1/2020).
Here's the expect output if the query was ran today on Feb 27.
Select CASE
WHEN Datediff(day, Getdate(), CASE
WHEN a.wadpl = 0
THEN NULL
ELSE Dateadd(d, Substring(Cast(wadpl AS VARCHAR(6)), 4, 3) - 1, CONVERT(DATETIME, CASE
WHEN LEFT(Cast(wadpl AS VARCHAR(6)), 1) = '1'
THEN '20'
ELSE '21'
END + Substring(Cast(wadpl AS VARCHAR(6)), 2, 2) + '-01-01'))
END) < 0
THEN 'Overdue Now'
WHEN Datediff(day, Getdate(), CASE
WHEN a.wadpl = 0
THEN NULL
ELSE Dateadd(d, Substring(Cast(wadpl AS VARCHAR(6)), 4, 3) - 1, CONVERT(DATETIME, CASE
WHEN LEFT(Cast(wadpl AS VARCHAR(6)), 1) = '1'
THEN '20'
ELSE '21'
END + Substring(Cast(wadpl AS VARCHAR(6)), 2, 2) + '-01-01'))
END) <= 30
THEN 'Coming due in 01-30 days'
ELSE 'Not Overdue'
END [Overdue Status]
FROM Table_X
Here is a easy one to understand, assuming a.wadpl is an integer:
SELECT CASE
WHEN DATEDIFF(DAY, GETDATE(), DATEADD(DAY, a.wadpl % 1000, DATEADD(YEAR,a.wadpl / 1000,'1899-12-31'))) <0 THEN 'Overdue now'
WHEN DATEDIFF(DAY, GETDATE(), DATEADD(DAY, a.wadpl % 1000, DATEADD(YEAR,a.wadpl / 1000,'1899-12-31'))) <= 30 THEN 'Coming due in 01-30 days'
ELSE 'Not Overdue'
END [Overdue Status]
FROM Table_X
or you can simplify by using a subquery (or you can use a WITH):
SELECT CASE
WHEN Age <0 THEN 'Overdue now'
WHEN Age <= 30 THEN 'Coming due in 01-30 days'
ELSE 'Not Overdue'
END [Overdue Status]
FROM (
SELECT DATEDIFF(DAY,GETDATE(),
DATEADD(DAY,wadpl%1000,DATEADD(YEAR,wadpl/1000,'1899-12-31'))) Age, *
FROM Table_X) a
This will of course cause you to do this arithmetic for each row, and you can't easily use any indexes. If you were asking about aggregates, then I would suggest doing the opposite, and pre-calculating the dates and use those in your query instead. You might also want to consider putting a persisted computed column on table_x:
ALTER TABLE TABLE_X
ADD wadpl_dt AS
(DATEADD(DAY,wadpl%1000,DATEADD(YEAR,wadpl/1000,'1899-12-31'))) PERSISTED;
Now you can just refer to table_x.wadpl_dt whenever you want the datetime, and your query would become:
SELECT CASE
WHEN Age <0 THEN 'Overdue now'
WHEN Age <= 30 THEN 'Coming due in 01-30 days'
ELSE 'Not Overdue'
END [Overdue Status]
FROM (
SELECT DATEDIFF(DAY,GETDATE(), wadpl_dt) Age, *
FROM Table_X) a
Here is the easy way to convert a date to what you refer to as the julian date:
SELECT (DATEPART(YEAR,GETDATE())-1900) * 1000 + DATEPART(DAYOFYEAR, GETDATE())
And this is how you can use it:
DECLARE #overdue int;
DECLARE #next30 int;
SET #overdue = (SELECT (DATEPART(YEAR,GETDATE())-1900) * 1000 + DATEPART(DAYOFYEAR, GETDATE()));
SET #next30 = (SELECT (DATEPART(YEAR,GETDATE()+30)-1900) * 1000 + DATEPART(DAYOFYEAR, GETDATE()+30));
SELECT CASE
WHEN wadpl < #overdue THEN 'Overdue now'
WHEN wadpl <= #next30 THEN 'Coming due in 01-30 days'
ELSE 'Not Overdue'
END [Overdue Status]
FROM Table_X

Function to exclude Saturdays and Sundays

I created a "Daily Sales Query" that captures all the Total Sales entered from the previous work day which runs Mon-Fri at 8AM.
Question is, if it is Monday today, how can I capture the records from Friday.
so that I can exclude weekends.
Because if it is Monday, the total sales displays 0 which actually makes sense because Sunday is a not a work day. Please assist.
See my current code:
SELECT
CONVERT(VARCHAR, DATEADD(dd, - 1, GETDATE()), 103) AS Date,
'Sales Orders' AS Type,
COUNT(o.SalesOrderID) AS Orders,
SUM(d.QtyOrdered) AS Chairs,
ISNULL(ROUND(SUM(d.ExtendedPrice), 2), 0) AS [Total Ex GST]
FROM
dbo.SalesOrder o
LEFT OUTER JOIN
dbo.SalesOrderDetails d ON o.SalesOrderID = d.SalesOrderID
WHERE
(o.EntryDate >= CONVERT(CHAR(8), DATEADD(dd, - 1, GETDATE()), 112))
AND (o.EntryDate < CONVERT(CHAR(8), GETDATE(), 112))
AND (o.CustomerID <> 187);
You can use datediff(dd,0,getdate()) % 7 = 0 to determine if the current date is a Monday regardless of any other server settings (this is because the zero date in SQL Server is 1900-01-01 which happens to be a Monday).
declare #start date;
declare #finish date;
set #start = dateadd(dd, case when datediff(dd,0,getdate()) % 7 = 0 then -3 else -1 end, getdate());
set #finish = dateadd(dd,1,#start);
select
#start, datename(weekday,#start)
, #finish, datename(weekday,#finish)
, datename(weekday,getdate())
;
So in your query I would use:
declare #start date;
declare #finish date;
set #start = dateadd(dd, case when datediff(dd,0,getdate()) % 7 = 0 then -3 else -1 end, getdate());
set #finish = dateadd(dd,1,#start);
SELECT
CONVERT(VARCHAR, #start, 103) AS Date,
'Sales Orders' AS Type,
COUNT(o.SalesOrderID) AS Orders,
SUM(d.QtyOrdered) AS Chairs,
ISNULL(ROUND(SUM(d.ExtendedPrice), 2), 0) AS [Total Ex GST]
FROM
dbo.SalesOrder o
LEFT OUTER JOIN
dbo.SalesOrderDetails d ON o.SalesOrderID = d.SalesOrderID
WHERE
o.EntryDate >= #start
AND o.EntryDate < #finish
AND o.CustomerID <> 187
;
If you use a case statement to determine how many days in the past you need to go e.g.
dateadd(dd, case when datepart(weekday,getdate()) = 1 then -3 else -1 end, getdate()) -- StartDate
dateadd(dd, case when datepart(weekday,getdate()) = 1 then -2 else 0 end, getdate()) -- EndDate
so your code would look like
SELECT
CONVERT(VARCHAR, dateadd(dd, case when datepart(weekday,getdate()) = 1 then -3 else -1 end, getdate()), 103) AS Date,
'Sales Orders' AS Type,
COUNT(o.SalesOrderID) AS Orders,
SUM(d.QtyOrdered) AS Chairs,
ISNULL(ROUND(SUM(d.ExtendedPrice), 2), 0) AS [Total Ex GST]
FROM
dbo.SalesOrder o
LEFT OUTER JOIN
dbo.SalesOrderDetails d ON o.SalesOrderID = d.SalesOrderID
WHERE
(o.EntryDate >= CONVERT(CHAR(8), dateadd(dd, case when datepart(weekday,getdate()) = 1 then -3 else -1 end, getdate()), 112))
AND (o.EntryDate < CONVERT(CHAR(8), dateadd(dd, case when datepart(weekday,getdate()) = 1 then -2 else 0 end, getdate()), 112))
AND (o.CustomerID <> 187);
PS: Do confirm that weekday 1 is Monday on your server.

Calculate total and elapsed number of working days in the month for a given date

I am stuck with a SQL query.
I am trying to calculate two different things in a same query:
Number of business days in a month (this will exclude weekends).
How many days working days have been passed in a month.
Let's say for November (as on 11/9/2018)
no.of business days no. of business days passed
22 7
I tried like this :
WITH cteAllDates AS
(
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SET #StartDate = '10/01/2018'
SET #EndDate = '10/31/2018'
SELECT
(DATEDIFF(dd, #StartDate, #EndDate) + 1)
- (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 x
) AS y
SELECT x
FROM cteAllDates
I would like to create a virtual table so that I can use these fields in my complete query. And if its possible I can do this with GETDATE() and not to declare dates every time.
Since you want to do it in a CTE, and based on the earlier answer you found on SO, here is a version that does it all for the current month without needing to define start and end dates:
EDIT: To create a holiday table
First, create a holiday table. Don't do this every time, make it a persistent table, and keep it filled up with all holidays you need - easter, xmas etc.
create table holidays(holiday date)
insert holidays values ('2018-09-23'),('2018-09-24')
Now the query, including the check for number of holidays between the dates
;with dates as(
select dateadd(d,-day(getdate())+1,convert(date,getdate())) as startofmonth,
dateadd(d,-1,dateadd(m,1,dateadd(d,-day(getdate())+1,convert(date,getdate())))) as endofmonth,
convert(date,getdate()) as today
)
,holidaycount as (
select count(*) as holidaysinmonth,
sum(case when holiday<=today then 1 else 0 end) as holidaystodate
from dates
join holidays on holiday between startofmonth and endofmonth
)
,daycounts as(
select dates.*,
(DATEDIFF(dd, startofmonth, endofmonth) + 1)
-(DATEDIFF(wk, startofmonth, endofmonth) * 2)
-(CASE WHEN DATENAME(dw, startofmonth) = 'Sunday' THEN 1 ELSE 0 END)
-(CASE WHEN DATENAME(dw, endofmonth) = 'Saturday' THEN 1 ELSE 0 END)
-isnull(holidaysinmonth,0) as wkdaysinmonth,
(DATEDIFF(dd, startofmonth, today) + 1)
-(DATEDIFF(wk, startofmonth, today) * 2)
-(CASE WHEN DATENAME(dw, startofmonth) = 'Sunday' THEN 1 ELSE 0 END)
-(CASE WHEN DATENAME(dw, today) = 'Saturday' THEN 1 ELSE 0 END)
-isnull(holidaystodate,0) as wkdaystodate
from dates
cross join holidaycount
)
select * from daycounts
EDIT: If you cant create a temp table, add this as an additional CTE before the holidaycount one like so:
,holidays as (
select holiday from (values ('2018-11-23'),('2018-11-24')) t(holiday)
)
,holidaycount as (
You can try to use cte recursive make a calendar table, then use condition aggregate function to get your result.
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SET #StartDate = DATEADD(DAY, 1, EOMONTH(GETDATE(), -1));
SET #EndDate = DATEADD(DAY, 1, EOMONTH(GETDATE()));
;WITH CTE AS (
select #StartDate startdt,#EndDate enddt
UNION ALL
SELECT DATEADD (day ,1 , startdt) , #EndDate
FROM CTE
WHERE DATEADD (day,1,startdt) <= #EndDate
)
select SUM(CASE WHEN DATENAME(dw, startdt) NOT IN ('Sunday','Saturday') THEN 1 END) 'no.of business days',
SUM(CASE WHEN DATENAME(dw, startdt) NOT IN ('Sunday','Saturday') AND GETDATE() >= startdt THEN 1 END) 'no. of business days passed'
FROM CTE
sqlfiddle
Result
no.of business days no. of business days passed
22 7

COUNT total by weekly and monthly SQL

I have a table in which I have to count total rows assigned to each USER by daily, weekly and monthly.
Table BooksIssued
BOOKID ISSUEDUSER DATE
1 A 20160708
2 A 20160709
3 A 20160708
4 A 20150102
5 B 20160709
6 C 20160708
7 C 20160708
Now I have to COUNT daily, weekly and monthly books issued to each user
Daily is today (20160709)
Weekly is Sunday through Saturday
Monthly is whole month
The result should be
ISSUEDUSER DAILYBOOKS WEEKLYBOOKS MONTHLYBOOKS
A 1 3 3
B 1 1 1
C 0 2 2
I have done this SQL for daily issued
SELECT ISSUEDUSER, COUNT(BOOKID) AS DAILYBOOKS
FROM BOOKSISSUED
WHERE DATE = CONVERT(VARCHAR(11), SYSDATETIME(), 112)
GROUP BY ISSUEDUSER
Can someone please help me write a combined SQL for all three ?
Thanks
Aiden
you might need to add a WHERE clause to only retrieve current month's records
SELECT ISSUEDUSER,
SUM(CASE WHEN DATE = DATEADD(DAY, DATEDIFF(DAY, 0, SYSDATETIME()), 0))
THEN 1 ELSE 0 END) AS DAILYBOOKS,
SUM(CASE WHEN DATE >= DATEADD(WEEK, DATEDIFF(WEEK, 0, SYSDATETIME()), 0)
AND DATE < DATEADD(WEEK, DATEDIFF(WEEK, 0, SYSDATETIME()) + 1, 0)
THEN 1 ELSE 0 END) AS WEEKLYBOOKS,
COUNT(*) AS MONTHLYBOOKS
FROM BOOKSISSUED
WHERE DATE >= DATEADD(MONTH, DATEDIFF(MONTH, 0, SYSDATETIME()), 0)
AND DATE < DATEADD(MONTH, DATEDIFF(MONTH, 0, SYSDATETIME()) + 1, 0)
GROUP BY ISSUEDUSER
EDIT : for [DATE] column is INT
SELECT ISSUEDUSER,
SUM(CASE WHEN DATE = CONVERT(INT, CONVERT(VARCHAR(8), SYSDATETIME(), 112))
THEN 1 ELSE 0 END) AS DAILYBOOKS,
SUM(CASE WHEN DATE >= CONVERT(INT, CONVERT(VARCHAR(8), DATEADD(WEEK, DATEDIFF(WEEK, 0, SYSDATETIME()), 0), 112))
AND DATE < CONVERT(INT, CONVERT(VARCHAR(8), DATEADD(WEEK, DATEDIFF(WEEK, 0, SYSDATETIME()) + 1, 0), 112))
THEN 1 ELSE 0 END) AS WEEKLYBOOKS,
COUNT(*) AS MONTHLYBOOKS
FROM BOOKSISSUED
WHERE DATE >= CONVERT(INT, CONVERT(VARCHAR(6), SYSDATETIME(), 112) + '01')
AND DATE < CONVERT(INT, CONVERT(VARCHAR(6), DATEADD(MONTH, 1, SYSDATETIME()), 112) + '01')
GROUP BY ISSUEDUSER
You should consider investing in a legitimate Date_Time table.
It makes comparing the official beginning and ending of the weeks MUCH easier and practical. And hey, you might even be able to use indexing!
However, there is another way. AS you shall see, DATEPART returns the ISO Month and Week we are looking for.
So provided our year is right, we now know where our boundaries are and can easily use an IIF(<boolean_expression>, <true_expression>, <false_expression>) statement inside of a COUNT(<column>). COUNT ignores NULLs, so we set TRUE to 1 and FALSE to NULL. :D
-- Note, I changed the column [Date] to [Dates]
DECLARE #Date INT
SET #Date = CAST(CAST(SYSDATETIME() AS VARCHAR(4) ) + '0101' AS INT)
SELECT ISSUEDUSER--DATEPART(YYYY, CAST(Dates AS VARCHAR(10) ) )
, COUNT( IIF(DATEDIFF(MM, CAST(Dates AS VARCHAR(10) ), GETDATE() ) = 0
, 1
, NULL) ) AS MONTHS
, COUNT( IIF(DATEDIFF(WW, CAST(Dates AS VARCHAR(10) ), GETDATE() ) = 0
, 1
, NULL) ) AS Weeks
, COUNT( IIF(DATEDIFF(DD, CAST(Dates AS VARCHAR(10) ), GETDATE() ) = 0
, 1
, NULL) ) AS Days
FROM #BookReport
WHERE DATES >= #Date
GROUP BY ISSUEDUSER
--results
ISSUEDUSER MONTHS Weeks Days
A 3 3 1
B 1 1 1
C 2 2 0
Note that you can expand the allowable date difference by adjusting the boolean statement! No extra coding required.
Also note that your examples actually only have one date that is not of the same Month, Week, or Day (within one day), although in my example I required Days to be of the same day as the query to make it look a bit different.
Cool Observations:
DATE by definition has no formatting and DATEPART can guess from a well-formed Datetime string, so there was no reason to double cast your Date column. However, if your pattern changes, you may need to add a CONVERT.
DATEPART gives you the standard (ISO) Month and Week recognized, which means no Date_Time table required here. :)
DATEDIFF is the magic here, and makes your Boolean statement REALLY easy to work with.
Pretty slick, no?
MSDN's page on DATEPART is worth a quick glance.

DATEDIFF between two dates, excluding two specific days

In Dubai, the standard weekend runs from Friday - Saturday rather than the traditional Saturday / Sunday.
I am trying to calculate the amount of working days between two given dates, and this is the code I have;
DATEDIFF(dd, DATEADD(hour, DATEDIFF(hh, getdate(), getutcdate()), #StartDate),
DATEADD(hour, DATEDIFF(hh, getdate(), getutcdate()), #EndDate)+1)
-
(
(DATEDIFF(wk, #StartDate, #EndDate) * 2
+(CASE WHEN DATENAME(dw, #StartDate) = 'Saturday' then 1 else 0 end)
+(CASE WHEN DATENAME(dw, #EndDate) = 'Friday' then 1 else 0 end)
))
)
However it is calculating the wrong amount of days. For example;
When the start date is 02-03-2016 and the end date is 02-06-2016 the returns as 4, but it should be 2.
When the start date is 02-03-2016 and the end date is 02-07-2016 the result is 3 which is correct.
The code below calculates the working days from your examples correctly. You can wrap it in a scalar function if you want.
declare #from date, #to date;
set #from = '2016-02-03';
set #to = '2016-02-06';
with dates(date)
as
(
select #from
union all
select dateadd(dd, 1, [date])
from dates
where [date] < #to
)
select count(*) [working days]
from (
select date, case when datename(dw, [date]) in ('Friday', 'Saturday') then 0 else 1 end as working
from dates) as dt
where dt.working = 1