Algorithm to get next weekday based on today - sql

Suppose today is Thursday. I have 1 flag for both Wednesday and Friday.
If I create a temporary table then it will look like
ID WeekDay XFlag
==================================
1 Mon 0
2 Tue 0
3 Wed 1
4 Thu 0
5 Fri 1
6 Sat 0
7 Sun 0
Now, as we assumed today is Thursday, the next day when XFlag value 1 is Friday.
Any or all weekdays can be marked/flagged 1. But, I ultimately want to get the next weekday based on today. So, my result will be Fri for this in a varchar variable in a stored procedure.
Here, if today is wed then also result will be "Fri". But if today is "Fri" result will be "Wed". So, please care for such cases also.
How can I do it?

So you want the next day but the challenge is that the week days form a cycle. You can handle this using order by and some cleverness:
select top 1 tt.*
from TemporaryTable tt
where flag = 1
order by (7 + tt.id - datepart(dw, getdate())) % 7
Here is a SQL Fiddle.
EDIT:
If datefirst might be set differently, you can do the join on the date name. Just a bit more complicated with the order by condition:
select top 1 tt.*
from TemporaryTable tt cross join
(select id from TemporaryTable tt where Weekday = left(datepart(dw, getdate()), 3)
) as startid
where flag = 1
order by (tt.id - startid.id + 7) % 7;
This assumes, of course, that the language being returned is English.

