SQL Server - Iterate, aggregate, and insert

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
3
down vote

favorite












I'm trying to find an efficient way to aggregate data for reporting. Let's say I need to aggregate the following data in 5-second intervals:



CREATE TABLE RawData
(
Result FLOAT,
CaptureTime DATETIME
);
INSERT INTO RawData VALUES
(2.3, '2018-04-01 00:00:00'),
(2.5, '2018-04-01 00:00:01'),
(2.8, '2018-04-01 00:00:02'),
(2.8, '2018-04-01 00:00:03'),
(3.4, '2018-04-01 00:00:04'),
(5.1, '2018-04-01 00:00:05'),
(2.2, '2018-04-01 00:00:06'),
(4.1, '2018-04-01 00:00:07'),
(4.3, '2018-04-01 00:00:08'),
(5.9, '2018-04-01 00:00:09'),
...


The aggregated data will be stored in another table:



CREATE TABLE AggregateData
(
Result FLOAT,
StartCaptureTime DATETIME,
EndCaptureTime DATETIME
);


As an example: if my aggregate function is SUM, the data in AggregateData would be:



+--------+----------------------+---------------------+
| Result | StartCaptureTime | EndCaptureTime |
+--------+----------------------+---------------------+
| 13.8 | 2018-04-01 00:00:00 | 2018-04-01 00:00:05 |
| 21.6 | 2018-04-01 00:00:05 | 2018-04-01 00:00:10 |
+--------+----------------------+---------------------+


The best solution that I've come up with uses a loop:



CREATE PROCEDURE [dbo].[spPerformAggregateCalculation]
@UpdateIntervalSeconds INT,
@StartTime DATETIME,
@EndTime DATETIME
AS
BEGIN
SET NOCOUNT ON;

DECLARE @RunningStartTime DATETIME = @StartTime;
DECLARE @RunningEndTime DATETIME = DATEADD(SECOND, @UpdateIntervalSeconds, @RunningStartTime);
DECLARE @AggregateValue FLOAT;

WHILE @RunningStartTime < @EndTime
BEGIN
SELECT @AggregateValue =
(
SELECT SUM(Result)
FROM RawData
WHERE CaptureTime >= @RunningStartTime AND CaptureTime < @RunningEndTime
);

INSERT INTO AggregateData VALUES
(@AggregateValue, @RunningStartTime, @RunningEndTime);

SELECT
@RunningStartTime = @RunningEndTime,
@RunningEndTime = DATEADD(SECOND, @UpdateIntervalSeconds, @RunningEndTime);
END
END

EXEC spPerformAggregateCalculation
@UpdateIntervalSeconds = 5,
@StartTime = '2018-04-01 00:00:00',
@EndTime = '2018-04-01 00:00:10'


Is there a more efficient way to get this data? I'll be performing more complex aggregations on millions of rows on a daily basis, so every bit helps.



P.S. In case "increase your interval" is offered as a suggestion, my data will actually be aggregated in much larger intervals than 5 seconds; I just whittled the problem down for the example.




Update



Below was my final solution, based dnoeth's answer:



CREATE PROCEDURE [dbo].[spPerformAggregateCalculation]
@UpdateIntervalSeconds INT,
@StartTime DATETIME,
@EndTime DATETIME
AS
BEGIN
SET NOCOUNT ON;

WITH cte AS
(
SELECT
-- Adjust each CaptureTime to the beginning of the interval.
DATEADD(SECOND, -DATEDIFF(SECOND, @StartTime , CaptureTime) % @UpdateIntervalSeconds, CaptureTime) as StartCaptureTime,
Result
FROM RawData
WHERE CaptureTime >= @StartTime
)
INSERT INTO AggregateData
SELECT
SUM(Result),
StartCaptureTime,
DATEADD(SECOND, @UpdateIntervalSeconds, StartCaptureTime)
FROM cte
GROUP BY StartCaptureTime
ORDER BY StartCaptureTime;
END






share|improve this question





















  • Is it always 5 rows. Can time skip or duplicate?
    – paparazzo
    Apr 30 at 10:48










  • Good question. It is always 5 rows and there are no skips or duplicates.
    – Jake Reece
    Apr 30 at 11:57










  • Then there is a very simple solution but you don't to be enthused with my answer.
    – paparazzo
    Apr 30 at 12:01










  • A simple solution, in addition to the one you already answered, or are you referring to your answer below? I haven't had time to test it yet.
    – Jake Reece
    Apr 30 at 12:56










  • Should the intervals always start at 00 or at the minimum CaptureTime? And what are those much larger intervals than 5 seconds?
    – dnoeth
    Apr 30 at 12:59

















