TSQL - How to optimize the query? - sql-server

I have the table with News
News
-------
NewsId
NewsText
CREATED_DATE
I need to get news starting from a specified date to a unknown date, but result should contain news for 5 days.
For example:
if I have news related to these dates: 29th, 28th, 27th, 5th, 4th, 3rd
and the starting date specified to 29th, I need to get news where created date between 29 and 4.
I don't know how to get the low date (4th) in that case without brute force:
declare #highDate date = '2011-09-20';
declare #rows int = 0;
declare #lowDate date = #highDate;
declare #i int = 0;
--Querying while rows count != 5
WHILE (#rows != 5)
BEGIN
if (#i = 60)
break;
set #i = #i + 1;
set #lowDate = (select DATEADD(day, -1, #lowDate));
set #rows = (select COUNT(*) from
(SELECT DAY(CAST(CREATED_DATE AS date)) as c1
FROM .[dbo].[NEWS]
and CREATED_DATE > #lowDate
and CREATED_DATE < #highDate
group by DAY(CAST(CREATED_DATE AS date))) as rowsCount);
END
--then return news between to known dates
SELECT *
FROM [dbo].[NEWS]
and CREATED_DATE > #lowDate
and CREATED_DATE < #highDate
order by CREATED_DATE desc
I guess in that algorithm there are too much queries against a DB and I'd like to get rid of 60-days old limitation

declare #highDate date = '2011-09-20'
select * from (
select *,
dense_rank() over (order by cast(created_date as date) desc) rnk
from News
where CREATED_DATE <= #highDate
) as t
where t.rnk <= 5

This might do the trick for you.
declare #HighDate date = '2011-11-29'
declare #LowDate date
select #LowDate = min(N3.created_date)
from (
select top(5) N2.created_date
from (
select distinct cast(N1.created_date as date) as created_date
from news as N1
where cast(N1.created_date as date) <= #HighDate
) as N2
order by 1 desc
) as N3
Or you can use dense_rank
select #LowDate = N.created_date
from (
select created_date,
dense_rank() over(order by cast(created_date as date) desc) as rn
from News
where cast(created_date as date) <= #HighDate
) as N
where N.rn = 5

Related

Get dates between date of ID and date of plus or minus days

I have the following sample data:
CREATE TABLE tblDates
(
ID int,
Dates DATE
);
SELECT * FROM tblDates
INSERT INTO tblDates VALUES(1,'2019-12-01');
INSERT INTO tblDates VALUES(2,'2019-12-05');
INSERT INTO tblDates VALUES(3,'2019-12-02');
INSERT INTO tblDates VALUES(4,'2019-12-09');
INSERT INTO tblDates VALUES(5,'2019-12-11');
Here I am looking for dates between date of ID = 4 and plus or minus of days 1,2,....n days.
Try 1: I tried using UNION ALL.
SELECT Dates FROM tblDates WHERE ID = 4
UNION ALL
SELECT DATEADD(day,1,Dates) FROM tblDates WHERE ID = 4;
This approach is not good for when I am looking for 50 or more number of days difference.
Try 2:
SELECT Dates FROM tblDates WHERE ID = 4 AND Dates between Dates AND DATEADD(day,1,Dates);
Got single date.
Try 3:
Created function: function for get dates
CREATE FUNCTION udf_GetDates(#MinDate DATE,#MaxDate DATE)
RETURNS TABLE
AS
RETURN
SELECT TOP (DATEDIFF(DAY, #MinDate, #MaxDate) + 1)
Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, #MinDate)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b;
Query:
SELECT f.*
FROM udf_GetDates(t.Dates,DATEADD(day,1,t.Dates)) f
INNER JOIN tblDates t ON f.[Date] = t.[Dates]
WHERE t.ID = 4
Got an error:
Msg 4104, Level 16, State 1, Line 2 The multi-part identifier
"t.Dates" could not be bound. Msg 4104, Level 16, State 1, Line 2 The
multi-part identifier "t.Dates" could not be bound.
Expected Output:
Given: ID = 4 and day=+1
Dates
-----------
2019-12-09
2019-12-10
Given: ID = 4 and day=+10
Dates
-----------
2019-12-09
2019-12-10
2019-12-11
2019-12-12
2019-12-13
2019-12-14
2019-12-15
2019-12-16
2019-12-17
2019-12-18
Given: ID = 4 and day=-5
Dates
----------
2019-12-05
2019-12-06
2019-12-07
2019-12-08
2019-12-09
Try this query
FIDDLE DEMO
Function
CREATE FUNCTION udf_GetDates (#StartDate DATE, #Range INT)
RETURNS TABLE
AS
RETURN
SELECT
DATEADD(DAY, nbr - 1, #StartDate) myDate
FROM
(SELECT
ROW_NUMBER() OVER (ORDER BY c.object_id) AS Nbr
FROM
sys.columns c) nbrs
WHERE
nbr - 1 <= #Range
Query usage #1:
SELECT f.myDate
FROM udf_GetDates((SELECT dates FROM tblDates WHERE ID = 4), 2) f
Query usage #2:
SELECT t.*, P.*
FROM tblDates t
OUTER APPLY udf_GetDates(t.Dates, 5) p
WHERE t.ID = 4
Updated answer:
Next Dates
CREATE FUNCTION udf_GetDates (#StartDate DATE, #Range INT)
RETURNS TABLE
AS
RETURN
SELECT
DATEADD(DAY, nbr - 1, #StartDate) myDate
FROM
(SELECT
ROW_NUMBER() OVER (ORDER BY c.object_id) AS Nbr
FROM
sys.columns c) nbrs
WHERE
nbr - 1 <= #Range
Previous Dates
CREATE FUNCTION [udf_GetDates_Minuus] (#StartDate DATE, #Range INT)
RETURNS TABLE
AS
RETURN
SELECT
DATEADD(DAY, -(nbr - 1), #StartDate) myDate
FROM
(SELECT
ROW_NUMBER() OVER (ORDER BY c.object_id) AS Nbr
FROM
sys.columns c) nbrs
WHERE
nbr - 1 <= #Range
Next and previous dates in single function
CREATE FUNCTION udf_GetDatesNextandPrevious(#StartDate DATE, #Range INT)
RETURNS TABLE
AS
RETURN
SELECT
DATEADD(DAY, nbr - 1, #StartDate) myDate
FROM
(SELECT
ROW_NUMBER() OVER (ORDER BY c.object_id) AS Nbr
FROM
sys.columns c) nbrs
WHERE
nbr - 1 <= #Range
UNION
SELECT
DATEADD(DAY, -(nbr - 1), #StartDate) myDate
FROM
(SELECT
ROW_NUMBER() OVER (ORDER BY c.object_id) AS Nbr
FROM
sys.columns c) nbrs
WHERE
nbr - 1 <= #Range
Updated Fiddle
Try this code in sql server
DECLARE #selecteddate DATE
DECLARE #day INT = 10
DECLARE #id INT = 4;
DECLARE #count INT = 0;
DECLARE #table1 TABLE
(
date_ DATETIME
)
SELECT #selecteddate = dates
FROM tbldates
WHERE id = #id;
IF( #count <= #day )
BEGIN
if(#day > 1)
begin
set #day = #day - 1
end
WHILE #count <= #day
BEGIN
INSERT INTO #table1
VALUES (Dateadd(day, #count, #selecteddate))
SET #count = #count + 1
END
END
ELSE
BEGIN
WHILE #count > #day
BEGIN
INSERT INTO #table1
VALUES (Dateadd(day, #count, #selecteddate))
SET #count = #count - 1
END
END
SELECT *
FROM #table1
ORDER BY 1
I tried to create function using another format
create FUNCTION udf_GetDates(#MinDate DATE,#MaxDate DATE)
RETURNS #_result table (dt date)
AS
begin
insert into #_result
SELECT TOP (DATEDIFF(DAY, #MinDate, #MaxDate) + 1)
Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, #MinDate)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b;
return
end
and select the result like this
declare #_date date=(select Dates from tblDates where ID=4)
select *
from udf_GetDates(#_date,DATEADD(day,1,#_date))
and I got the result what you wanted
dt
2019-12-09
2019-12-10
This SQL will return the correct dates between two ranges. Just adapt it to suite your needs.
DECLARE #From DATETIME,
#To DATETIME
SELECT #From = '2019-11-13',
#To = '2019-11-19'
;WITH Numbers AS
(
SELECT 0 AS Number
UNION ALL
SELECT Number + 1 AS Number
FROM Numbers
WHERE Number < DATEDIFF(d, #From, #To)
)
SELECT DATEADD(d,
Number, #From) AS Date
FROM Numbers
-- Results
2019-11-13 00:00:00.000
2019-11-14 00:00:00.000
2019-11-15 00:00:00.000
2019-11-16 00:00:00.000
2019-11-17 00:00:00.000
2019-11-18 00:00:00.000
2019-11-19 00:00:00.000

Stored procedure to accept starting date and number of consecutive dates beginning from start date

The parameter should consider, A starting date and the number of the consecutive dates beginning with the starting date.
The stored procedure then should populate all columns of the DateRange table according to the two provided parameters.
I created a table :
CREATE TABLE DateRange
(
DateID INT IDENTITY,
DateValue DATE,
Year INT,
Quarter INT,
Month INT,
DayOfWeek INT
);
Stored procedure code:
CREATE FUNCTION dbo.DateRange_sp4
(#StartDate DATE,
#NumberofConsecutivedays INT)
RETURNS #DateList TABLE
(
DateID INT,
DateValue DATE,
Year INT,
Quarter INT,
Month INT,
DayOfWeek INT
)
AS
BEGIN
DECLARE #Counter INT = 0;
WHILE (#Counter < #NumberofConsecutivedays)
BEGIN
INSERT INTO #DateList
VALUES (#Counter + 1,
DATEADD(DAY, #Counter, #StartDate),
DATEPART(YEAR, #StartDate),
DATEPART(QUARTER, #StartDate),
DATEPART(MONTH, #StartDate),
DatePart(WEEKDAY, #StartDate) );
SET #Counter += 1
END
RETURN;
END
GO
SELECT *
FROM dbo.DateRange_sp4('2018-07-13', 20);
My output returns the same result for year, quarter, month and dayofweek. How to split the date in different columns? Or is there any other way to do it?
Use a tally table... it'll be A LOT faster. Check it out for 10K days... and run your loop code for 10K days.
declare #dateparameter date = '1900-04-12'
declare #numOfDays int = 10000
;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), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select
datevalue = #dateparameter
,year = datepart(year,#dateparameter)
,quarter = datepart(quarter,#dateparameter)
,month = datepart(month,#dateparameter)
,dayofweek = datepart(weekday,#dateparameter)
union all
select
datevalue = dateadd(day,N,#dateparameter)
,year = datepart(year,dateadd(day,N,#dateparameter))
,quarter = datepart(quarter,dateadd(day,N,#dateparameter))
,month = datepart(month,dateadd(day,N,#dateparameter))
,dayofweek = datepart(weekday,dateadd(day,N,#dateparameter))
from cteTally
where N <= #numOfDays
But, if you are going to reference this a lot, why not make a persisted table? Aaron Bertran has a great article on this:
https://www.mssqltips.com/sqlservertip/4054/creating-a-date-dimension-or-calendar-table-in-sql-server/
You're using the same value for #StartDate each time you iterate through the loop.
Assuming you want the date parts of each date in your sample output, at the end of your loop you should update the value of #StartDate.
INSERT INTO #DateList
VALUES(#Counter + 1, DATEADD(day,#Counter, #StartDate), DATEPART(year, #StartDate), DATEPART(QUARTER, #StartDate),DATEPART(month, #StartDate), DatePart(WEEKDAY,#StartDate)) ;
SET #StartDate = DATEADD(day,#Counter + 1, #StartDate);
SET #Counter +=1;

Select N rows before and N rows after the record

I have a table where some values are stored for months and years.
Example:
Month | Year | Value
1 | 2013 | 1.86
2 | 2013 | 2.25
3 | 2013 | 2.31
...
3 | 2016 | 1.55
4 | 2016 | 1.78
Month and Year combination is a complex primary key. It is guaranteed that all values for all past years exist in the table.
User can select specific month and specific year. Let's say user selected 2014 as year and 6 as month, I need to show 15 rows before and 15 rows after the selected combination.
But if there are not enough rows (less than 15) after the selected combination than I need to get more rows before.
Basically all i need is to return 31 rows (always 31 unless there are not enough rows in the entire table) of data where the selected combination will be as close as possible to the center.
What is the proper way to do that?
Currently I'm stuck with this:
;WITH R(N) AS
(
SELECT 0
UNION ALL
SELECT N+1
FROM R
WHERE N < 29
)
SELECT * FROM MyTable e
LEFT OUTER JOIN (
SELECT N, MONTH(DATEADD(MONTH,-N,iif(#year != Year(GETDATE()), DATEFROMPARTS(#year, 12, 31) ,GETDATE()))) AS [Month],
YEAR(DATEADD(MONTH,-N,iif(#year!= Year(GETDATE()), DATEFROMPARTS(#year, 12, 31) ,GETDATE()))) AS [Year]
FROM R) s
ON s.[Year] = e.[Year] AND s.[Month] = e.[Month]
WHERE s.[N] is not null
This is not really what I want to do, since it just cuts off next year months
How about something simple like this:
;WITH CTE AS (
SELECT Month
,Year
,Value
,ROW_NUMBER() OVER (ORDER BY Year, Month) rn
FROM MyTable
)
SELECT Month
,Year
,Value
FROM CTE
WHERE rn >= (SELECT rn - 15 FROM MyTable WHERE Year = #Year AND Month = #Month)
AND rn <= (SELECT rn + 15 FROM MyTable WHERE Year = #Year AND Month = #Month);
I'm sure there's a more efficient way to do it, but this strikes me as the most maintainable way to do it. It should even work when you pick a value close to the first or last records in the table.
I can't tell if you want 31 rows no matter what. At one point it sounds like you do, and at another point it sounds like you don't.
EDIT: Ok, so you do always want 31 rows if available.
Alright, try this:
;WITH CTE AS (
SELECT Month
,Year
,Value
,ROW_NUMBER() OVER (ORDER BY Year, Month) rn
FROM MyTable
),
CTE_2 AS (
SELECT TOP (31) Month
,Year
,Value
FROM CTE
ORDER BY ABS(rn - (SELECT rn FROM MyTable WHERE Year = #Year AND Month = #Month)) ASC
)
SELECT Month
,Year
,Value
FROM CTE_2
ORDER BY Year, Month;
Basically, you calculate the difference from the target row number, get the first 31 rows there, and then resort them for output.
Check this out,
DECLARE #iPrevRows int
DECLARE #iPostRows int
DECLARE #Year int = 2016
DECLARE #Month int = 2
SELECT #iPrevRows= Count(*)
FROM
[GuestBook].[dbo].[tblTest]
where (year < #Year )
or (year =#Year and month < #Month)
SELECT #iPostRows= count(*) from
[GuestBook].[dbo].[tblTest]
where (year > #Year )
or (year =#Year and month > #Month)
if (#iPrevRows > 15)
select #iPrevRows =15
if (#iPostRows > 15)
select #iPostRows =15
if (#iPrevRows < 15 )
select #iPostRows = #iPostRows + (15-#iPrevRows)
else if (#iPostRows < 15 )
select #iPrevRows = #iPrevRows + (15-#iPostRows)
CREATE TABLE #tempValues
(
Year int NOT NULL,
Month int NOT NULL,
Value float
)
insert into #tempValues
SELECT top (#iPrevRows) Month, Year, Value
from
[GuestBook].[dbo].[tblTest]
where (year < #Year )
or (year =#Year and month < #Month)
order by 2 desc,1 desc
insert into #tempValues
SELECT Month, Year, Value
from
[GuestBook].[dbo].[tblTest]
where (year =#Year and month = #Month)
insert into #tempValues
SELECT top (#iPostRows) Month, Year, Value
from
[GuestBook].[dbo].[tblTest]
where (year > #Year )
or (year =#Year and month > #Month)
order by 2 ,1
select * from #tempValues
order by 2,1
Here is what I've done, seems to be working
select * from (
select top(31) * from MyTable r
order by ABS(DATEDIFF(month, DATEFROMPARTS(r.Year, r.Month, 1), DATEFROMPARTS(#Year, #Month, 1)))) s
order by Year, Month
I did it that way.
DECLARE #year INT = 2014, #month INT = 6;
WITH TableAux
AS (SELECT MyTable.Month
, MyTable.Year
FROM MyTable
WHERE MyTable.Year = #year
AND MyTable.Month = #month)
SELECT tb1.Month
, tb1.Year
, tb1.Value
FROM
(
SELECT TOP 16 MyTable.Month
, MyTable.Year
, MyTable.Value
FROM MyTable
CROSS JOIN TableAux
WHERE MyTable.Month <= TableAux.Month
AND MyTable.Year <= TableAux.Year
ORDER BY MyTable.Month DESC, MyTable.Year DESC
) tb1
UNION ALL
SELECT tb2.Month
, tb2.Year
, tb2.Value
FROM
(
SELECT TOP 15 MyTable.Month
, MyTable.Year
, MyTable.Value
FROM MyTable
CROSS JOIN TableAux
WHERE MyTable.Month > TableAux.Month
AND MyTable.Year > TableAux.Year
ORDER BY MyTable.Month, MyTable.Year
) tb2
ORDER BY Year, Month

How to insert all dates in 2015 in Date field?

I am using SQL Server and have a table (Table_Date) with a 'date' field. I want to insert all 2015 dates in this field.
It should have 365 distinct rows, 1 row for each day of 2015.
One method is with a recursive CTE:
with dates as (
select cast('2015-01-01' as date) as thedate
union all
select dateadd(day, 1, thedate)
from dates
where thedate < '2015-12-31'
)
select *
from dates
option (maxrecursion 0);
An alternative is to use a table that has at least 365 rows. master..spt_values is often used for this purpose:
select dateadd(day, seqnum - 1, '2015-01-01')
from (select row_number() over (order by ()) as seqnum
from master..spt_values
) t
where seqnum <= 365;
Here's one way:
CREATE TABLE #nums(num INT);
INSERT INTO #nums VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
WITH cteDays AS
(
SELECT 100*d100.num + 10*d10.num + d1.num AS YearDay
FROM #nums AS d1
CROSS JOIN #nums AS d10
CROSS JOIN #nums AS d100
WHERE d100.num <=3
)
SELECT CAST('2015-01-01' AS DATETIME) + YearDay AS YearDate
FROM cteDays
WHERE YEAR(CAST( CAST('2015-01-01' AS DATETIME) + YearDay AS DATETIME)) = 2015
Something like this could work as well:
declare #count int = 0
while (#count < 365)
begin
--make this the insert
select DATEADD(DD, #count, getdate())
set #count = #count + 1
end
Not sure what context this will applied to though... This is very basic, but if this is a one-time event it won't matter.

Get a list of dates between two dates using a function

My question is similar to this MySQL question, but intended for SQL Server:
Is there a function or a query that will return a list of days between two dates? For example, lets say there is a function called ExplodeDates:
SELECT ExplodeDates('2010-01-01', '2010-01-13');
This would return a single column table with the values:
2010-01-01
2010-01-02
2010-01-03
2010-01-04
2010-01-05
2010-01-06
2010-01-07
2010-01-08
2010-01-09
2010-01-10
2010-01-11
2010-01-12
2010-01-13
I'm thinking that a calendar/numbers table might be able to help me here.
Update
I decided to have a look at the three code answers provided, and the results of the execution - as a % of the total batch - are:
Rob Farley's answer : 18%
StingyJack's answer : 41%
KM's answer : 41%
Lower is better
I have accepted Rob Farley's answer, as it was the fastest, even though numbers table solutions (used by both KM and StingyJack in their answers) are something of a favourite of mine. Rob Farley's was two-thirds faster.
Update 2
Alivia's answer is much more succinct. I have changed the accepted answer.
this few lines are the simple answer for this question in sql server.
WITH mycte AS
(
SELECT CAST('2011-01-01' AS DATETIME) DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue + 1 < '2021-12-31'
)
SELECT DateValue
FROM mycte
OPTION (MAXRECURSION 0)
Try something like this:
CREATE FUNCTION dbo.ExplodeDates(#startdate datetime, #enddate datetime)
returns table as
return (
with
N0 as (SELECT 1 as n UNION ALL SELECT 1)
,N1 as (SELECT 1 as n FROM N0 t1, N0 t2)
,N2 as (SELECT 1 as n FROM N1 t1, N1 t2)
,N3 as (SELECT 1 as n FROM N2 t1, N2 t2)
,N4 as (SELECT 1 as n FROM N3 t1, N3 t2)
,N5 as (SELECT 1 as n FROM N4 t1, N4 t2)
,N6 as (SELECT 1 as n FROM N5 t1, N5 t2)
,nums as (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) as num FROM N6)
SELECT DATEADD(day,num-1,#startdate) as thedate
FROM nums
WHERE num <= DATEDIFF(day,#startdate,#enddate) + 1
);
You then use:
SELECT *
FROM dbo.ExplodeDates('20090401','20090531') as d;
Edited (after the acceptance):
Please note... if you already have a sufficiently large nums table then you should use:
CREATE FUNCTION dbo.ExplodeDates(#startdate datetime, #enddate datetime)
returns table as
return (
SELECT DATEADD(day,num-1,#startdate) as thedate
FROM nums
WHERE num <= DATEDIFF(day,#startdate,#enddate) + 1
);
And you can create such a table using:
CREATE TABLE dbo.nums (num int PRIMARY KEY);
INSERT dbo.nums values (1);
GO
INSERT dbo.nums SELECT num + (SELECT COUNT(*) FROM nums) FROM nums
GO 20
These lines will create a table of numbers containing 1M rows... and far quicker than inserting them one by one.
You should NOT create your ExplodeDates function using a function that involves BEGIN and END, as the Query Optimizer becomes unable to simplify the query at all.
This does exactly what you want, modified from Will's earlier post. No need for helper tables or loops.
WITH date_range (calc_date) AS (
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, '2010-01-13') - DATEDIFF(DAY, '2010-01-01', '2010-01-13'), 0)
UNION ALL SELECT DATEADD(DAY, 1, calc_date)
FROM date_range
WHERE DATEADD(DAY, 1, calc_date) <= '2010-01-13')
SELECT calc_date
FROM date_range;
DECLARE #MinDate DATETIME = '2012-09-23 00:02:00.000',
#MaxDate DATETIME = '2012-09-25 00:00:00.000';
SELECT TOP (DATEDIFF(DAY, #MinDate, #MaxDate) + 1) Dates = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, #MinDate)
FROM sys.all_objects a CROSS JOIN sys.all_objects b;
I'm an oracle guy, but I believe MS SQL Server has support for the connect by clause:
select sysdate + level
from dual
connect by level <= 10 ;
The output is:
SYSDATE+LEVEL
05-SEP-09
06-SEP-09
07-SEP-09
08-SEP-09
09-SEP-09
10-SEP-09
11-SEP-09
12-SEP-09
13-SEP-09
14-SEP-09
Dual is just a 'dummy' table that comes with oracle (it contains 1 row and the word 'dummy' as the value of the single column).
A few ideas:
If you need the list dates in order to loop through them, you could have a Start Date and Day Count parameters and do a while loop whilst creating the date and using it?
Use C# CLR Stored Procedures and write the code in C#
Do this outside the database in code
Would all these dates be in the database already or do you just want to know the days between the two dates? If it's the first you could use the BETWEEN or <= >= to find the dates between
EXAMPLE:
SELECT column_name(s)
FROM table_name
WHERE column_name
BETWEEN value1 AND value2
OR
SELECT column_name(s)
FROM table_name
WHERE column_name
value1 >= column_name
AND column_name =< value2
All you have to do is just change the hard coded value in the code provided below
DECLARE #firstDate datetime
DECLARE #secondDate datetime
DECLARE #totalDays INT
SELECT #firstDate = getDate() - 30
SELECT #secondDate = getDate()
DECLARE #index INT
SELECT #index = 0
SELECT #totalDays = datediff(day, #firstDate, #secondDate)
CREATE TABLE #temp
(
ID INT NOT NULL IDENTITY(1,1)
,CommonDate DATETIME NULL
)
WHILE #index < #totalDays
BEGIN
INSERT INTO #temp (CommonDate) VALUES (DATEADD(Day, #index, #firstDate))
SELECT #index = #index + 1
END
SELECT CONVERT(VARCHAR(10), CommonDate, 102) as [Date Between] FROM #temp
DROP TABLE #temp
A Bit late to the party, but I like this solution quite a bit.
CREATE FUNCTION ExplodeDates(#startDate DateTime, #endDate DateTime)
RETURNS table as
return (
SELECT TOP (DATEDIFF(DAY, #startDate, #endDate) + 1)
DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, #startDate) AS DATE
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
)
Before you use my function, you need to set up a "helper" table, you only need to do this one time per database:
CREATE TABLE Numbers
(Number int NOT NULL,
CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
DECLARE #x int
SET #x=0
WHILE #x<8000
BEGIN
SET #x=#x+1
INSERT INTO Numbers VALUES (#x)
END
here is the function:
CREATE FUNCTION dbo.ListDates
(
#StartDate char(10)
,#EndDate char(10)
)
RETURNS
#DateList table
(
Date datetime
)
AS
BEGIN
IF ISDATE(#StartDate)!=1 OR ISDATE(#EndDate)!=1
BEGIN
RETURN
END
INSERT INTO #DateList
(Date)
SELECT
CONVERT(datetime,#StartDate)+n.Number-1
FROM Numbers n
WHERE Number<=DATEDIFF(day,#StartDate,CONVERT(datetime,#EndDate)+1)
RETURN
END --Function
use this:
select * from dbo.ListDates('2010-01-01', '2010-01-13')
output:
Date
-----------------------
2010-01-01 00:00:00.000
2010-01-02 00:00:00.000
2010-01-03 00:00:00.000
2010-01-04 00:00:00.000
2010-01-05 00:00:00.000
2010-01-06 00:00:00.000
2010-01-07 00:00:00.000
2010-01-08 00:00:00.000
2010-01-09 00:00:00.000
2010-01-10 00:00:00.000
2010-01-11 00:00:00.000
2010-01-12 00:00:00.000
2010-01-13 00:00:00.000
(13 row(s) affected)
Perhaps if you wish to go an easier way, this should do it.
WITH date_range (calc_date) AS (
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, CURRENT_TIMESTAMP) - 6, 0)
UNION ALL SELECT DATEADD(DAY, 1, calc_date)
FROM date_range
WHERE DATEADD(DAY, 1, calc_date) < CURRENT_TIMESTAMP)
SELECT calc_date
FROM date_range;
But the temporary table is a very good approach also. Perhaps shall you also consider a populated calendar table.
Definately a numbers table, though tyou may want to use Mark Redman's idea of a CLR proc/assembly if you really need the performance.
How to create the table of dates (and a super fast way to create a numbers table)
/*Gets a list of integers into a temp table (Jeff Moden's idea from SqlServerCentral.com)*/
SELECT TOP 10950 /*30 years of days*/
IDENTITY(INT,1,1) as N
INTO #Numbers
FROM Master.dbo.SysColumns sc1,
Master.dbo.SysColumns sc2
/*Create the dates table*/
CREATE TABLE [TableOfDates](
[fld_date] [datetime] NOT NULL,
CONSTRAINT [PK_TableOfDates] PRIMARY KEY CLUSTERED
(
[fld_date] ASC
)WITH FILLFACTOR = 99 ON [PRIMARY]
) ON [PRIMARY]
/*fill the table with dates*/
DECLARE #daysFromFirstDateInTheTable int
DECLARE #firstDateInTheTable DATETIME
SET #firstDateInTheTable = '01/01/1998'
SET #daysFromFirstDateInTheTable = (SELECT (DATEDIFF(dd, #firstDateInTheTable ,GETDATE()) + 1))
INSERT INTO
TableOfDates
SELECT
DATEADD(dd,nums.n - #daysFromFirstDateInTheTable, CAST(FLOOR(CAST(GETDATE() as FLOAT)) as DateTime)) as FLD_Date
FROM #Numbers nums
Now that you have a table of dates, you can use a function (NOT A PROC) like KM's to get the table of them.
CREATE FUNCTION dbo.ListDates
(
#StartDate DATETIME
,#EndDate DATETIME
)
RETURNS
#DateList table
(
Date datetime
)
AS
BEGIN
/*add some validation logic of your own to make sure that the inputs are sound.Adjust the rest as needed*/
INSERT INTO
#DateList
SELECT FLD_Date FROM TableOfDates (NOLOCK) WHERE FLD_Date >= #StartDate AND FLD_Date <= #EndDate
RETURN
END
Declare #date1 date = '2016-01-01'
,#date2 date = '2016-03-31'
,#date_index date
Declare #calender table (D date)
SET #date_index = #date1
WHILE #date_index<=#date2
BEGIN
INSERT INTO #calender
SELECT #date_index
SET #date_index = dateadd(day,1,#date_index)
IF #date_index>#date2
Break
ELSE
Continue
END
-- ### Six of one half dozen of another. Another method assuming MsSql
Declare #MonthStart datetime = convert(DateTime,'07/01/2016')
Declare #MonthEnd datetime = convert(DateTime,'07/31/2016')
Declare #DayCount_int Int = 0
Declare #WhileCount_int Int = 0
set #DayCount_int = DATEDIFF(DAY, #MonthStart, #MonthEnd)
select #WhileCount_int
WHILE #WhileCount_int < #DayCount_int + 1
BEGIN
print convert(Varchar(24),DateAdd(day,#WhileCount_int,#MonthStart),101)
SET #WhileCount_int = #WhileCount_int + 1;
END;
In case you want to print years starting from a particular year till current date. Just altered the accepted answer.
WITH mycte AS
(
SELECT YEAR(CONVERT(DATE, '2006-01-01',102)) DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue + 1 < = YEAR(GETDATE())
)
SELECT DateValue
FROM mycte
OPTION (MAXRECURSION 0)
This query works on Microsoft SQL Server.
select distinct format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) as aDate
from (
SELECT ones.n + 10 * tens.n + 100 * hundreds.n + 1000 * thousands.n as v
FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n)
) a
where format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) < cast('2010-01-13' as datetime)
order by aDate asc;
Now let's look at how it works.
The inner query merely returns a list of integers from 0 to 9999. It will give us a range of 10,000 values for calculating dates. You can get more dates by adding rows for ten_thousands and hundred_thousands and so forth.
SELECT ones.n + 10 * tens.n + 100 * hundreds.n + 1000 * thousands.n as v
FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n)
) a;
This part converts the string to a date and adds a number to it from the inner query.
cast('2010-01-01' as datetime) + ( a.v / 10 )
Then we convert the result into the format you want. This is also the column name!
format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' )
Next we extract only the distinct values and give the column name an alias of aDate.
distinct format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) as aDate
We use the where clause to filter in only dates within the range you want. Notice that we use the column name here since SQL Server does not accept the column alias, aDate, within the where clause.
where format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) < cast('2010-01-13' as datetime)
Lastly, we sort the results.
order by aDate asc;
if you're in a situation like me where procedures and functions are prohibited, and your sql user does not have permissions for insert, therefore insert not allowed, also "set/declare temporary variables like #c is not allowed", but you want to generate a list of dates in a specific period, say current year to do some aggregation, use this
select * from
(select adddate('1970-01-01',t4*10000 + t3*1000 + t2*100 + t1*10 + t0) gen_date from
(select 0 t0 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
(select 0 t1 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
(select 0 t2 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
(select 0 t3 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
(select 0 t4 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) v
where gen_date between '2017-01-01' and '2017-12-31'
WITH TEMP (DIA, SIGUIENTE_DIA ) AS
(SELECT
1,
CAST(#FECHAINI AS DATE)
FROM
DUAL
UNION ALL
SELECT
DIA,
DATEADD(DAY, DIA, SIGUIENTE_DIA)
FROM
TEMP
WHERE
DIA < DATEDIFF(DAY, #FECHAINI, #FECHAFIN)
AND DATEADD(DAY, 1, SIGUIENTE_DIA) <= CAST(#FECHAFIN AS DATE)
)
SELECT
SIGUIENTE_DIA AS CALENDARIO
FROM
TEMP
ORDER BY
SIGUIENTE_DIA
The detail is on the table DUAL but if your exchange this table for a dummy table this works.
SELECT dateadd(dd,DAYS,'2013-09-07 00:00:00') DATES
INTO #TEMP1
FROM
(SELECT TOP 365 colorder - 1 AS DAYS from master..syscolumns
WHERE id = -519536829 order by colorder) a
WHERE datediff(dd,dateadd(dd,DAYS,'2013-09-07 00:00:00'),'2013-09-13 00:00:00' ) >= 0
AND dateadd(dd,DAYS,'2013-09-07 00:00:00') <= '2013-09-13 00:00:00'
SELECT * FROM #TEMP1
Answer is avialbe here
How to list all dates between two dates
Create Procedure SelectDates(#fromDate Date, #toDate Date)
AS
BEGIN
SELECT DATEADD(DAY,number,#fromDate) [Date]
FROM master..spt_values
WHERE type = 'P'
AND DATEADD(DAY,number,#fromDate) < #toDate
END
DECLARE #StartDate DATE = '2017-09-13', #EndDate DATE = '2017-09-16'
SELECT date FROM ( SELECT DATE = DATEADD(DAY, rn - 1, #StartDate) FROM (
SELECT TOP (DATEDIFF(DAY, #StartDate, DATEADD(DAY,1,#EndDate)))
rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
ORDER BY s1.[object_id] ) AS x ) AS y
Result:
2017-09-13
2017-09-14
2017-09-15
2017-09-16

Resources