How to get the count of current month Sunday's in psql? - postgresql

How to get total number of Sunday's for given date in postgresql

You need EXTRACT:
SELECT
EXTRACT(DOW FROM DATE '2011-02-16') = 0; -- 0 is Sunday
This can result in true or false, it's a sunday or it's not. I have no idea what you mean by "total number" because that will always be 0 (the date is not a sunday) or 1 (the given data is a sunday).
Edit: Something like this?
SELECT
COUNT(*)
FROM
generate_series(timestamp '2011-01-01', '2011-03-01', '1 day') AS g(mydate)
WHERE
EXTRACT(DOW FROM mydate) = 0;

The total number of Sundays for a given date can only be either 0 or 1.
But if you want the number of Sundays within a given date range, then your best bet is a calendar table. To find how many Sundays are in February this year, I'd just
select count(*)
from calendar
where cal_date between '2011-02-01' and '2011-02-28' and
day_of_week = 'Sun';
or
select count(*)
from calendar
where year_of_date = 2011 and
month_of_year = 2 and
day_of_week = 'Sun';
Here's a basic calendar table that you can start with. I also included a PostgreSQL function to populate the calendar table. I haven't tested this in 8.3, but I'm pretty sure I'm not using any features that 8.3 doesn't support.
Note that the "dow" parts assume your days are in English. But you can easily edit those parts to match any language. (I think. But I could be wrong about "easily".)
-- Table: calendar
-- DROP TABLE calendar;
CREATE TABLE calendar
(
cal_date date NOT NULL,
year_of_date integer NOT NULL,
month_of_year integer NOT NULL,
day_of_month integer NOT NULL,
day_of_week character(3) NOT NULL,
CONSTRAINT calendar_pkey PRIMARY KEY (cal_date),
CONSTRAINT calendar_check CHECK (year_of_date::double precision = date_part('year'::text, cal_date)),
CONSTRAINT calendar_check1 CHECK (month_of_year::double precision = date_part('month'::text, cal_date)),
CONSTRAINT calendar_check2 CHECK (day_of_month::double precision = date_part('day'::text, cal_date)),
CONSTRAINT calendar_check3 CHECK (day_of_week::text =
CASE
WHEN date_part('dow'::text, cal_date) = 0::double precision THEN 'Sun'::text
WHEN date_part('dow'::text, cal_date) = 1::double precision THEN 'Mon'::text
WHEN date_part('dow'::text, cal_date) = 2::double precision THEN 'Tue'::text
WHEN date_part('dow'::text, cal_date) = 3::double precision THEN 'Wed'::text
WHEN date_part('dow'::text, cal_date) = 4::double precision THEN 'Thu'::text
WHEN date_part('dow'::text, cal_date) = 5::double precision THEN 'Fri'::text
WHEN date_part('dow'::text, cal_date) = 6::double precision THEN 'Sat'::text
ELSE NULL::text
END)
)
WITH (
OIDS=FALSE
);
ALTER TABLE calendar OWNER TO postgres;
-- Index: calendar_day_of_month
-- DROP INDEX calendar_day_of_month;
CREATE INDEX calendar_day_of_month
ON calendar
USING btree
(day_of_month);
-- Index: calendar_day_of_week
-- DROP INDEX calendar_day_of_week;
CREATE INDEX calendar_day_of_week
ON calendar
USING btree
(day_of_week);
-- Index: calendar_month_of_year
-- DROP INDEX calendar_month_of_year;
CREATE INDEX calendar_month_of_year
ON calendar
USING btree
(month_of_year);
-- Index: calendar_year_of_date
-- DROP INDEX calendar_year_of_date;
CREATE INDEX calendar_year_of_date
ON calendar
USING btree
(year_of_date);
And a rudimentary function to populate the table. I haven't tested this in 8.3 either.
-- Function: insert_range_into_calendar(date, date)
-- DROP FUNCTION insert_range_into_calendar(date, date);
CREATE OR REPLACE FUNCTION insert_range_into_calendar(from_date date, to_date date)
RETURNS void AS
$BODY$
DECLARE
this_date date := from_date;
BEGIN
while (this_date <= to_date) LOOP
INSERT INTO calendar (cal_date, year_of_date, month_of_year, day_of_month, day_of_week)
VALUES (this_date, extract(year from this_date), extract(month from this_date), extract(day from this_date),
case when extract(dow from this_date) = 0 then 'Sun'
when extract(dow from this_date) = 1 then 'Mon'
when extract(dow from this_date) = 2 then 'Tue'
when extract(dow from this_date) = 3 then 'Wed'
when extract(dow from this_date) = 4 then 'Thu'
when extract(dow from this_date) = 5 then 'Fri'
when extract(dow from this_date) = 6 then 'Sat'
end);
this_date = this_date + interval '1 day';
end loop;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;

