linq to sql merge dates into groups - tsql

I have a simple sql query like so:
SELECT dt AS 'startDate'
, dt AS 'endDate'
FROM
WorkCalendar
WHERE
dt BETWEEN dateadd(yy, datediff(yy, 0, getdate()), 0) AND dateadd(MILLISECOND, -3, dateadd(YEAR, datediff(YEAR, 0, getdate()) + 1, 0))
AND isWorkDay = 0
This returns all dates from my table containing days free from work in current year.
This is sample output:
startDate endDate
2012-01-01 00:00:00 2012-01-01 00:00:00
2012-01-06 00:00:00 2012-01-06 00:00:00
2012-01-07 00:00:00 2012-01-07 00:00:00
2012-01-08 00:00:00 2012-01-08 00:00:00
2012-01-14 00:00:00 2012-01-14 00:00:00
2012-01-15 00:00:00 2012-01-15 00:00:00
2012-01-21 00:00:00 2012-01-21 00:00:00
2012-01-22 00:00:00 2012-01-22 00:00:00
What I would like to do is to group near dates like so:
startDate endDate
2012-01-01 00:00:00 2012-01-01 00:00:00
2012-01-06 00:00:00 2012-01-08 00:00:00
2012-01-14 00:00:00 2012-01-15 00:00:00
2012-01-21 00:00:00 2012-01-22 00:00:00
If I have 2 or more days that are one by another I would like to join them into groups.
I would like this done with linq to sql as it will be simpler for me to use in webservice, but simple sql will do the trick :)

Got something like this:
WITH d (d1)
AS (SELECT dt
FROM workcalendar
WHERE dt BETWEEN Dateadd(yy, Datediff(yy, 0, Getdate()), 0) AND
Dateadd(millisecond, -3,
Dateadd(year, Datediff(year,
0,
Getdate()) + 1
, 0))
AND isworkday = 0)
SELECT Z1.d1 AS startDate,
Z2.d1 AS endDate
FROM (SELECT Row_number()
OVER (
ORDER BY A.d1) AS 'ID',
A.d1
FROM d AS A
WHERE NOT EXISTS (SELECT *
FROM d AS C
WHERE A.d1 = Dateadd(d, 1, C.d1))) AS Z1,
(SELECT Row_number()
OVER (
ORDER BY A.d1) AS 'ID',
A.d1
FROM d AS A
WHERE NOT EXISTS (SELECT *
FROM d AS C
WHERE A.d1 = Dateadd(d, -1, C.d1))) AS Z2
WHERE Z1.id = Z2.id
But any comments and optimization are welcome :)

Related

PostgreSQL creating timestamp ranges

In PostgreSQL 11, I am trying to get a weekend time range. From 17:00 Friday to Sunday 17:00.
So far I am able to get a working day by doing
select * from generate_series(date '2021-01-01',date '2021-12-31',interval '1' day) as t(dt) where extract (dow from dt) between 1 and 5;
However, I am have trouble creating 2 columns from start (17:00 Friday) to finish (17:00 Sunday).
Expected output should be something like this:
start stop
2022-10-07 17:00 2022-10-09 17:00
2022-10-14 17:00 2022-10-16 17:00
2022-10-21 17:00 2022-10-23 17:00
To get a series of all hours between 17:00 on Friday and 17:00 on Sunday.
SELECT
*
FROM
generate_series(timestamp '2021-01-01', timestamp '2021-12-31', interval '1' hour) AS t (dt)
WHERE
extract(dow FROM dt) IN (5, 6, 0)
AND CASE WHEN extract(dow FROM dt) = 5 THEN
extract(hour FROM dt) >= 17
WHEN extract(dow FROM dt) = 0 THEN
extract(hour FROM dt) <= 17
ELSE
extract(hour FROM dt) IS NOT NULL
END;
UPDATE
Get two timestamps that represent start and stop of each period Friday 17:00 to Sunday 17:00 over a range of dates.
SELECT
dt + '17:00'::time as start, (dt + '17:00'::time) + '2 days'::interval as stop
FROM
generate_series(date '2022-01-01', date '2022-12-31', interval '1' day) AS t (dt)
WHERE
extract(dow FROM dt) = 5
;
start | stop
-------------------------+-------------------------
01/07/2022 17:00:00 PST | 01/09/2022 17:00:00 PST
01/14/2022 17:00:00 PST | 01/16/2022 17:00:00 PST
01/21/2022 17:00:00 PST | 01/23/2022 17:00:00 PST
01/28/2022 17:00:00 PST | 01/30/2022 17:00:00 PST
02/04/2022 17:00:00 PST | 02/06/2022 17:00:00 PST
02/11/2022 17:00:00 PST | 02/13/2022 17:00:00 PST
02/18/2022 17:00:00 PST | 02/20/2022 17:00:00 PST
02/25/2022 17:00:00 PST | 02/27/2022 17:00:00 PST
03/04/2022 17:00:00 PST | 03/06/2022 17:00:00 PST
03/11/2022 17:00:00 PST | 03/13/2022 17:00:00 PDT
03/18/2022 17:00:00 PDT | 03/20/2022 17:00:00 PDT
03/25/2022 17:00:00 PDT | 03/27/2022 17:00:00 PDT
...
--timestamptz type.
SELECT
(day + interval '17:30') AS start,
(day + interval '17:30' + interval '2 days') AS
END
FROM
generate_series(date '2022-10-01', date '2022-12-31', interval '1' day) _ (day)
WHERE
EXTRACT(ISODOW FROM day) = 5;
--timestamp type.
SELECT
(day + interval '17:30')::timestamp AS start,
(day + interval '17:30' + interval '2 days')::timestamp AS
END
FROM
generate_series(date '2022-10-01', date '2022-12-31', interval '1' day) _ (day)
WHERE
EXTRACT(ISODOW FROM day) = 5;
I do checked the calendar, it works.

