Stack Overflow Asked by titatovenaar on January 8, 2021
I ran into the issue that we cannot use underneath query inside a view, but only in a table. Unfortunately we deal with the situation that we don’t have tables as an option for this project.
I am curious whether someone knows in what direction I should look for to substitute for underneath logic:
What I try to do is create a record for each day within a date range, by doing the following query:
WITH CTE_PerDay AS (
SELECT
TableDateRange.objectId
,TableDateRange.amount
,TableDateRange.beginDate
,COALESCE(TableDateRange.endDate, '2099-12-31') AS endDate
FROM TableDateRange
UNION ALL
SELECT
CTE_PerDay.objectId
,CTE_PerDay.amount
,DATEADD(DAY, 1, CTE_PerDay.beginDate) AS beginDate
,CTE_PerDay.endDate
FROM CTE_PerDay
WHERE GETDATE() > DATEADD(DAY, 1, CTE_PerDay.beginDate)
)
SELECT * FROM CTE_PerDay
OPTION (maxrecursion 0)
Sample Dataset TableDataRange
ObjectId | Amount | beginDate | endDate |
---|---|---|---|
1 | 500 | 2020-01-03 | |
2 | 35 | 2015-05-31 | 2019-10-01 |
3 | 200 | 2017-03-15 | 2020-06-02 |
CREATE TABLE TableDateRange
(
ObjectId varchar(300),
Amount int,
beginDate date,
endDate date
);
INSERT INTO TableDateRange ( ObjectId , Amount , beginDate , endDate )
VALUES
('1', 500, '2020-01-03', NULL),
('2', 35, '2015-05-31', '2019-10-01'),
('3', 200, '2017-03-15', '2020-06-02');
So the query runs fine, however in a view I can’t use the OPTION
functionality, and without it I get the error ‘The statement terminated. The maximum recursion 100 has been exhausted before statement completion.’
Any suggestions?
You could use a tally: that's a set-based solution, that performs better than recursion when the number of iterations increases - and it is supported in views.
Here is an approach:
select t.objectid, t.amount, dateadd(day, x.n, t.begindate) as dt
from (
select row_number() over (order by (select null)) - 1
from (values(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
cross join (values(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
cross join (values(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
) x(n)
inner join tabledatarange t
on dateadd(day, x.n, t.begindate) <= case
when enddate <= convert(date, getdate()) then enddate
else convert(date, getdate())
end
The tally generates all numbers between 0 and 999 (you can easily expand it by adding cross join
s). We use it to "multiply" the rows of the original table and generate the date range.
I attempted to rewrite the part the handles the end date. I understand that you don't want future dates, so that's what the condition in the on
clause does.
For this sample data:
ObjectId | Amount | beginDate | endDate -------: | -----: | :--------- | :--------- 1 | 500 | 2020-12-28 | null 2 | 35 | 2019-09-26 | 2019-10-01 3 | 200 | 2020-05-28 | 2020-06-02
The query returns:
objectid | amount | dt -------: | -----: | :--------- 1 | 500 | 2020-12-28 1 | 500 | 2020-12-29 1 | 500 | 2020-12-30 1 | 500 | 2020-12-31 2 | 35 | 2019-09-26 2 | 35 | 2019-09-27 2 | 35 | 2019-09-28 2 | 35 | 2019-09-29 2 | 35 | 2019-09-30 2 | 35 | 2019-10-01 3 | 200 | 2020-05-28 3 | 200 | 2020-05-29 3 | 200 | 2020-05-30 3 | 200 | 2020-05-31 3 | 200 | 2020-06-01 3 | 200 | 2020-06-02
Correct answer by GMB on January 8, 2021
The answer to the question you asked is: Add OPTION
to the outer query. A view is just a subquery and cannot therefore contain a query level hint.
The question that you didn't ask is: is this actually the best method for a calendar table?
And the answer is: no. Recursing 28854 times in a CTE is very bad for performance.
Much better is either to have a calendar table on disk, or to use Itzik Ben-Gan's tally table (I would probably do this in a Table Valued Function):
CREATE FUNCTION dbo.GetDates
( @startDate as DateTime, @endDate as DateTime )
RETURNS TABLE WITH SCHEMABINDING
AS RETURN
(
WITH
L0 AS ( SELECT 1 AS c FROM (VALUES(1),(1)) AS D(c) ),
L1 AS ( SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B ),
L2 AS ( SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B ),
L3 AS ( SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B ),
L4 AS ( SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B ),
L5 AS ( SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B ),
Nums AS ( SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rownum
FROM L5 )
SELECT TOP (DATEDIFF(day, @startDate, @endDate) + 1)
DATEADD(day, rownum, '1999-12-31')
FROM Nums
);
Answered by Charlieface on January 8, 2021
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP