Using iif to mimic CASE for days of week - sql

I've hit a little snag with one of my queries. I'm throwing together a simple chart to plot a number of reports being submitted by day of week.
My query to start was :
SELECT Weekday(incidentdate) AS dayOfWeek
, Count(*) AS NumberOfIncidents
FROM Incident
GROUP BY Weekday(incidentdate);
This works fine and returns what I want, something like
1 200
2 323
3 32
4 322
5 272
6 282
7 190
The problem is, I want the number returned by the weekday function to read the corresponding day of week, like case when 1 then 'sunday' and so forth. Since Access doesn;t have the SQL server equivalent that returns it as the word for the weekday, I have to work around.
Problem is, it's not coming out the way I want. So I wrote it using iif since I can't use CASE. The problem is, since each iif statement is treated like a column selection (the way I'm writing it), my data comes out unusable, like this
SELECT
iif(weekday(incidentdate) =1,'Sunday'),
iif(weekday(incidentdate) =2,'Monday')
'so forth
, Count(*) AS NumberOfIncidents
FROM tblIncident
GROUP BY Weekday(incidentdate);
Expr1000 Expr1001 count
Sunday 20
Monday 106
120
186
182
164
24
Of course, I want my weekdays to be in the same column as the original query. Halp pls

Use the WeekdayName() function.
SELECT
WeekdayName(Weekday(incidentdate)) AS dayOfWeek,
Count(*) AS NumberOfIncidents
FROM Incident
GROUP BY WeekdayName(Weekday(incidentdate));

As BWS Suggested, Switch was what I wanted. Here's what I ended up writing
SELECT
switch(
Weekday(incidentdate) = 1, 'Sunday',
Weekday(incidentdate) = 2,'Monday',
Weekday(incidentdate) = 3,'Tuesday',
Weekday(incidentdate) = 4,'Wednesday',
Weekday(incidentdate) = 5,'Thursday',
Weekday(incidentdate) = 6,'Friday',
Weekday(incidentdate) = 7,'Saturday'
) as DayOfWeek
, Count(*) AS NumberOfIncidents
FROM tblIncident
GROUP BY Weekday(incidentdate);
Posting this here so there's actual code for future readers
Edit: WeekdayName(weekday(yourdate)) as HansUp said it probably a little easier :)

check this previous post:
What is the equivalent of Select Case in Access SQL?

Why not just create a 7 row table with day number & day name then just join to it?

Related

Data aggregation by sliding time periods