Related

Same query takes 5x as long when inside a function - what is wrong with the function?

I have the following query to run, which takes 50-60ms (query plan here):
SELECT (SUM(added_vacancies) + (SELECT active_vacancies FROM report.vacancy_stats WHERE date = make_date(2021,06,01) AND company_id = 6886 LIMIT 1) - SUM(archived_vacancies)) as result
FROM report.vacancy_stats
WHERE company_id = 6886
AND vacancy_stats.date >= (make_date(2021,06,01) + integer '1')::DATE
AND vacancy_stats.date <= make_date(2021,09,25)
LIMIT 1;
Now I've put it into a function like this; and suddenly, it takes at least 300ms to run:
CREATE OR REPLACE FUNCTION report.cp_active_vacancy_count_during_period(cid integer, date_start date, date_end date)
RETURNS bigint
LANGUAGE sql
STABLE LEAKPROOF PARALLEL SAFE
AS $function$
SELECT (SUM(added_vacancies) + (SELECT active_vacancies FROM report.vacancy_stats WHERE date = date_start AND company_id = cid LIMIT 1) - SUM(archived_vacancies)) as result
FROM report.vacancy_stats
WHERE company_id = cid
AND vacancy_stats.date >= (date_start + integer '1')::DATE
AND vacancy_stats.date <= date_end
LIMIT 1;
$function$;
What could be the reason for this huge difference, and how can I solve that issue?

SQL deduct some numbers from rows conditionally

