How to split the weeks equally between the year change - sql

The query worked fine in generating weeks until the year change. As you can see in the generated output (given in the following image) during year change I got a different pattern. Please help. I want equal split of 7 days. At the moment order is the order is not equal as you can see in the output 28/12/2014, 01/01/2015 and 04/01/2015 are in wrong order. Similarly the first difference is not 7 days but 6 days ( from 04/09/2014 to 09/11/2014). I want this to be 7 days to i.e. ( from 03/09/2014 to 09/11/2014)

you could use a cte to generate the 14 weeks
This will generate a table that has the weeks in 7 day increments, regardless of the start date, which you could then use to classify your data:
declare #startdate date
set #startdate = '2014-11-13'
;with cte (SOW,EOW,cnt)
as
(
select #startdate as SOW,dateadd(dd,6,#startdate) as EOW, 1 as cnt
union all
select dateadd(dd,7,SOW),dateadd(dd,7,EOW),cnt+1
from cte
where cnt<14
)
select * from cte

Solution is to use. Look here for more details.
GROUP BY
DATEDIFF(week, 0, ad.xDate)
Thanks to MatBailie for solution.

Try something like this. Which day do you want your weeks to start on?
GROUP BY datediff(dd, ad.xDate, #MaxDate) / 7
EDIT: I think I understand what you're trying to do with the AllDates CTE but I can't figure out how it works. Based on what it see it looks like it duplicates the #MinDate value as well as pulling out every date prior to #MaxDate. My expectation is to see something like this which should take care of your extra day problem. I'm also guessing that your 14-week blocks of weeks are overlapping by a single day.
;WITH AllDates AS (
SELECT xDate FROM AllDates WHERE xDate > #MinDate AND xDate <= #MaxDate
)

Related

SQL Get all Saturdays between two Dates

I'm trying to find all the saturdays between two dates (inclusive) without using a loop.
For example, April 1, 2021, to May 1, 2021 should return:
04-03-2021
04-10-2021
04-17-2021
04-24-2021
05-01-2021
You can use datename
select *
from Table
where Datename(dw,Datecolumn)='Saturday'
and DateColumn >= start and Datecolumn < End;
Alternatively, if you just want to generate a list on the fly, you can do
declare #StartDate date = '20210401', #EndDate date = '20210501';
select DateAdd(day, rn-1, #StartDate)
from (
select Row_Number() over (order by object_id) rn
from sys.columns
) d
where rn - 1 <= DateDiff(day, #StartDate, #EndDate)
and DateName(dw, DateAdd(day, rn-1, #StartDate))='Saturday';
This first generates a list of numbers in the inner query by utilising one of several possible system tables to select just a row number. This is essentially building a numbers / tally table on the fly, and it's usually a good idea to have a permanent table like this available (a single column of integers starting at 1 and going up to a squintillion or whatever you need). You can see how this works by just highlighting the inner query and running it (F5 in SSMS).
An out query selects from this and filters the row numbers to just the right sequential range for the number of days between the two specified dates. Again, you can check this by highlighting the entire query except for the last line, you'll see it generates the list of dates between the specified start and end dates inclusively.
Finally the and criteria extracts the name of the day for each date and further filters the list of dates based on the day's name.

Calculate Recurring User For 12 Months with SQL

I'm trying to see if there is a better way to achieve what I am doing right now. For example, I need to know total number of users who have logged in for the past 12 months. So each user who has logged in at least once a month, for twelve months in a row would count towards the total.
The way I am doing this right now is: I query my table and get all user ids and timestamps of when they were active and return them to my c# code. Then with bunch of loops and LINQ I calculate the value (Its too much code to dump into this question and since I'm trying to get away from doing it in c# I don't believe there is a need for it).
Now this takes some time to run and I'm sure there has to be a better way to do this with SQL. I've searched but haven't found any SQL functions that let you count based on a recurring condition.
For an answer I'm hoping to either get an example or a link to a similar SO question or an article that talks about achieving this.
An example of MyUsersTable:
UserId | Timestamp
1 | '2018-12-23 00:00:00.000'
1 | '2018-11-23 00:00:00.000'
1 | '2018-10-23 00:00:00.000'
EDIT: I did thought of using SUM(CASE WHEN month = 1 and month = 2 and month = 3) but that seems also like not a great solution.
Expected Result:
Total number of users who were active at least once a month in the last 12 months.
If you need users who logged in every month in 2018:
select ut.userid
from MyUsersTable ut
where timestamp >= '2018-01-01' and timestamp < '2019-01-01'
group by ut.userid
having count(distinct month(timestamp)) = 12;
I'd count the distinct number of months a user logged in on:
SELECT userid
FROM mytable
WHERE YEAR(timestamp) = 2018
GROUP BY userid
HAVING COUNT(DISTINCT MONTH(timestamp)) = 12
To get userIDs who logged in for a specific number of consecutive months, you can use:
/* These are your input values */
DECLARE #searchDate date = '2018-12-15' ;
DECLARE #monthsToSearch int = 12 ;
/* First day of search month */
DECLARE #EndDate date = DATEADD(month, DATEDIFF(month, 0, #searchDate), 0) ;
/* First day of month to search from */
DECLARE #StartDate date = DATEADD(month, -#monthsToSearch, #EndDate) ;
SELECT userID --, #StartDate AS startDate, #EndDate AS endDate
FROM (
SELECT userID, ( (YEAR(userLoginDT)*100)+MONTH(userLoginDT) ) AS datePoint /* YYYYMM */
FROM t1
WHERE userLoginDT >= #StartDate
AND userLoginDT < #EndDate
) s1
GROUP BY userID
HAVING count(distinct(datePoint)) = #monthsToSearch
;
See the db<>fiddle here for my examples.
The fist two declared variables are your input variables. You feed it the date your are running the report on and then telling it how many months you want to go back to. So you can search any number of months. After that, it's pretty much date manipulation and math.
#EndDate essentially takes your declared date and calculates the first day of the month you are currently searching in. You will search for any dates before this date.
#StartDate counts back from your #EndDate to calculate the number of months you want to search.
(YEAR(userLoginDT)*100)+MONTH(userLoginDT) in your sub-select creates an integer variable that you can GROUP BY to get a distinct count of months you're searching over. This part could be sped up with the Calendar Table.
Then you just use the HAVING to pick out how many distinct records your want for #monthsToSearch.
NOTE: As many here can attest, I'm a huge fan of working with Calendar Tables when dealing with date calculations and large amounts of search data. Something like that would likely speed the query up a bit.

Select Month w/ Most Days in Multi Month Range in SQL

Sorry if this has been asked, but I didn't find anything when searching: I have a large table of ~100k rows in SQL Server. Within each row is a date range, which in many cases spreads across multiple months (and years to a lesser extent). The ranges are typically about 30-35 days however they usually don't start at the the 1st of the month. An example of a typical date range is 01/10/2017-02/11/2017.
I'm looking for the most efficient way to output the month with the most days within in that range as it's own column. I'm doing the same thing for the year
Right now I have the following in my query:
SELECT DISTINCT
a.START_DATE,
a.END_DATE,
cast(month(dateadd(day, datediff(day, a.Start_Date, a.End_Date)/2, a.Start_Date)) as tinyint) as Main_Month,
cast(year(dateadd(day, datediff(day, a.Start_Date, a.End_Date)/2, a.Start_Date)) as smallint) as Main_Year
FROM TABLE
The output from that query using the above date range example would give me:
Start_Date: 01/10/2017 End_Date: 02/11/2017 Main_Month: 1 Main Year: 2017
That method has worked alright, but it slows when being done for all rows in the table. Are there any more efficient alternatives that I can use for the Main_Month and Main_Year columns?
EDIT IN RESPONSE TO COMMENTS:
By "month w/ the most days within a date range", using my example range of 01/10/2017-02/11/2017, since that range contains 21 days in January and only 11 in February, the output I'd get for Main_Month is 1. Also since the year is 2017 throughout the range the output I'd get for Main_Year is 2017
For ties, I'd go with the 1st month containing the max # of days. In the example of, 6/20/2017 - 9/5/2017 I'd go with 7, since that is the 1st month with 31 days in the range
As discused in the comments above, this solution is not perfectly accurate, but it mirrors the accuracy of the original slower solution:
SELECT b.START_DATE, b.END_DATE, month(b.mid_point) as Main_Month, year(b.midpoint) as Main_Year FROM
(SELECT DISTINCT
a.START_DATE,
a.END_DATE,
dateadd(day, datediff(day, a.Start_Date, a.End_Date)/2, a.Start_Date) as mid_Point
FROM a) as b
You should be able to speed it up by making these two changes. First, only compute the datediff and dateadd once, then take it from the derived table to get the two fields you need. Next, don't bother with casting, since Month() and year() both do that for you. Were you able to see a speed difference with this method?
I think using CASE statements could be more efficient, and if you can avoid casting, like this:
, CASE WHEN datediff(day, a.Start_Date, a.End_Date) + 1 - DAY(a.END_Date) < DAY(a.END_DATE) THEN MONTH(a.End_Date)
ELSE MONTH(a.Start_Date) END AS Main_Month
, CASE WHEN YEAR(a.END_Date) = YEAR(a.Start_Date) THEN YEAR(a.Start_Date)
WHEN datediff(day, a.Start_Date, a.End_Date) + 1 - DAY(a.END_Date) < DAY(a.END_DATE) THEN YEAR(a.End_Date)
ELSE YEAR(a.Start_Date) END AS Main_Year

Get date for nth day of week in nth week of month

I have a column with values like '3rd-Wednesday', '2nd-Tuesday', 'Every-Thursday'.
I'd like to create a column that reads those strings, and determines if that date has already come this month, and if it has, then return that date of next month. If it has not passed yet for this month, then it would return the date for this month.
Expected results (on 4/22/16) from the above would be: '05-18-2016', '05-10-2016', '04-28-2016'.
I'd prefer to do it mathematically and avoid creating a calendar table if possible.
Thanks.
Partial answer, which is by no means bug free.
This doesn't cater for 'Every-' entries, but hopefully will give you some inspiration. I'm sure there are plenty of test cases this will fail on, and you might be better off writing a stored proc.
I did try to do this by calculating the day name and day number of the first day of the month, then calculating the next wanted day and applying an offset, but it got messy. I know you said no date table but the CTE simplifies things.
How it works
A CTE creates a calendar for the current month of date and dayname. Some rather suspect parsing code pulls the day name from the test data and joins to the CTE. The where clause filters to dates greater than the Nth occurrence, and the select adds 4 weeks if the date has passed. Or at least that's the theory :)
I'm using DATEFROMPARTS to simplify the code, which is a SQL 2012 function - there are alternatives on SO for 2008.
SELECT * INTO #TEST FROM (VALUES ('3rd-Wednesday'), ('2nd-Tuesday'), ('4th-Monday')) A(Value)
SET DATEFIRST 1
;WITH DAYS AS (
SELECT
CAST(DATEADD(MONTH,DATEDIFF(MONTH,0,GETDATE()),N.Number) AS DATE) Date,
DATENAME(WEEKDAY, DATEADD(MONTH,DATEDIFF(MONTH,0,GETDATE()),N.Number)) DayName
FROM master..spt_values N WHERE N.type = 'P' AND N.number BETWEEN 0 AND 31
)
SELECT
T.Value,
CASE WHEN MIN(D.Date) < GETDATE() THEN DATEADD(WEEK, 4, MIN(D.DATE)) ELSE MIN(D.DATE) END Date
FROM #TEST T
JOIN DAYS D ON REVERSE(SUBSTRING(REVERSE(T.VALUE), 1, CHARINDEX('-', REVERSE(T.VALUE)) -1)) = D.DayName
WHERE D.Date >=
DATEFROMPARTS(
YEAR(GETDATE()),
MONTH(GETDATE()),
1+ 7*(CAST(SUBSTRING(T.Value, 1,1) AS INT) -1)
)
GROUP BY T.Value
Value Date
------------- ----------
2nd-Tuesday 2016-05-10
3rd-Wednesday 2016-05-18
4th-Monday 2016-04-25

Get the actual month difference between two date

I working with strange queries in SQL, I want to find the actual month difference between two dates, for example
StartDate = '1-1-2013'
EndDate = '4-30-2013'
Here I want result to be 4 not 3
select datediff(mm, '1-1-2013', '4-30-2013')
This query will provide me result 3 but actual result is 4. Can anyone help me on this?
Not quite sure if this helps but here's my stab at it:
declare
#date1 datetime = '2013-01-01',
#date2 datetime = '2013-04-30'
select Cast(Round(Cast(DATEDIFF(DD,#date1, #date2) as decimal(5,2)) / 30, 1) as int) as Months
What you're asking isn't as simple as it first appears. One obvious way would be to add one day to your end date before doing the datediff. This would then be doing 1 Jan to 1 May which would be reported as four months.
However you need to consider if there's any tolerances; for example, should 1 Jan to 29 Apr be considered four months, or is that still three months? Obviously this will depend heavily on the context in which you are using these dates.
Please try to add 01-01-2013 and 04-30-2013 instead of what you added here
So query will be as below
select datediff(mm, '1-1-2013', '4-30-2013')
Thanks
Vasanthan
As others have pointed out, the result is absolutely correct: 3 months and 29 days.
You can of course check if the date in the end date is the last date of the month and make an adjustment to produce the desired result:
DECLARE #START DATETIME, #END DATETIME
SELECT #START = '2014-01-01', #END = '2014-04-30'
SELECT #START, #END
SELECT DATEDIFF(MONTH, #START, CASE WHEN MONTH(DATEADD(DAY, 1, #END)) > MONTH(#END) THEN DATEADD(DAY, +1, #END) ELSE #END END)
Not sure about the performance of said example (you're free to experiment with similar ideas), but what it does is add the last missing day to produce the 4 month difference whenever the end date represents the last day of that month.
Now, I agree with the others here that this is a weird thing to do, but if this is what your case requires, then so be it. We can't really comment on your business / programming specific needs here. :)