SQL Server: How to run a query every hour, and sto

2019-09-01 00:34发布

问题:

I have this taxing query that takes 5 minutes, and I'd like to have its results stored in a table, which will be queried by a backend.

Also, the table should be updated every hour and completely replace the table's content with the new data.

I found this Server Agent solution, and from my understanding I should to this:

  1. Create the results table.
  2. Create a stored procedure that drops the results table data, runs the 5 min query, and inserts the new data.
  3. Create the Server Agent job that runs the procedure in intervals.

Is this the optimal way?

Here is the query in question. It goes over a ton of rows, which I think is the main speed impactor.

WITH 
salesCTE AS (
    select
        concat(year(m.addate), '-', format(m.addate, 'MM')) AS ym,
        year(m.addate) as y,
        format(m.addate, 'MM') as m,
        sum(M.anvalue) as salesRev
    FROM tHE_Move m
    WHERE ( 
        RIGHT(LEFT(M.acKey,5),3) = '300'
        OR RIGHT(LEFT(M.acKey,5),3) = '305'
        OR RIGHT(LEFT(M.acKey,5),3) = '319'
        OR RIGHT(LEFT(M.acKey,5),3) = '380'
        OR RIGHT(LEFT(M.acKey,5),3) = '355'
        OR RIGHT(LEFT(M.acKey,5),3) = '360'
        OR RIGHT(LEFT(M.acKey,5),3) = '3X1'
        OR RIGHT(LEFT(M.acKey,5),3) = '395'
    ) and m.adDate between '01.01.2014' and '01.01.2030'
    GROUP BY 
        concat(year(m.addate), '-', format(m.addate, 'MM')),
        year(m.addate),
        format(m.addate, 'MM')
),
retailCTE AS (
    select
        concat(year(m.addate), '-', format(m.addate, 'MM')) AS ym,
        year(m.addate) as y,
        format(m.addate, 'MM') as m,
        sum(M.anvalue) as retailRev
    FROM tHE_Move m
    where (
        RIGHT(LEFT(M.acKey,5),3) = '321'
        OR RIGHT(LEFT(M.acKey,5),3) = '322'
        OR RIGHT(LEFT(M.acKey,5),3) = '323'
        OR RIGHT(LEFT(M.acKey,5),3) = '324'
        OR RIGHT(LEFT(M.acKey,5),3) = '325'
        OR RIGHT(LEFT(M.acKey,5),3) = '326'
        OR RIGHT(LEFT(M.acKey,5),3) = '327'
        OR RIGHT(LEFT(M.acKey,5),3) = '328'
        OR RIGHT(LEFT(M.acKey,5),3) = '329'
        OR RIGHT(LEFT(M.acKey,5),3) = '331'
        OR RIGHT(LEFT(M.acKey,5),3) = '332'
        OR RIGHT(LEFT(M.acKey,5),3) = '333'
        OR RIGHT(LEFT(M.acKey,5),3) = '334'
        OR RIGHT(LEFT(M.acKey,5),3) = '335'
        OR RIGHT(LEFT(M.acKey,5),3) = '336'
        OR RIGHT(LEFT(M.acKey,5),3) = '337'
        OR RIGHT(LEFT(M.acKey,5),3) = '338'
        OR RIGHT(LEFT(M.acKey,5),3) = '339'
        OR RIGHT(LEFT(M.acKey,5),3) = '341'
        OR RIGHT(LEFT(M.acKey,5),3) = '342'
        OR RIGHT(LEFT(M.acKey,5),3) = '343'
        OR RIGHT(LEFT(M.acKey,5),3) = '344'
        OR RIGHT(LEFT(M.acKey,5),3) = '345'
        OR RIGHT(LEFT(M.acKey,5),3) = '346'
        OR RIGHT(LEFT(M.acKey,5),3) = '347'
        OR RIGHT(LEFT(M.acKey,5),3) = '348'
        OR RIGHT(LEFT(M.acKey,5),3) = '349'
        OR RIGHT(LEFT(M.acKey,5),3) = '352'
        OR RIGHT(LEFT(M.acKey,5),3) = '353'
        ) and m.adDate between '01.01.2014' and '01.01.2030'
    GROUP BY 
        concat(year(m.addate), '-', format(m.addate, 'MM')),
        year(m.addate),
        format(m.addate, 'MM')
)
SELECT 
    s1.ym,
    s1.salesRev,
    (s1.salesRev / s2.salesRev - 1) * 100 salesDelta,
    r1.retailRev,
    (r1.retailRev / r2.retailRev - 1) * 100 retailDelta,
    s1.salesRev + r1.retailRev totalRev,
    ((s1.salesRev + r1.retailRev) / (s2.salesRev + r2.retailRev) - 1) * 100 totalDelta
FROM salesCTE s1
    left join salesCTE s2
        on s2.y = s1.y - 1 and s1.m = s2.m
    left join retailCTE r1
        on s1.ym = r1.ym
    left join retailCTE r2
        on r2.y = r1.y - 1 and r1.m = r2.m
order by s1.ym desc

回答1:

As mentioned in the comment FORMAT is a massive performance hitter; and my massive i really do mean massive. Take this DB<>Fiddle which uses FORMAT and CONVERT to change the value of a date. The query using CONVERT executes if 46ms (on db fiddle), yet the FORMAT query took 1218 ms! That's 26 times slower.

Changing format(m.addate, 'MM') to RIGHT('00' + CONVERT(varchar(2),DATEPART(MONTH,DATEADD(DAY,N-1,0))),2) will have significant performance benefits on your query. Although the latter looks more complex, it will (as the fiddle shows) out perform FORMAT by a significant margin. Honestly, I recommend never using FORMAT, Microsoft got things really wrong with that function.

I do, also, however, suggest adding RIGHT(LEFT(M.acKey,5),3) as a persisted column to your table:

ALTER TABLE tHE_Move ADD {Meaningful Name} AS RIGHT(LEFT(M.acKey,5),3) PERSISTED;

Then you can also add that value to an index (new or existing) and it'll also greatly benefit the performance of your query; maybe pushing it to only a few seconds.