up vote
3
down vote

favorite












I'm trying to find an efficient way to aggregate data for reporting. Let's say I need to aggregate the following data in 5-second intervals:



CREATE TABLE RawData
(
Result FLOAT,
CaptureTime DATETIME
);
INSERT INTO RawData VALUES
(2.3, '2018-04-01 00:00:00'),
(2.5, '2018-04-01 00:00:01'),
(2.8, '2018-04-01 00:00:02'),
(2.8, '2018-04-01 00:00:03'),
(3.4, '2018-04-01 00:00:04'),
(5.1, '2018-04-01 00:00:05'),
(2.2, '2018-04-01 00:00:06'),
(4.1, '2018-04-01 00:00:07'),
(4.3, '2018-04-01 00:00:08'),
(5.9, '2018-04-01 00:00:09'),
...


The aggregated data will be stored in another table:



CREATE TABLE AggregateData
(
Result FLOAT,
StartCaptureTime DATETIME,
EndCaptureTime DATETIME
);


As an example: if my aggregate function is SUM, the data in AggregateData would be:



+--------+----------------------+---------------------+
| Result | StartCaptureTime | EndCaptureTime |
+--------+----------------------+---------------------+
| 13.8 | 2018-04-01 00:00:00 | 2018-04-01 00:00:05 |
| 21.6 | 2018-04-01 00:00:05 | 2018-04-01 00:00:10 |
+--------+----------------------+---------------------+


The best solution that I've come up with uses a loop:



CREATE PROCEDURE [dbo].[spPerformAggregateCalculation]
@UpdateIntervalSeconds INT,
@StartTime DATETIME,
@EndTime DATETIME
AS
BEGIN
SET NOCOUNT ON;

DECLARE @RunningStartTime DATETIME = @StartTime;
DECLARE @RunningEndTime DATETIME = DATEADD(SECOND, @UpdateIntervalSeconds, @RunningStartTime);
DECLARE @AggregateValue FLOAT;

WHILE @RunningStartTime < @EndTime
BEGIN
SELECT @AggregateValue =
(
SELECT SUM(Result)
FROM RawData
WHERE CaptureTime >= @RunningStartTime AND CaptureTime < @RunningEndTime
);

INSERT INTO AggregateData VALUES
(@AggregateValue, @RunningStartTime, @RunningEndTime);

SELECT
@RunningStartTime = @RunningEndTime,
@RunningEndTime = DATEADD(SECOND, @UpdateIntervalSeconds, @RunningEndTime);
END
END

EXEC spPerformAggregateCalculation
@UpdateIntervalSeconds = 5,
@StartTime = '2018-04-01 00:00:00',
@EndTime = '2018-04-01 00:00:10'


Is there a more efficient way to get this data? I'll be performing more complex aggregations on millions of rows on a daily basis, so every bit helps.



P.S. In case "increase your interval" is offered as a suggestion, my data will actually be aggregated in much larger intervals than 5 seconds; I just whittled the problem down for the example.




Update



Below was my final solution, based dnoeth's answer:



CREATE PROCEDURE [dbo].[spPerformAggregateCalculation]
@UpdateIntervalSeconds INT,
@StartTime DATETIME,
@EndTime DATETIME
AS
BEGIN
SET NOCOUNT ON;

WITH cte AS
(
SELECT
-- Adjust each CaptureTime to the beginning of the interval.
DATEADD(SECOND, -DATEDIFF(SECOND, @StartTime , CaptureTime) % @UpdateIntervalSeconds, CaptureTime) as StartCaptureTime,
Result
FROM RawData
WHERE CaptureTime >= @StartTime
)
INSERT INTO AggregateData
SELECT
SUM(Result),
StartCaptureTime,
DATEADD(SECOND, @UpdateIntervalSeconds, StartCaptureTime)
FROM cte
GROUP BY StartCaptureTime
ORDER BY StartCaptureTime;
END