I have a table (showing temp table)
CREATE TABLE #TempTable
(
TempID INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
CustID INT NOT NULL,
RODate DATE NULL,
Operation INT NULL
);
This table has this sample data:
INSERT INTO #TempTable (CustID, RODate, Operation)
VALUES (10, DATEADD(MONT, -2, GETDATE()), 2),
(10, DATEADD(MONT, -1, GETDATE()), 3),
(10, GETDATE(), 5)
So table have below data
TempID CustID RODate Operation
-----------------------------------------------------------
1 10 2019-03-17 2
2 10 2019-04-17 3
3 10 2019-05-17 5
Requirement is I will get one integer variable in parameter which is #noOfOperation, let's say its value is 10
I will also get no of months in parameter, let's say it's 3
I have to query the table to return data for last 3 months only (excluding current month (date asc)
Then I have to deduct #noOfOperation from the table and update.
Deduction will be based on availability in operation column.
For example: in this case first we will deduct from 2019-03-17
10 - 2 = 8 (operation column for this row becomes 0)
Next we will deduct from 2019-04-17
8 - 3 = 5 (operation column for this row becomes 0)
Similarly for 2019-05-17
5-5 = 0 (operation column for this row becomes 0)
I have to check if #noOfOperation is less than or more than the number of operation of individual months then do the above accordingly
Check this out, The idea is to use the accumulated operation and then subtracted the needed value as below:
declare #TempTable TABLE
(
TempID INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
CustID INT NOT NULL,
RODate DATE NULL,
Operation INT NULL
);
INSERT INTO #TempTable (CustID, RODate, Operation)
VALUES (10, DATEADD(MONTH, -2, GETDATE()), 5),
(10, DATEADD(MONTH, -1, GETDATE()), 6),
(10, GETDATE(), 7)
select * from #TempTable
Declare #noOfOperation int =8
Declare #noOfMonths int =3
Declare #StartDate date,#DateEnd date,#avNoOfOperation int
--get the range you are working for
select
#StartDate=cast(cast(year(dateadd(Month,-#noOfMonths+1,getdate())) as varchar(4))+'-'+cast(Month(dateadd(Month,-#noOfMonths+1,getdate())) as varchar(2))+'-01' as date)
,#DateEnd=dateadd(day,-1,cast(cast(year(getdate()) as varchar(4))+'-'+cast(Month(getdate()) as varchar(2))+'-01' as date)) ;
--get the total of avaliable operation, for validating before subtracting
select #avNoOfOperation=sum(t.Operation) from #TempTable t where cast(t.RODate as date) between #StartDate and #DateEnd
--review the variables if needed
--select #StartDate [#StartDate],#DateEnd [#DateEnd],#avNoOfOperation [#avNoOfOperation]
if(#avNoOfOperation>=#noOfOperation and #noOfOperation>0)
begin
--only here we can start subtracting
;with DataIncluded as (
select *,#noOfOperation [noOfOperation],sum(Operation) over (order by RODate) [AcOp] from #TempTable t where cast(t.RODate as date) between #StartDate and #DateEnd
),SubtractDataSet as (
select *,AcOp-#noOfOperation [leftOp],
case when (AcOp-#noOfOperation)<=0 then 0 else
case when (AcOp-#noOfOperation)<Operation then AcOp-#noOfOperation else Operation end end [UpOp]
from DataIncluded
)
Update #TempTable
set A.Operation=B.[UpOp]
From #TempTable A
inner join SubtractDataSet B on A.TempID=B.TempID
end
select * from #TempTable
Note: Im not using the current month so my output is different then the one you suggested. if the inputs was as follow:
TempID CustID RODate Operation
1 10 2019-03-17 5
2 10 2019-04-17 6
3 10 2019-05-17 7
The output would be :-
TempID CustID RODate Operation
1 10 2019-03-17 0
2 10 2019-04-17 3
3 10 2019-05-17 7
--Change the value of #OperationsToBeDeducted, to see different results
declare #OperationsToBeDeducted int
declare #OperationsRemaining int
declare #RODate date
set #OperationsToBeDeducted = 4
declare #TempID int
set #TempID = 1
DROP TABLE IF EXISTS #TempOperation
create table #TempOperation
(
TempID INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
CustID INT NOT NULL,
RODate DATE NULL,
Operation INT NULL
);
insert into #TempOperation (CustID,RODate,Operation)
values
(10,DATEADD(month, -3, getdate()),2),
(10,DATEADD(month, -2, getdate()), 2),
(10,DATEADD(month, -1, getdate()),3)
DROP TABLE IF EXISTS #TempOperation2
create table #TempOperation2
(
TempID INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
CustID INT NOT NULL,
RODate DATE NULL,
Operation INT NULL
);
insert into #TempOperation2 select CustID,RODate, Operation from #TempOperation
select * from #TempOperation2 order by RODate asc
declare #maxID int;
select #maxID = max(TempID) from #TempOperation2
while (#TempID <= #maxID)
begin
set #OperationsRemaining = 0
select #OperationsRemaining = Operation, #RODate = RODate from #TempOperation2 where TempID = #TempID
if(#OperationsToBeDeducted is not null and #OperationsRemaining is not null and
#OperationsRemaining > 0 and #OperationsRemaining > #OperationsToBeDeducted)
begin
update #TempOperation set Operation = #OperationsRemaining - #OperationsToBeDeducted where TempID = #TempID
set #OperationsToBeDeducted = 0
end
else if(#OperationsToBeDeducted is not null and #OperationsRemaining is not null and
#OperationsRemaining > 0 and #OperationsRemaining <= #OperationsToBeDeducted)
begin
set #OperationsToBeDeducted = #OperationsToBeDeducted - #OperationsRemaining
update #TempOperation set Operation = #OperationsRemaining - #OperationsRemaining where TempID = #TempID
end
SET #TempID = #TempID + 1
end
select * from #TempOperation order by RODate asc
DROP TABLE #TempOperation
DROP TABLE #TempOperation2

Postgres select concatenated column name

For the sake of example, there's five columns in a table month:
month.week1
month.week2
month.week3
month.week4
month.week5
The number of col is determined by a function
EXTRACT(WEEK FROM NOW()) - EXTRACT(WEEK FROM DATE_TRUNC('month', NOW())) + 1
How can I select the column colX? This is what I have so far
SELECT month.week || (
EXTRACT(WEEK FROM NOW())
- EXTRACT(WEEK FROM DATE_TRUNC('month', NOW())) + 1
)::text
FROM month
But that gives me the error
ERROR: column month.week doesn't exist
SQL state: 42703
Use case statement
SELECT
CASE WHEN (week expresion) = 1 THEN month.week1
WHEN (week expresion) = 2 THEN month.week2
WHEN (week expresion) = 3 THEN month.week3
WHEN (week expresion) = 4 THEN month.week4
ELSE month.week5
END as WeekValue
FROM month
OR
SELECT
CASE (week expresion)
WHEN 1 THEN month.week1
WHEN 2 THEN month.week2
WHEN 3 THEN month.week3
WHEN 4 THEN month.week4
ELSE month.week5
END as WeekValue
FROM month
dynamic sql sample:
t=# create table so58(i int,w1 text);
CREATE TABLE
t=# create or replace function so59(_n int) returns table (i int,w text) as $$begin
return query execute format('select i,w%s from so58',_n);
end;
$$ language plpgsql;
CREATE FUNCTION
t=# select * from so59(1);
i | w
---+---
(0 rows)

TSQL date comparison returns erroneus text centric error

Right then, I have another one. This is the same proc as I was working with before. The results weren't what I needed so went back to the drawing board. The system is the same:
SQL Server 2005 ||
Excel 2007 ||
SQL Management studio 2008R2
I am having a problem comparing two dates. The error that SSMS is throwing makes no sense. Here is the code:
declare #calendar_start char(8)
declare #years int
set #calendar_start = '20130101'
set #years = 1
----------------------------------------------------------------------------
-- standard declaration stuff. -
----------------------------------------------------------------------------
-- for ease of entry, I convert the start timeto a real time ---------------
----------------------------------------------------------------------------
declare #startdate datetime
set #startdate = CONVERT (datetime, #calendar_start, 112)
-- to calculate the end of the forecast, I use the start date and add the --
-- provided number of year -------------------------------------------------
----------------------------------------------------------------------------
declare #enddate datetime
set #enddate = dateadd(year,+#years,#startdate)
----------------------------------------------------------------------------
-- I need a variable to advance the plotting month. the plotting month is --
-- how I am going to spread out the project all year from a single date ----
----------------------------------------------------------------------------
declare #counter int
set #counter = 0
----------------------------------------------------------------------------
----------------------------------------------------------------------------
-- this table will be used to have all the calendar dates by year-month ----
----------------------------------------------------------------------------
create table #calendar (calenderid char(6))
insert into #calendar
select
distinct left(calendarid,6) [yearmonth]
from
[cmdb_core].[dbo].[Calendar]
where
datevalue between #startdate and #enddate
----------------------------------------------------------------------------
----------------------------------------------------------------------------
-- rather than hitting the database any number of times, I load the whole --
-- of the computed estimates table into memory. it is faster that way. ----
----------------------------------------------------------------------------
create table #baseline (
[adjusted_ExpectedActionDt] datetime
,[key] text
,projectid text
,projectnm text
,ParentChaseProjectNo text
,VersionTag text
,itemid text
,Qty int
,ItemNotes text
,CashflowType text
,frequency text
,UnitPrice float
,[cost] float
)
insert into #baseline (
[adjusted_ExpectedActionDt]
,[key]
,projectid
,projectnm
,ParentChaseProjectNo
,VersionTag
,itemid
,Qty
,ItemNotes
,CashflowType
,frequency
,UnitPrice
,[cost]
)
select
case
when (ExpectedActionDt is not null)
then ExpectedActionDt
when (IntegratedReleasePlanDt is not null)
then IntegratedReleasePlanDt
else
DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0)
end [adjusted_ExpectedActionDt]
,cast(ModelEstimateId as nvarchar(max))+cast(BucketId as nvarchar(max))+cast(ItemNo as nvarchar(max)) [key]
,projectid
,projectnm
,ParentChaseProjectNo
,VersionTag
,itemid
,Qty
,ItemNotes
,CashflowType
,frequency
,UnitPrice
,null [cost]
from
estimate.ComputedEstimates
where
[status] <> 'Hold'
and CostCategory <> 'Assembly'
and includeinforecast = 'Y'
and cashflowtype <> 'Notional'
and Qty <> 0
and case
when (ExpectedActionDt is not null)
then ExpectedActionDt
when (IntegratedReleasePlanDt is not null)
then IntegratedReleasePlanDt
else
DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0)
end between #startdate and #enddate
--------------------------------------------------------------------------
--------------------------------------------------------------------------
-- we need a place to contain the results of the interation through the --
-- baseline temp table. the results table will be that. ------------------
--------------------------------------------------------------------------
create table #results (
[adjusted_ExpectedActionDt] datetime
,[plot_date] datetime
,[key] text
,projectid text
,projectnm text
,ParentChaseProjectNo text
,VersionTag text
,itemid text
,Qty int
,ItemNotes text
,CashflowType text
,frequency text
,UnitPrice float
,[cost] float
)
-- this loop is how we will build the results. it is governed by the -----
-- date. as I iterate through the loop, I incriment the plot date so -----
-- that I can show a project and it's costs over the range of dates ------
-- rather than only the month it goes into production --------------------
--------------------------------------------------------------------------
WHILE (#startdate <= #enddate)
BEGIN
insert into #results (
[adjusted_ExpectedActionDt]
,[plot_date]
,[key]
,projectid
,projectnm
,ParentChaseProjectNo
,VersionTag
,itemid
,Qty
,ItemNotes
,CashflowType
,frequency
,UnitPrice
,[cost]
)
select
[adjusted_ExpectedActionDt]
,dateadd(month,+#counter,[adjusted_ExpectedActionDt])
,[key]
,projectid
,projectnm
,ParentChaseProjectNo
,VersionTag
,itemid
,Qty
,ItemNotes
,CashflowType
,frequency
,UnitPrice
,case
when frequency = 'OneTime'
then
--=====================================================================--
--===================== this is where the problem is ==================--
--=====================================================================--
case
when dateadd(mm, datediff(mm,0, [adjusted_ExpectedActionDt]), 0) =
dateadd(mm, datediff(mm,0, dateadd(month,+#counter,[adjusted_ExpectedActionDt])), 0)
then [Qty]
else
0
end
--=====================================================================--
--=====================================================================--
else
cast(round((UnitPrice*Qty)/12,0) as int)
end [cost]
from #baseline
set #counter = #counter+1
set #startdate = dateadd(month,+1,#startdate)
END
--------------------------------------------------------------------------
--------------------------------------------------------------------------
-- now we have to return the results but it is not enough to just dump ---
-- the table, I have to add a date that covers all month regardless of ---
-- that month's presence in the results table. I use the calendar temp, --
-- which has those dates as a basis of the outer join --------------------
--------------------------------------------------------------------------
select
c.calenderid
,r.[adjusted_ExpectedActionDt]
,r.[plot_date]
,r.[key]
,r.projectid
,r.projectnm
,r.ParentChaseProjectNo
,r.VersionTag
,r.itemid
,r.Qty
,r.ItemNotes
,r.CashflowType
,r.frequency
,r.UnitPrice
,r.[cost]
from
#calendar c
left outer join
#results r
on c.calenderid = cast(year(r.[adjusted_ExpectedActionDt])as char(4))+RIGHT('0'+ CONVERT(VARCHAR,month(r.[adjusted_ExpectedActionDt])),2)
--------------------------------------------------------------------------
GO
The problem is:
--=====================================================================--
--===================== this is where the problem is ==================--
--=====================================================================--
case
when dateadd(mm, datediff(mm,0, [adjusted_ExpectedActionDt]), 0) =
dateadd(mm, datediff(mm,0, dateadd(month,+#counter,[adjusted_ExpectedActionDt])), 0)
then [Qty]
else
0
end
--=====================================================================--
--=====================================================================--
When I try to run this code, I get the following error:
Msg 402, Level 16, State 1, Line 149
The data types text and varchar are incompatible in the equal to operator.
This is most vexxing as the comparison is a date match and not text.
This I have tried:
1. lots of internet searching
2. changing the case statement to an if statement but couldn't get the syntax right.
3. changing the dates to datepart functions only pulling the year. we know that should return an int
4. changing the comparison to "1=1" which is clearly numbers and should always be true
5. tried converting to concatenated text (yyyymm)
6. talked to one of our SQL developers (which I am not by title but expect to be by the end of this project :) )
the really strange thing is that this general code works
select
dateadd(mm, datediff(mm,0, getdate()), 0)
,dateadd(mm, datediff(mm,0, getdate()+60), 0)
,case
when dateadd(mm, datediff(mm,0, getdate()), 0) =
dateadd(mm, datediff(mm,0, getdate()+60), 0)
then 'matches'
else
'different'
end
The last bit of information is that if I comment the offending code leaving only the [Qty] field, it runs fine.
help me Obi-anyone, you're my only hope. Thank you in advance.
I think the problem isn't that part of your CASE statement, I think it's:
when frequency = 'OneTime'
because the frequency column is of datatype text.
You might try:
when cast(frequency as varchar(20)) = 'OneTime'
or something.

Split datetime SQL Server 2008

I have a table with 3 columns StartDate, EndDate, ElapsedTimeInSec.
I use an AFTER INSERT trigger to calculate the ElapsedTimeInSec.
I would like to do this:
If my start date is 2011-11-18 07:30:00 and my end date 2011-11-18 9:30:00 which give me a ElapsedtimeInSec of 7200 I would like to be able to split it this way.
Row 1 : 2011-11-18 07:30:00 / 2011-11-18 08:00:00 / 1800
Row 2 : 2011-11-18 08:00:00 / 2011-11-18 09:00:00 / 3600
Row 3 : 2011-11-18 09:00:00 / 2011-11-18 09:30:00 / 1800
How can I achieve this result ?
I dont think I made my explaination clear enough.
I have an actual table with data in it which as 2 field one with a StratDowntime and one with a EndDowntime and I would like to create a view of hours per hour base on a production shift of 12 hours (07:00:00 to 19:00:00) of the downtime.
So If I have a downtime from 2011-11-19 06:00:00 to 2011-11-19 08:00:00 I want in my report to see from 07:00:00 so the new rocrd should look like 2011-11-19 07:00:00 to 2011-11-19 08:00:00.
Another example if I do have downtime from 2011-11-19 10:30:00 to 2011-11-19 13:33:00 I should get in my report this
- 2011-11-19 10:30:00 to 2011-11-19 11:00:00
- 2011-11-19 11:00:00 to 2011-11-19 12:00:00
- 2011-11-19 12:00:00 to 2011-11-19 13:00:00
- 2011-11-19 13:00:00 to 2011-11-19 13:33:00
I hope this will clarify the question because none of the solution down there is actually doing this it is close but not on it.
thanks
You could try something like:
DECLARE #StartDate DATETIME = '11/18/2011 07:30:00',
#EndDate DATETIME = '11/18/2011 09:30:00',
#Runner DATETIME
IF DATEDIFF (mi, #StartDate, #EndDate) < 60
BEGIN
SELECT #StartDate,
#EndDate,
DATEDIFF (s, #StartDate, #EndDate)
RETURN
END
SET #Runner = CONVERT (VARCHAR (10), #StartDate, 101) + ' ' + CAST (DATEPART(hh, #StartDate) + 1 AS VARCHAR) + ':00:00'
WHILE #Runner <= #EndDate
BEGIN
SELECT #StartDate,
#Runner,
DATEDIFF (s, #StartDate, #Runner)
SET #StartDate = #Runner
SET #Runner = DATEADD(hh, 1, #Runner)
END
SET #Runner = CONVERT (VARCHAR (10), #EndDate, 101) + ' ' + CAST (DATEPART(hh, #EndDate) AS VARCHAR) + ':00:00'
SELECT #Runner,
#EndDate,
DATEDIFF (s, #Runner, #EndDate)
CTE:
DECLARE #beginDate DATETIME,
#endDate DATETIME
SELECT #beginDate = '2011-11-18 07:30:00',
#endDate = '2011-11-18 09:33:10'
DECLARE #mytable TABLE
(
StartDowntime DATETIME,
EndDowntime DATETIME,
ElapsedDowntimesec INT
)
-- Recursive CTE
;WITH Hours
(
BeginTime,
EndTime,
Seconds
)
AS
(
-- Base case
SELECT #beginDate,
DATEADD(MINUTE, ( DATEPART(MINUTE, #beginDate) * -1 ) + 60, #beginDate),
DATEDIFF
(
SECOND,
#beginDate,
CASE
WHEN #endDate < DATEADD(MINUTE, ( DATEPART(MINUTE, #beginDate) * -1 ) + 60, #beginDate) THEN #endDate
ELSE DATEADD(MINUTE, ( DATEPART(MINUTE, #beginDate) * -1 ) + 60, #beginDate)
END
)
UNION ALL
-- Recursive
SELECT Hours.EndTime,
CASE
WHEN #endDate < DATEADD(MINUTE, ( DATEPART(MINUTE, Hours.BeginTime) * -1 ) + 120, Hours.BeginTime) THEN #endDate
ELSE DATEADD(minute, ( DATEPART(MINUTE, Hours.BeginTime) * -1 ) + 120, Hours.BeginTime)
END,
DATEDIFF
(
SECOND,
Hours.EndTime,
CASE
WHEN #endDate < DATEADD(MINUTE, ( DATEPART(MINUTE, Hours.BeginTime) * -1 ) + 120, Hours.BeginTime) THEN #endDate
ELSE DATEADD(MINUTE, ( DATEPART(MINUTE, Hours.BeginTime) * -1 ) + 120, Hours.BeginTime)
END
)
FROM Hours
WHERE Hours.BeginTime < #endDate
)
INSERT INTO #myTable
SELECT *
FROM Hours
WHERE BeginTime < #endDate
SELECT * FROM #myTable
Results
BeginTime EndTime Seconds
2011-11-18 07:30:00.000 2011-11-18 08:00:00.000 1800
2011-11-18 08:00:00.000 2011-11-18 09:00:00.000 3600
2011-11-18 09:00:00.000 2011-11-18 09:33:10.000 1990
You can use a table valued function applied like SELECT * FROM [dbo].split('2011-11-02 12:55:00','2011-11-02 13:05:00')
Function defintion:
CREATE FUNCTION [dbo].[split] (#d1 DATETIME, #d2 DATETIME)
RETURNS #result TABLE (
StartDate DATETIME,
EndDate DATETIME,
ElapsedTimeSeconds INT
)
AS
BEGIN
-- Store intermediate values in #tmp, using ix as driver for start times.
DECLARE #tmp TABLE (ix INT NOT NULL IDENTITY(0,1) PRIMARY KEY
, d1 DATETIME, d2 DATETIME)
-- Insert first hole hour lower than start time
INSERT INTO #tmp (d1) SELECT DATEADD(HOUR, DATEDIFF(HOUR, -1, #d1), -1)
-- Calculate expected number of intervals
DECLARE #intervals INT = DATEDIFF(HOUR, #d1, #d2) - 1
-- insert all intervals
WHILE #intervals > 0
BEGIN
INSERT INTO #tmp (d1, d2) select top 1 d1, d2 FROM #tmp
SET #intervals = #intervals - 1
END
-- Set start and end time for all whole hour intervals
UPDATE #tmp SET d1 = DATEADD(hour, ix, d1)
, d2 = DATEADD(hour, ix + 1, d1)
-- Set correct start time for first interval
UPDATE #tmp SET d1 = #d1 WHERE d1 <= #d1
-- Insert end interval
INSERT INTO #tmp (d1, d2)
SELECT MAX(d2), #d2 FROM #tmp
-- Delete non-empty last interval
DELETE FROM #tmp WHERE d1 = d2
-- Insert #tmp to #result
INSERT INTO #result (StartDate, EndDate)
SELECT d1, d2 FROM #tmp
-- Set interval lengths
UPDATE #result SET ElapsedTimeSeconds = DATEDIFF(second, StartDate, EndDate)
return
END
GO
To get a result from an existing table, you can use CROSS APPLY. Assuming a table YourTable with StartTime and EndTime you can do something like
SELECT s.*, y.* FROM YourTable y
cross apply dbo.split(y.StartTime, y.EndTime) s
WHERE y.EndTime < '2011-09-11'
to get a result with a kind of join between input data and output table.