How to efficiently loop through using Sql query - sql-server

As I have From and To date. Something like below,
BeginDate End Date
1989-01-01 00:00:00.000 2015-12-31 00:00:00.000
I need to loop through until i get the list of all the Date's between those 2 (Begin & End Date's) records. I need to know what will be the efficient way of doing this. I have no clue on how to do this. Any help to this will be highly appreciated.
Thanks

This method uses a generated numbers table and is probably faster than looping.
DECLARE #BeginDate DATETIME = '19890101';
DECLARE #EndDate DATETIME = '20151231';
WITH
E1(N) AS ( SELECT 1 FROM ( VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) DT(N)),
E2(N) AS ( SELECT 1 FROM E1 A, E1 B),
E4(N) AS ( SELECT 1 FROM E2 A, E2 B),
Numbers(N) AS
(
SELECT ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL)) - 1 FROM E4
)
SELECT
N,
DATEADD(D, N, #BeginDate) AS TheDate
FROM Numbers
WHERE N <= DATEDIFF(D, #BeginDate, #EndDate)

You can do this with WHILE loop:
DECLARE #sdt DATE = '1989-01-01'
DECLARE #edt DATE = '2015-12-31'
WHILE #sdt <= #edt
BEGIN
PRINT #sdt
SET #sdt = DATEADD(dd, 1, #sdt )
END
Or with recursive CTE:
DECLARE #sdt DATE = '1989-01-01'
DECLARE #edt DATE = '2015-12-31';
WITH cte
AS ( SELECT #sdt AS sdt
UNION ALL
SELECT DATEADD(dd, 1, sdt)
FROM cte
WHERE DATEADD(dd, 1, sdt) <= #edt
)
SELECT *
FROM cte
OPTION ( MAXRECURSION 10000 )
There is also tally table method as in link provided by #Bridge
Actually the answer is tally tables. But if there is not a big interval the difference will be insignificant.

Something like this should work for your purposes:
DECLARE #sd date = '1989-01-01 00:00:00.000'
, #ed date = '2015-12-31 00:00:00.000'
DECLARE #tt TABLE(
[Date] date
)
WHILE(#sd <= #ed) --Loop which checks each iteration if the date has reached the end
BEGIN
INSERT INTO #tt
SELECT #sd AS Date
SET #sd = DATEADD(dd,1,#sd) --This willl increment the date so you actually advance the loop
END
SELECT * FROM #tt

Related

Stored procedure to add 30 days using DATEDIFF within while loop condition in Date Dimension table

I want to add 30 consecutive days of data in my Date Dimension table using DATEDIFF() but I am getting blank result. Can you please help me correct the code below to get the desired result?
CREATE TABLE dbo.dateDimension (
DateKey INT NOT NULL
,DateValue DATE NOT NULL
,CYear SMALLINT NOT NULL
,CMonth TINYINT NOT NULL
,CONSTRAINT PK_DimDate PRIMARY KEY ( DateKey )
);
GO
CREATE PROC dbo.dateTest
#StartDate DATETIME
AS
WHILE (DATEDIFF(day, #StartDate, GETDATE()) <=30)
BEGIN
INSERT into dbo.dateDimension
SELECT CAST( YEAR(#StartDate) * 10000 + MONTH(#StartDate) * 100 + DAY(#StartDate) AS INT)
,#StartDate
,YEAR(#StartDate)
,MONTH(#StartDate)
SET #StartDate = DATEADD(d,1,#StartDate)
END;
GO
EXECUTE dbo.dateTest '2010-01-01'
SELECT * FROM dbo.dateDimension
The issue is that this logic:
DATEDIFF(day, #StartDate, GETDATE())
gives 3739 days with your current start date, so its never less than 30. Personally I would simply count it as follows:
DECLARE #StartDate DATETIME = '2010-01-01', #Count INT = 0;
WHILE #Count <= 30 BEGIN
INSERT into dbo.dateDimension
SELECT CAST( YEAR(#StartDate) * 10000 + MONTH(#StartDate) * 100 + DAY(#StartDate) AS INT)
, #StartDate
, YEAR(#StartDate)
, MONTH(#StartDate);
SET #StartDate = DATEADD(d,1,#StartDate);
set #Count = #Count + 1;
END;
SELECT *
FROM dbo.dateDimension;
If you are using SQL Server 2016 or above, this solution will not use a while loop, instead it uses a CTE to generate 30 rows numbered I to 30 and then uses the date to convert to yyyymmdd.
DECLARE #NUM_DAYS INT=30;
DECLARE #STARTDATE DATETIME='2020-01-01';
WITH CTE AS(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS SRL
FROM STRING_SPLIT(REPLICATE(',',#num_days-1), ',') AS A
)
INSERT INTO dbo.dateDimension
SELECT
CONVERT(INT, CONVERT(CHAR(8), DATEADD(DAY, SRL-1, #STARTDATE), 112))
, #STARTDATE
, YEAR(#STARTDATE)
, MONTH(#STARTDATE)
FROM CTE
SELECT * FROM dbo.dateDimension

Get dates between 2 dates with equal intervals

I have 2 dates 01/04/2017 and 30/04/2017. I want all the dates between these 2 dates with 7 days interval.
Expected Output :
01/04/2017
08/04/2017
15/04/2017
22/04/2017
29/04/2017
Please help!!
DECLARE #StartDate DATETIME,
#EndDate DATETIME
SELECT #StartDate = '2017-04-01',
#EndDate = '2017-04-30'
SELECT DATEADD(DAY, number*7, #StartDate)
FROM master.dbo.spt_values
WHERE type='P'
AND #EndDate >= DATEADD(DAY, number*7, #StartDate)
One method would be to use a Calendar table. Then return the results from there using the modulus to get the 7th rows:
WITH Dates AS(
SELECT *,
ROW_NUMBER() OVER (ORDER BY [date]) AS RN
FROM DateTable
WHERE [Date] BETWEEN '20170401' AND '20170430')
SELECT *
FROM Dates
WHERE (RN - 1) % 7 = 0;
I've used this solution, as from your post you imply that you might supply any date range, and that the 1st day may not necessarily be a Monday (or other specific day).
Try this
DECLARE #STRT DATETIME='04/01/2017',#END DATETIME ='04/30/2017'
;WITH CTE
AS
(
SELECT
MyDate = CAST(#STRT AS DATETIME)
UNION ALL
SELECT
MyDate = CAST(MyDate AS DATETIME)+7
FROM CTE
WHERE CAST(MyDate AS DATETIME)+7 < CAST(#END AS DATETIME)
)
SELECT
*
FROM CTE
result
Declare #StartDate DATE=CONVERT(DATE,'01/04/2017',104),#EndDate DATE=CONVERT(DATE,'01/12/2017',104)
Declare #String NVARCHAR(MAX)=''
WHILE (#StartDate<=#EndDate AND DATEDIFF(wk,#StartDate,#EndDate)>=0)
BEGIN
SET #String=#String+CONVERT(NVARCHAR(100),#StartDate)+CHAR(10)+CHAR(13)
SET #StartDate=DATEADD(d,7,#StartDate)
END
PRINT #String
GO

SQL: Can't insert a date loop into a column

I have a table like this:
id: PK bigint
RatePercent: decimal(4, 4)
DateRange: date
I am trying to populate the table as follows:
RatePercentage with all of them 0.12
Date starting from '01-01-2015' to '12-31-2099'
Unfortunately with my query it won't do that and it keeps saying that
Operand type clash: date is incompatible with int
I haven't assigned an int datatype asides from the id bigint. I'm a bit confused.
Here is my query so far:
DECLARE #Date Date
SET #Date = '01-01-2015'
WHILE #Date <= '12-31-2099'
BEGIN
INSERT INTO [dbo].[IMF_Main_VATHistory] (VATRate, VATDate)
VALUES (0.12, #Date + 1);
END
Try this:
DECLARE #Date Date
SET #Date = '01-01-2015'
WHILE #Date <= '12-31-2099'
BEGIN
INSERT INTO [dbo].[IMF_Main_VATHistory] (VATRate, VATDate)
VALUES (0.12, DATEADD(DAY, 1, #Date));
SET #Date = DATEADD(DAY, 1, #Date);
END
You can't issue a direct addition to a DATE datatype, in SQL Server (for reference, I think you can in Oracle). You have to use functions in order to modify a DATE/DATETIME variable (or column).
Here is an example SQLFiddle.
The problem is in you "#Date + 1" I think - The SQL-Server likes to try and convert to INT :)
Use DATEADD that should work
DECLARE #Date Date
SET #Date = '01-01-2015'
WHILE #Date <= '12-31-2099'
BEGIN
INSERT INTO [dbo].[IMF_Main_VATHistory] (VATRate, VATDate)
VALUES (0.12, #Date);
SET #Date = DATEADD(DAY, 1, #Date);
END
I'll advise against using any loop-based or RBAR solution. You can do this using a set-based approach with the help of a Tally Table.
DECLARE #startDate DATE
DECLARE #endDate DATE
SET #startDate = '20150101'
SET #endDate = '20991231';
WITH E1(N) AS(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
),
E2(N) AS(SELECT 1 FROM E1 a, E1 b),
E4(N) AS(SELECT 1 FROM E2 a, E2 b),
E8(N) AS(SELECT 1 FROM E4 a, E4 b),
Tally(n) AS(
SELECT TOP(DATEDIFF(DAY, #startDate, #endDate) + 1)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM E8
)
INSERT INTO IMF_Main_VATHistory(RatePercent, DateRange)
SELECT
0.02, DATEADD(DAY, N-1, #startDate)
FROM Tally
It's faster compared to using CURSORs or WHILE loops.

Searching date between two given Dates

I have two specific dates.
lets say from 2014-04-04 To 2015-10-04.
I have a plan that says classes of Blah course will be on mon, wed and Fri.
Now I want to get the dates of each mon, wed and Fri from 2014-04-04 to 2015-10-04.
With a calendar table it is simple:
SELECT t.*
FROM dbo.TableName t
INNER JOIN CalendarTable c
ON t.DateColumn = c.Date
WHERE c.Date between '2014-04-04' AND '2015-10-04'
AND DATEPART(dw, c.Date) IN (1,3,5)
How to generate a calendar table (look for "Calendar table").
The standard reply to this by any seasoned SQL Server veteran, would be to create a calendar table. But all too often this is scoffed. So here's a slow and out-of-the-box method:
DECLARE #startDate DATE = '2014-04-04', #endDate DATE = '2015-10-04';
WITH CTE(N) AS (SELECT 1 FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))a(N)),
CTE2(N) AS (SELECT 1 FROM CTE x CROSS JOIN CTE y),
CTE3(N) AS (SELECT 1 FROM CTE2 x CROSS JOIN CTE2 y),
CTE4(N) AS (SELECT 1 FROM CTE3 x CROSS JOIN CTE3 y),
CTE5(N) AS (SELECT 1 FROM CTE4 x CROSS JOIN CTE4 y),
CTE6(N) AS (SELECT 0 UNION ALL
SELECT TOP (DATEDIFF(day,#startDate,#endDate))
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM CTE5),
TALLY(N) AS (SELECT DATEADD(day, N, #startDate)
FROM CTE6
WHERE DATENAME(weekday,DATEADD(day, N, #startDate)) IN ('Monday','Tuesday','Wednesday'))
SELECT N
FROM TALLY
ORDER BY N;
You can use a Recursive CTE if you do not have numbers table. Something like this. Note that the DATEPART(weekday,Dates) IN(1,3,5) is based on your setting of SELECT ##DATEFIRST.
For example If ##DATEFIRST is 1 then use DATEPART(weekday,Dates) IN(1,3,5)
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SET #StartDate = '2014-04-04'
SET #EndDate = '2015-10-04'
;WITH CTE AS
(
SELECT #StartDate Dates
UNION ALL SELECT DATEADD(d,1,Dates) FROM CTE WHERE DATEADD(d,1,Dates) <=#EndDate
)
SELECT Dates,DATENAME(weekday,Dates) FROM CTE
WHERE DATEPART(weekday,Dates) IN(1,3,5)
OPTION (MAXRECURSION 0);
GO
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SET #StartDate = '2014-04-04'
SET #EndDate = '2015-10-04'
;WITH CTE AS
(
SELECT #StartDate Dates
UNION ALL SELECT DATEADD(d,1,Dates) FROM CTE WHERE DATEADD(d,1,Dates) <=#EndDate
)
SELECT Dates,DATENAME(weekday,Dates) FROM CTE
WHERE DATEPART(weekday,Dates) IN(1,3,5)
OPTION (MAXRECURSION 0);
GO
--- thanks to #ughai
I've comeup with another way to do this.
this query will not only Show the dates in between but also it will compare the dates u want to compare with.
Q.Dates Comparison
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SET #StartDate = '2014-12-22'
SET #EndDate = '2015-02-13'
drop table #TabCourseDetailID
SELECT ROW_NUMBER() OVER(ORDER BY CourseDetailID asc) AS Row,
CourseDetailID
into #TabCourseDetailID
from coursedetail
where UnitType='T' and courseID =1
Declare #counter as int
Declare #CourseDetailID varchar(500)
set #counter = 0
Declare #TempTable Table (FirstValue datetime,LastValue int)
while #StartDate <= #EndDate
begin
If DATEPART(WeekDay,#StartDate) IN (2,4,6)
begin
Set #Counter = #counter+1
Select #CoursedetailId=CoursedetailId
from #TabCourseDetailID
where row=#counter
insert into #TempTable (FirstValue,LastValue)
values (#StartDate,#CoursedetailId)
end
set #StartDate =DATEADD(d,1,#StartDate)
end
select t.LastValue,
t.FirstValue,
bp.conductedDate
from batchprogress bp
Join #TempTable t on (t.LastValue=bp.CoursedetailID)
where CourseID =1 and batchID =5
order by t.lastvalue

SQL Server related question

I have this thing that i need to do and some advices will be greatly appreciated.
I have a SQL server table with some phone calls.For each phone call i have the start and end time.
What i need to accomplish: a stored procedure which for a certain period of time, let's say 5 hours at a x interval, lets say 2 minutes returns the number of connected calls.
Something like:
Interval Nr of Calls Connected
01-01-2010 12:00:00 - 01-01-2010 12:05:00 30
01-01-2010 12:05:01 - 01-01-2010 12:10:00 10
.............
Which will be the fastest way to do that? Thank you for your help
This will work for intervals that have calls ...
Declare #datetimestart datetime
Declare #interval int
Set #datetimestart = '2009-01-01 12:00:00'
Set #interval = 5 --in minutes
Select
[start_interval], [end_interval] , count([start_interval]) as [calls]
From
(
Select
DateAdd( Minute,Floor(DateDiff(Minute,#datetimestart,[date])/#interval)*#interval
,#datetimestart) ,
DateAdd( Minute,#interval + Floor(DateDiff(Minute,#datetimestart,[date])/#interval)*#interval
,#datetimestart)
From yourTable
) As W([start_interval],[end_interval])
group by [start_interval], [end_interval]
This will work for all intervals regardless of number of calls..
Declare #datetimestart datetime, #datetimeend datetime, #datetimecurrent datetime
Declare #interval int
Set #datetimestart = '2009-01-01 12:00:00'
Set #interval = 10
Set #datetimeend = (Select max([date]) from yourtable)
SET #datetimecurrent = #datetimestart
declare #temp as table ([start_interval] datetime, [end_interval] datetime)
while #datetimecurrent < #datetimeend
BEGIN
insert into #temp select (#datetimecurrent), dateAdd( minute, #interval, #datetimecurrent)
set #datetimecurrent = dateAdd( minute, #interval, #datetimecurrent)
END
Select
*
From
(
Select
[start_interval],[end_interval], count(d.[start_time])
From #temp t left join yourtable d on d.[start_time] between t.[start_interval] and t.[end_interval]
) As W([start_interval],[end_interval], [calls])
I Altered Gaby's example a little to do What you expected
Declare #datetimeend datetime
,#datetimecurrent datetime
,#interval int
Set #interval = 10
Set #datetimeend = (Select max([end_time]) from Calls)
SET #datetimecurrent = '2010-04-17 14:20:00'
declare #temp as table ([start_interval] datetime, [end_interval] datetime)
while #datetimecurrent < #datetimeend
BEGIN
insert into #temp select (#datetimecurrent), dateAdd( minute, #interval, #datetimecurrent)
set #datetimecurrent = dateAdd( minute, #interval, #datetimecurrent)
END
Select
[start_interval],[end_interval], count(d.id) [COUNT]
From #temp t
left join Calls d on
d.end_time >= t.start_interval
AND d.start_time <= t.end_interval
GROUP BY [start_interval],[end_interval]
used this to create the table and fill it
CREATE TABLE dbo.Calls
(
id int NOT NULL IDENTITY (1, 1),
start_time datetime NOT NULL,
end_time datetime NULL,
caller nvarchar(50) NULL,
receiver nvarchar(50) NULL
) ON [PRIMARY]
GO
ALTER TABLE dbo.Calls ADD CONSTRAINT
PK_Calls PRIMARY KEY CLUSTERED
(
id
) ON [PRIMARY]
GO
DECLARE #I INT
SET #I = 0
WHILE #I < 100
BEGIN
INSERT INTO Calls
(start_time, end_time)
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-10,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-9,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-9,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-8,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-8,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-7,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-7,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-6,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-6,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-5,GETDATE()))
UNION
SELECT
DATEADD(HOUR,-#I,DATEADD(MINUTE,-5,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-4,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-4,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-3,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-3,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-2,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-2,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-1,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-1,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-0,GETDATE()));
SET #I = #I + 1
END
Done in SQL Server 2008
but the script would work in other versions
I would use a Numbers pivot table to get the time intervals and then count all calls which overlapped the interval:
SELECT Intervals.IntervalStart
,Intervals.IntervalEnd
,COUNT(*)
FROM (
SELECT DATEADD(MINUTE, Numbers * 2, #StartTime) AS IntervalStart
,DATEADD(MINUTE, (Numbers + 1) * 2, #StartTime) AS IntervalEnd
FROM Numbers
WHERE Numbers BETWEEN 0 AND (5 * 60 / 2)
) AS Intervals
LEFT JOIN Calls
ON Calls.CallEnd >= Intervals.IntervalStart
AND Calls.CallStart < Intervals.IntervalEnd
GROUP BY Intervals.IntervalStart
,Intervals.IntervalEnd
To get the empty intervals, you would need to LEFT JOIN to this from another "Intervals" derived table.
How about this approach:
select Year(StartTime) as Year, Month(StartTime) as Month, Day(StartTime) as Day, datepart(hh, StartTime) as Hour, datepart(mm, StartTime) / 2 as TwoMinuteSegment, count(*)
from MyTable
where StartDate between '01-01-2010 12:00:00' and '01-01-2010 17:00:00'
group by Year(StartTime), Month(StartTime), Day(StartTime), datepart(hh, StartTime), datepart(mm, StartTime) / 2

Resources