DMV’s are very useful. In fact, they are like glass windows that lets you look at a running engine. There are lots of them and they give us lots of numbers but
- Most are cumulative in nature! i.e., the numbers keep on increasing and never decrease.
- A variation of above like space usage where the numbers mostly go up but seldom come down except on rare occasions (like DB shrink, tempdb resets on system restarts, etc).
In both cases above, we need “delta values” – the difference in value between the previous time and the current time. Let us say we collect the size of all the databases once a day. Over time, we want to see how much the databases grew by each day, not the line that goes from the floor to ceiling at an angle for the year when visualized in a BI tool. With the “each day” delta number, we may get to know that certain databases grow on weekends, month-ends or quarter-ends more than other days. If we are collecting data more frequently, we may notice that we can get even more insights using delta values (like what I blogged before for backups).
The tedium of “Deltas”
As useful as DMV’s are, they are just as notorious when it comes to interpreting, visualizing and making sense of the ever-growing, cumulative numbers. I will give you an example to prove my point.
Let us look at wait statistics as explained by Paul Randall here (Thank you Paul) – SQL is pretty straight-forward and it ignores benign waits
WITH [Waits] AS (SELECT [wait_type], [wait_time_ms] / 1000.0 AS [WaitS], ([wait_time_ms] - [signal_wait_time_ms]) / 1000.0 AS [ResourceS], [signal_wait_time_ms] / 1000.0 AS [SignalS], [waiting_tasks_count] AS [WaitCount], 100.0 * [wait_time_ms] / SUM ([wait_time_ms]) OVER() AS [Percentage], ROW_NUMBER() OVER(ORDER BY [wait_time_ms] DESC) AS [RowNum] FROM sys.dm_os_wait_stats WHERE [wait_type] NOT IN ( -- These wait types are almost 100% never a problem and so they are -- filtered out to avoid them skewing the results. Click on the URL -- for more information. N'BROKER_EVENTHANDLER', -- https://www.sqlskills.com/help/waits/BROKER_EVENTHANDLER N'BROKER_RECEIVE_WAITFOR', -- https://www.sqlskills.com/help/waits/BROKER_RECEIVE_WAITFOR N'BROKER_TASK_STOP', -- https://www.sqlskills.com/help/waits/BROKER_TASK_STOP N'BROKER_TO_FLUSH', -- https://www.sqlskills.com/help/waits/BROKER_TO_FLUSH N'BROKER_TRANSMITTER', -- https://www.sqlskills.com/help/waits/BROKER_TRANSMITTER N'CHECKPOINT_QUEUE', -- https://www.sqlskills.com/help/waits/CHECKPOINT_QUEUE N'CHKPT', -- https://www.sqlskills.com/help/waits/CHKPT N'CLR_AUTO_EVENT', -- https://www.sqlskills.com/help/waits/CLR_AUTO_EVENT N'CLR_MANUAL_EVENT', -- https://www.sqlskills.com/help/waits/CLR_MANUAL_EVENT N'CLR_SEMAPHORE', -- https://www.sqlskills.com/help/waits/CLR_SEMAPHORE -- Maybe comment these four out if you have mirroring issues N'DBMIRROR_DBM_EVENT', -- https://www.sqlskills.com/help/waits/DBMIRROR_DBM_EVENT N'DBMIRROR_EVENTS_QUEUE', -- https://www.sqlskills.com/help/waits/DBMIRROR_EVENTS_QUEUE N'DBMIRROR_WORKER_QUEUE', -- https://www.sqlskills.com/help/waits/DBMIRROR_WORKER_QUEUE N'DBMIRRORING_CMD', -- https://www.sqlskills.com/help/waits/DBMIRRORING_CMD N'DIRTY_PAGE_POLL', -- https://www.sqlskills.com/help/waits/DIRTY_PAGE_POLL N'DISPATCHER_QUEUE_SEMAPHORE', -- https://www.sqlskills.com/help/waits/DISPATCHER_QUEUE_SEMAPHORE N'EXECSYNC', -- https://www.sqlskills.com/help/waits/EXECSYNC N'FSAGENT', -- https://www.sqlskills.com/help/waits/FSAGENT N'FT_IFTS_SCHEDULER_IDLE_WAIT', -- https://www.sqlskills.com/help/waits/FT_IFTS_SCHEDULER_IDLE_WAIT N'FT_IFTSHC_MUTEX', -- https://www.sqlskills.com/help/waits/FT_IFTSHC_MUTEX -- Maybe comment these six out if you have AG issues N'HADR_CLUSAPI_CALL', -- https://www.sqlskills.com/help/waits/HADR_CLUSAPI_CALL N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', -- https://www.sqlskills.com/help/waits/HADR_FILESTREAM_IOMGR_IOCOMPLETION N'HADR_LOGCAPTURE_WAIT', -- https://www.sqlskills.com/help/waits/HADR_LOGCAPTURE_WAIT N'HADR_NOTIFICATION_DEQUEUE', -- https://www.sqlskills.com/help/waits/HADR_NOTIFICATION_DEQUEUE N'HADR_TIMER_TASK', -- https://www.sqlskills.com/help/waits/HADR_TIMER_TASK N'HADR_WORK_QUEUE', -- https://www.sqlskills.com/help/waits/HADR_WORK_QUEUE N'KSOURCE_WAKEUP', -- https://www.sqlskills.com/help/waits/KSOURCE_WAKEUP N'LAZYWRITER_SLEEP', -- https://www.sqlskills.com/help/waits/LAZYWRITER_SLEEP N'LOGMGR_QUEUE', -- https://www.sqlskills.com/help/waits/LOGMGR_QUEUE N'MEMORY_ALLOCATION_EXT', -- https://www.sqlskills.com/help/waits/MEMORY_ALLOCATION_EXT N'ONDEMAND_TASK_QUEUE', -- https://www.sqlskills.com/help/waits/ONDEMAND_TASK_QUEUE N'PREEMPTIVE_XE_GETTARGETSTATE', -- https://www.sqlskills.com/help/waits/PREEMPTIVE_XE_GETTARGETSTATE N'PWAIT_ALL_COMPONENTS_INITIALIZED', -- https://www.sqlskills.com/help/waits/PWAIT_ALL_COMPONENTS_INITIALIZED N'PWAIT_DIRECTLOGCONSUMER_GETNEXT', -- https://www.sqlskills.com/help/waits/PWAIT_DIRECTLOGCONSUMER_GETNEXT N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', -- https://www.sqlskills.com/help/waits/QDS_PERSIST_TASK_MAIN_LOOP_SLEEP N'QDS_ASYNC_QUEUE', -- https://www.sqlskills.com/help/waits/QDS_ASYNC_QUEUE N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP', -- https://www.sqlskills.com/help/waits/QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP N'QDS_SHUTDOWN_QUEUE', -- https://www.sqlskills.com/help/waits/QDS_SHUTDOWN_QUEUE N'REDO_THREAD_PENDING_WORK', -- https://www.sqlskills.com/help/waits/REDO_THREAD_PENDING_WORK N'REQUEST_FOR_DEADLOCK_SEARCH', -- https://www.sqlskills.com/help/waits/REQUEST_FOR_DEADLOCK_SEARCH N'RESOURCE_QUEUE', -- https://www.sqlskills.com/help/waits/RESOURCE_QUEUE N'SERVER_IDLE_CHECK', -- https://www.sqlskills.com/help/waits/SERVER_IDLE_CHECK N'SLEEP_BPOOL_FLUSH', -- https://www.sqlskills.com/help/waits/SLEEP_BPOOL_FLUSH N'SLEEP_DBSTARTUP', -- https://www.sqlskills.com/help/waits/SLEEP_DBSTARTUP N'SLEEP_DCOMSTARTUP', -- https://www.sqlskills.com/help/waits/SLEEP_DCOMSTARTUP N'SLEEP_MASTERDBREADY', -- https://www.sqlskills.com/help/waits/SLEEP_MASTERDBREADY N'SLEEP_MASTERMDREADY', -- https://www.sqlskills.com/help/waits/SLEEP_MASTERMDREADY N'SLEEP_MASTERUPGRADED', -- https://www.sqlskills.com/help/waits/SLEEP_MASTERUPGRADED N'SLEEP_MSDBSTARTUP', -- https://www.sqlskills.com/help/waits/SLEEP_MSDBSTARTUP N'SLEEP_SYSTEMTASK', -- https://www.sqlskills.com/help/waits/SLEEP_SYSTEMTASK N'SLEEP_TASK', -- https://www.sqlskills.com/help/waits/SLEEP_TASK N'SLEEP_TEMPDBSTARTUP', -- https://www.sqlskills.com/help/waits/SLEEP_TEMPDBSTARTUP N'SNI_HTTP_ACCEPT', -- https://www.sqlskills.com/help/waits/SNI_HTTP_ACCEPT N'SP_SERVER_DIAGNOSTICS_SLEEP', -- https://www.sqlskills.com/help/waits/SP_SERVER_DIAGNOSTICS_SLEEP N'SQLTRACE_BUFFER_FLUSH', -- https://www.sqlskills.com/help/waits/SQLTRACE_BUFFER_FLUSH N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', -- https://www.sqlskills.com/help/waits/SQLTRACE_INCREMENTAL_FLUSH_SLEEP N'SQLTRACE_WAIT_ENTRIES', -- https://www.sqlskills.com/help/waits/SQLTRACE_WAIT_ENTRIES N'WAIT_FOR_RESULTS', -- https://www.sqlskills.com/help/waits/WAIT_FOR_RESULTS N'WAITFOR', -- https://www.sqlskills.com/help/waits/WAITFOR N'WAITFOR_TASKSHUTDOWN', -- https://www.sqlskills.com/help/waits/WAITFOR_TASKSHUTDOWN N'WAIT_XTP_RECOVERY', -- https://www.sqlskills.com/help/waits/WAIT_XTP_RECOVERY N'WAIT_XTP_HOST_WAIT', -- https://www.sqlskills.com/help/waits/WAIT_XTP_HOST_WAIT N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', -- https://www.sqlskills.com/help/waits/WAIT_XTP_OFFLINE_CKPT_NEW_LOG N'WAIT_XTP_CKPT_CLOSE', -- https://www.sqlskills.com/help/waits/WAIT_XTP_CKPT_CLOSE N'XE_DISPATCHER_JOIN', -- https://www.sqlskills.com/help/waits/XE_DISPATCHER_JOIN N'XE_DISPATCHER_WAIT', -- https://www.sqlskills.com/help/waits/XE_DISPATCHER_WAIT N'XE_TIMER_EVENT' -- https://www.sqlskills.com/help/waits/XE_TIMER_EVENT ) AND [waiting_tasks_count] > 0 ) SELECT MAX ([W1].[wait_type]) AS [WaitType], CAST (MAX ([W1].[WaitS]) AS DECIMAL (16,2)) AS [Wait_S], CAST (MAX ([W1].[ResourceS]) AS DECIMAL (16,2)) AS [Resource_S], CAST (MAX ([W1].[SignalS]) AS DECIMAL (16,2)) AS [Signal_S], MAX ([W1].[WaitCount]) AS [WaitCount], CAST (MAX ([W1].[Percentage]) AS DECIMAL (5,2)) AS [Percentage], CAST ((MAX ([W1].[WaitS]) / MAX ([W1].[WaitCount])) AS DECIMAL (16,4)) AS [AvgWait_S], CAST ((MAX ([W1].[ResourceS]) / MAX ([W1].[WaitCount])) AS DECIMAL (16,4)) AS [AvgRes_S], CAST ((MAX ([W1].[SignalS]) / MAX ([W1].[WaitCount])) AS DECIMAL (16,4)) AS [AvgSig_S], CAST ('https://www.sqlskills.com/help/waits/' + MAX ([W1].[wait_type]) as XML) AS [Help/Info URL] FROM [Waits] AS [W1] INNER JOIN [Waits] AS [W2] ON [W2].[RowNum] <= [W1].[RowNum] GROUP BY [W1].[RowNum] HAVING SUM ([W2].[Percentage]) - MAX( [W1].[Percentage] ) <span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span>< 95; -- percentage threshold
In the view above the following values are "cumulative"
- [Wait_S]
- [Resource_S]
- [Signal_S]
- [WaitCount]
The current results looks like this
WaitType Wait_S Resource_S Signal_S WaitCount Percentage AvgWait_S AvgRes_S AvgSig_S Help/Info URL ------------------------------------------- ----------- ---------------------------------------------------- ------------------------------------------------------------ CXPACKET 32796.43 30367.47 2428.96 3464474 19.35 0.0095 0.0088 0.0007 https://www.sqlskills.com/help/waits/CXPACKET SOS_SCHEDULER_YIELD 28522.83 104.53 28418.30 42182626 16.83 0.0007 0.0000 0.0007 https://www.sqlskills.com/help/waits/SOS_SCHEDULER_YIELD BACKUPBUFFER 23754.61 22785.43 969.18 671743 14.02 0.0354 0.0339 0.0014 https://www.sqlskills.com/help/waits/BACKUPBUFFER ASYNC_IO_COMPLETION 23368.86 23367.88 0.98 451 13.79 51.8156 51.8135 0.0022 https://www.sqlskills.com/help/waits/ASYNC_IO_COMPLETION BACKUPIO 22491.44 22313.35 178.09 473663 13.27 0.0475 0.0471 0.0004 https://www.sqlskills.com/help/waits/BACKUPIO PAGELATCH_EX 8779.09 5529.01 3250.08 5340424 5.18 0.0016 0.0010 0.0006 https://www.sqlskills.com/help/waits/PAGELATCH_EX PAGEIOLATCH_SH 7054.89 6778.47 276.43 2260281 4.16 0.0031 0.0030 0.0001 https://www.sqlskills.com/help/waits/PAGEIOLATCH_SH ASYNC_NETWORK_IO 4612.66 3143.56 1469.10 150094 2.72 0.0307 0.0209 0.0098 https://www.sqlskills.com/help/waits/ASYNC_NETWORK_IO PAGEIOLATCH_EX 3752.04 3704.58 47.47 667718 2.21 0.0056 0.0055 0.0001 https://www.sqlskills.com/help/waits/PAGEIOLATCH_EX LATCH_EX 3128.80 2618.06 510.74 1137880 1.85 0.0027 0.0023 0.0004 https://www.sqlskills.com/help/waits/LATCH_EX LCK_M_IS 2796.12 2780.73 15.39 1096 1.65 2.5512 2.5372 0.0140 https://www.sqlskills.com/help/waits/LCK_M_IS (11 rows affected)
Let us say, I was recording the results every 5 seconds, which I do, this data is pretty useless (because of the cumulative nature)!
I scheduled the collection using the method I already blogged about.
With a few additional collection related columns added, it looks like this:
CaptureSetID CaptureSetLine CaptureInstance CaptureDB CaptureDate WaitType Wait_S Resource_S Signal_S WaitCount Percentage AvgWait_S AvgRes_S AvgSig_S Help/Info URL -------------------- -------------- ------------------ ---------- --------------------------- --------------------- -------------- -------------- ------------ ---------- ----------- ---------- ---------- ------------------------------------------------------------------- 20180403080300 1 MyHost\MyInst01 master 2018-04-03 08:03:11.1929721 BACKUPIO 705006.46000 704915.23000 91.23000 2876876 37.86000 0.24510 0.24500 0.00000 https://www.sqlskills.com/help/waits/BACKUPIO 20180403080300 1 MyHost\MyInst02 master 2018-04-03 08:03:33.0743938 CXPACKET 2940080.46000 2886831.36000 53249.10000 50827248 49.50000 0.05780 0.05680 0.00100 https://www.sqlskills.com/help/waits/CXPACKET 20180403080300 1 MyHost\MyInst03 master 2018-04-03 08:03:55.1837616 BACKUPIO 681128.60000 681095.14000 33.47000 493116 42.70000 1.38130 1.38120 0.00010 https://www.sqlskills.com/help/waits/BACKUPIO 20180403080300 1 MyHost\MyInst04 master 2018-04-03 08:04:15.1897426 BACKUPIO 643736.30000 643731.51000 4.79000 62013 49.46000 10.38070 10.38060 0.00010 https://www.sqlskills.com/help/waits/BACKUPIO 20180403080300 2 MyHost\MyInst01 master 2018-04-03 08:03:11.1949855 BACKUPTHREAD 575383.30000 575382.97000 0.33000 2678 30.90000 214.85560 214.85550 0.00010 https://www.sqlskills.com/help/waits/BACKUPTHREAD 20180403080300 2 MyHost\MyInst02 master 2018-04-03 08:03:33.0754266 BACKUPIO 1266207.89000 1266154.55000 53.34000 479336 21.32000 2.64160 2.64150 0.00010 https://www.sqlskills.com/help/waits/BACKUPIO 20180403080300 2 MyHost\MyInst03 master 2018-04-03 08:03:55.1857702 BACKUPTHREAD 649904.40000 649903.48000 0.92000 6786 40.75000 95.77140 95.77120 0.00010 https://www.sqlskills.com/help/waits/BACKUPTHREAD 20180403080300 2 MyHost\MyInst04 master 2018-04-03 08:04:15.1907440 BACKUPTHREAD 641486.63000 641485.60000 1.03000 9000 49.29000 71.27630 71.27620 0.00010 https://www.sqlskills.com/help/waits/BACKUPTHREAD 20180403080300 3 MyHost\MyInst01 master 2018-04-03 08:03:11.1959796 BACKUPBUFFER 164609.00000 163585.17000 1023.83000 11992672 8.84000 0.01370 0.01360 0.00010 https://www.sqlskills.com/help/waits/BACKUPBUFFER 20180403080300 3 MyHost\MyInst02 master 2018-04-03 08:03:33.0764331 BACKUPTHREAD 1233437.60000 1233435.99000 1.61000 8938 20.77000 137.99930 137.99910 0.00020 https://www.sqlskills.com/help/waits/BACKUPTHREAD 20180403080300 3 MyHost\MyInst03 master 2018-04-03 08:03:55.1887777 CXPACKET 112756.87000 103714.22000 9042.66000 10464996 7.07000 0.01080 0.00990 0.00090 https://www.sqlskills.com/help/waits/CXPACKET 20180403080300 4 MyHost\MyInst01 master 2018-04-03 08:03:11.1959796 ASYNC_IO_COMPLETION 149335.30000 149335.27000 0.03000 133 8.02000 1122.82180 1122.82160 0.00020 https://www.sqlskills.com/help/waits/ASYNC_IO_COMPLETION 20180403080300 4 MyHost\MyInst02 master 2018-04-03 08:03:33.0774024 PAGEIOLATCH_SH 161344.74000 158575.77000 2768.97000 45762856 2.72000 0.00350 0.00350 0.00010 https://www.sqlskills.com/help/waits/PAGEIOLATCH_SH 20180403080300 4 MyHost\MyInst03 master 2018-04-03 08:03:55.1907830 BACKUPBUFFER 35434.30000 35042.90000 391.40000 1317652 2.22000 0.02690 0.02660 0.00030 https://www.sqlskills.com/help/waits/BACKUPBUFFER 20180403080300 5 MyHost\MyInst01 master 2018-04-03 08:03:11.1989926 CXPACKET 126486.27000 107031.54000 19454.73000 30498347 6.79000 0.00410 0.00350 0.00060 https://www.sqlskills.com/help/waits/CXPACKET 20180403080300 5 MyHost\MyInst02 master 2018-04-03 08:03:33.0774024 PAGEIOLATCH_EX 91936.24000 91458.77000 477.47000 29816143 1.55000 0.00310 0.00310 0.00000 https://www.sqlskills.com/help/waits/PAGEIOLATCH_EX 20180403080300 5 MyHost\MyInst03 master 2018-04-03 08:03:55.1917829 ASYNC_IO_COMPLETION 33942.18000 33942.08000 0.10000 231 2.13000 146.93580 146.93540 0.00040 https://www.sqlskills.com/help/waits/ASYNC_IO_COMPLETION 20180403080300 6 MyHost\MyInst01 master 2018-04-03 08:03:11.1999648 PAGEIOLATCH_SH 47167.55000 46897.61000 269.94000 12877141 2.53000 0.00370 0.00360 0.00000 https://www.sqlskills.com/help/waits/PAGEIOLATCH_SH 20180403080300 6 MyHost\MyInst03 master 2018-04-03 08:03:55.1937883 PAGEIOLATCH_SH 28045.20000 27511.73000 533.48000 4153090 1.76000 0.00680 0.00660 0.00010 https://www.sqlskills.com/help/waits/PAGEIOLATCH_SH 20180403080300 7 MyHost\MyInst01 master 2018-04-03 08:03:11.2009665 OLEDB 29010.44000 29010.44000 0.00000 750259205 1.56000 0.00000 0.00000 0.00000 https://www.sqlskills.com/help/waits/OLEDB 20180403080826 1 MyHost\MyInst01 master 2018-04-03 08:08:37.0393672 BACKUPIO 705006.46000 704915.23000 91.23000 2876876 37.86000 0.24510 0.24500 0.00000 https://www.sqlskills.com/help/waits/BACKUPIO 20180403080826 2 MyHost\MyInst01 master 2018-04-03 08:08:37.0424049 BACKUPTHREAD 575383.30000 575382.97000 0.33000 2678 30.90000 214.85560 214.85550 0.00010 https://www.sqlskills.com/help/waits/BACKUPTHREAD 20180403080826 3 MyHost\MyInst01 master 2018-04-03 08:08:37.0434104 BACKUPBUFFER 164609.00000 163585.17000 1023.83000 11992672 8.84000 0.01370 0.01360 0.00010 https://www.sqlskills.com/help/waits/BACKUPBUFFER 20180403080826 4 MyHost\MyInst01 master 2018-04-03 08:08:37.0444077 ASYNC_IO_COMPLETION 149335.30000 149335.27000 0.03000 133 8.02000 1122.82180 1122.82160 0.00020 https://www.sqlskills.com/help/waits/ASYNC_IO_COMPLETION 20180403080826 5 MyHost\MyInst01 master 2018-04-03 08:08:37.0463854 CXPACKET 126486.27000 107031.54000 19454.73000 30498356 6.79000 0.00410 0.00350 0.00060 https://www.sqlskills.com/help/waits/CXPACKET 20180403080826 6 MyHost\MyInst01 master 2018-04-03 08:08:37.0483907 PAGEIOLATCH_SH 47197.13000 46927.15000 269.97000 12880473 2.53000 0.00370 0.00360 0.00000 https://www.sqlskills.com/help/waits/PAGEIOLATCH_SH 20180403080826 7 MyHost\MyInst01 master 2018-04-03 08:08:37.0493932 OLEDB 29010.44000 29010.44000 0.00000 750259205 1.56000 0.00000 0.00000 0.00000 https://www.sqlskills.com/help/waits/OLEDB 20180403081326 1 MyHost\MyInst01 master 2018-04-03 08:13:38.8994288 BACKUPIO 705006.46000 704915.23000 91.23000 2876876 37.86000 0.24510 0.24500 0.00000 https://www.sqlskills.com/help/waits/BACKUPIO
Deltas using Windowing functions
What we need is the delta values for each capture compared to the previous capture. To do so, we have to write code such as this. Basically, we PARTITION BY CaptureInstance, CaptureDB, WaitType ORDER BY CaptureSetID to segment the data for comparison. Then we self-join on the previous row to current row and get the delta – (cur.[Wait_S]-prev.[Wait_S]) AS Wait_S_Delta
WITH prev AS ( SELECT LEAD(CaptureSetID) OVER (PARTITION BY CaptureInstance, CaptureDB, WaitType ORDER BY CaptureSetID) NextCaptureSetID, prev.* FROM OS_Wait_Stats AS prev ) SELECT cur.[CaptureSetID] ,cur.[CaptureSetLine] ,cur.[CaptureInstance] ,cur.[CaptureDB] ,cur.[CaptureDate] ,prev.[CaptureDate] AS CaptureDate_Prev ,cur.[WaitType] ,prev.[WaitType] AS WaitTypePrev ,cur.[Wait_S] ,prev.[Wait_S] AS Wait_S_Prev ,(cur.[Wait_S]-prev.[Wait_S]) AS Wait_S_Delta ,cur.[Resource_S] ,prev.[Resource_S] AS Resource_S_Prev ,(cur.[Resource_S]-prev.[Resource_S]) AS Resource_S_Delta ,cur.[Signal_S] ,prev.[Signal_S] AS Signal_S_Prev ,(cur.[Signal_S]-prev.[Signal_S]) AS Signal_S_Delta ,cur.[WaitCount] ,prev.[WaitCount] AS WaitCount_Prev ,(cur.[WaitCount]-prev.[WaitCount]) AS WaitCount_Delta ,cur.[Percentage] ,prev.[Percentage] AS Percentage_Prev ,(cur.[Percentage]-prev.[Percentage]) AS Percentage_Delta ,cur.[AvgWait_S] ,prev.[AvgWait_S] AS AvgWait_S_Prev ,(cur.[AvgWait_S]-prev.[AvgWait_S]) AS AvgWait_S_Delta ,cur.[AvgRes_S] ,prev.[AvgRes_S] AS AvgRes_S_Prev ,(cur.[AvgRes_S]-prev.[AvgRes_S]) AS AvgRes_S_Delta ,cur.[AvgSig_S] ,prev.[AvgSig_S] AS AvgSig_S_Prev ,(cur.[AvgSig_S]-prev.[AvgSig_S]) AS AvgSig_S_Delta ,cur.[Help/Info URL] ,prev.[Help/Info URL] AS [Help/Info URL_Prev] FROM OS_Wait_Stats AS cur INNER JOIN prev ON cur.CaptureInstance = prev.CaptureInstance AND cur.CaptureDB = prev.CaptureDB AND cur.WaitType = prev.WaitType AND cur.CaptureSetID = prev.NextCaptureSetID
It is not a big deal to code it except that it becomes a tedious task if you have to do it over and over for various views. The concept is the same for all views however what we PARTITION BY and ORDER BY might change slightly. I saw the potential for automating the creation of the above SQL and went for it.
Delta SQL Generation – SQL Output Mode
What if I told you that the whole view could be generated by a simple call such as this:
DECLARE @SQLOut NVARCHAR(4000); exec [dbo].[usp_Make_Delta_View] @SelectColumns='*', @PartitionByColumns='CaptureInstance, CaptureDB, WaitType', @PartitionOrderByColumns='CaptureSetId', @TableName='dbo.OS_Wait_Stats', @OutputResultsOrSQL='SQL', @SQLOutput = @SQLOut OUTPUT; SELECT @SQLOut;
The above call generates the SQL which recognizes all numeric/date columns and performs deltas. The date column deltas are in minutes. I guess that can also be parameterized if you wish so. You can create a view if you want.
Delta Results Generation – Actual Output Mode
The code below actually generates the SQL delta columns and runs it to give you the results straight-away!
exec [dbo].[usp_Make_Delta_View] @SelectColumns='*', @PartitionByColumns='CaptureInstance, CaptureDB, WaitType', @PartitionOrderByColumns='CaptureSetId', @TableName='dbo.OS_Wait_Stats', @OutputResultsOrSQL='Results'
It took me a while to get it quite right but it is a very effective genericization (not an actual word!) of a simple concept.
Delta Results To Output Table:
--To return the SQL that inserts into a results table named OS_Wait_Stats_With_Deltas...Just EXEC the returned SQL! DECLARE @SQLOutwInto NVARCHAR(4000); exec [dbo].[usp_Make_Delta_View] @SelectColumns='*', @PartitionByColumns='CaptureInstance, CaptureDB, WaitType', @PartitionOrderByColumns='CaptureSetId', @TableName='dbo.OS_Wait_Stats', @OutputResultsOrSQL='SQL', @OutputResultsIntoTableName = 'OS_Wait_Stats_With_Deltas', @SQLOutput = @SQLOutwInto OUTPUT; EXECUTE sp_ExecuteSQL @SQLOutwInto; SELECT * FROM OS_Wait_Stats_With_Deltas; DROP TABLE OS_Wait_Stats_With_Deltas;
The TSQL code
The code is not very hard to understand. Please feel free to change it as you see fit
SET QUOTED_IDENTIFIER OFF GO ALTER PROCEDURE [dbo].[usp_Make_Delta_View] ( @SelectColumns VARCHAR(255), @PartitionByColumns VARCHAR(255), @PartitionOrderByColumns VARCHAR(255), @DatabaseName VARCHAR(128) = DB_NAME, --For temporary tables, pass in - 'tempdb' @TableName VARCHAR(128), --For temporary tables, pass in the format - 'tempdb.dbo.#temp' @OutputResultsOrSQL VARCHAR(10) = 'Results', @OutputResultsIntoTableName VARCHAR(100) = NULL, @SQLOutput VARCHAR(MAX) = NULL OUT ) AS BEGIN /* v1.0 - Apr 05, 2018. Jana Sattainathan [Twitter: @SQLJana] [Blog: sqljana.wordpress.com] ---------- --Usage Examples ---------- --To return the actual DELTA results! exec [dbo].[usp_Make_Delta_View] @SelectColumns='*', @PartitionByColumns='CaptureInstance, CaptureDB, WaitType', @PartitionOrderByColumns='CaptureSetId', @TableName='dbo.OS_Wait_Stats', @OutputResultsOrSQL='Results' --To return the DELTA results as a SQL statement! DECLARE @SQLOut NVARCHAR(4000); exec [dbo].[usp_Make_Delta_View] @SelectColumns='*', @PartitionByColumns='CaptureInstance, CaptureDB, WaitType', @PartitionOrderByColumns='CaptureSetId', @TableName='dbo.OS_Wait_Stats', @OutputResultsOrSQL='SQL', @SQLOutput = @SQLOut OUTPUT; SELECT @SQLOut; --To return the SQL that inserts into a results table named OS_Wait_Stats_With_Deltas...Just EXEC the returned SQL! DECLARE @SQLOutwInto NVARCHAR(4000); exec [dbo].[usp_Make_Delta_View] @SelectColumns='*', @PartitionByColumns='CaptureInstance, CaptureDB, WaitType', @PartitionOrderByColumns='CaptureSetId', @TableName='dbo.OS_Wait_Stats', @OutputResultsOrSQL='SQL', @OutputResultsIntoTableName = 'OS_Wait_Stats_With_Deltas', @SQLOutput = @SQLOutwInto OUTPUT; EXECUTE sp_ExecuteSQL @SQLOutwInto; SELECT * FROM OS_Wait_Stats_With_Deltas; DROP TABLE OS_Wait_Stats_With_Deltas; */ <span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span> --Below is the illustration of what the procedure does -- ..an example of the DELTA we want to generate (for all the numeric columns) /* -- --This is the actual SQL that has columns which have cumulative values SELECT TOP (1000) [CaptureSetID] ,[CaptureSetLine] ,[CaptureInstance] ,[CaptureDB] ,[CaptureDate] ,[WaitType] ,[Wait_S] ,[Resource_S] ,[Signal_S] ,[WaitCount] ,[Percentage] ,[AvgWait_S] ,[AvgRes_S] ,[AvgSig_S] ,[Help/Info URL] FROM [PowerMon].[dbo].[OS_Wait_Stats] --It needs to be converted to add columns so that the current value can be compared to -- previous value to get a delta for the time period --The SQL to get the Delta looks like this -- (which is what we want to generate with the proc!) WITH ranked AS ( SELECT DENSE_RANK() OVER (ORDER BY CaptureInstance, CaptureDB, WaitType) as grp, RANK() OVER (PARTITION BY CaptureInstance, CaptureDB, WaitType ORDER BY CaptureSetId) as grprnk, a.* FROM OS_Wait_Stats AS a ) SELECT cur.[CaptureSetID] ,cur.[CaptureSetLine] ,cur.[CaptureInstance] ,cur.[CaptureDB] ,cur.[CaptureDate] ,prev.[CaptureDate] AS CaptureDate_Prev ,cur.[WaitType] ,prev.[WaitType] AS WaitTypePrev ,cur.[Wait_S] ,prev.[Wait_S] AS Wait_S_Prev ,(cur.[Wait_S]-prev.[Wait_S]) AS Wait_S_Delta ,cur.[Resource_S] ,prev.[Resource_S] AS Resource_S_Prev ,(cur.[Resource_S]-prev.[Resource_S]) AS Resource_S_Delta ,cur.[Signal_S] ,prev.[Signal_S] AS Signal_S_Prev ,(cur.[Signal_S]-prev.[Signal_S]) AS Signal_S_Delta ,cur.[WaitCount] ,prev.[WaitCount] AS WaitCount_Prev ,(cur.[WaitCount]-prev.[WaitCount]) AS WaitCount_Delta ,cur.[Percentage] ,prev.[Percentage] AS Percentage_Prev ,(cur.[Percentage]-prev.[Percentage]) AS Percentage_Delta ,cur.[AvgWait_S] ,prev.[AvgWait_S] AS AvgWait_S_Prev ,(cur.[AvgWait_S]-prev.[AvgWait_S]) AS AvgWait_S_Delta ,cur.[AvgRes_S] ,prev.[AvgRes_S] AS AvgRes_S_Prev ,(cur.[AvgRes_S]-prev.[AvgRes_S]) AS AvgRes_S_Delta ,cur.[AvgSig_S] ,prev.[AvgSig_S] AS AvgSig_S_Prev ,(cur.[AvgSig_S]-prev.[AvgSig_S]) AS AvgSig_S_Delta ,cur.[Help/Info URL] ,prev.[Help/Info URL] AS [Help/Info URL_Prev] FROM ranked AS cur INNER JOIN ranked AS prev ON cur.grp = prev.grp AND cur.grprnk = (prev.grprnk + 1) */ DECLARE @DeltaSQL NVARCHAR(MAX), @SQL NVARCHAR(MAX), @ColumnsCSV NVARCHAR(MAX), @OutputResultsIntoTableSQL NVARCHAR(MAX); --Create the SQL that will produce the delta results SET @DeltaSQL = 'WITH ranked ' + CHAR(13) + ' AS ' + CHAR(13) + ' ( ' + CHAR(13) + ' SELECT ' + CHAR(13) + ' DENSE_RANK() OVER (ORDER BY ' + @PartitionByColumns + ') as grp, ' + CHAR(13) + ' RANK() OVER (PARTITION BY ' + @PartitionByColumns + ' ORDER BY ' + @PartitionOrderByColumns +') as grprnk, ' + CHAR(13) + ' a.* ' + CHAR(13) + ' FROM ( ' + CHAR(13) + ' SELECT ' + @SelectColumns + ' FROM '+@TableName+' AS innr ' + CHAR(13) + ' ) a ' + CHAR(13) + ' ) ' + CHAR(13) + 'SELECT ' + CHAR(13) + ' ' + CHAR(13) + 'FROM ranked AS cur ' + CHAR(13) + ' INNER JOIN ranked AS prev ' + CHAR(13) + ' ON cur.grp = prev.grp ' + CHAR(13) + ' AND cur.grprnk = (prev.grprnk + 1) ' + CHAR(13) + 'ORDER BY ' + @PartitionByColumns + ',' + @PartitionOrderByColumns PRINT @DeltaSQL; PRINT '-------------------------------------------'; SET @SQL = " --Get the list of columns with their datatypes for the table -- so that we can do deltas on the numeric columns + -- sensible deltas on the datetime columns SELECT /* c.column_id, c.name AS ColName, QUOTENAME(c.name + '_Prev') AS ColumnNamewSuffix, t.Name AS DataType, c.max_length AS MaxLength, c.precision , c.scale , c.is_nullable, ISNULL(i.is_primary_key, 0) AS PrimaryKey, CASE WHEN (c.precision > 0 AND t.name NOT LIKE 'datetime%' AND t.name NOT LIKE 'bit%') THEN 1 ELSE 0 END IsNumber, */ CASE --This is for numbers ---------------------- WHEN (c.precision > 0 AND t.name NOT LIKE 'datetime%' AND t.name NOT LIKE 'bit%') THEN 'cur.' + QUOTENAME(c.name) + ', ' + 'prev.' + QUOTENAME(c.name) + ' AS [' + c.name + '_Prev], ' + '(cur.' + QUOTENAME(c.name) + ' - prev.' + QUOTENAME(c.name) + ') AS [' + c.name + '_Delta]' --This is for dates ---------------------- WHEN (c.precision > 0 AND t.name LIKE 'datetime%') THEN 'cur.' + QUOTENAME(c.name) + ', ' + 'prev.' + QUOTENAME(c.name) + ' AS [' + c.name + '_Prev], ' + 'DATEDIFF(MINUTE, prev.' + QUOTENAME(c.name) + ', cur.' + QUOTENAME(c.name) + ') AS [' + c.name + '_DeltaMins]' --This is for non-numbers/non-dates ---------------------- ELSE 'cur.' + QUOTENAME(c.name) END AS Cols FROM sys.columns c INNER JOIN sys.types t ON c.user_type_id = t.user_type_id /* --Messes-up when table has indexes LEFT OUTER JOIN sys.index_columns ic ON ic.object_id = c.object_id AND ic.column_id = c.column_id LEFT OUTER JOIN sys.indexes i ON ic.object_id = i.object_id AND ic.index_id = i.index_id */ WHERE 1=1 AND c.object_id = OBJECT_ID('" + @TableName + "') ORDER BY c.column_id" PRINT @SQL PRINT '-------------------------------------------'; CREATE TABLE #Temp(Cols NVARCHAR(1024)); INSERT INTO #Temp EXEC SP_EXECUTESQL @SQL; --https://www.sqlmatters.com/Articles/Converting%20row%20values%20in%20a%20table%20to%20a%20single%20concatenated%20string.aspx SELECT TOP 1 @ColumnsCSV = STUFF((SELECT ',' + Cols FROM #Temp FOR XML PATH('')) ,1,1,'') DROP TABLE #Temp; --Should the results be output to a table? SELECT @OutputResultsIntoTableSQL = CASE WHEN @OutputResultsIntoTableName IS NULL THEN '' ELSE (' INTO ' + @OutputResultsIntoTableName + ' ') END; --Create the SQL that will produce the results SET @DeltaSQL = REPLACE(@DeltaSQL, '', @ColumnsCSV); SET @DeltaSQL = REPLACE(@DeltaSQL, '', @OutputResultsIntoTableSQL); PRINT @DeltaSQL; --Return either the SQL or the actual results! IF (@OutputResultsOrSQL = 'SQL') SELECT @SQLOutput = @DeltaSQL; ELSE EXEC SP_EXECUTESQL @DeltaSQL; END; GO SET QUOTED_IDENTIFIER ON GO
PowerBI analysis:
I am not a PowerBI expert but the tool is easy to use. We just need to drag and drop a few things to get a meaningful output/visualization in minutes! Using the procedure above, I created a view which acted as the source of the PowerBI report.
With the delta columns created, my analysis of the data is a breeze with PowerBI:
I can clearly see what my instance was waiting on and when. This was based on a light-weight capture in 5 minute intervals.
I could do the same thing for other things like file read/writes using sys.dm_io_virtual_file_stats
The possibilities suddenly open up quite a bit without actually have to do the delta related work!