share|improve this question





















  • Is it always 5 rows. Can time skip or duplicate?
    – paparazzo
    Apr 30 at 10:48










  • Good question. It is always 5 rows and there are no skips or duplicates.
    – Jake Reece
    Apr 30 at 11:57










  • Then there is a very simple solution but you don't to be enthused with my answer.
    – paparazzo
    Apr 30 at 12:01










  • A simple solution, in addition to the one you already answered, or are you referring to your answer below? I haven't had time to test it yet.
    – Jake Reece
    Apr 30 at 12:56










  • Should the intervals always start at 00 or at the minimum CaptureTime? And what are those much larger intervals than 5 seconds?
    – dnoeth
    Apr 30 at 12:59













up vote
3
down vote

favorite









up vote
3
down vote

favorite











I'm trying to find an efficient way to aggregate data for reporting. Let's say I need to aggregate the following data in 5-second intervals:



CREATE TABLE RawData
(
Result FLOAT,
CaptureTime DATETIME
);
INSERT INTO RawData VALUES
(2.3, '2018-04-01 00:00:00'),
(2.5, '2018-04-01 00:00:01'),
(2.8, '2018-04-01 00:00:02'),
(2.8, '2018-04-01 00:00:03'),
(3.4, '2018-04-01 00:00:04'),
(5.1, '2018-04-01 00:00:05'),
(2.2, '2018-04-01 00:00:06'),
(4.1, '2018-04-01 00:00:07'),
(4.3, '2018-04-01 00:00:08'),
(5.9, '2018-04-01 00:00:09'),
...


The aggregated data will be stored in another table:



CREATE TABLE AggregateData
(
Result FLOAT,
StartCaptureTime DATETIME,
EndCaptureTime DATETIME
);


As an example: if my aggregate function is SUM, the data in AggregateData would be:



+--------+----------------------+---------------------+
| Result | StartCaptureTime | EndCaptureTime |
+--------+----------------------+---------------------+
| 13.8 | 2018-04-01 00:00:00 | 2018-04-01 00:00:05 |
| 21.6 | 2018-04-01 00:00:05 | 2018-04-01 00:00:10 |
+--------+----------------------+---------------------+


The best solution that I've come up with uses a loop:



CREATE PROCEDURE [dbo].[spPerformAggregateCalculation]
@UpdateIntervalSeconds INT,
@StartTime DATETIME,
@EndTime DATETIME
AS
BEGIN
SET NOCOUNT ON;

DECLARE @RunningStartTime DATETIME = @StartTime;
DECLARE @RunningEndTime DATETIME = DATEADD(SECOND, @UpdateIntervalSeconds, @RunningStartTime);
DECLARE @AggregateValue FLOAT;

WHILE @RunningStartTime < @EndTime
BEGIN
SELECT @AggregateValue =
(
SELECT SUM(Result)
FROM RawData
WHERE CaptureTime >= @RunningStartTime AND CaptureTime < @RunningEndTime
);

INSERT INTO AggregateData VALUES
(@AggregateValue, @RunningStartTime, @RunningEndTime);

SELECT
@RunningStartTime = @RunningEndTime,
@RunningEndTime = DATEADD(SECOND, @UpdateIntervalSeconds, @RunningEndTime);
END
END

EXEC spPerformAggregateCalculation
@UpdateIntervalSeconds = 5,
@StartTime = '2018-04-01 00:00:00',
@EndTime = '2018-04-01 00:00:10'


Is there a more efficient way to get this data? I'll be performing more complex aggregations on millions of rows on a daily basis, so every bit helps.



P.S. In case "increase your interval" is offered as a suggestion, my data will actually be aggregated in much larger intervals than 5 seconds; I just whittled the problem down for the example.




Update



Below was my final solution, based dnoeth's answer:



CREATE PROCEDURE [dbo].[spPerformAggregateCalculation]
@UpdateIntervalSeconds INT,
@StartTime DATETIME,
@EndTime DATETIME
AS
BEGIN
SET NOCOUNT ON;

WITH cte AS
(
SELECT
-- Adjust each CaptureTime to the beginning of the interval.
DATEADD(SECOND, -DATEDIFF(SECOND, @StartTime , CaptureTime) % @UpdateIntervalSeconds, CaptureTime) as StartCaptureTime,
Result
FROM RawData
WHERE CaptureTime >= @StartTime
)
INSERT INTO AggregateData
SELECT
SUM(Result),
StartCaptureTime,
DATEADD(SECOND, @UpdateIntervalSeconds, StartCaptureTime)
FROM cte
GROUP BY StartCaptureTime
ORDER BY StartCaptureTime;
END






