SQL Start Date End Date Spread difference across months - sql

I currently have a database which has a start date and and end date when when a Car is being held for Maintenance. I am able to figure the difference between these dates using datediff but the problem is that the value when across multiple months is linked either to the month of the start date or the end date. I am looking to spread the difference between the months so in my analysis I can see how long the cars have been held each month in days.
Example:
StartDate: '2022-04-28 06:33:34.000'
EndDate: '2022-06-20 14:09:45.000'
Days Difference: 53 days 7 Hours 36 Minutes 11 Seconds
What I need to do is spread the 54 rounded up as
April: 3
May: 31
June: 20
I currently calculating the difference either as day percentage or in days using this logic.
'CAST(CAST(DATEDIFF(s, STARTDATE, ENDDATE)AS float)/86400 AS DECIMAL(16,3)) AS CAR_TOTAL_DAYS_PERC' returns 53.317
'DATEDIFF(s, STARTDATE, ENDDATE) / 86400 AS CAR_TOTAL_DAYS' returns 53
Any assistance would be greatly appreciated.

You may want to implement this as a stored procedure, something like GetDateSpreadString
--Variables for start and end date
DECLARE #startDate AS DATETIME
DECLARE #endDate AS DATETIME
SET #startDate='2022-04-28 06:33:34.000'
SET #endDate ='2022-06-20 14:09:45.000';
--CTE to expand the count of date numbers to be used for DATEADD
WITH numbers
as
(
Select 1 as value
Union ALL
Select value + 1 from numbers
where value + 1 <= DATEDIFF( dd,#startDate, #endDate)
),
--CTE to group individual days covered by spread in each of the months.
MonthsDays AS (
Select EOMONTH(DATEADD(d,value, #startDate)) as MonthSpread, DATEPART(dd,DATEADD(d,value, #startDate)) as DaySpread
From numbers
)
--USE FOR XML PATH() and STUFF() to make it a single string result
SELECT STUFF(
(
Select CONCAT(' ',COUNT(daySpread),','), CONCAT( DATENAME(month,MonthSpread),',')
FROM MonthsDays
GROUP BY MonthSpread
FOR XML PATH('')
),1,1,'') AS Result

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!

How to get six weeks data from a week column?

I have a legacy query in which I am looking data for six weeks as shown below. In my below AND condition I get data for past six weeks and it worked fine in 2020 middle and end. But since 2021 started, this stopped working because of obvious subtraction I am doing with 6.
AND data.week_col::integer BETWEEN DATE_PART(w, CURRENT_DATE) - 6 AND DATE_PART(w, CURRENT_DATE) - 1
There is a bug in above query because of which it stopped working in 2021. How can I change above condition so that it can work entire year without any issues and give me data for past 6 weeks.
Update
Below is my query which I am running:
select *,
dateadd(d, - datepart(dow, trunc(CONVERT_TIMEZONE('UTC','PST8PDT',client_date))), trunc(CONVERT_TIMEZONE('UTC','PST8PDT',client_date)) + 6) as day,
date_part(week, day) as week_col
from holder data
where data.week_col::integer BETWEEN DATE_PART(w, CURRENT_DATE) - 6 AND DATE_PART(w, CURRENT_DATE) - 1
client_date column has values like this - 2021-01-15 21:30:00.0. And from that I get value of day column and from day column I get value of
week_col column as shown above.
week_col column has values like 53, 52 .... It's a week number in general.
Because of my AND condition I am getting data for week 1 only but technically I want data for 49, 50, 51, 52, 53 and 1 as it is past six weeks. Can I use day column here to get correct past six weeks?
Would this serve as a solution? I do not know much about the redshirt syntax but I read it supports dateadd(). If you are normalizing client_date to a time zone converted day with no time then why not simply use that in the comparison to the current date converted to the same time zone.
WHERE
client_date BETWEEN
DATEADD(WEEK,-6,trunc(CONVERT_TIMEZONE('UTC','PST8PDT',CURRENT_DATE)))
AND
DATEADD(WEEK,-1,trunc(CONVERT_TIMEZONE('UTC','PST8PDT',CURRENT_DATE)))
If the above logic works out then you may want to convert the -6 and -1 week to variables, if that is supported.
Solution 2
This is a bit more verbose but involves virtualizing a calender table and then joining your current date parameter into the calender data, for markers. Finally, you can join your data against the calender which has been normalized by weeks in time chronologically.
This is SQL Server syntax, however, I am certain it can be converted to RS.
DECLARE #D TABLE(client_date DATETIME)
INSERT #D VALUES
('11/20/2020'),('11/27/2020'),
('12/4/2020'),('12/11/2020'),('12/18/2020'),('12/25/2020'),
('01/8/2021'),('01/8/2021'),('1/15/2021'),('1/22/2021'),('1/29/2021')
DECLARE #Date DATETIME = '1/23/2021'
DECLARE #StartDate DATETIME = '01/01/2010'
DECLARE #NumberOfDays INT = 6000
;WITH R1(N) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
R2(N) AS (SELECT 1 FROM R1 a, R1 b),
R3(N) AS (SELECT 1 FROM R2 a, R2 b),
Tally(Number) AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM R3)
,WithTally AS
(
SELECT CalendarDate = DATEADD(DAY,T.Number,#StartDate)
FROM Tally T
WHERE T.Number < #NumberOfDays
)
,Calendar AS
(
SELECT
CalendarDate,
WeekIndex = DENSE_RANK() OVER(ORDER BY DATEPART(YEAR, CalendarDate), DATEPART(WEEK, CalendarDate))
FROM
WithTally
),
CalendarAlignedWithCurrentDateParamater AS
(
SELECT *
FROM
Calendar
CROSS JOIN (SELECT WeekIndexForToday=WeekIndex FROM Calendar WHERE Calendar.CalendarDate=#Date ) AS X
)
SELECT
D.*,
C.WeekIndex,
C.WeekIndexForToday
FROM
CalendarAlignedWithCurrentDateParamater C
INNER JOIN #D D ON D.client_date = C.CalendarDate
WHERE
C.WeekIndex BETWEEN C.WeekIndexForToday-6 AND C.WeekIndexForToday-1
OPTION (MAXRECURSION 0)

Dynamic Grouping based on date field SSRS

In my SSRS report which has a dataset with date field as below
The dataset returns the following type of data (just an example)
Voucher Amount Date
R3221 € 3,223.00 1-Dec-17
R3222 € 123.00 28-Nov-17
R3223 € 1,233.00 19-Oct-17
R3224 € 442.00 27-Sep-17
R3225 € 123.00 17-Nov-17
R3226 € 423.00 29-Oct-17
R3227 € 1,234.00 8-Oct-17
What I would like to know is how to show this data grouped by Voucher and Due Date
User should be able to select the Start Date and the period type (Day, Week, month,) and the interval between the two columns (e.g 3 , 10 or 30 or any other number)
so the user should be able to select the period type, e.g if he select Day and interval as 3 then the report should show
**voucher start date <Dynamic grouping columns based on the selection criteria>**
R3221
R3222
R3223
R3224
R3225
R3226
R3227
Any kind of hint will be much appreciated!
This is only part of the solution. First generate date periods you want in the report, then do a LEFT JOIN against your table, filter and group.
declare #dateFrom date
declare #periodInDays int
declare #periods int --how many periods of 10 days
set #periodInDays = 10;
set #periods = 15; --how many periods of 10 days
set #dateFrom = getdate();
with [dates] as (
select #dateFrom as date --start
union all
select dateadd(day, #periodInDays, [date]) as date
from [dates]
where [date] < DATEADD(day, #periodInDays * #periods, #dateFrom) --end
)
select [date]
from [dates]
option (maxrecursion 0)
In this example returns 16 dates (1 per row), from start date (2017-12-01), every 10 days
2017-12-01, 2017-12-11, 2017-12-21, 2017-12-31, 2018-01-10, 2018-01-20
2018-01-30, 2018-02-09, 2018-02-19, 2018-03-01, 2018-03-11, 2018-03-21
2018-03-31, 2018-04-10, 2018-04-20, 2018-04-30
To filter by period get start date from the above date table, so the end date for that period can be calculated as DATEADD(day, #periodInDays, [date]) and we don't need to look into next row at this point.
Just let me know if someone find more straight forward solution.

Find previous equivalent dates over the past two calender years

If today is say 15th August 2012 then the query should return the following
15/01/2011,
15/02/2011,
...
...
15/07/2012
15/08/2012
If today is 31st August 2012 then the query would return
31/01/2011,
28/02/2011, <<<<this is the nearest date
...
...
31/07/2012
31/08/2012
We have a vw_DimDate in our Warehouse which should help
edit
It contains the following fields
Currently I'm using the following but it seems rather convoluted! ...
DECLARE #Dt DATETIME = '31 JUL 2012'--GETDATE()
;WITH DateSet_cte(DayMarker)
AS
(
SELECT DayMarker
FROM WHData.dbo.vw_DimDate
WHERE
DayMarker >= CONVERT(DATETIME,CONVERT(CHAR(4),DATEADD(YEAR,-1,#Dt),112) + '0101') AND
DayMarker <=#Dt
)
, MaxDate_cte(MaxDate)
AS
(
SELECT [MaxDate] = MAX(DayMarker)
FROM DateSet_cte
)
SELECT
[Mth] = CONVERT(DATETIME,CONVERT(CHAR(6),a.DayMarker,112) + '01')
, MAX(a.DayMarker) [EquivDate]
FROM DateSet_cte a
WHERE DAY(a.DayMarker) <= (SELECT DAY([MaxDate]) FROM MaxDate_cte)
GROUP BY CONVERT(DATETIME,CONVERT(CHAR(6),a.DayMarker,112) + '01')
;with Numbers as (
select distinct number from master..spt_values where number between 0 and 23
), Today as (
select CONVERT(date,CURRENT_TIMESTAMP) as d
)
select
DATEADD(month,-number,d)
from
Numbers,Today
where DATEPART(year,DATEADD(month,-number,d)) >= DATEPART(year,d) - 1
Seems odd to want a variable number of returned values based on how far through the year we are, but that's what I've implemented.
When you use DATEADD to add months to a value, then it automatically adjusts the day number if it would have produced an out of range date (e.g. 31st February), such that it's the last day of the month. Or, as the documentation puts it:
If datepart is month and the date month has more days than the return month and the date day does not exist in the return month, the last day of the return month is returned.
Of course, if you already have a numbers table in your database, you can eliminate the first CTE. You mentioned that you "have a vw_DimDate in our Warehouse which should help", but since I have no idea on what that (presumably, a) view contains, it wasn't any help.

Return just the last day of each month with SQL

I have a table that contains multiple records for each day of the month, over a number of years. Can someone help me out in writing a query that will only return the last day of each month.
SQL Server (other DBMS will work the same or very similarly):
SELECT
*
FROM
YourTable
WHERE
DateField IN (
SELECT MAX(DateField)
FROM YourTable
GROUP BY MONTH(DateField), YEAR(DateField)
)
An index on DateField is helpful here.
PS: If your DateField contains time values, the above will give you the very last record of every month, not the last day's worth of records. In this case use a method to reduce a datetime to its date value before doing the comparison, for example this one.
The easiest way I could find to identify if a date field in the table is the end of the month, is simply adding one day and checking if that day is 1.
where DAY(DATEADD(day, 1, AsOfDate)) = 1
If you use that as your condition (assuming AsOfDate is the date field you are looking for), then it will only returns records where AsOfDate is the last day of the month.
Use the EOMONTH() function if it's available to you (E.g. SQL Server). It returns the last date in a month given a date.
select distinct
Date
from DateTable
Where Date = EOMONTH(Date)
Or, you can use some date math.
select distinct
Date
from DateTable
where Date = DATEADD(MONTH, DATEDIFF(MONTH, -1, Date)-1, -1)
In SQL Server, this is how I usually get to the last day of the month relative to an arbitrary point in time:
select dateadd(day,-day(dateadd(month,1,current_timestamp)) , dateadd(month,1,current_timestamp) )
In a nutshell:
From your reference point-in-time,
Add 1 month,
Then, from the resulting value, subtract its day-of-the-month in days.
Voila! You've the the last day of the month containing your reference point in time.
Getting the 1st day of the month is simpler:
select dateadd(day,-(day(current_timestamp)-1),current_timestamp)
From your reference point-in-time,
subtract (in days), 1 less than the current day-of-the-month component.
Stripping off/normalizing the extraneous time component is left as an exercise for the reader.
A simple way to get the last day of month is to get the first day of the next month and subtract 1.
This should work on Oracle DB
select distinct last_day(trunc(sysdate - rownum)) dt
from dual
connect by rownum < 430
order by 1
I did the following and it worked out great. I also wanted the Maximum Date for the Current Month. Here is what I my output is. Notice the last date for July which is 24th. I pulled it on 7/24/2017, hence the result
Year Month KPI_Date
2017 4 2017-04-28
2017 5 2017-05-31
2017 6 2017-06-30
2017 7 2017-07-24
SELECT B.Year ,
B.Month ,
MAX(DateField) KPI_Date
FROM Table A
INNER JOIN ( SELECT DISTINCT
YEAR(EOMONTH(DateField)) year ,
MONTH(EOMONTH(DateField)) month
FROM Table
) B ON YEAR(A.DateField) = B.year
AND MONTH(A.DateField) = B.Month
GROUP BY B.Year ,
B.Month
SELECT * FROM YourTableName WHERE anyfilter
AND "DATE" IN (SELECT MAX(NameofDATE_Column) FROM YourTableName WHERE
anyfilter GROUP BY
TO_CHAR(NameofDATE_Column,'MONTH'),TO_CHAR(NameofDATE_Column,'YYYY'));
Note: this answer does apply for Oracle DB
Here's how I just solved this. day_date is the date field, calendar is the table that holds the dates.
SELECT cast(datepart(year, day_date) AS VARCHAR)
+ '-'
+ cast(datepart(month, day_date) AS VARCHAR)
+ '-'
+ cast(max(DATEPART(day, day_date)) AS VARCHAR) 'DATE'
FROM calendar
GROUP BY datepart(year, day_date)
,datepart(month, day_date)
ORDER BY 1