T-SQL Retrieve Last 6 Weeks Data to the Previous Saturday

SELECT TOP 100 *
FROM FactSalesDetail
WHERE TradingDate >= DATEADD(ww, -6, (Select MAX([TradingDate]) From FactSalesDetail))
ORDER BY TradingDate
Can anyone advise how I can convert the above WHERE Clause from retrieving the last 6 weeks data from Max Date in my Fact Table to the last 6 weeks to the Previous Saturday?
So as of Today that would Saturday 1st Jan and then back 6 weeks from that?
Using the current weekday can get you last Saturday.
And if you bring ##DATEFIRST into the equation then it won't depend on the DATEFIRST setting.
SELECT TOP 100 *
FROM FactSalesDetail
WHERE TradingDate >= CONVERT(DATE, DATEADD(WEEK, -6, DATEADD(DAY, -(##DATEFIRST+DATEPART(WEEKDAY,GETDATE()))%7,
GETDATE())))
AND TradingDate <= CONVERT(DATE, DATEADD(DAY, -(##DATEFIRST+DATEPART(WEEKDAY,GETDATE()))%7,
GETDATE()))
ORDER BY TradingDate
Test snippet for date range
SET DATEFIRST 7;
SELECT
datename(weekday, date_now) AS weekday_now, date_now
, datename(weekday, date1) AS wd1, date1
, datename(weekday, date2) AS wd2, date2
FROM
(
SELECT
CAST(GETDATE() AS DATE) AS date_now
, CONVERT(DATE, DATEADD(WEEK, -6, DATEADD(DAY, -(##DATEFIRST+DATEPART(WEEKDAY,GETDATE()))%7,
GETDATE()))) AS date1
, CONVERT(DATE, DATEADD(DAY, -(##DATEFIRST+DATEPART(WEEKDAY,GETDATE()))%7,
GETDATE())) AS date2
) q
weekday_now
date_now
wd1
date1
wd2
date2
Tuesday
2022-01-04
Saturday
2021-11-20
Saturday
2022-01-01
db<>fiddle here

Monthly-hourly-average calculate from Postgresql database

I have the time and the values in the data base. I need to calculate for a given month the average during each hour i.e.
YYYY-mm-dd (the day can be omitted)
2021-01-01 00:00:00 value=avg(values from 00:00:00 until 00:59:59 for every day of this month at this hour interval)
2021-01-01 01:00:00 value=avg(values from 01:00:00 until 01:59:59 idem as above)
...
2021-01-01 23:00:00 value=avg(values from 23:00:00 until 23:59:59)
2021-02-01 00:00:00 value=avg(values from 00:00:00 until 00:59:59)
2021-02-01 01:00:00 value=avg(values from 01:00:00 until 01:59:59)
...
2021-02-01 23:00:00 value=avg(values from 23:00:00 until 23:59:59)
...
You can use date_trunc('hour', datestamp) in a GROUP BY statement, something like this.
SELECT DATE_TRUNC('hour', datestamp) hour_beginning, AVG(value) average_value
FROM mytable
WHERE datestamp >= '2021-01-01'
AND datestamp < '2021-02-01'
GROUP BY DATE_TRUNC('hour', datestamp)
ORDER BY DATE_TRUNC('hour', datestamp)
To generalize, in place of DATE_TRUNC you can use any injective function.
You could use
to_char(datestamp, 'YYYY-MM-01 HH24:00:00')
to get one result row per hour for every month in your date range.
SELECT to_char(datestamp, 'YYYY-MM-01 HH24:00:00') hour,
AVG(value) average_value
FROM mytable
GROUP BY to_char(datestamp, 'YYYY-MM-01 HH24:00:00')
ORDER BY to_char(datestamp, 'YYYY-MM-01 HH24:00:00')

Find overlapping time ranges

I'm surprised this hasn't come up yet.
In T-SQL, I need to find the intervals (defined by startDateTime and endDateTime) that overlap with daily interval (say 9am-5pm).
For example, with table:
CREATE TABLE [dbo].[Interval](
[startDateTime] [datetime] NOT NULL,
[endDateTime] [datetime] NOT NULL
)
Solution would be the procedure that returns only overlapping intervals:
CREATE PROCEDURE FindIntervals
-- Add the parameters for the stored procedure here
#from varchar(5) = '9:00',
#to varchar(5) = '17:00'
AS
BEGIN
select * from Interval
where ...
END
GO
EDIT:
Example intervals:
Sep 7 2011 8:00 AM - Sep 7 2011 8:30 PM
Sep 7 2011 11:00 AM - Sep 7 2011 1:00 PM
Sep 7 2011 1:00 PM - Sep 7 2011 6:00 PM
Sep 9 2011 8:00 AM - Sep 9 2011 8:30 PM
Sep 9 2011 11:00 AM - Sep 9 2011 1:00 PM
Sep 9 2011 1:00 PM - Sep 9 2011 6:00 PM
So, for given interval "nine to five", 2, 3, 5 and 6 should be returned, as they overlap the given input.
But,
Sep 9 2011 8:00 AM - Sep 10 2011 8:30 PM
also fits, because it includes entire day.
Please, I need help with matching string and datetime values in T-SQL, not abstract "less then"/"greater then" solutions.
declare #Interval table
(
startDateTime datetime,
endDateTime datetime
)
insert into #Interval values
('2011-09-07T08:00:00', '2011-09-07T08:30:00'),
('2011-09-07T11:00:00', '2011-09-07T13:00:00'),
('2011-09-07T13:00:00', '2011-09-07T18:00:00'),
('2011-09-09T08:00:00', '2011-09-09T08:30:00'),
('2011-09-09T11:00:00', '2011-09-09T13:00:00'),
('2011-09-09T13:00:00', '2011-09-09T18:00:00'),
('2011-09-09T08:00:00', '2011-09-10T08:30:00')
declare #from varchar(5) = '09:00'
declare #to varchar(5) = '17:00'
;with L(MinDate, MaxDate) as
(
select dateadd(day, datediff(day, 0, min(startDateTime)), 0),
dateadd(day, datediff(day, 0, max(endDateTime)), 0)
from #Interval
),
D(fromTime, endTime) as
(
select dateadd(day, Number.number, L.MinDate)+cast(#from as datetime),
dateadd(day, Number.number, L.MinDate)+cast(#to as datetime)
from L
inner join master..spt_values as Number
on Number.number <= datediff(day, L.MinDate, L.MaxDate)
where Number.type = 'P'
)
select I.startDateTime,
I.endDateTime
from #Interval as I
where exists (select *
from D
where I.startDateTime < D.endTime and
I.endDateTime > D.fromTime)
Result:
startDateTime endDateTime
----------------------- -----------------------
2011-09-07 11:00:00.000 2011-09-07 13:00:00.000
2011-09-07 13:00:00.000 2011-09-07 18:00:00.000
2011-09-09 11:00:00.000 2011-09-09 13:00:00.000
2011-09-09 13:00:00.000 2011-09-09 18:00:00.000
2011-09-09 08:00:00.000 2011-09-10 08:30:00.000
If you expect to have a date range of more than 2048 days you need to replace master..spt_values with a numbers table. Make sure the numbers table starts with 0.
SQL Server 2008 version
;with L(MinDate, MaxDate) as
(
select cast(min(startDateTime) as date),
cast(max(endDateTime) as date)
from #Interval
),
D(fromTime, endTime) as
(
select dateadd(day, Number.number, L.MinDate)+cast(#from as datetime),
dateadd(day, Number.number, L.MinDate)+cast(#to as datetime)
from L
inner join master..spt_values as Number
on Number.number <= datediff(day, L.MinDate, L.MaxDate)
where Number.type = 'P'
)
select I.startDateTime,
I.endDateTime
from #Interval as I
where exists (select *
from D
where I.startDateTime < D.endTime and
I.endDateTime > D.fromTime)

Understanding TSQL Coalesce function

I am trying to select all 12 months / year. And I thought following TSQL code would do this. However, this does not include all months like I want. What is the cause of this? This is modified code:
DECLARE #END_YEAR VARCHAR(10)
DECLARE #END_MONTH VARCHAR(10)
SET #END_YEAR = '2010'
SET #END_MONTH = '10'
DECLARE #TheMonthLastDate DATETIME
DECLARE #TempDate DATETIME
SET #TempDate = '2010-11-01 00:00:00.000'
SET #TheMonthLastDate = '2010-11-01 00:00:00.000'
;with months
as
(
select dateadd(month, -1, dateadd(day, datediff(day, 0, #TempDate), 0)) as m
union all
select dateadd(month, -1, m)
from months
where m > dateadd(month, -12, #TempDate)
)
,yourTable(DateOpened, DateClosed)
as
(select TSK_START_DATE, BTK_CLOSED_DATE
FROM [PROC].ALL_AUDIT
WHERE
(BTK_CLOSED_DATE < #TheMonthLastDate OR
TSK_START_DATE < #TheMonthLastDate
)
)
select yt.DateClosed 'r2', m.m 'r3',
month(coalesce(yt.DateClosed, m.m)) as 'MonthClosed',
year(coalesce(yt.DateClosed, m.m)) as 'YearClosed'
from months m
left join yourTable yt
on
( datepart(year, yt.DateClosed) = DATEPART(year, m.m)
and datepart(month, yt.DateClosed) = DATEPART(month, m.m)
or
datepart(year, yt.DateOpened) = DATEPART(year, m.m)
and datepart(month, yt.DateOpened) = DATEPART(month, m.m)
)
AND year(coalesce(yt.DateClosed, m.m)) = 2010
order by yt.DateClosed
So above query does not return all months. But if I change above WHERE lines to:
FROM [PROC].ALL_AUDIT
WHERE
BTK_CLOSED_DATE < #TheMonthLastDate
then this query does return all 12 months. How can this be?
Output that I want and that I see when WHERE is BTK_CLOSED_DATE < #TheMonthLastDate:
r2 r3 MonthClosed YearClosed
NULL 2010-06-01 00:00:00.000 6 2010
NULL 2009-11-01 00:00:00.000 11 2009
2010-01-06 20:02:19.127 2010-01-01 00:00:00.000 1 2010
2010-01-27 23:13:45.570 2010-01-01 00:00:00.000 1 2010
2010-02-15 14:49:14.427 2010-02-01 00:00:00.000 2 2010
2010-02-15 14:49:14.427 2009-12-01 00:00:00.000 2 2010
But if I instead use WHERE:
(BTK_CLOSED_DATE < #TheMonthLastDate OR
TSK_START_DATE < #TheMonthLastDate
)
then I see:
r2 r3 MonthClosed YearClosed
NULL 2010-10-01 00:00:00.000 10 2010
NULL 2010-09-01 00:00:00.000 9 2010
NULL 2010-09-01 00:00:00.000 9 2010
NULL 2010-08-01 00:00:00.000 8 2010
NULL 2010-08-01 00:00:00.000 8 2010
...
So notice that in first result I see NULL for June 2010, which is what I want.
I think the problem has something to do with the fact that my data contains 2009-2011 data, but I only compare months and not years. How would I add in this additional logic?
The only place where you are reducing the data is with the WHERE clause you have already identified. Therefore, the reason you are not getting all the months you expect is down to the column TSK_START_DATE not being less than #TheMonthLastDate for all months.
To prove this hypothesis, run the following section of your query (I have commented out part of the where clause and removed everything under 'yourTable' cte). The results should show you what is being returned in the TSK_Start_Date column for your missing months and help you identify why the rows are missing when applying the < #TheMonthLastDate clause.
DECLARE #END_YEAR VARCHAR(10)
DECLARE #END_MONTH VARCHAR(10)
SET #END_YEAR = '2010'
SET #END_MONTH = '10'
DECLARE #TheMonthLastDate DATETIME
DECLARE #TempDate DATETIME
SET #TempDate = '2010-11-01 00:00:00.000'
SET #TheMonthLastDate = '2010-11-01 00:00:00.000'
;with months
as
(
select dateadd(month, -1, dateadd(day, datediff(day, 0, #TempDate), 0)) as m
union all
select dateadd(month, -1, m)
from months
where m > dateadd(month, -12, #TempDate)
)
,yourTable(DateOpened, DateClosed)
as
(select TSK_START_DATE, BTK_CLOSED_DATE
FROM [PROC].ALL_AUDIT
WHERE
(BTK_CLOSED_DATE < #TheMonthLastDate OR
--TSK_START_DATE < #TheMonthLastDate
)
)
select * , #TheMonthLastDate TheMonthLastDate from yourTable