share|improve this question













I'm trying to find an efficient way to aggregate data for reporting. Let's say I need to aggregate the following data in 5-second intervals:



CREATE TABLE RawData
(
Result FLOAT,
CaptureTime DATETIME
);
INSERT INTO RawData VALUES
(2.3, '2018-04-01 00:00:00'),
(2.5, '2018-04-01 00:00:01'),
(2.8, '2018-04-01 00:00:02'),
(2.8, '2018-04-01 00:00:03'),
(3.4, '2018-04-01 00:00:04'),
(5.1, '2018-04-01 00:00:05'),
(2.2, '2018-04-01 00:00:06'),
(4.1, '2018-04-01 00:00:07'),
(4.3, '2018-04-01 00:00:08'),
(5.9, '2018-04-01 00:00:09'),
...


The aggregated data will be stored in another table:



CREATE TABLE AggregateData
(
Result FLOAT,
StartCaptureTime DATETIME,
EndCaptureTime DATETIME
);


As an example: if my aggregate function is SUM, the data in AggregateData would be:



+--------+----------------------+---------------------+
| Result | StartCaptureTime | EndCaptureTime |
+--------+----------------------+---------------------+
| 13.8 | 2018-04-01 00:00:00 | 2018-04-01 00:00:05 |
| 21.6 | 2018-04-01 00:00:05 | 2018-04-01 00:00:10 |
+--------+----------------------+---------------------+


The best solution that I've come up with uses a loop:



CREATE PROCEDURE [dbo].[spPerformAggregateCalculation]
@UpdateIntervalSeconds INT,
@StartTime DATETIME,
@EndTime DATETIME
AS
BEGIN
SET NOCOUNT ON;

DECLARE @RunningStartTime DATETIME = @StartTime;
DECLARE @RunningEndTime DATETIME = DATEADD(SECOND, @UpdateIntervalSeconds, @RunningStartTime);
DECLARE @AggregateValue FLOAT;

WHILE @RunningStartTime < @EndTime
BEGIN
SELECT @AggregateValue =
(
SELECT SUM(Result)
FROM RawData
WHERE CaptureTime >= @RunningStartTime AND CaptureTime < @RunningEndTime
);

INSERT INTO AggregateData VALUES
(@AggregateValue, @RunningStartTime, @RunningEndTime);

SELECT
@RunningStartTime = @RunningEndTime,
@RunningEndTime = DATEADD(SECOND, @UpdateIntervalSeconds, @RunningEndTime);
END
END

EXEC spPerformAggregateCalculation
@UpdateIntervalSeconds = 5,
@StartTime = '2018-04-01 00:00:00',
@EndTime = '2018-04-01 00:00:10'


Is there a more efficient way to get this data? I'll be performing more complex aggregations on millions of rows on a daily basis, so every bit helps.



P.S. In case "increase your interval" is offered as a suggestion, my data will actually be aggregated in much larger intervals than 5 seconds; I just whittled the problem down for the example.




Update



Below was my final solution, based dnoeth's answer:



CREATE PROCEDURE [dbo].[spPerformAggregateCalculation]
@UpdateIntervalSeconds INT,
@StartTime DATETIME,
@EndTime DATETIME
AS
BEGIN
SET NOCOUNT ON;

WITH cte AS
(
SELECT
-- Adjust each CaptureTime to the beginning of the interval.
DATEADD(SECOND, -DATEDIFF(SECOND, @StartTime , CaptureTime) % @UpdateIntervalSeconds, CaptureTime) as StartCaptureTime,
Result
FROM RawData
WHERE CaptureTime >= @StartTime
)
INSERT INTO AggregateData
SELECT
SUM(Result),
StartCaptureTime,
DATEADD(SECOND, @UpdateIntervalSeconds, StartCaptureTime)
FROM cte
GROUP BY StartCaptureTime
ORDER BY StartCaptureTime;
END








share|improve this question












share|improve this question




share|improve this question








edited Apr 30 at 22:39
























asked Apr 28 at 14:26









