Tsql union in while loop - sql

I am trying to union the same table together with itself while changing some value in the where clause. The problem i have is with the union between the loops. I can not use a table variable since the schema is too complicated to write by hand each time. Temp tables seem to be the way to go but I do not know how to get it to work and the correct syntax.
psuedo code of what I am trying to achieve:
DECLARE #var int, #tempTable
SET #var = someValue
WHILE expressionIncludingVar
#tempTable = SELECT *
FROM someTable
WHERE column = #var
UNION ALL #tempTable
SET #var = someChangeToVar
RETRUN #tempTable
The result of the query should be #tempTable hence the weird "RETURN #tempTable".
Thank you in advance.
EDIT:
Another hardcoded example:
I am trying to unhardcode something like this:
SELECT someAggregateColumns
FROM table
WHERE someDateColumn > #date and < someDateColumn < DATEADD(month, 2, #date)
GROUP BY someColumn
UNION ALL
SELECT someAggregateColumns
FROM table
WHERE someDateColumn > DATEADD(month, 1, #date) and and < someDateColumn < DATEADD(month, 1, DATEADD(month, 3, #date))
GROUP BY someColumn
SELECT someAggregateColumns
FROM table
WHERE someDateColumn = DATEADD(month, 2, #date) DATEADD(month, 1, DATEADD(month, 4, #date))
GROUP BY someColumn
UNION ALL
....etc

Maybe Recursive CTE works for you.
You can try this.
DECLARE #MyTable TABLE(ID INT, ColumnA VARCHAR(10), ColumnB VARCHAR(10))
INSERT INTO #MyTable VALUES
(1,'A', '10'),
(2,'B', '11'),
(3,'C', '12'),
(4,'D', '13'),
(5,'E', '14'),
(6,'F', '15'),
(7,'H', '16')
DECLARE #var INT = 4
;WITH CTE AS (
SELECT * FROM #MyTable WHERE ID = #var
UNION ALL
SELECT T.* FROM CTE INNER JOIN #MyTable T ON CTE.ID - 1 = T.ID
)
SELECT * INTO #tempTable FROM CTE
SELECT * FROM #tempTable
DROP TABLE #tempTable

If the only thing what is different in each cycle is a counter, then why aren't you just write one query including all data?
Instead of WHERE column = #var use WHERE column >= 0 AND column <= #maxVarValue.
If your conditions are more complex, you should consider to have a small (temp) table which contains the columns to be filtered, then just join that table to your source to get the desired result.
According to the comments, you can use a tally table (or numbers table).
Example:
DECLARE #Tally (Number INT);
INSERT INTO #Tally (Number) VALUES (0),(1),(2),(3),(4),(5);
SELECT
someAggregateColumns
FROM
table AGG
INNER JOIN #Tally T
ON AGG.someDateColumn = DATEADD(month, T.Number, #date)
WHERE
T.Number >= 0
AND T.Number <= 3
;
The above query will return the results for the current and the next 3 months.
You can persist a numbers table and re-use it. I usually have one called util.Number.

based in second example what you want is
SELECT someAggregateColumns
FROM table
WHERE someDateColumn IN (
#date,
DATEADD(month, 1, #date),
DATEADD(month, 2, #date),
DATEADD(month, 3, #date)
)
GROUP BY someColumn
Now if you want a range is even easier:
WHERE someDateColumn BETWEEN #date
AND DATEADD(month, 3, #date)

Using a TALLY table and a CROSS APPLY you can generate the Dates to check:
DECLARE #Var INT = 4
DECLARE #Date Date = '2017-01-01'
;WITH Tally
AS
(
SELECT ROW_NUMBER() OVER (ORDER BY Object_Id) -1 AS Num
FROM sys.columns
)
SELECT *
FROM MyTable
CROSS APPLY Tally
WHERE Num < #var AND
MyDate = DATEADD(month, num, #date)

Related

Generate date range in between 2 dates

for the last couple of hours I have been breaking my head over this.
I want to create a result set which contains a series of dates like this:
2011-07-05
2011-07-04
2011-07-03
2011-07-02
2011-07-01
2011-06-30
2011-06-29
2011-06-28
...
Ideally between 2 dates given. But If I can say the last 30 days or the last 100 days from now that would be fine also.
Normally I would this with a CTE like this
;WITH Dates AS
(
SELECT CONVERT(DATE, GETDATE()) [Date]
UNION ALL
SELECT DATEADD(DAY,-1, [Date])
FROM Dates
WHERE [Date] > DATEADD(DAY, -30, CONVERT(DATE, GETDATE()))
)
SELECT [Date]
But I am not allowed to use any statements that can't be executed in a subquery. The program I am using executes queries like this:
Select *
From (
TheQuery
) as t1
This means I can't use declares, no stored procedures, no CTEs..
Is there any way I can obtain the dataset I need with these limitations?
I am using azure SQL
You can use a recursive cte if you put it in a table valued function
CREATE FUNCTION FnDateRange
(
#startDate date,
#endDate date
)
RETURNS #DateRange Table
(myDate date)
AS
begin
with Dates_rte as
(
select #startDate myDate
union all
select cast(dateadd(day,1,myDate) as date)
from Dates_rte
where cast(dateadd(day,1,myDate) as date) <= #endDate
)
insert into #DateRange
select * from Dates_rte option (maxrecursion 0)
return
end
GO
select * from fnDateRange('2017-07-01','2017-07-06')
If you dont't want create a calendar table or a number table, nor use existing table to generate numbers/ date (see for example https://sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1)
you could use something like this:
SELECT DATEADD(DAY, -B.N1+1, CONVERT(DATE, GETDATE())) AS D1
FROM
(SELECT 1 AS N1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10) A
CROSS JOIN (SELECT 1 AS N1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10) B
DECLARE #fromdate DATE
DECLARE #todate DATE
DECLARE #tcaldate Table (CalenderDate Date);
set #fromdate='2017-04-17'
set #todate='2017-05-13'
INSERT INTO #tcaldate SELECT TOP (DATEDIFF(DAY, #fromdate, #todate) + 1)
Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, #fromdate)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b;
Select * from #tcaldate
Hope this helps...
Well, I think the easiest way is to create calendar table and in subquery just select dates between dates.
You can do this by this query:
CREATE TABLE dbo.Calendar ([Date] date)
DECLARE #startDate date, #endDate date
SET #startDate = '2000-01-01'
SET #endDate = '2020-12-31'
WHILE #startDate <= #endDate
BEGIN
INSERT INTO dbo.Calendar
SELECT #startDate
SET #startDate = DATEADD(DD,1,#startDate)
END
Selecting dates:
Select *
From dbo.Calendar WHERE [Date] BETWEEN #date1 AND #date2

Populating a Datetime Column

I want to populate a datetime column on the fly within a stored procedure. below is the query that I currently have that does same but slows down query performance.
CREATE TABLE #TaxVal
(
ID INT
, PaidDate DATETIME
, CustID INT
, CompID INT
)
INSERT INTO #TaxVal(ID, PaidDate, CustID, CompID)
VALUES(01, '20150201',12, 100)
, (03,'20150301', 18,101)
, (10,'20150401',19,22)
, (17,'20150401',02,11)
, (11,'20150411',18,201)
, (78,'20150421',18,299)
, (133,'20150407',18,101)
-- SELECT * FROM #TaxVal
DECLARE #StartDate DATETIME = '20150101'
, #EndDate DATETIME = '20150501'
DECLARE #Tab TABLE
(
CompID INT
, DateField DATETIME
)
DECLARE #T INT
SET #T = 0
WHILE #EndDate >= #StartDate + #T
BEGIN
INSERT INTO #Tab
SELECT CompID
, #StartDate + #T AS DateField
FROM #TaxVal
WHERE CustID = 18
AND CompID = 101
ORDER BY DateField DESC
SET #T = #T + 1
END
SELECT DISTINCT * FROM #Tab
DROP TABLE #TaxVal
Which is the best way to write this query for better performance?
Change this:
DECLARE #T INT
SET #T = 0
WHILE #EndDate >= #StartDate + #T
BEGIN
INSERT INTO #Tab
SELECT CompID
, #StartDate + #T AS DateField
FROM #TaxVal
WHERE CustID = 18
AND CompID = 101
ORDER BY DateField DESC
SET #T = #T + 1
END
to this:
;with cte as(
select cast('20150101' as date) as d
union all
select dateadd(dd, 1, d) as d from cte where d < '20150501'
)
INSERT INTO #Tab
SELECT CompID, d
FROM #TaxVal
cross join cte
WHERE CustID = 18 AND CompID = 101
Option(maxrecursion 0)
Here is recursive common table expression to get all dates in range. Then you do a cross join and insert. Notice that there is no sense to order set while inserting.
Giorgi's answer of
;with cte as(
select cast('20150101' as date) as d
union all
select dateadd(dd, 1, d) as d from cte where d < '20150501'
)
INSERT INTO #Tab
SELECT CompID, d
FROM #TaxVal
cross join cte
WHERE CustID = 18 AND CompID = 101
will work, but be careful with recursive CTE's. If the date range is large, you'll quickly hit your maximum recursion level. Often, a numbers table is used much like HABO mentioned. This is simply a table with a single column that only a integer so the rows would be 1, 2, 3, 4, 5, etc. You can then join the Numbers table (outer apply works well for this) and use the numbers with dateadd to get your incremental dates. Also note that you can run into an issue where the Numbers table doesn't contain enough rows for you date range.

T-SQL to select every nth date from column of dates

I have a column of dates. They are all workdays. I would like to generate a list of dates that are 'n' days apart. For example, starting with the most recent date, I want to find the date n days before it, 2n days before it, 3n days before it, etc. I could use a while loop but I wanted to know if I could use SQL set operations instead. Can it be done?
Find the difference between the most_recent_date and dates in your table, then use the modulo function, where n is the interval.
SELECT date
FROM my_table
WHERE mod(most_recent_date - date, n) = 0
This is the perfect case for a CTE:
DECLARE #LastDate datetime;
DECLARE #N int;
DECLARE #NCoefficientMax;
SELECT #N = 1, #NCoefficientMax = 10;
SELECT #LastDate = MyDate
FROM MyTable
ORDER BY MyDate DESC
WITH mycte
AS
(
SELECT DATEADD(dd, #N, #LastDate) AS NextDate, #N AS NCoefficient
UNION ALL
SELECT DATEADD(dd, #N, NextDate), #N + NCoefficient AS NCoefficient
FROM mycte WHERE NCoefficient < #NCoefficientMax
)
SELECT NextDate FROM mycte
Where #NCoefficientMax is the max coefficient for N.
You can use the dateadd funcion
and make select with join to self table.
What that you need to do it -
Insert the result to temporary table,
with additional column then contain the row_number then order like the result
simple example:
declare #t1 table (d datetime, row int)
insert #t1
select d, row_number()over(order by d)
from T1
order by d
select T1A.*, datediff(day,T1A.d,T1B.d) as dif
from #t1 as T1A
left join #t1 as T1B on T1A.row = T1B.row-1
DECLARE #mostRecent datetime2
SELECT #mostRecent = MAX(dateColumn)
FROM table
SELECT columns
FROM table
WHERE (DATEDIFF(day, dateColumn, #mostRecent) % n) = 0

SQL: How to create a temp table and fill it with date within a select-from statement

I wish to create a temp table with 1 datetime column and then fill it with date(30 days before today). I wish to do all these in a select-from statement.
I could do it with a "WITH" loop as below prior to the select-from statement. However, I wish to do it within a select-from statement.
declare #endDate datetime
set #endDate = dateadd(day,-30,getdate())
with CTE_Table (
Select dataDate = dateadd(day,-1,getdate()) from CTE_Table
where datediff(day,dataDate,#endDate) < 0
)
select * from CTE_Table
Please help... :....(
You can use SELECT ... INTO.
BTW Your recursive CTE is invalid. A fixed version is below
DECLARE #endDate DATETIME
SET #endDate = dateadd(day, -30, getdate());
WITH CTE_Table(dataDate)
AS (SELECT dateadd(day, -1, getdate())
UNION ALL
SELECT dateadd(day, -1, dataDate)
FROM CTE_Table
WHERE datediff(day, dataDate, #endDate) < 0)
SELECT dataDate
INTO #T
FROM CTE_Table
You could do:
CREATE TABLE #temptable
(
DateColumn DATETIME
)
INSERT INTO #temptable
SELECT dataDate FROM CTE_Table

How to create a list of dates from a daterange without using a CTE

The following link explains how to turn a date range into a list of dates.
I used this approach and it works fine but the query is not performing (I used Maxrecursion 0 to unlimit).
http://blog.justinstolle.com/sql-turn-a-date-range-into-a-list-of-dates/
Is there any other solution to get this done? (using subquery or declare table?)
Try
declare #datestart date = '2012-1-1', #dateend date = '2012-10-31'
declare #days int = datediff(d,#datestart,#dateend)
select
dateadd(d, number, #datestart)
from master..spt_values
where type='p'
and number<=#days
If your date range is more than 2047 days, you can extend it by self joining the table - the below will allow you up to 27 years..
select
dateadd(d, v1.number+v2.number*2048, #datestart)
from master..spt_values v1
cross join (select number from master..spt_values where number<5 and type='p') v2
where type='p'
and (v1.number+v2.number*2048)<=#days
This is the same query in the link with a minor modification (using CTE). Please check:
DECLARE #dateranges TABLE (range_id VARCHAR(2), date_begin DATETIME, date_end DATETIME)
INSERT #dateranges SELECT 'A', '2010-01-01', '2010-01-03'
INSERT #dateranges SELECT 'B', '2008-02-27', '2008-03-01'
INSERT #dateranges SELECT 'C', '2010-04-26', '2010-04-26'
INSERT #dateranges SELECT 'D', '2000-02-01', '2001-02-05'
;WITH cte (id, d)
AS (SELECT tbl.range_id AS id, tbl.date_begin AS d
FROM #dateranges tbl
WHERE DATEDIFF(DAY, tbl.date_begin, tbl.date_end) >0
UNION ALL
SELECT tbl.range_id AS id, DATEADD(DAY, 1, cte.d) AS d
FROM cte INNER JOIN #dateranges tbl
ON cte.id = tbl.range_id
WHERE cte.d < tbl.date_end)
SELECT id AS range_id, d AS date_within_range FROM cte
ORDER BY id, d
OPTION (MAXRECURSION 0);