MSSQL generating a column with rows dynamically - sql-server

Hope you all have a fantastic time!
I am trying to create a temporary column with date values from the past 14 days.
so in essence, it should for example the below list whenever i re-call this stored procedure:
Reason for this is that we might not have data for a specific date, and we need to show that the count returns zero.
So the idea is to create a column, and then join with another table i created based on the date to do the counts per day.
I tried below code, but it does not work:
declare #a int
set #a = 1
select
[Date] = while (#a <= 14)
begin
cast(dateadd(day, -#a, getdate()) as date)
set #a = #a + 1
end
into #Temp1
select
*
from #Temp1
Any help will be greatly appreciated!

Using a simulated numbers table in a cte (you would ideally have a permanent table) you can simply do
with numbers as (
select top (14) Row_Number() over (order by (select null)) as n from sys.sysobjects
)
select cast(dateadd(day, -n, getdate()) as date)
from numbers

Instead of querying sys.sysobjects unnecessarily, you can generate the table on the fly with no reads, by using a VALUES constructor.
with numbers as (
select Row_Number() over (order by (select null)) as n
from (values(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) v(x)
)
select cast(dateadd(day, -n, getdate()) as date)
from numbers

Related

Sum values if they are between date range sql

I want to sum values where date is between de creationdate and endDate,, hence ValueEnd.
For instances the second row, the creationDate is the same as the endDate, so I have to sum the ValuePerDay of this day to the previsou value. So in the column ValueEnd it is 3.4+1.17 = 4.57
I started by calculating the sum from the days where de Difference is 1, like this:
SELECT
CONVERT(CHAR(10), CreationDate,103) CreationDate
,CONVERT(CHAR(10), EndDate,103) EndDate
,SUM(Values_an) Values_an
FROM Dat1
WHERE Difference=1
GROUP BY CONVERT(CHAR(10), CreationDate,103), CONVERT(CHAR(10), EndDate,103), Difference
However, I'm having trouble sum the values where the difference if higher than 1. Can someone help me please?
OK, judging by the provided information - and as far as I understood everything right - the following approach might solve your problem:
DECLARE #t TABLE(
CreationDate date,
EndDate date,
Value_An decimal(19,4)
)
INSERT INTO #t VALUES
('2019-03-01', '2019-03-01', 3.4)
,('2019-03-01', '2019-03-03', 3.5)
,('2019-05-01', '2019-05-01', 3.6)
,('2019-06-01', '2019-06-04', 3.7)
;WITH cteMultiRow AS(
SELECT CreationDate, COUNT(*) cntRows
FROM #t
GROUP BY CreationDate
HAVING COUNT(*) > 1
),
cte AS(
SELECT t.*
,ROW_NUMBER() OVER (PARTITION BY t.CreationDate ORDER BY t.EndDate) AS rn
,DATEDIFF(d, t.CreationDate, t.EndDate)+1 AS Difference
,CASE WHEN m.CreationDate IS NOT NULL THEN t.Value_An/(DATEDIFF(d, t.CreationDate, t.EndDate)+1) ELSE t.Value_An END AS ValuePerD
FROM #t t
LEFT JOIN cteMultiRow m ON t.CreationDate = m.CreationDate
),
cteSums AS(
SELECT c.CreationDate, SUM(c.ValuePerD) AS ValuePerD
FROM cte c
GROUP BY c.CreationDate
)
SELECT c.CreationDate, c.EndDate, c.Value_An, c.Difference, c.ValuePerD, ISNULL(s.ValuePerD, c.Value_An) AS ValueEnd
FROM cte c
LEFT JOIN cteSums s ON c.CreationDate = s.CreationDate AND c.rn = 1

Comments please on my script - how to make it more efficient

I have a table (SYS_Holidays) that have start and end dates of each holiday period. I need to output all holiday dates in a relational form. For example, I have 25-Dec-2017 to 2-Jan-2018 as one row in the input, I want to output 25-Dec, 26-Dec ... through 2-Jan as 9 rows.
I have written this script, could you please tell me how I can make it more efficient?
SELECT
H.HolidayName
, DATEADD(DAY, Number-1, H.StartDate) AS HolidayDate
FROM
SYS_Holidays AS H
CROSS JOIN Config_Numbers AS N
WHERE
-- Figure the # of days between start and end: one row for each holiday-date
-- If EndDate is null, just use StartDate (i.e. 1-day holiday)
N.Number <= DATEDIFF(DAY, H.StartDate, ISNULL(H.EndDate, H.StartDate) ) + 1
NB: Config_Numbers is a table I have created with a huge list of integers (as BIGINT)
It can be done using a date table and an inner join. Use the subquery to make a table if it is not efficient enough:
Create table #Test (HolidayName nvarchar(100), StartDate Date, EndDate Date)
Insert Into #Test Values ('Christmas', '2017-12-22', '2018-01-03'), ('Easter' , '2017-04-10', '2017-04-16')
SELECT HolidayName, DatesList.[Date] as HolidayDate
FROM #Test t
inner join (
SELECT cast(dateadd(day, number, '2017-1-1') as date) as [Date]
FROM master..spt_values WHERE type='P' AND number < 1000) AS DatesList
on t.StartDate<=DatesList.[Date] and t.EndDate>=DatesList.[Date]
I modified #cloudsafe's answer, to yield the code below. It is still much faster than any of the joins using Config_Numbers. Subtree cost came to ~0.2785.
I figured that 2048 can cover a little more than 5 years, so I broke my code up into 5-year blocks, and did a UNION to join them up.
Trouble is, I'd have to remember to do another UNION every 5-years :-(
SELECT HolidayName, DatesList.[Date] as HolidayDate
FROM SYS_Holidays AS H
inner join (
SELECT cast(dateadd(day, number, '2013-01-01') as date) as [Date]
FROM master..spt_values WHERE type='P' AND number < 2048) AS DatesList
on H.StartDate <= DatesList.[Date] and H.EndDate >=DatesList.[Date]
UNION
SELECT HolidayName, DatesList.[Date] as HolidayDate, H.HolidayId, H.CampusId, H.CategoryId
FROM SYS_Holidays AS H
inner join (
SELECT cast(dateadd(day, number, '2018-01-01') as date) as [Date]
FROM master..spt_values WHERE type='P' AND number < 2048) AS DatesList
on H.StartDate <= DatesList.[Date] and H.EndDate >=DatesList.[Date]
Any further suggestions for improvement, please?

How to improve speed of the query using SQL Server?

I have created a stored procedure to get data. In this stored procedure, I have returned 1 table and table stores the data above 1 lakh + data. So right now I have run the stored procedure that time I get the data in above 1 minute time take. I want just with in 1 second get data. I have set also SET NOCOUNT ON; and also create missing index. Still I am getting same time for the get data.
This is my query:
DECLARE #CurMon int
DECLARE #year nvarchar(max)
SELECT #CurMon = month(getdate())
SELECT #year = year(getdate())
SELECT
FORMAT(dateadd(MM, T.i, getdate()),'MMM-yy') AS DateColumn,
ISNULL(uf.TotalCount, 0) as TotalCount
FROM
(VALUES (12-#CurMon),(11-#CurMon),(10-#CurMon),(9-#CurMon),(8-#CurMon),(7-#CurMon),(6-#CurMon), (5-#CurMon), (4-#CurMon), (3-#CurMon), (2-#CurMon), (1-#CurMon)) AS T(i)
OUTER APPLY
(SELECT DISTINCT
COUNT(datepart(MM,UF.InsertDateTime)) OVER (partition by datepart(MM,UF.InsertDateTime)) AS TotalCount
FROM dbo.UserFollowers UF
INNER JOIN dbo.Users U on U.UserId = UF.FollowerId
WHERE DATEDIFF(mm,UF.InsertDateTime, DATEADD(mm, T.i, GETDATE())) = 0 and UF.IsFollowed = 1
) uf
order by DATEPART(MM,convert(datetime,FORMAT(dateadd(MM, T.i, getdate()),'MMMM') +'01 '+#year,110))
i am also try some other query for the improve speed of query but still i am getting same time. here this query also print.
declare #StartDate datetime = dateadd(year , datediff(year , 0, getdate() ) , 0)
declare #tempT2 table
(
MNo int,
[Month] datetime,
NextMonth datetime)
;with Months as (
select top (12)
MNo = row_number() over (order by number)
,[Month] = dateadd(month, row_number() over (order by number) -1, #StartDate)
, NextMonth = dateadd(month, row_number() over (order by number), #StartDate)
from master.dbo.spt_values
)
insert into #tempT2
select * from Months
select
m.MNo
, Month = format(m.Month, 'MMM-yy')
, tally = count(UF.InsertDateTime)
from #tempT2 m
left join dbo.UserFollowers UF
INNER JOIN dbo.Users U on U.UserId = UF.FollowerId
on UF.InsertDateTime >= m.Month
and UF.InsertDateTime < m.NextMonth where UF.IsFollowed = 1
group by m.MNo,format(m.Month, 'MMM-yy')
order by MNo
here this is my both query i have try but still i am not getting success for the improve the speed of the query. and sorry but i can not see here my execution plan of the query actually i have not permission for that.
You can gain a little bit of performance by switching to a temporary table instead of a table variable, and by getting rid of format():
declare #StartDate datetime = dateadd(year , datediff(year , 0, getdate() ) , 0)
create table #Months (
MNo int not null primary key
, Month char(6) not null
, MonthStart datetime not null
, NextMonth datetime not null
)
;with Months as (
select top (12)
MNo = row_number() over (order by number)
, MonthStart = dateadd(month, row_number() over (order by number) -1, #StartDate)
, NextMonth = dateadd(month, row_number() over (order by number), #StartDate)
from master.dbo.spt_values
)
insert into #Months (MNo, Month, MonthStart, NextMonth)
select
MNo
, Month = stuff(convert(varchar(9),MonthStart,6),1,3,'')
, MonthStart
, NextMonth
from Months;
select
m.MNo
, m.Month
, tally = count(UF.InsertDateTime)
from #tempT2 m
inner join dbo.Users U
on UF.InsertDateTime >= m.MonthStart
and UF.InsertDateTime < m.NextMonth
inner join dbo.UserFollowers UF
on U.UserId = UF.FollowerId
and UF.IsFollowed = 1
group by
m.MNo
, m.Month
order by MNo
After that, you should evaluate the execution plan to determine if you need a better indexing strategy.
If you still need it to go faster, you could create an actual calendar table and look into creating an indexed view. An indexed view can be a chore get it to behave correctly depending on your sql server version, but will be faster.
Reference:
format performance - Aaron Bertrand
What is the difference between a temp table and table variable in SQL Server? - Answer by Martin Smith
When should I use a table variable vs temporary table in sql server? - Answer by Martin Smith
Indexed Views and Statistics - Paul White
Generate a set or sequence without loops - 2 - Aaron Bertrand
The "Numbers" or "Tally" Table: What it is and how it replaces a loop - Jeff Moden
Creating a Date Table/Dimension in sql Server 2008 - David Stein
Calendar Tables - Why You Need One - David Stein
Creating a date dimension or calendar table in sql Server - Aaron Bertrand

Return 0 for NULL values in a simple count and group by date

I'm doing a simple count query for an SSRS chart, and while it looks like a common question, I've not really found an answer that suits my situation. This is my query:
SELECT TOP 30 CAST(qa.Created As Date) As 'Date',
COUNT(qa.Created) As 'Count'
FROM QAs qa
GROUP BY CAST(qa.Created As Date)
ORDER BY 'Date' DESC
This returns something like:
Date | Count
2014-11-10 | 2
2014-11-08 | 3
2014-11-07 | 8
Which when put into a line chart, doesn't show the dip down to 0 on the 9th and is a bit confusing for my users. What I want to do is have all of the last 30 days appear in order, even if they are at 0. I've been told to do this with COALESCE() but I can't seem to get that working either. Where am I going wrong?
Use a Recursive CTE to generate dates for last 30 days.
;WITH cte
AS (SELECT Cast(dateadd(day,-30,Getdate()) AS DATE) AS dates
UNION ALL
SELECT Dateadd(day, 1, dates)
FROM cte
WHERE dates < cast(Getdate() as date)
SELECT a.Dates AS [Date],
Count(qa.Created) AS [Count]
FROM cte a
LEFT JOIN QAs qa
ON a.dates = Cast(qa.Created AS DATE)
GROUP BY a.Dates
ORDER BY a.Dates
I'd probably go with NoDisplayName's recursive CTE but for an alternative, if you happen to be stuck in something prior to 2005 which I was for a long time.
--Build a table of dates
DECLARE #dates AS TABLE([date] date)
DECLARE #i int
SET #i = 30
WHILE #i > 0
BEGIN
INSERT INTO #dates([date])
SELECT DATEADD(d, -1 * #i, GETDATE())
SET #i = (#i - 1)
END
--Join into those dates so that no date is excluded
SELECT [date], SUM(dateCount)
FROM (
SELECT d.[date], CASE WHEN qa.Created IS NULL THEN 0 ELSE 1 END AS dateCount
FROM #dates d
LEFT JOIN QAs qa ON CAST(qa.Created AS date) = d.[date]
) AS dateCounts
GROUP BY [date]

How to set the maxrecursion option for a CTE inside a Table-Valued-Function

I'm facing a problem declaring the maxrecursion option for a CTE inside a TVF.
Here is the CTE (a simple calendar):
DECLARE #DEBUT DATE = '1/1/11', #FIN DATE = '1/10/11';
WITH CTE as(
SELECT #debut as jour
UNION ALL
SELECT DATEADD(day, 1, jour)
FROM CTE
WHERE DATEADD(day, 1, jour) <= #fin)
SELECT jour FROM CTE option (maxrecursion 365)
and the TVF:
CREATE FUNCTION [liste_jour]
(#debut date,#fin date)
RETURNS TABLE
AS
RETURN
(
WITH CTE as(
SELECT #debut as jour
UNION ALL
SELECT DATEADD(day, 1, jour)
FROM CTE
WHERE DATEADD(day, 1, jour) <= #fin)
SELECT jour FROM CTE
--option (maxrecursion 365)
)
The above TVF is running OK without the maxrecursion option
but there is a syntax error with the option.
What is the solution?
From this MSDN forums thread I learn that
[the] OPTION clause can be used only at the statement level
So you cannot use it within a query expression inside view definitions or inline TVFs etc. The only way to use it in your case is to create the TVF without the OPTION clause and specify it in the query that uses the TVF. We have a bug that tracks request for allowing use of OPTION clause inside any query expression (for example, if exists() or CTE or view).
and further
You can not change the default value of that option inside a udf. You
will have to do it in the statement referencing the udf.
So in your example, you must specify the OPTION when you call your function:
CREATE FUNCTION [liste_jour]
(#debut date,#fin date)
RETURNS TABLE
AS
RETURN
(
WITH CTE as(
SELECT #debut as jour
UNION ALL
SELECT DATEADD(day, 1, jour)
FROM CTE
WHERE DATEADD(day, 1, jour) <= #fin)
SELECT jour FROM CTE -- no OPTION here
)
(later)
SELECT * FROM [liste_jour] ( #from , #to ) OPTION ( MAXRECURSION 365 )
Note that you can't work round this by having a second TVF that just does the above line - you get the same error, if you try. "[the] OPTION clause can be used only at the statement level", and that's final (for now).
Old thread, I know, but I needed the same thing and just dealt with it by using a multi-statement UDF:
CREATE FUNCTION DatesInRange
(
#DateFrom datetime,
#DateTo datetime
)
RETURNS
#ReturnVal TABLE
(
date datetime
)
AS
BEGIN
with DateTable as (
select dateFrom = #DateFrom
union all
select DateAdd(day, 1, df.dateFrom)
from DateTable df
where df.dateFrom < #DateTo
)
insert into #ReturnVal(date)
select dateFrom
from DateTable option (maxrecursion 32767)
RETURN
END
GO
There are probably efficiency issues with this, but I can afford it in my case.
A little creative use of CTEs and cartesian products (cross joins) will get you around the MAXRECURSION limit of 100. 3 CTEs with a limit of 4 records on the last one nets you 40,000 records, which will be good for more than 100 years worth of data. If you expect more difference between #debut and #fin, you can adjust cte3.
-- please don't SHOUTCASE your SQL anymore... this ain't COBOL
alter function liste_jour(#debut date, #fin date) returns table as
return (
with cte as (
select 0 as seq1
union all
select seq1 + 1
from cte
where seq1 + 1 < 100
),
cte2 as (
select 0 as seq2
union all
select seq2 + 1
from cte2
where seq2 + 1 < 100
),
cte3 as (
select 0 as seq3
union all
select seq3 + 1
from cte3
where seq3 + 1 <= 3 -- increase if 100 years isn't good enough
)
select
dateadd(day, (seq1 + (100 * seq2) + (10000 * seq3)), #debut) as jour
from cte, cte2, cte3
where (seq1 + (100 * seq2) + (10000 * seq3)) <= datediff(day, #debut, #fin)
)
go
-- test it!
select * from liste_jour('1/1/2000', '2/1/2000')
Old issue but... I just wanted to clarify why OPTION(MAXRECURSION x) is not allowed in an in-line table-valued function. This is because iTVF's get inlined when you use them in a query. And, as we all know, you cannot put this option anywhere else save at the very end of the query. This is THE reason it will never be possible to put it inside an iTVF (unless the parser and/or algebrizer does some magic behind the scenes, which I don't think it will any time soon). mTVF's (multi-statement table-valued functions) is a different story because they don't get inlined (and are so slow that they should never be used in queries; it's OK to use them in an assignment to a variable, though, but then again---beware of loops!).
Another way to handle this is to break up the problem into a pair of CTEs, neither of which hits the recursion limit of 100. The first CTE creates a list with the begin date for each month in the range. The second CTE then fills in all the days of each month. As long as the input range is fewer than 100 months, it should work fine. If an input range of greater than 100 months is required, the same idea could be expanded with a third CTE for years added ahead of the months CTE.
CREATE FUNCTION [liste_jour]
(#debut datetime, #fin datetime)
RETURNS TABLE
AS
RETURN
(
WITH CTE_MOIS AS
(
SELECT JOUR_DEBUT = #debut
UNION ALL
SELECT DATEADD(MONTH, 1, CTE_MOIS.JOUR_DEBUT)
FROM CTE_MOIS
WHERE DATEADD(MONTH, 1, CTE_MOIS.JOUR_DEBUT) <= #fin
),
CTE_JOUR AS
(
SELECT JOUR = CTE_MOIS.JOUR_DEBUT
FROM CTE_MOIS
UNION ALL
SELECT DATEADD(DAY, 1, CTE_JOUR.JOUR)
FROM CTE_JOUR
WHERE MONTH(CTE_JOUR.JOUR) = MONTH(DATEADD(DAY, 1, CTE_JOUR.JOUR)) AND
DATEADD(DAY, 1, CTE_JOUR.JOUR) <= #FIN
)
SELECT JOUR
FROM CTE_JOUR
)
create simple sample for you :)
/* block create function for test in sql*/
/*FUNCTION [fn_CTE_withLevel] (#max_level int)
RETURNS TABLE
AS
RETURN
( */
/******************* declare table just replace real table *****/
declare #tbl table(pid varchar(15),id varchar(15))
/* use function argument */
declare #max_level int = 3
Insert Into #tbl(pid , id)
values
/*lev1*/ ('0','1') ,
/*lev2*/ ('1','101') ,
/*lev2*/ ('1','102') ,
/*lev1*/ ('0','2') ,
/*lev2*/ ('2','201') ,
/*lev3*/ ('201','20101') ,
/*lev3*/ ('201','20102') ,
/*lev2*/ ('2','202') ,
/*lev1*/ ('0','3') ,
/*lev2*/ ('3','301') ,
/*lev2*/ ('3','302') ,
/*lev1*/ ('0','4') ,
/*lev2*/ ('4','401'),
/*lev2*/ ('4','402');
/******************* declare table just replace real table *****/
With cte_result(pid , id , lev)
As(
Select pid , id , 1 as lev From #tbl t
Where pid = '0' /* change to another values from list to test sub items */
Union All
Select t.pid , t.id , cte.lev + 1 as lev
From cte_result cte
inner Join #tbl t
On cte.id = t.pid
Where cte.lev < #max_level -- :) this is my idea
)
Select * From cte_result
--OPTION (MAXRECURSION 100)
-- uncomment for create function
/*)*/

Resources