Jake Reece

1188




1188











  • Is it always 5 rows. Can time skip or duplicate?
    – paparazzo
    Apr 30 at 10:48










  • Good question. It is always 5 rows and there are no skips or duplicates.
    – Jake Reece
    Apr 30 at 11:57










  • Then there is a very simple solution but you don't to be enthused with my answer.
    – paparazzo
    Apr 30 at 12:01










  • A simple solution, in addition to the one you already answered, or are you referring to your answer below? I haven't had time to test it yet.
    – Jake Reece
    Apr 30 at 12:56










  • Should the intervals always start at 00 or at the minimum CaptureTime? And what are those much larger intervals than 5 seconds?
    – dnoeth
    Apr 30 at 12:59

















  • Is it always 5 rows. Can time skip or duplicate?
    – paparazzo
    Apr 30 at 10:48










  • Good question. It is always 5 rows and there are no skips or duplicates.
    – Jake Reece
    Apr 30 at 11:57










  • Then there is a very simple solution but you don't to be enthused with my answer.
    – paparazzo
    Apr 30 at 12:01










  • A simple solution, in addition to the one you already answered, or are you referring to your answer below? I haven't had time to test it yet.
    – Jake Reece
    Apr 30 at 12:56










  • Should the intervals always start at 00 or at the minimum CaptureTime? And what are those much larger intervals than 5 seconds?
    – dnoeth
    Apr 30 at 12:59
















Is it always 5 rows. Can time skip or duplicate?
– paparazzo
Apr 30 at 10:48




Is it always 5 rows. Can time skip or duplicate?
– paparazzo
Apr 30 at 10:48












Good question. It is always 5 rows and there are no skips or duplicates.
– Jake Reece
Apr 30 at 11:57




Good question. It is always 5 rows and there are no skips or duplicates.
– Jake Reece
Apr 30 at 11:57












Then there is a very simple solution but you don't to be enthused with my answer.
– paparazzo
Apr 30 at 12:01




Then there is a very simple solution but you don't to be enthused with my answer.
– paparazzo
Apr 30 at 12:01












A simple solution, in addition to the one you already answered, or are you referring to your answer below? I haven't had time to test it yet.
– Jake Reece
Apr 30 at 12:56




A simple solution, in addition to the one you already answered, or are you referring to your answer below? I haven't had time to test it yet.
– Jake Reece
Apr 30 at 12:56












Should the intervals always start at 00 or at the minimum CaptureTime? And what are those much larger intervals than 5 seconds?
– dnoeth
Apr 30 at 12:59





Should the intervals always start at 00 or at the minimum CaptureTime? And what are those much larger intervals than 5 seconds?
– dnoeth
Apr 30 at 12:59











2 Answers
2






active

oldest

votes

















up vote
3
down vote



accepted










There's no need for a cursor or recursion, both are not really performant for larger amounts of data.



Simply adjust the CaptureTime to the start of each interval.



declare @UpdateIntervalSeconds integer = 5;
declare @minTime datetime = (select min(CaptureTime) from RawData);
-- if you want to start at exactly `00` instead of `01`,`02` etc.
-- you can modify `@minTime` using another DATEADD/DATEDIFF

with cte as
( select
-- adjust each CaptureTime to the begin of an interval
DATEADD(SECOND, -DATEDIFF(SECOND, @minTime , CaptureTime) % @UpdateIntervalSeconds, CaptureTime) as StartCaptureTime
,Result
from RawData
)
select StartCaptureTime
,DATEADD(SECOND, @UpdateIntervalSeconds, StartCaptureTime) AS EndCaptureTime
,sum(Result)
from cte
group by StartCaptureTime
order by StartCaptureTime





share|improve this answer





















  • Very clever! I just had to add a filter to the CTE to make it work with a passed-in Start Time instead processing the entire table at once. Thanks for your help!
    – Jake Reece
    Apr 30 at 22:33

















up vote
1
down vote













I don't see any problems with the cursor.



I think a recursive cte is more efficient and cleaner here.



