Use Dynamic Syntax in a View or a Function - sql-server
I have a dynamic query that I've written that looks like the following:
DECLARE #sql nvarchar(max)
SELECT #sql =
'select distinct datetable.Date
from (
select cast(DATEADD(day,-(a.a + (10 * b.a) + (100 * c.a) + (1000 * d.a)),getdate()) as date) AS Date
from (select 0 as a union all select 1 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) as a
cross join (select 0 as a union all select 1 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) as b
cross join (select 0 as a union all select 1 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) as c
cross join (select 0 as a union all select 1 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) as d
union all
select cast(DATEADD(day,(a.a + (10 * b.a) + (100 * c.a) + (1000 * d.a)),getdate()) as date) AS Date
from (select 0 as a union all select 1 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) as a
cross join (select 0 as a union all select 1 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) as b
cross join (select 0 as a union all select 1 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) as c
cross join (select 0 as a union all select 1 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) as d
) datetable
where '
+
replace(replace(replace(stuff((SELECT ' or datetable.Date between cast(''' + cast(cast(hld1.StrDate as date) as nvarchar(12)) + N''' as date) and cast(''' + cast(cast(hld1.endDate as date) as nvarchar(12))+ N''' as date)
'
from hld1 for xml path('')),1,3,''), '<', '<'), '>', '>'), '
', char(13)) +
'order by datetable.Date '
--print #sql
EXEC sys.sp_executeSQL #SQL
HLD1 is a list of holidays, where each holiday has a start and end date. The query itself returns a list of dates that are defined as holidays. (The reason that I can't just select the start dates and union them to the end dates is that there could very feasibly be a holiday with three days, and the middle day wouldn't show up in either list.
However, I'm using this monstrosity to create a function, and, as part of the function, I want to be able to do something like "If the date is in this list, then do the following."
My original plan was to set up a view that would just be the list of dates; however, this is not possible, because it uses a variable, and variables aren't allowed in views.
My next thought was to create a function that would just return the list. However, when I put in the syntax to create it as a function, I get the error The last statement included within a function must be a return statement.
I am unsure what path I should pursue from here. The reason that I can't just make a table and list out the dates manually is that currently the list only extends through 2016. In addition, the holiday list (start and end dates) may be created/added differently for different databases that the end goal function would be added to and used on.
If you need more background/information, please let me know and I'd be happy to provide. I'm just learning as I go. :)
Edit 1: I found the following link, but it doesn't appear to apply in this case: Create A View With Dynamic Sql
Why not just create a numbers or tally table as a persistent table or a view and avoid all this nastiness. 99% of this query is just generating a bunch of numbers.
For example, here is a view that performs 0 reads and will generate 10,000 rows of sequential numbers nearly instantly.
create View [dbo].[cteTally] as
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 N from cteTally
GO
There is your numbers portion. The next part would be to create the persistent dates table with the holidays and such like you are doing.
Here is an awesome article from my buddy Dwain Camps (RIP) about creating a calendar table. http://www.sqlservercentral.com/blogs/dwainsql/2014/03/30/calendar-tables-in-t-sql/
--EDIT--
Here is an example of having a table (#Something) with start and end dates for a holiday. This will list each date between those two dates. Unless I am missing something this should be pretty much what you are trying to do.
create table #Something
(
HolidayName varchar(10)
, StartDate date
, EndDate date
)
insert #Something
select 'phroureo', '2016-03-01', '2016-03-05' union all
select 'Sean', '2016-07-04', '2016-07-05'
select HolidayName
, StartDate
, EndDate
, DATEADD(day, t.N - 1, StartDate) as ResultDate
from #Something s
join cteTally t on t.N <= DATEDIFF(day, StartDate, EndDate) + 1
order by HolidayName
drop table #Something
Related
SSMS Rolling Average over Day of Week
Leadership wants to know how Teammates are performing on Mondays & Fridays in comparison to the rest of the work week. Below is a sample temp dbo of a Teammate X's daily performance over a two-month period. Each subsequent Teammate has a different starting point from whence they are measured. I initially looked at using UNBOUNDED PRECEDING in conjunction with the various start dates, but windows functions are not cooperating. Help! CREATE TABLE #RollingAverage ( [Date] DATE PRIMARY KEY ,[Value] INT ); INSERT INTO #RollingAverage SELECT '2019-01-02',626 UNION ALL SELECT '2019-01-03',231 UNION ALL SELECT '2019-01-04',572 UNION ALL SELECT '2019-01-07',775 UNION ALL SELECT '2019-01-09',660 UNION ALL SELECT '2019-01-10',662 UNION ALL SELECT '2019-01-11',541 UNION ALL SELECT '2019-01-14',849 UNION ALL SELECT '2019-01-15',632 UNION ALL SELECT '2019-01-16',906 UNION ALL SELECT '2019-01-18',961 UNION ALL SELECT '2019-01-21',501 UNION ALL SELECT '2019-01-24',311 UNION ALL SELECT '2019-01-25',614 UNION ALL SELECT '2019-01-28',296 UNION ALL SELECT '2019-01-29',390 UNION ALL SELECT '2019-01-31',804 UNION ALL SELECT '2019-02-01',928 UNION ALL SELECT '2019-02-05',855 UNION ALL SELECT '2019-02-06',605 UNION ALL SELECT '2019-02-08',283 UNION ALL SELECT '2019-02-12',144 UNION ALL SELECT '2019-02-14',382 UNION ALL SELECT '2019-02-15',862 UNION ALL SELECT '2019-02-18',549 UNION ALL SELECT '2019-02-19',401 UNION ALL SELECT '2019-02-20',515 UNION ALL SELECT '2019-02-21',590 UNION ALL SELECT '2019-02-22',625 UNION ALL SELECT '2019-02-25',304 UNION ALL SELECT '2019-02-26',402 UNION ALL SELECT '2019-02-27',326; AVG(Value) over (ORDER BY [Date] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) did not work
The first thing you need to understand, is that your "daily" performance is not daily. A simple solution would be to fill the gaps to be able to effectively count the days. I filled the gaps using a CTE that generates a calendar table on the fly, but you could use a permanent calendar table if available. WITH E(n) AS( SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0))E(n) ), E2(n) AS( SELECT a.n FROM E a, E b ), cteCalendar(calDate) AS( SELECT TOP (61) CAST( DATEADD( DD, 1-ROW_NUMBER() OVER(ORDER BY (SELECT NULL)), GETDATE()) AS date) AS calDate FROM E2 ), cteRollingAverages AS( SELECT ra.[Date], ra.value, AVG(Value) over (ORDER BY calDate ROWS BETWEEN 7 PRECEDING AND CURRENT ROW) RollingAverage FROM #RollingAverage AS ra RIGHT JOIN cteCalendar AS c ON ra.[Date] = c.calDate ) SELECT * FROM cteRollingAverages WHERE [Date] IS NOT NULL ORDER BY [Date]; A different option is to use APPLY. This is not limited by a certain date. SELECT * FROM #RollingAverage r CROSS APPLY( SELECT AVG(i.[Value]) AS RollingAvg FROM #RollingAverage i WHERE i.[Date] BETWEEN DATEADD( DD, -7, r.[Date]) AND r.[Date]) av ORDER BY [Date];
SQL Server 2016 - Running Count and Sum for a 24 hours sliding window
I am trying to count orders over a 24 hours sliding window. I have a 'detetime' field and I'm calculating the 24 hours window aggregating at the minute level. It should re-start counting every time the order time between two consecutive orders is over 1440 minutes or when the running time of consecutive orders is over 1440 minutes. Environment is SQL server 2016, I can create Temp tables but no physical tables and no memory-optimized objects (I guess anything working on 2012+ should work). I tried an inner join on the same table and tested with recursive CTEs, ROW_NUMBER etc. but the issue is that there is never a set number of rows for the 24 hours window and the base time from which to calculate the start of the period changes. The only constant I have is the 24 hours time span. Tried the following: https://www.red-gate.com/simple-talk/sql/t-sql-programming/calculating-values-within-a-rolling-window-in-transact-sql/ Calculate running total / running balance Cross apply seems to be working for the most part but in some instances - when calculating the running 24 hours window - it isn't. I tried changing the datetime conditions in the WHERE clause in many ways but I still can't figure out how to get it to work correctly. I thought about creating a reset event at the 24 hours mark as showed here https://blog.jooq.org/2015/05/12/use-this-neat-window-function-trick-to-calculate-time-differences-in-a-time-series/ but at this point my brain is melting and I can't even get the logic straight. DROP TABLE IF EXISTS #Data CREATE TABLE #Data ( START_TIME DATETIME ,ORDER_ID NUMERIC(18,0) ,PROD_ID NUMERIC(18,0) ,ACC_ID NUMERIC(18,0) ); INSERT INTO #Data SELECT '2018-06-22 11:00:00.000', 198151606, 58666, 1601554883 UNION ALL SELECT '2018-07-09 10:15:00.000',2008873061,58666,1601554883 UNION ALL SELECT '2018-07-09 12:33:00.000',2009269222,58666,1601554883 UNION ALL SELECT '2018-07-10 08:29:00.000',2010735393,58666,1601554883 UNION ALL SELECT '2018-07-10 10:57:00.000',2010735584,58666,1601554883 UNION ALL SELECT '2018-06-27 23:53:00.000',1991467555,58666,2300231016 UNION ALL SELECT '2018-06-28 00:44:00.000',1991583916,58666,2300231016 UNION ALL SELECT '2018-07-04 04:15:00.000',2001154497,58666,2300231016 UNION ALL SELECT '2018-07-04 15:44:00.000',2001154818,58666,2300231016 UNION ALL SELECT '2018-07-04 21:30:00.000',2002057919,58666,2300231016 UNION ALL SELECT '2018-07-05 02:09:00.000',1200205808,58666,2300231016 UNION ALL SELECT '2018-07-05 04:15:00.000',2200205814,58666,2300231016 UNION ALL SELECT '2018-07-05 17:23:00.000',3200370070,58666,2300231016 UNION ALL SELECT '2018-07-05 18:07:00.000',4200370093,58666,2300231016 UNION ALL SELECT '2018-07-06 20:15:00.000',5200571962,58666,2300231016 UNION ALL SELECT '2018-07-07 07:45:00.000',6200571987,58666,2300231016 UNION ALL SELECT '2018-07-07 12:13:00.000',7200571993,58666,2300231016 UNION ALL SELECT '2018-07-09 18:29:00.000',8200939551,58666,2300231016 UNION ALL SELECT '2018-07-09 21:05:00.000',9200939552,58666,2300231016 UNION ALL SELECT '2018-07-11 21:31:00.000',2011107311,58666,2300231016 UNION ALL SELECT '2018-06-27 18:23:00.000',1991016382,58669,2300231016 UNION ALL SELECT '2018-06-27 19:07:00.000',1991181363,58669,2300231016 UNION ALL SELECT '2018-06-27 19:28:00.000',1991181374,58669,2300231016 UNION ALL SELECT '2018-06-28 01:44:00.000',1991583925,58669,2300231016 UNION ALL SELECT '2018-06-28 02:19:00.000',1991583946,58669,2300231016 UNION ALL SELECT '2018-07-03 10:15:00.000',1999231747,58669,2300231016 UNION ALL SELECT '2018-07-03 10:45:00.000',2000293678,58669,2300231016 UNION ALL SELECT '2018-07-03 14:22:00.000',200029380,58669,2300231016 UNION ALL SELECT '2018-07-04 19:45:00.000',2002057789,58669,2300231016 UNION ALL SELECT '2018-07-04 21:00:00.000',1200205781,58669,2300231016 UNION ALL SELECT '2018-07-05 15:12:00.000',2200254833,58669,2300231016 UNION ALL SELECT '2018-07-05 17:52:00.000',3200370071,58669,2300231016 UNION ALL SELECT '2018-07-09 22:30:00.000',4200939553,58669,2300231016 UNION ALL SELECT '2018-07-09 23:23:00.000',5200939566,58669,2300231016 UNION ALL SELECT '2018-07-30 17:45:00.000',6204364207,58666,2300231016 UNION ALL SELECT '2018-07-30 23:30:00.000',7204364211,58666,2300231016 ;WITH TimeBetween AS( SELECT ACC_ID ,PROD_ID ,ORDER_ID ,START_TIME ,TIME_BETWEEN_ORDERS = COALESCE(CASE WHEN DATEDIFF(MINUTE, LAG(START_TIME) OVER(PARTITION BY ACC_ID, PROD_ID ORDER BY START_TIME), START_TIME) >= 1440 THEN 0 ELSE DATEDIFF(MINUTE, LAG(START_TIME) OVER(PARTITION BY ACC_ID, PROD_ID ORDER BY START_TIME), START_TIME) END, 0) FROM #Data ) SELECT TimeBetween.ACC_ID ,TimeBetween.PROD_ID ,TimeBetween.ORDER_ID ,TimeBetween.START_TIME ,TIME_BETWEEN_ORDERS --Not working correctly, repeats the previous time at the end of the window when it should be 0. ,RUNNING_TIME_BETWEEN_ORDERS = SUM(TIME_BETWEEN_ORDERS) OVER(PARTITION BY ACC_ID, PROD_ID ORDER BY START_TIME) ,Running24h.* FROM TimeBetween CROSS APPLY(SELECT TOP 1 RUNNING_COUNT_24h = COUNT(*) OVER() --Count admin units within the time window in the WHERE clause --Check what APPLY is returning for running time ,RUNNING_TIME_BETWEEN_ORDERS_Apply = DATEDIFF(MINUTE, StageBaseApply.START_TIME, TimeBetween.START_TIME) --Check what APPLY is using as base event anchor for the calculation ,START_TIME_Apply = StageBaseApply.START_TIME FROM #Data AS StageBaseApply WHERE StageBaseApply.ACC_ID = TimeBetween.ACC_ID AND StageBaseApply.PROD_ID = TimeBetween.PROD_ID AND (StageBaseApply.START_TIME > DATEADD(MINUTE, -1440, TimeBetween.START_TIME) AND StageBaseApply.START_TIME <= TimeBetween.START_TIME ) ORDER BY StageBaseApply.START_TIME ) AS Running24h ORDER BY ACC_ID,PROD_ID, START_TIME When the running time between orders is over 24 hours the running count should re-start from 1. Currently it repeats the last value and the time it's using for the calculation seems to be off. Current result from CROSS APPLY with notes on where it's not working and what it should be for what I'm trying to achieve
First create a Numbers table with at least as many rows as the minutes in the maximum time range you will ever be dealing with CREATE TABLE dbo.Numbers(Number INT PRIMARY KEY); 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 ) -- 1*10^1 or 10 rows , E2(N) AS (SELECT 1 FROM E1 a, E1 b) -- 1*10^2 or 100 rows , E4(N) AS (SELECT 1 FROM E2 a, E2 b) -- 1*10^4 or 10,000 rows , E8(N) AS (SELECT 1 FROM E4 a, E4 b) -- 1*10^8 or 100,000,000 rows , Nums AS (SELECT TOP (10000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS N FROM E8) INSERT INTO dbo.Numbers SELECT N FROM Nums And then you should be able to use something like this (I'm assuming that all start times are exact minutes and there are no duplicates per ACC_ID,PROD_ID,START_TIME as shown in your example data, if there are you will need to pre-aggregate at the minute level before participating in the left join) WITH G AS (SELECT ACC_ID, PROD_ID, MIN = MIN(START_TIME), MAX = MAX(START_TIME), Range = DATEDIFF(MINUTE, MIN(START_TIME), MAX(START_TIME)) FROM #Data GROUP BY ACC_ID, PROD_ID), E AS (SELECT * FROM G JOIN dbo.Numbers N ON N.Number <= Range + 1), R AS (SELECT E.ACC_ID, E.PROD_ID, D.START_TIME, Cnt = COUNT(D.START_TIME) OVER (PARTITION BY E.ACC_ID, E.PROD_ID ORDER BY DATEADD(MINUTE, NUMBER-1, MIN) ROWS BETWEEN 1439 PRECEDING AND CURRENT ROW) FROM E LEFT JOIN #Data D ON D.ACC_ID = E.ACC_ID AND D.PROD_ID = E.PROD_ID AND D.START_TIME = DATEADD(MINUTE, NUMBER-1, MIN) ) SELECT * FROM R WHERE START_TIME IS NOT NULL ORDER BY ACC_ID, PROD_ID, START_TIME
After finding this post on how to reset a running sum, I think I may have finally been able to crack this nut. Not sure about how well it scales but it is working. I also added a new column for order quantity since it may be useful sometimes to track the orders running total during the same time window. The sliding time window can be set in this CASE statement: CASE WHEN RunningOrders.LAG_LESS_THAN_24h + NextEventLag.NEXT_ORDER_TIME_LAG >= 1440 THEN 0 ELSE RunningOrders.LAG_LESS_THAN_24h + NextEventLag.NEXT_ORDER_TIME_LAG END DROP TABLE IF EXISTS #Data CREATE TABLE #Data ( ORDER_TIME DATETIME ,ORDER_ID NUMERIC(18,0) ,PROD_ID NUMERIC(18,0) ,ACCOUNT_ID NUMERIC(18,0) ,ORDER_QUANTITY INT ); INSERT INTO #Data SELECT '2018-06-22 11:00:00.000', 1981516061, 158666, 1601554883,5 UNION ALL SELECT '2018-07-09 10:15:00.000',2008873062,158666,1601554883,3 UNION ALL SELECT '2018-07-09 12:33:00.000',2009269223,158666,1601554883,2 UNION ALL SELECT '2018-07-10 08:29:00.000',2010735394,158666,1601554883,4 UNION ALL SELECT '2018-07-10 10:57:00.000',2010735584,158666,1601554883,7 UNION ALL SELECT '2018-06-27 23:53:00.000',1991467553,158666,2300231016,6 UNION ALL SELECT '2018-06-28 00:44:00.000',1991583913,158666,2300231016,6 UNION ALL SELECT '2018-07-04 04:15:00.000',2001154492,158666,2300231016,4 UNION ALL SELECT '2018-07-04 15:44:00.000',2001154814,158666,2300231016,5 UNION ALL SELECT '2018-07-04 21:30:00.000',2002057915,158666,2300231016,4 UNION ALL SELECT '2018-07-05 02:09:00.000',2002058086,158666,2300231016,4 UNION ALL SELECT '2018-07-05 04:15:00.000',2002058147,158666,2300231016,3 UNION ALL SELECT '2018-07-05 17:23:00.000',2003700706,158666,2300231016,2 UNION ALL SELECT '2018-07-05 18:07:00.000',2003700938,158666,2300231016,1 UNION ALL SELECT '2018-07-06 20:15:00.000',2005719626,158666,2300231016,7 UNION ALL SELECT '2018-07-07 07:45:00.000',2005719879,158666,2300231016,8 UNION ALL SELECT '2018-07-07 12:13:00.000',2005719931,158666,2300231016,9 UNION ALL SELECT '2018-07-09 18:29:00.000',2009395510,158666,2300231016,8 UNION ALL SELECT '2018-07-09 21:05:00.000',2009395523,158666,2300231016,6 UNION ALL SELECT '2018-07-11 21:31:00.000',2011107312,158666,2300231016,5 UNION ALL SELECT '2018-06-27 18:23:00.000',1991016381,258669,2300231016,4 UNION ALL SELECT '2018-06-27 19:07:00.000',1991181365,258669,2300231016,4 UNION ALL SELECT '2018-06-27 19:28:00.000',1991181376,258669,2300231016,3 UNION ALL SELECT '2018-06-28 01:44:00.000',1991583923,258669,2300231016,9 UNION ALL SELECT '2018-06-28 02:19:00.000',1991583943,258669,2300231016,2 UNION ALL SELECT '2018-07-03 10:15:00.000',1999231742,258669,2300231016,1 UNION ALL SELECT '2018-07-03 10:45:00.000',2000293679,258669,2300231016,1 UNION ALL SELECT '2018-07-03 14:22:00.000',2000293804,258669,2300231016,3 UNION ALL SELECT '2018-07-04 19:45:00.000',2002057785,258669,2300231016,2 UNION ALL SELECT '2018-07-04 21:00:00.000',2002057813,258669,2300231016,1 UNION ALL SELECT '2018-07-05 15:12:00.000',2002548332,258669,2300231016,7 UNION ALL SELECT '2018-07-05 17:52:00.000',2003700719,258669,2300231016,6 UNION ALL SELECT '2018-07-09 22:30:00.000',2009395530,258669,2300231016,5 UNION ALL SELECT '2018-07-09 23:23:00.000',2009395666,258669,2300231016,3 UNION ALL SELECT '2018-07-30 17:45:00.000',2043642075,158666,2300231016,2 UNION ALL SELECT '2018-07-30 23:30:00.000',2043642114,158666,2300231016,4 ;WITH NextEventLag AS( --Returns the next event information. SELECT ORDER_TIME ,ORDER_ID ,PROD_ID ,ACCOUNT_ID ,RowNum = ROW_NUMBER() OVER(PARTITION BY ACCOUNT_ID, PROD_ID ORDER BY ORDER_TIME) --NEXT_ORDER_TIME_LAG: Returns the time difference between two consecutive order times. ,NEXT_ORDER_TIME_LAG = DATEDIFF(MINUTE, LAG(ORDER_TIME, 1, ORDER_TIME) OVER(PARTITION BY ACCOUNT_ID, PROD_ID ORDER BY ORDER_TIME), ORDER_TIME) ,ORDER_QUANTITY FROM #Data ) ,RunningOrders AS( SELECT RowNum ,ORDER_TIME ,ACCOUNT_ID ,PROD_ID ,NEXT_ORDER_TIME_LAG ,LAG_LESS_THAN_24h = 0 ,ORDER_QUANTITY FROM NextEventLag WHERE RowNum = 1 UNION ALL SELECT NextEventLag.RowNum ,NextEventLag.ORDER_TIME ,NextEventLag.ACCOUNT_ID ,NextEventLag.PROD_ID ,NextEventLag.NEXT_ORDER_TIME_LAG --If the time lag between consecutive events and the time running sum is over 1440 minutes then set the value to 0. --Change the NEXT_ORDER_TIME_LAG time interval to the desired interval value in minutes. ,LAG_LESS_THAN_24h = CASE WHEN RunningOrders.LAG_LESS_THAN_24h + NextEventLag.NEXT_ORDER_TIME_LAG >= 1440 THEN 0 ELSE RunningOrders.LAG_LESS_THAN_24h + NextEventLag.NEXT_ORDER_TIME_LAG END ,NextEventLag.ORDER_QUANTITY FROM RunningOrders INNER JOIN NextEventLag ON RunningOrders.RowNum + 1 = NextEventLag.RowNum AND RunningOrders.ACCOUNT_ID = NextEventLag.ACCOUNT_ID AND RunningOrders.PROD_ID = NextEventLag.PROD_ID ) ,GroupedLags AS( --This Groups together the LAG(s) less than 1440 minutes and is used by the outer query window functions --to calculate the running aggregates. SELECT RunningOrders.* ,Running24h.* FROM RunningOrders CROSS APPLY(SELECT TOP 1 Groups = COUNT(*) OVER(ORDER BY GroupApply.LAG_LESS_THAN_24h) --Count admin units within the time window in the WHERE clause FROM RunningOrders AS GroupApply WHERE GroupApply.ACCOUNT_ID = RunningOrders.ACCOUNT_ID AND GroupApply.PROD_ID = RunningOrders.PROD_ID AND GroupApply.ORDER_TIME <= RunningOrders.ORDER_TIME --ORDER BY StageBaseApply.ORDER_TIME ) AS Running24h ) select GroupedLags.ACCOUNT_ID ,GroupedLags.PROD_ID ,GroupedLags.ORDER_TIME ,GroupedLags.NEXT_ORDER_TIME_LAG ,GroupedLags.LAG_LESS_THAN_24h ,RUNNING_COUNT_24h = ROW_NUMBER() OVER(PARTITION BY GroupedLags.ACCOUNT_ID, GroupedLags.PROD_ID, GroupedLags.Groups ORDER BY GroupedLags.ORDER_TIME) ,RUNNING_SUM_24h = SUM(ORDER_QUANTITY) OVER(PARTITION BY GroupedLags.ACCOUNT_ID, GroupedLags.PROD_ID, GroupedLags.Groups ORDER BY GroupedLags.ORDER_TIME) from GroupedLags ORDER BY GroupedLags.ACCOUNT_ID ,GroupedLags.PROD_ID ,GroupedLags.ORDER_TIME Here is the db<>fiddle demo
. It’s working fine when I pass 10-15 input but taking more than 5 minutes for 30 input. I need to make it work for 100 inputs
Here is my ask: Go through the code and understand it. As first solution, query should complete within 10 secs for 30 input It should be working with good performance for 100 input as well. My code: /************************************************** Populating the Array values in table variable **************************************************/ DECLARE #PUZZLE table( ID int IDENTITY(1,1) NOT NULL, Value int NOT NULL) /****Sample 1*****/ INSERT INTO #PUZZLE (value) --SELECT 0 UNION ALL --SELECT -22 UNION ALL --SELECT -33 UNION ALL --SELECT -44 UNION ALL --SELECT 55 UNION ALL --SELECT -100 UNION ALL --SELECT 100 UNION ALL --SELECT 10 UNION ALL --SELECT -30 UNION ALL --SELECT -60 UNION ALL --SELECT -60 UNION ALL SELECT -60 UNION ALL SELECT -10 UNION ALL SELECT 10 UNION ALL SELECT 10 UNION ALL SELECT -10 UNION ALL SELECT 0 UNION ALL SELECT -22 UNION ALL SELECT -33 UNION ALL SELECT -44 UNION ALL SELECT 55 UNION ALL SELECT -100 UNION ALL SELECT 100 UNION ALL SELECT 10 UNION ALL SELECT -30 UNION ALL SELECT -60 UNION ALL SELECT -60 UNION ALL SELECT -60 UNION ALL SELECT -10 UNION ALL SELECT 10 UNION ALL SELECT 10 /************************************************** Populating possible hierarchy/path **************************************************/ DECLARE #puzHierarchy table (parentid int, childid int,value int) INSERT #puzHierarchy (parentid,childid,value) SELECT *-- INTO #puzHierarchy FROM ( SELECT NULL AS ParentId,ID AS ChildId, Value FROM #PUZZLE WHERE ID = (SELECT MIN(ID) FROM #PUZZLE) UNION ALL SELECT B.Id,C.ID,C.Value FROM #PUZZLE B JOIN #PUZZLE C ON C.ID > B.ID AND C.ID < (B.ID + 7) ) A --SELECT * FROM #puzHierarchy order by parentid /******************************************************* Logic using recursive CTE to get the path with max value *******************************************************/ ;WITH children AS ( SELECT ParentId ,CAST(ISNULL(CAST(ParentId AS NVARCHAR) + '->' ,'') + CAST(ChildId AS NVARCHAR) AS NVARCHAR(Max)) AS Path ,value As PathValue FROM #puzHierarchy WHERE ChildId = (SELECT MAX(ChildId) FROM #puzHierarchy) UNION ALL SELECT t.ParentId ,list= CAST(ISNULL(CAST(t.ParentId AS NVARCHAR) + '->' ,'') + d.Path AS NVARCHAR(Max)) ,(t.value+d.PathValue) As PathValue FROM #puzHierarchy t INNER JOIN children AS d ON t.ChildId = d.ParentId ) SELECT [Path],PathValue FROM children c WHERE ParentId IS NULL AND c.PathValue = (SELECT max(PathValue) FROM children WHERE ParentId IS NULL)
A. Your code goes through too many cycles/data unrelated to result you want. B. After running your sample data, the results are not accurate. Parentid Path PathValue NULL 1->3->4->6->10->12->13->19->20 145 NULL 1->3->4->10->12->13->19->20 145 The first result is wrong. Basically you just want starting from ParentId IS NULL and ChildId = 1, among ParentId = 1 finding which ChildId has the MAX value, this ChildId becomes ParentID to find next MAX value, and so on. ;WITH cte_base AS (SELECT Parentid , Childid , Value , ROW_NUMBER() OVER(PARTITION BY Parentid ORDER BY Value DESC) AS Rownum FROM #puzHierarchy ), cte_re AS (SELECT ParentId , Childid , CAST(CAST(ChildId AS NVARCHAR) AS NVARCHAR(Max)) AS Path , Value As PathValue , Rownum FROM cte_base WHERE Parentid IS NULL UNION ALL SELECT b.parentid, b.childid , CAST(Path + '->' + ISNULL(CAST(b.parentid AS NVARCHAR) ,'') AS NVARCHAR(Max)) ,(b.value + r.PathValue) As PathValue , b.Rownum FROM cte_base AS b INNER JOIN cte_re AS r ON b.Parentid = r.childid where b.Rownum = 1 ) SELECT * FROM cte_re (I changed your sample table variable to a temporary table.)
Recursive CTE generating extra record
I have following Recursive SQL query to generate all dates for any month provided in a date. It always generate extra date(1st of next month). DECLARE #currentDate DATE = '2016-5-25'; WITH cte AS( SELECT DATEADD(DAY, -DATEPART(DAY, #currentDate) + 1, #currentDate) AS firstDay UNION ALL SELECT DATEADD(DAY, 1, firstDay) FROM cte WHERE DATEPART(MONTH, firstDay) = DATEPART(MONTH, #currentDate) ) SELECT * FROM cte What am I doing wrong?
In Recursive CTE, the recursive query will be first executed then the Where clause is validated Here is the correct way DECLARE #currentDate DATE = '2016-5-25'; ;WITH cte AS( SELECT dateadd(dd,1,eomonth(#currentDate,-1)) AS fd UNION ALL SELECT DATEADD(DAY, 1, fd) FROM cte WHERE fd < eomonth(#currentDate) ) SELECT * FROM cte Instead you can use tally table to do this which is way better in performance when compared to Recursive CTE ;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 ), -- 10 e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b), -- 10*10 e3(n) AS (SELECT 1 FROM e1 CROSS JOIN e2), -- 10*100 tally as (SELECT id = ROW_NUMBER() OVER (ORDER BY n) FROM e3 ) select dateadd(DD,ID-1,dateadd(dd,1,eomonth(#currentDate,-1))) as Dates from Tally where dateadd(DD,ID-1,dateadd(dd,1,eomonth(#currentDate,-1)))<=eomonth(#currentDate)
Getting quantity between a range of months from 2 date parameters
I have a table that stores budget quantities for a company whose fiscal year begins 1st April and ends on 31st March the next year. I have this query to extract figures for a particular month. SELECT SUM(T1.U_Quantity) AS 'YTDBOwnMadeTea' FROM [SL_NTEL_DB_LIVE].[dbo].[#U_BUDG_MADETEA] T0 INNER JOIN [SL_NTEL_DB_LIVE].[dbo].[#U_BUDG_MADETEA_ROW] T1 ON T0.DocEntry = T1.DocEntry WHERE T1.U_Month = DATENAME(MONTH, '2015-04-01') AND T0.U_Source = 'NTEL' There is an existing report that takes two parameters, a Start and End Date. (type datetime) Table below: The month column is of type nvarchar. How do I modify the query such when a user enters StartDate and EndDate e.g. 1st May 2015 and 31st July 2015, I will get a quantity result of 12640.
You can use couple of ways to do this. One way would be to use PARSE. Like this. SELECT SUM(T1.U_Quantity) AS 'YTDBOwnMadeTea' FROM [SL_NTEL_DB_LIVE].[dbo].[#U_BUDG_MADETEA] T0 INNER JOIN [SL_NTEL_DB_LIVE].[dbo].[#U_BUDG_MADETEA_ROW] T1 ON T0.DocEntry = T1.DocEntry WHERE PARSE((T1.U_Month + CONVERT(VARCHAR(4),YEAR(CURRENT_TIMESTAMP))) as datetime) BETWEEN #StartDate AND #EndDate AND T0.U_Source = 'NTEL' Another way would be to use a numbers table to map your month name to a month number and use it in your query. ;WITH CTE AS ( SELECT 1 as rn 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 UNION ALL SELECT 1 UNION ALL SELECT 1 ), MonthMap AS ( SELECT ROW_NUMBER()OVER(ORDER BY rn ASC) as monthnumber FROM CTE ) SELECT monthnumber,DATENAME(MONTH,DATEFROMPARTS(2016,monthnumber,1)) FROM MonthMap; and then join it with your month table like this. ;WITH CTE AS ( SELECT 1 as rn 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 UNION ALL SELECT 1 UNION ALL SELECT 1 ), MonthMap AS ( SELECT ROW_NUMBER()OVER(ORDER BY rn ASC) as monthnumber FROM CTE ) SELECT SUM(T1.U_Quantity) AS 'YTDBOwnMadeTea' FROM [SL_NTEL_DB_LIVE].[dbo].[#U_BUDG_MADETEA] T0 INNER JOIN [SL_NTEL_DB_LIVE].[dbo].[#U_BUDG_MADETEA_ROW] T1 ON T0.DocEntry = T1.DocEntry INNER JOIN MonthMap M ON T1.U_Month = DATENAME(MONTH,DATEFROMPARTS(2016,monthnumber,1)) WHERE M.monthnumber BETWEEN DATEPART(MONTH,#StartDate) AND DATEPART(MONTH,#EndDate) AND T0.U_Source = 'NTEL'; You should compare both the approaches for performance. PARSE is simpler to use but would be difficult to index properly. On a Separate note, you should avoid storing dates or date parts as month names as these take up more storage(even more since you are using NVARCHAR), and are difficult to use efficiently.