[Query and question edited and fixed thanks to comments from #Gordon Linoff and #shawnt00]
I recently inherited a SQL query that calculates the number of some events in time windows of 30 days from a log database. It uses a CTE (Common Table Expression) to generate the 30 days ranges since '2019-01-01' to now. And then it counts the cases in those 30/60/90 days intervals. I am not sure this is the best method. All I know is that it takes a long time to run and I do not understand 100% how exactly it works. So I am trying to rebuild it in an efficient way (maybe as it is now is the most efficient way, I do not know).
I have several questions:
One of the things I notice is that instead of using DATEDIFF the query simply substracts a number of days from the date.Is that a good practice at all?
Is there a better way of doing the time comparisons?
Is there a better way to do the whole thing? The bottom line is: I need to aggregate data by number of occurrences in time periods of 30, 60 and 90 days.
Note: LogDate original format is like 2019-04-01 18:30:12.000.
DECLARE #dt1 Datetime='2019-01-01'
DECLARE #dt2 Datetime=getDate();
WITH ctedaterange
AS (SELECT [Dates]=#dt1
UNION ALL
SELECT [dates] + 30
FROM ctedaterange
WHERE [dates] + 30<= #dt2)
SELECT
[dates],
lt.Activity, COUNT(*) as Total,
SUM(CASE WHEN lt.LogDate <= dates and lt.LogDate > dates - 90 THEN 1 ELSE 0 END) AS Activity90days,
SUM(CASE WHEN lt.LogDate <= dates and lt.LogDate > dates - 60 THEN 1 ELSE 0 END) AS Activity60days,
SUM(CASE WHEN lt.LogDate <= dates and lt.LogDate > dates - 30 THEN 1 ELSE 0 END) AS Activity30days
FROM ctedaterange AS cte
JOIN (SELECT Activity, CONVERT(DATE, LogDate) as LogDate FROM LogTable) AS lt
ON cte.[dates] = lt.LogDate
group by [dates], lt.Activity
OPTION (maxrecursion 0)
Sample dataset (LogTable):
LogDate, Activity
2020-02-25 01:10:10.000, Activity01
2020-04-14 01:12:10.000, Activity02
2020-08-18 02:03:53.000, Activity02
2019-10-29 12:25:55.000, Activity01
2019-12-24 18:11:11.000, Activity03
2019-04-02 03:33:09.000, Activity01
Expected Output (the output does not reflect the data shown above for I would need too many lines in the sample set to be shown in this post)
As I said above, the bottom line is: I need to aggregate data by number of occurrences in time periods of 30, 60 and 90 days.
Activity, Activity90days, Activity60days, Activity30days
Activity01, 3, 0, 1
Activity02, 1, 10, 2
Activity03, 5, 1, 3
Thank you for any suggestion.
SQL Server doesn't yet have the option to range over values of the window frame of an analytic function. Since you've generated all possible dates though and you've already got the counts by date, it's very easy to look back a specific number of (aggregated) rows to get the right totals. Here is my suggested expression for 90 days:
sum(count(LogDate)) over (
partition by Activity order by [dates]
with rows between 89 preceding and current row
)

How can I express a SUM condition using SWITCH or nested IIF?

I have a request with a SQL code which calculates working days of each of these activities: Interstaff, Mission, ``Congés` using a defined VBA Function.
When there are two activities on the same day, it counts 1 day for each activity, I want the Sum function to ignore this day, and go to the next one.
I would like to modify my SQL code to add this condition:
"When calculating mission, if there is a Congés within the same period, ignore this day (and give priority to only count it in congés)"
I read that there is a SWITCH or a nested IIF conditions, but I couldn't translate that within my actual code...
Please do consider that I am still a beginner, on his way to learning!
SELECT
Z.Planning_Consultants.ID_Consultant,
Sum(IIf([Activité]="(2) Interstaff",WorkingDaysInDateRange([maxBegin],[minEnd])*Planning_Consultants.Time_Allocated,0)) AS NonBillable,
Sum(IIf([Activité]="(1) Mission",WorkingDaysInDateRange([maxBegin],[minEnd])*Planning_Consultants.Time_Allocated,0)) AS Billable,
Sum(IIf([Activité]="(3) Congés/Arrêt",WorkingDaysInDateRange([maxBegin],[minEnd])*Planning_Consultants.Time_Allocated,0)) AS Absent,
For example Mr A got a mission from 03/06/2019 to 07/06/2019. With a day off (congé) on 06/06/2019. I expect the output to be Mission 4 days and congé 1 day, in my case I will have mission 5 days, and congé 1 day,
Here's an example of a Dataset :
Activity BegDate EndDate Time_Allocated
(1)Mission 01/01/2019 31/12/2019 100%
(3)congé 02/04/2019 05/04/2019 100%
For April for example, I would like to have 18 working days and 4 congé, instead of 22 working days and 4 congé
This is the sort of query that should work for you. I've built this in sql server rather than access so you might have to make some mods, but it will give you the idea. It will give you the actual days for the activity, plus the total leave days that cover the activity. The rest I will leave up to you.
select t1.Activity, t1.Time_Allocated,
WorkingDaysInDateRange(t1.BegDate, t1.EndDate) as days,
sum(WorkingDaysInDateRange(
case when t2.BegDate<T1.BegDate then T1.BegDate else t2.BegDate end,
case when t2.EndDate>T1.EndDate then T1.EndDate else t2.EndDate end))
as LeaveDays
from times t1
left join times t2 on t2.BegDate<=t1.Enddate and t2.EndDate>=t1.BegDate
and t2.Activity='(3)congé'
and t1.Activity!='(3)congé'
group by t1.Activity, t1.Time_Allocated, t1.BegDate, t1.EndDate

Select two rows that are closest to a given value

I have the below table in MS Access.
Day ABC
365 25
548 35
730 37
913 58
1095 146
I want to query it such that I get the row before and after a given value of the Day column. This value is variable and can be e.g. value = 432
For this example the query would result the following table.
Day ABC
365 25
548 35
Because the given value = 432 is larger than the Day value of 365 and smaller than the Day value of 548.
What I managed to do is get one field, but not both. The following query gave me correct rows of the Day field.
Select Max(Day) As Day From Table Where Day < 432
UNION
Select Min(Day) As Day From Table Where Day > 432
When I use this code and add another field like ABC I get an error.
You tried to execute a query that does not include the specified
expression 'ABC' as part of an aggregate function.
Could you help me? I think this should be a really easy task. Thank you!
There's a couple of problems with your SQL. The error message occurs because you didn't add a GROUP BY ABC clause to both the SELECT statements.
You'll also get a circular reference as due to MAX(DAY) as DAY - you need to change the as Day to something else maybe as lDay.
You'll probably (I did) get a error Syntax Error calling it Table - pretty sure that's a reserved word.
Maybe this query will work better:
Select Top 1 Day, ABC From Table3 Where Day <= 913 ORDER BY Day Desc
UNION ALL
Select Top 1 Day, ABC From Table3 Where Day >= 913 ORDER BY Day Asc
You could also be a little fancier:
Select Top 2 Day, ABC From Table3 Order By Abs(Day - 432) Desc
Note please, that the result will match the title of the question, not necessarily your detailed explanation ...

How to Get the total number of (the differnece between two dates)?

I i have a table [holidays] with the following structure :
id | start_date | end_date | user_id
How to get the number of holidays for a user in the previous year ?
I wanna something like that with correct syntax:
SELECT SUM(end_date - start_date)
FROM holidays
WHERE user_id = 342
AND YEAR(end_date) = YEAR(CURRENT) - 1
I think this should work in informix:
SELECT SUM(end_date-start_date)
from holidays
where user_id = 342 and
YEAR(end_date) = YEAR(TODAY)-1;
Note: it is not clear whether the end date is inclusive or not. You might want:
SELECT SUM((end_date-start_date) + 1)
from holidays
where user_id = 342 and
YEAR(end_date) = YEAR(TODAY)-1;
Your query is perfectly fine. You don't have any syntax improvements to be done. The only thing Gordon Linoff's approach (which will work) differs from yours is that he is using TODAY (that is YEAR TO DAY) instead of CURRENT (that is YEAR TO FRACTION), but YEAR(CURRENT) and YEAR(TODAY) would get the exact same result.
The only way you would get a different result was if you had CURRENT inside the SUM, like SUM(CURRENT-start_date). That way you would have more precision than only the days (You would get the days and then hh:mm:ss.fff) like you want, but if you used TODAY, you would get only the days.
Other than that, it's perfectly fine.

Change select to a previous date

I have basic knowledge of SQL and have a question:
I am trying to select data from a time series (date and windspeed). I want to select the original wind speed value if it lies between hours 7 and 21. If the hour is outside this range I would like to assign the wind speed to the previous wind speed at hour 21. There is also a concern that there is the occasional point where hour 21 does not exist and would like to assign the windspeed as hour 20... 19 etc until it finds the next available hour.
SELECT
date,
CASE WHEN DATEPART(HH,date) < 7 OR DATEPART(HH,date) > 21
THEN '<WIND SPEED AT HOUR 21> ELSE <WIND SPEED> END AS ModifiedWindspeed
,WindSpeed, winddirection
from TerrainCorrectedHourlyWind w
This might make things clearer. If the hour is in the specified range, select windspeed. If not then select the wind speed from the prior day at 21 hours.
Though you've tagged the question mysql, I'm guessing this is actually SQL Server because of the DATEPART() function used. Try the following, which uses an OUTER APPLY to get your alternate value:
SELECT Date
, CASE
WHEN DATEPART(HOUR, Date)BETWEEN 7 AND 21 THEN w.WindSpeed
ELSE m.WindSpeed
END AS ModifiedWindSpeed
, w.WindSpeed
, w.WindDirection
FROM TerrainCorrectedHourlyWind AS w
OUTER APPLY(SELECT TOP 1 WindSpeed
FROM TerrainCorrectedHourlyWind
WHERE DATEPART(HOUR, Date)BETWEEN 7 AND 21
AND Date < w.Date
ORDER BY Date DESC)AS m;
Just to explain what this is doing--the OUTER APPLY will get the single most recent record (TOP 1 and ORDER BY Date DESC) for dates prior to the record in question (Date < w.Date) as well as within the hours specified. The CASE near the top chooses whether to use the current value or this alternate one based on the hour.