INSERT INTO @RawData VALUES
(2.3, '2018-04-01 00:00:00'),
(2.5, '2018-04-01 00:00:01'),
(2.8, '2018-04-01 00:00:02'),
(2.8, '2018-04-01 00:00:03'),
(3.4, '2018-04-01 00:00:04'),
(5.1, '2018-04-01 00:00:05'),
(2.2, '2018-04-01 00:00:06'),
(4.1, '2018-04-01 00:00:07'),
(4.3, '2018-04-01 00:00:08'),
(5.9, '2018-04-01 00:00:09');
declare @start datetime = (select min(CaptureTime) from @RawData);
declare @end datetime = (select max(CaptureTime) from @RawData);
with cte as
( select @start st, DATEADD(ss, 4, @start) as ed
union all
select DATEADD(ss, 5, st), DATEADD(ss, 5, ed)
from cte
where ed < @end
)
select sum(rd.Result), cte.st, cte.ed
from @RawData rd
join cte
on rd.CaptureTime between cte.st and cte.ed
group by cte.st, cte.ed;





share|improve this answer





















    Your Answer




    StackExchange.ifUsing("editor", function ()
    return StackExchange.using("mathjaxEditing", function ()
    StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
    StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
    );
    );
    , "mathjax-editing");

    StackExchange.ifUsing("editor", function ()
    StackExchange.using("externalEditor", function ()
    StackExchange.using("snippets", function ()
    StackExchange.snippets.init();
    );
    );
    , "code-snippets");

    StackExchange.ready(function()
    var channelOptions =
    tags: "".split(" "),
    id: "196"
    ;
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function()
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled)
    StackExchange.using("snippets", function()
    createEditor();
    );

    else
    createEditor();

    );

    function createEditor()
    StackExchange.prepareEditor(
    heartbeatType: 'answer',
    convertImagesToLinks: false,
    noModals: false,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: null,
    bindNavPrevention: true,
    postfix: "",
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    );



    );








     

    draft saved


    draft discarded


















    StackExchange.ready(
    function ()
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f193149%2fsql-server-iterate-aggregate-and-insert%23new-answer', 'question_page');

    );

    Post as a guest






























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    3
    down vote



    accepted










    There's no need for a cursor or recursion, both are not really performant for larger amounts of data.



    Simply adjust the CaptureTime to the start of each interval.



    declare @UpdateIntervalSeconds integer = 5;
    declare @minTime datetime = (select min(CaptureTime) from RawData);
    -- if you want to start at exactly `00` instead of `01`,`02` etc.
    -- you can modify `@minTime` using another DATEADD/DATEDIFF

    with cte as
    ( select
    -- adjust each CaptureTime to the begin of an interval
    DATEADD(SECOND, -DATEDIFF(SECOND, @minTime , CaptureTime) % @UpdateIntervalSeconds, CaptureTime) as StartCaptureTime
    ,Result
    from RawData
    )
    select StartCaptureTime
    ,DATEADD(SECOND, @UpdateIntervalSeconds, StartCaptureTime) AS EndCaptureTime
    ,sum(Result)
    from cte
    group by StartCaptureTime
    order by StartCaptureTime





    share|improve this answer





















    • Very clever! I just had to add a filter to the CTE to make it work with a passed-in Start Time instead processing the entire table at once. Thanks for your help!
      – Jake Reece
      Apr 30 at 22:33














    up vote
    3
    down vote



    accepted










    There's no need for a cursor or recursion, both are not really performant for larger amounts of data.



    Simply adjust the CaptureTime to the start of each interval.



    declare @UpdateIntervalSeconds integer = 5;
    declare @minTime datetime = (select min(CaptureTime) from RawData);
    -- if you want to start at exactly `00` instead of `01`,`02` etc.
    -- you can modify `@minTime` using another DATEADD/DATEDIFF

    with cte as
    ( select
    -- adjust each CaptureTime to the begin of an interval
    DATEADD(SECOND, -DATEDIFF(SECOND, @minTime , CaptureTime) % @UpdateIntervalSeconds, CaptureTime) as StartCaptureTime
    ,Result
    from RawData
    )
    select StartCaptureTime
    ,DATEADD(SECOND, @UpdateIntervalSeconds, StartCaptureTime) AS EndCaptureTime
    ,sum(Result)
    from cte
    group by StartCaptureTime
    order by StartCaptureTime





    share|improve this answer





















    • Very clever! I just had to add a filter to the CTE to make it work with a passed-in Start Time instead processing the entire table at once. Thanks for your help!
      – Jake Reece
      Apr 30 at 22:33












    up vote
    3
    down vote



    accepted







    up vote
    3
    down vote



    accepted






    There's no need for a cursor or recursion, both are not really performant for larger amounts of data.



    Simply adjust the CaptureTime to the start of each interval.



    declare @UpdateIntervalSeconds integer = 5;
    declare @minTime datetime = (select min(CaptureTime) from RawData);
    -- if you want to start at exactly `00` instead of `01`,`02` etc.
    -- you can modify `@minTime` using another DATEADD/DATEDIFF

    with cte as
    ( select
    -- adjust each CaptureTime to the begin of an interval
    DATEADD(SECOND, -DATEDIFF(SECOND, @minTime , CaptureTime) % @UpdateIntervalSeconds, CaptureTime) as StartCaptureTime
    ,Result
    from RawData
    )
    select StartCaptureTime
    ,DATEADD(SECOND, @UpdateIntervalSeconds, StartCaptureTime) AS EndCaptureTime
    ,sum(Result)
    from cte
    group by StartCaptureTime
    order by StartCaptureTime





    share|improve this answer













    There's no need for a cursor or recursion, both are not really performant for larger amounts of data.



    Simply adjust the CaptureTime to the start of each interval.



    declare @UpdateIntervalSeconds integer = 5;
    declare @minTime datetime = (select min(CaptureTime) from RawData);
    -- if you want to start at exactly `00` instead of `01`,`02` etc.
    -- you can modify `@minTime` using another DATEADD/DATEDIFF

    with cte as
    ( select
    -- adjust each CaptureTime to the begin of an interval
    DATEADD(SECOND, -DATEDIFF(SECOND, @minTime , CaptureTime) % @UpdateIntervalSeconds, CaptureTime) as StartCaptureTime
    ,Result
    from RawData
    )
    select StartCaptureTime
    ,DATEADD(SECOND, @UpdateIntervalSeconds, StartCaptureTime) AS EndCaptureTime
    ,sum(Result)
    from cte
    group by StartCaptureTime
    order by StartCaptureTime






    share|improve this answer













    share|improve this answer



    share|improve this answer











    answered Apr 30 at 13:31









    dnoeth

    1,14235




    1,14235











    • Very clever! I just had to add a filter to the CTE to make it work with a passed-in Start Time instead processing the entire table at once. Thanks for your help!
      – Jake Reece
      Apr 30 at 22:33
















    • Very clever! I just had to add a filter to the CTE to make it work with a passed-in Start Time instead processing the entire table at once. Thanks for your help!
      – Jake Reece
      Apr 30 at 22:33















    Very clever! I just had to add a filter to the CTE to make it work with a passed-in Start Time instead processing the entire table at once. Thanks for your help!
    – Jake Reece
    Apr 30 at 22:33




    Very clever! I just had to add a filter to the CTE to make it work with a passed-in Start Time instead processing the entire table at once. Thanks for your help!
    – Jake Reece
    Apr 30 at 22:33












    up vote
    1
    down vote













    I don't see any problems with the cursor.



    I think a recursive cte is more efficient and cleaner here.



    INSERT INTO @RawData VALUES
    (2.3, '2018-04-01 00:00:00'),
    (2.5, '2018-04-01 00:00:01'),
    (2.8, '2018-04-01 00:00:02'),
    (2.8, '2018-04-01 00:00:03'),
    (3.4, '2018-04-01 00:00:04'),
    (5.1, '2018-04-01 00:00:05'),
    (2.2, '2018-04-01 00:00:06'),
    (4.1, '2018-04-01 00:00:07'),
    (4.3, '2018-04-01 00:00:08'),
    (5.9, '2018-04-01 00:00:09');
    declare @start datetime = (select min(CaptureTime) from @RawData);
    declare @end datetime = (select max(CaptureTime) from @RawData);
    with cte as
    ( select @start st, DATEADD(ss, 4, @start) as ed
    union all
    select DATEADD(ss, 5, st), DATEADD(ss, 5, ed)
    from cte
    where ed < @end
    )
    select sum(rd.Result), cte.st, cte.ed
    from @RawData rd
    join cte
    on rd.CaptureTime between cte.st and cte.ed
    group by cte.st, cte.ed;





    share|improve this answer

























      up vote
      1
      down vote













      I don't see any problems with the cursor.



      I think a recursive cte is more efficient and cleaner here.



      INSERT INTO @RawData VALUES
      (2.3, '2018-04-01 00:00:00'),
      (2.5, '2018-04-01 00:00:01'),
      (2.8, '2018-04-01 00:00:02'),
      (2.8, '2018-04-01 00:00:03'),
      (3.4, '2018-04-01 00:00:04'),
      (5.1, '2018-04-01 00:00:05'),
      (2.2, '2018-04-01 00:00:06'),
      (4.1, '2018-04-01 00:00:07'),
      (4.3, '2018-04-01 00:00:08'),
      (5.9, '2018-04-01 00:00:09');
      declare @start datetime = (select min(CaptureTime) from @RawData);
      declare @end datetime = (select max(CaptureTime) from @RawData);
      with cte as
      ( select @start st, DATEADD(ss, 4, @start) as ed
      union all
      select DATEADD(ss, 5, st), DATEADD(ss, 5, ed)
      from cte
      where ed < @end
      )
      select sum(rd.Result), cte.st, cte.ed
      from @RawData rd
      join cte
      on rd.CaptureTime between cte.st and cte.ed
      group by cte.st, cte.ed;





      share|improve this answer























        up vote
        1
        down vote










        up vote
        1
        down vote









        I don't see any problems with the cursor.



        I think a recursive cte is more efficient and cleaner here.



        INSERT INTO @RawData VALUES
        (2.3, '2018-04-01 00:00:00'),
        (2.5, '2018-04-01 00:00:01'),
        (2.8, '2018-04-01 00:00:02'),
        (2.8, '2018-04-01 00:00:03'),
        (3.4, '2018-04-01 00:00:04'),
        (5.1, '2018-04-01 00:00:05'),
        (2.2, '2018-04-01 00:00:06'),
        (4.1, '2018-04-01 00:00:07'),
        (4.3, '2018-04-01 00:00:08'),
        (5.9, '2018-04-01 00:00:09');
        declare @start datetime = (select min(CaptureTime) from @RawData);
        declare @end datetime = (select max(CaptureTime) from @RawData);
        with cte as
        ( select @start st, DATEADD(ss, 4, @start) as ed
        union all
        select DATEADD(ss, 5, st), DATEADD(ss, 5, ed)
        from cte
        where ed < @end
        )
        select sum(rd.Result), cte.st, cte.ed
        from @RawData rd
        join cte
        on rd.CaptureTime between cte.st and cte.ed
        group by cte.st, cte.ed;





        share|improve this answer













        I don't see any problems with the cursor.



        I think a recursive cte is more efficient and cleaner here.



        INSERT INTO @RawData VALUES
        (2.3, '2018-04-01 00:00:00'),
        (2.5, '2018-04-01 00:00:01'),
        (2.8, '2018-04-01 00:00:02'),
        (2.8, '2018-04-01 00:00:03'),
        (3.4, '2018-04-01 00:00:04'),
        (5.1, '2018-04-01 00:00:05'),
        (2.2, '2018-04-01 00:00:06'),
        (4.1, '2018-04-01 00:00:07'),
        (4.3, '2018-04-01 00:00:08'),
        (5.9, '2018-04-01 00:00:09');
        declare @start datetime = (select min(CaptureTime) from @RawData);
        declare @end datetime = (select max(CaptureTime) from @RawData);
        with cte as
        ( select @start st, DATEADD(ss, 4, @start) as ed
        union all
        select DATEADD(ss, 5, st), DATEADD(ss, 5, ed)
        from cte
        where ed < @end
        )
        select sum(rd.Result), cte.st, cte.ed
        from @RawData rd
        join cte
        on rd.CaptureTime between cte.st and cte.ed
        group by cte.st, cte.ed;






        share|improve this answer













        share|improve this answer



        share|improve this answer











        answered Apr 30 at 11:06









        paparazzo

        4,8131730




        4,8131730






















             

            draft saved


            draft discarded


























             


            draft saved


            draft discarded














            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f193149%2fsql-server-iterate-aggregate-and-insert%23new-answer', 'question_page');

            );

            Post as a guest













































































            Popular posts from this blog

            Chat program with C++ and SFML

            Function to Return a JSON Like Objects Using VBA Collections and Arrays

            Will my employers contract hold up in court?