I've gone quite procedural here, but the parts can be incorporated into a larger query, rather than using local variables, if required:
declare #t table (ID int not null,Weekday char(3) not null,XFlag bit not null)
insert into #t(ID,WeekDay,XFlag) values
(1,'Mon',0),(2,'Tue',0),(3,'Wed',1),
(4,'Thu',0),(5,'Fri',1),(6,'Sat',0),
(7,'Sun',0)
declare #Today int
declare #NextDay int
--Set today, in a DATEFIRST safe manner
set #Today = ((DATEPART(weekday,CURRENT_TIMESTAMP) + 7) --Today
- DATEPART(weekday,'20140106') --Known Monday
) % 7 + 1
set #NextDay = COALESCE((select MIN(ID) from #t where XFlag = 1 and ID > #Today),
(select MIN(ID) from #t where XFlag = 1))
select Weekday from #t where ID = #NextDay
Hopefully it's (relatively) easy to see how I'm thinking.
Setting #Today is probably the most complex part, and that only because I'm trying to write code that can be run by anyone, anywhere, without having to adjust either it or their DATEFIRST setting. We subtract the results of two calls to DATEPART(weekday,... where we know that one of them is definitely a Monday, and we've also set it up so that we always produce a positive result. We then use % 7 to ensure that it's in the range 0-6, corresponding to Monday-Sunday, and then add 1 so that the values produced by this expression match the IDs in your table.

Slightly improved version of #GordonLinoff's answer that doesn't rely on local datefirst settings
select top 1 *
from <table>
where Xflag = 1
order by datediff(d, id-1, current_timestamp) % 7 desc

Try this Here WorkingDay is TableName
Select *,
CASE
WHEN
(Select top 1 WeekDay From WorkingDay WHERE ID > W.ID AND XFLAG=1 ORDER BY ID) IS NOT NULL
THEN (Select top 1 WeekDay From WorkingDay WHERE ID > W.ID AND XFLAG=1 ORDER BY ID)
ELSE
(Select top 1 WeekDay From WorkingDay WHERE ID < W.ID AND XFLAG=1 ORDER BY ID)
END AS NextWorkingDay
From WorkingDay W
Order By ID

Related

Count # of Saturdays given a date range

I have a datetime field and a net field. The Sat Count field is done by =IIf(DatePart("w",Fields!DespatchDate.Value)=7,1,0)
I want to total the count of the Saturdays given a starting date and end date (typically a month).
I tried =Sum(IIf(DatePart("w",Fields!DespatchDate.Value)=7,1,0) but the total is wrong.
I also want to count Saturdays for rest of the month, e.g there's a missing 3rd Saturday in the picture.
I also want to do a total of the Net for Saturdays.
Can you point me in the direction. I can do it in SQL or in SSRS
Considering that we do not have any Input or desired output provided, I am assuming that You just want to count Saturdays in a given range:
Select COUNT(*), SUM(Net)
FROM table
WHERE Day# = 7 AND Date BETWEEN '2021-02-16' AND '2021-02-23'
Assuming you want to count saturdays even if it is not part of your dataset, what you need to do is pad out all your dates for the given range and then join it to your base data set.
This would ensure that it accounts for ALL days of the week regardless of a dispatch event occuring on that date / day.
Below is some SQL code that might help you make a start.
declare #startdate date = '2021-02-01'
declare #enddate date = '2021-02-28'
if OBJECT_ID ('tempdb..#dates') is not null
drop table #dates
;WITH mycte AS
(
SELECT CAST(#startdate AS DATETIME) DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue + 1 < #enddate
)
SELECT DateValue into #dates
FROM mycte
OPTION (MAXRECURSION 0)
select
d.DateValue
, datepart(weekday,d.DateValue) as day_no
,case when datepart(weekday,d.DateValue) = 7 then isnull(t.net,0) else 0 end as sat_net
,case when datepart(weekday,d.DateValue) = 1 then isnull(t.net,0) else 0 end as sun_net
from #dates d
left join your_table t
on d.DateValue = t.some_date
drop table #dates
Since I don't know what your required output is, I cannot summarise this any further. But you get the idea!

SQL Server - Split year into 4 weekly periods

I would like to split up the year into 13 periods with 4 weeks in each
52 weeks a year / 4 = 13 even periods
I would like each period to start on a saturday and end on a friday.
It should look like the below image
Obviously I could do this manually, but the dates would change each year and I am looking for a way to automate this with SQL rather than manually do this for each upcoming year
Is there a way to produce this yearly split automatically?
In this previous answer I show an approach to create a numbers/date table. Such a table is very handsome in many places.
With this approach you might try something like this:
CREATE TABLE dbo.RunningNumbers(Number INT NOT NULL,CalendarDate DATE NOT NULL, CalendarYear INT NOT NULL,CalendarMonth INT NOT NULL,CalendarDay INT NOT NULL, CalendarWeek INT NOT NULL, CalendarYearDay INT NOT NULL, CalendarWeekDay INT NOT NULL);
DECLARE #CountEntries INT = 100000;
DECLARE #StartNumber INT = 0;
WITH E1(N) AS(SELECT 1 FROM(VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(N)), --10 ^ 1
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b), -- 10 ^ 2 = 100 rows
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b), -- 10 ^ 4 = 10,000 rows
E8(N) AS(SELECT 1 FROM E4 a CROSS JOIN E4 b), -- 10 ^ 8 = 10,000,000 rows
CteTally AS
(
SELECT TOP(ISNULL(#CountEntries,1000000)) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) -1 + ISNULL(#StartNumber,0) As Nmbr
FROM E8
)
INSERT INTO dbo.RunningNumbers
SELECT CteTally.Nmbr,CalendarDate.d,CalendarExt.*
FROM CteTally
CROSS APPLY
(
SELECT DATEADD(DAY,CteTally.Nmbr,{ts'1900-01-01 00:00:00'})
) AS CalendarDate(d)
CROSS APPLY
(
SELECT YEAR(CalendarDate.d) AS CalendarYear
,MONTH(CalendarDate.d) AS CalendarMonth
,DAY(CalendarDate.d) AS CalendarDay
,DATEPART(WEEK,CalendarDate.d) AS CalendarWeek
,DATEPART(DAYOFYEAR,CalendarDate.d) AS CalendarYearDay
,DATEPART(WEEKDAY,CalendarDate.d) AS CalendarWeekDay
) AS CalendarExt;
GO
NTILE - SQL Server 2008+ will create (almost) even chunks.
This the actual query
SELECT *,NTILE(13) OVER(ORDER BY CalendarDate) AS Periode
FROM RunningNumbers
WHERE CalendarWeekDay=6
AND CalendarDate>={d'2017-01-01'} AND CalendarDate <= {d'2017-12-31'};
GO
--Carefull with existing data!
--DROP TABLE dbo.RunningNumbers;
Hint 1: Place indexes!
Hint 2: Read the link about NTILE, especially the Remark-section.
I think this will fit for this case. You might think about using Prdp's approach with ROW_NUMBER() in conncetion with INT division. But - big advantage! - NTILE would allow PARTITION BY CalendarYear.
Hint 3: You might add a column to the table
...where you set the period's number as a fix value. This will make future queries very easy and would allow manual correction on special cases (53rd week..)
Here is one way using Calendar table
DECLARE #start DATE = '2017-04-01',
#end_date DATE = '2017-12-31'
SET DATEFIRST 7;
WITH Calendar
AS (SELECT 1 AS id,
#start AS start_date,
Dateadd(dd, 6, #start) AS end_date
UNION ALL
SELECT id + 1,
Dateadd(week, 1, start_date),
Dateadd(week, 1, end_date)
FROM Calendar
WHERE end_date < #end_date)
SELECT id,
( Row_number()OVER(ORDER BY id) - 1 ) / 4 + 1 AS Period,
start_date,
end_date
FROM Calendar
OPTION (maxrecursion 0)
I have generated dates using Recursive CTE but it is better to create a physical calendar table use it in queries like this
Firstly, you will never get 52 even weeks in a year, there are overlap weeks in most calendar standards. You will occasionally get a week 53.
You can tell SQL to use Saturday as the first day of the week with datefirst, then running a datepart on today's date with getdate() will tell you the week of the year:
SET datefirst 6 -- 6 is Saturday
SELECT datepart(ww,getdate()) as currentWeek
You could then divide this by 4 with a CEILING command to get the 4-week split:
SET datefirst 6
SELECT DATEPART(ww,getdate()) as currentWeek,
CEILING(DATEPART(ww,getdate())/4) as four_week_split

Growth Of Distinct Users Per Week

I need to get a report that shows distinct users per week to show user growth per week, but I need it to show cumulative distinct users.
So if I have 5 weeks of data, I want to show:
Distinct users from week 0 through week 1
Distinct users from week 0 through week 2
Distinct users from week 0 through week 3
Distinct users from week 0 through week 4
Distinct users from week 0 through week 5
I have a whole year's worth of data. The only way I know how to do this is to literally query the time ranges adjusting a week out at a time and this is very tedious. I just can't figure out how I could query everything from week 0 through week 1 all the way to week 0 through week 52.
EDIT - What I have so far:
select count(distinct user_id) as count
from tracking
where datepart(wk,login_dt_tm) >= 0 and datepart(wk,login_dt_tm) <= 1
Then I take that number, record it, and update it to -- datepart(wk,login_dt_tm) <= 2. And so on until I have all the weeks. That way I can chart a nice growth chart by week.
This is tedious and there has to be another way.
UPDATE-
I used the solution provided by #siyual but updated it to use a table variable so I could get all the results in one output.
Declare #Week Int = 0
Declare #Totals Table
(
WeekNum int,
UserCount int
)
While #Week < 52
Begin
insert into #Totals (WeekNum,UserCount)
select #Week,count(distinct user_id) as count
from tracking
where datepart(wk,login_dt_tm) >= #Week and datepart(wk,login_dt_tm) <= (#Week + 1)
Set #Week += 1
End
Select * from #Totals
Why not something like:
select count(distinct user_id) as count, datepartk(wk, login_dt_tm) as week
from tracking
group by datepart(wk,login_dt_tm)
order by week
You could try something like this:
Declare #Week Int = 1
While #Week <= 52
Begin
select count(distinct user_id) as count
from tracking
where datepart(wk,login_dt_tm) >= 0 and datepart(wk,login_dt_tm) <= #Week
Set #Week += 1
End
Just for the record, I would do this in one statement, using a recursive CTE to generate the numbers from 1 to 52 (you could also use a numbers table):
with numbers as (
select 1 as n
union all
select n + 1
from numbers
where n < 52
)
select count(distinct user_id) as count
from tracking t join
numbers n
on datepart(wk, login_dt_tm) >= 0 and datepart(wk, login_dt_tm) <= numbers.n;
Seems easier to put it all in one query.
SELECT
week_num,
distinct_count
FROM (
select distinct
datepart(wk,login_dt_tm) week_num
from #tracking
) t_week
CROSS APPLY (
select
count(distinct user_id) distinct_count
from #tracking
where datepart(wk,login_dt_tm) between 0 and t_week.week_num
) t_count

Set week number based on first Monday of Year

I have a requirement to set the week number of a table from the first day to the first Monday to the next Monday and so on. I can easily get the first Day and first Monday of year but I do not know how to increment trough the table in 7 days intervals from the first Monday so that I can set the week number.
I have something like this:
UPDATE table
SET weeknumberofyear = #WeekNumber + 1
WHERE datefield = DATEADD(Day,7,(SELECT DATEADD(DAY, (##DATEFIRST - DATEPART(WEEKDAY, #Date) + (8 - ##DATEFIRST) * 2) % 7, #Date)))
Since the datepart(week,datefield) function gets week number based on Sunday as the first day of the week, all you have to do is check datepart(weekday,datefield) and if it is 1 (Sunday) or 2 (Monday), subtract 1 from the datepart(week,datefield) function:
update table
set weeknumberofyear = datepart(week,datefield) -
case when datepart(weekday,datefield) in(1,2) then 1 else 0 end
EDIT This doesn't account for Years when Sunday or Monday are the first day of the year. In those cases, you would get 0 for weeknumberofyear. To fix this, perform a second update to your table. Even though this takes two updates, I still think it is more efficient than cycling through all the records.
update table
set weeknumberoftheyear = weeknumberoftheyear + 1
where year(datefield) in(
select distinct year(datefield)
from table
where weeknumberoftheyear = 0
)
EDIT WeekNumberOfTheMonth Update - Now that we have the WeekNumberOfTheYear value, we can use a ranking function on that field to update the WeekNumberOfTheMonth column without any recursion.
update t
set t.weeknumberofthemonth = u.weeknumberofthemonth
from table t
inner join (
select distinct weeknumberoftheyear,
dense_rank() over(partition by month(datefield)
order by weeknumberoftheyear) weeknumberofthemonth
from table ) u
on u.weeknumberofyear = t.weeknumberofyear
I'm not sure what you mean by all the talk about "Monday", but if you're looking to get the week number from a date, you can do something like this:
UPDATE table
SET weeknumberofyear = DATEPART(wk, datefield)

Data appear at least once for every month in the last X month

My problem:
Table: trans_detail:
PhoneNo | Datetime
01234 | 2013-01-05 20:40:10
01245 | 2013-04-02 21:00:13
05678 | 2013-04-16 01:24:07
04567 | 2013-07-23 07:00:00
etc | etc
I want to get all phoneNo that appears at least once for every month in the last X month (X month can be any month between 1-12).
For example: get all phone no. that appears at least once for Every Month in the last 3 months.
I am using SQL Server 2005.
Here is a quick query that comes close to what you want:
select PhoneNo
from trans_detail d
where d.datetime >= dateadd(mm, -#X, getdate())
group by PhoneNo
having count(distinct year(datetime)*12+month(datetime)) = #X
The where clause filters the data to only include rows in the last #X months. the having clause checks that each month is in the data, by counting the number of distinct months.
The above version of the query assumes that you mean calendar months. So, it has boundary condition problems. If you run it on June 16th, then it looks back one month and makes sure that the phone number appears at least once since May 16th. I am unclear on whether you want to insist that the number appear twice (once in May and once in June) or if once (once during the time period). The solution to this is to move the current date back to the end of the previous month:
select PhoneNo
from trans_detail d cross join
(select cast(getdate() - day(getdate) + 1 as date) as FirstOfMonth const
where d.datetime >= dateadd(mm, -#X, FirstOfMonth) and
d.datetime < FirstOfMonth
group by PhoneNo
having count(distinct year(datetime)*12+month(datetime)) = #X
Here it is. First two CTEs are to find and prepare last X months, third CTE is to group your data by phones and months. At the end just join the two and return where number of matching rows are equal to number of months.
DECLARE #months INT
SET #Months = 3
;WITH CTE_Dates AS
(
SELECT GETDATE() AS Dt
UNION ALL
SELECT DATEADD(MM,-1,Dt) FROM CTE_Dates
WHERE DATEDIFF(MM, Dt,GETDATE()) < #months-1
)
, CTE_Months AS
(
SELECT MONTH(Dt) AS Mn, YEAR(Dt) AS Yr FROM CTE_Dates
)
, CTE_Trans AS
(
SELECT PhoneNo, MONTH([Datetime]) AS Mn, YEAR([Datetime]) AS Yr FROM dbo.trans_detail
GROUP BY PhoneNo, MONTH([Datetime]), YEAR([Datetime])
)
SELECT PhoneNo FROM CTE_Months m
LEFT JOIN CTE_Trans t ON m.Mn = t.Mn AND m.Yr = t.Yr
GROUP BY PhoneNo
HAVING COUNT(*) = #months
SQLFiddle Demo - with added some more data that will match for last 3 months