SQLServerCentral Article

SQL ART: Who's Blocking Who? Visualising SQL Server Blocking With Spatial Geometry

,

Taking SQL art from shamrocks and Easter eggs to something your DBA manager might actually care about.

If you've been following my SQL spatial art series — shamrocks, Easter eggs, Christmas trees — you'll know I have a habit of finding increasingly creative misuses for SQL Server's geometry data type. Most of them have been purely decorative. Fun, but not exactly something you'd put in a production runbook.

That changed the other day when I read How to Draw Line Graphs in SSMS by Brent Ozar. It's a great piece — go and read it if you haven't. It got me thinking: what if I took that idea and pushed it somewhere more practical? What if instead of drawing shamrocks, I used spatial geometry to actually visualise something useful?

Blocking. Of course it was going to be blocking.

The Idea

SQL Server blocking is one of those things that's easy to detect but sometimes hard to explain to people who aren't staring at sys.dm_exec_requests every day. You can see the numbers, but a chart tells the story much faster — which sessions are waiting, how long they've been waiting, and critically, who is causing it.

So here's what I built: a live blocking visualiser that pulls straight from sys.dm_exec_requests and renders:

  • A line graph of wait time in seconds per session
  • A second line showing the running count of blocked SPIDs
  • A dot per SPID on the chart, coloured by status
  • SPID numbers stamped above blocked sessions and the head blocker
  • A diamond marker and HEAD BLOCKER label at the culprit session
  • Vertical marker lines dropped from any blocking or blocked session to the X axis

All of it rendered in SSMS's Spatial Results tab. No external tools. No SSRS. No Power BI. Just T-SQL and geometry.

How It Works

Step 1: Capture the Snapshot

First we pull the blocking picture from sys.dm_exec_requests:

SELECT
    r.session_id                                    AS spid,
    r.blocking_session_id                           AS blocking_spid,
    ISNULL(r.wait_time / 1000.0, 0)                 AS wait_secs,
    CASE WHEN EXISTS (
        SELECT 1 FROM sys.dm_exec_requests b
        WHERE b.blocking_session_id = r.session_id
    ) THEN 1 ELSE 0 END                             AS is_head_blocker,
    CASE WHEN r.blocking_session_id > 0
         THEN 1 ELSE 0 END                          AS is_blocked,
    ROW_NUMBER() OVER (ORDER BY r.session_id)       AS plot_x
INTO #blocking
FROM sys.dm_exec_requests r
WHERE r.session_id > 50
  AND r.session_id <> @@SPID;

The key columns are wait_time (converted from milliseconds to seconds), blocking_session_id, and two derived flags — is_blocked and is_head_blocker. The head blocker is identified by checking whether any other session lists this one as its blocker. plot_x gives us the X position on the chart.

The script also includes a demo dataset that kicks in if no blocking is found, so it always renders something useful even on a quiet server.

Step 2: Scale to Chart Coordinates

The chart area is 18 units wide and 8 units tall. We scale each SPID's wait time and position to fit:

SET @currX = @originX + (CAST(@px AS float) / @maxX) * @chartW;
SET @currY = @originY + (@wait / @maxWait) * @chartH;

Simple normalisation — the longest wait always reaches the top of the chart, everything else scales proportionally.

Step 3: Draw the Lines and Dots

Each SPID gets a dot at its chart position. The dot colour tells you its status at a glance:

  • Normal dot — session is running fine
  • Blocked dot — session is being blocked by someone else
  • Head blocker dot — this session is causing the queue

Line segments connect the dots to form the wait time trend line. A second line tracks the running count of blocked SPIDs.

Step 4: SPID Numbers Using the Pixel Font

Regular readers of this series will recognise the pixel font engine — a table variable of (character, row, column) coordinates stamped as tiny polygon squares. This time it's rendering SPID numbers rather than "HAPPY ST PATRICKS DAY", which feels like progress.

We only label the sessions that matter — blocked SPIDs and the head blocker. Normal running sessions just get a plain dot.

Font scale is set to 0.12 — learned the hard way that anything smaller collapses to a point after MakeValid() and causes SSMS to throw a geometry format error.

Step 5: The Head Blocker Diamond and Label

The head blocker gets a diamond shape, "HEAD BLOCKER" in pixel letters above it, and a vertical marker line to the X axis. The text sits in its own colour group so it renders separately from the diamond and SPID number — stops them merging into one unreadable blob, which they absolutely did on the first attempt.

Step 6: The Dummy Shape

Old friends will know this one. SSMS's spatial viewer assigns colours from a fixed 8-slot palette. Slot 4 is always purple. A microscopic off-screen point absorbs it:

INSERT INTO #graph VALUES
(geometry::Point(-0.5, -0.5, 0).STBuffer(0.01).MakeValid(), 'dummy');

The dummy shape. Series veteran. Undefeated.

Testing It With Real Blocking

To see the chart with real blocking, run these in separate SSMS windows.

First create the test table in any window:

USE tempdb;
GO
DROP TABLE IF EXISTS dbo.BlockingTest;
CREATE TABLE dbo.BlockingTest (id int PRIMARY KEY, val varchar(100));
INSERT INTO dbo.BlockingTest VALUES (1,'Row One'),(2,'Row Two'),(3,'Row Three');

Window 1 — Head blocker (leave this running, do not commit):

USE tempdb;
BEGIN TRANSACTION;
    UPDATE dbo.BlockingTest SET val = 'held' WHERE id = 1;
    WAITFOR DELAY '00:05:00';
ROLLBACK;

Window 2 — Blocked session 1 (will hang immediately):

USE tempdb;
SELECT * FROM dbo.BlockingTest WITH (UPDLOCK) WHERE id = 1;

Window 3 — Blocked session 2 (will also hang):

USE tempdb;
UPDATE dbo.BlockingTest SET val = 'Window 3 wants this' WHERE id = 1;

Then run the full visualiser script below in Window 4 and switch to the Spatial Results tab.

To clean up, run ROLLBACK in Window 1 and drop the table:

DROP TABLE IF EXISTS tempdb.dbo.BlockingTest;

What the Chart Tells You

At a glance you can see:

  • Which sessions have the longest wait times — the highest points on the wait time line
  • How many sessions are blocked — the second line climbing as the chain grows
  • Exactly which SPID is the head blocker — diamond, label, vertical marker, hard to miss
  • Which SPIDs are caught in the chain — different colour dots with their numbers visible

It won't replace sp_WhoIsActive or your favourite monitoring tool. But as a quick visual snapshot — especially for explaining blocking chains to someone who doesn't live in DMVs — it does a surprisingly good job.

SSMS Colour Slot Map

SSMS assigns its own palette — you can't specify actual colours, only control the order groups are returned. The slot map for this script is:

SlotSSMS ColourUsed For
1TealAxes and grid
2OrangeWait time line
3BlueNormal dots
4PurpleDummy — off-screen, sacrificed as always
5GreyBlocked dots + markers
6BeigeBlocked count line
7SageHead blocker diamond + SPID number
8Dark greyHEAD BLOCKER text label

The Full Script

USE tempdb;
GO

DROP TABLE IF EXISTS #blocking;
DROP TABLE IF EXISTS #graph;
GO
--------------------------------------------------------------------------------
-- 1) CAPTURE BLOCKING SNAPSHOT
--------------------------------------------------------------------------------  
SELECT r.session_id AS spid,
       r.blocking_session_id AS blocking_spid,
       ISNULL(r.wait_time / 1000.0, 0) AS wait_secs,
       r.wait_type,
       r.status,
       CASE
           WHEN EXISTS
                (
                    SELECT 1
                    FROM sys.dm_exec_requests b
                    WHERE b.blocking_session_id = r.session_id
                ) THEN
               1
           ELSE
               0
       END AS is_head_blocker,
       CASE
           WHEN r.blocking_session_id > 0 THEN
               1
           ELSE
               0
       END AS is_blocked,
       ROW_NUMBER() OVER (ORDER BY r.session_id) AS plot_x
INTO #blocking
FROM sys.dm_exec_requests r
WHERE r.session_id > 50
      AND r.session_id <> @@SPID; -- Demo data if no blocking found

IF NOT EXISTS (SELECT 1 FROM #blocking)
BEGIN
    INSERT INTO #blocking
    VALUES
    (51, 0, 0.0, 'RUNNING', 'running', 0, 0, 1);
    INSERT INTO #blocking
    VALUES
    (52, 51, 4.2, 'LCK_M_IX', 'suspended', 0, 1, 2);
    INSERT INTO #blocking
    VALUES
    (53, 51, 6.8, 'LCK_M_S', 'suspended', 0, 1, 3);
    INSERT INTO #blocking
    VALUES
    (54, 0, 0.5, 'RUNNING', 'running', 1, 0, 4);
    INSERT INTO #blocking
    VALUES
    (55, 54, 2.1, 'LCK_M_U', 'suspended', 0, 1, 5);
    INSERT INTO #blocking
    VALUES
    (56, 0, 1.2, 'PAGEIOLATCH', 'running', 0, 0, 6);
END;
CREATE TABLE #graph
(
    shape GEOMETRY,
    colour VARCHAR(30)
);
GO
--------------------------------------------------------------------------------
-- 2) ALL WORK IN ONE BATCH — no GO after this point
--------------------------------------------------------------------------------  
DECLARE @maxX FLOAT =
        (
            SELECT MAX(plot_x)FROM #blocking
        );
DECLARE @maxWait FLOAT =
        (
            SELECT MAX(wait_secs)FROM #blocking
        );
DECLARE @maxCount FLOAT =
        (
            SELECT COUNT(*)FROM #blocking WHERE is_blocked = 1
        );
DECLARE @chartW FLOAT = 18.0;
DECLARE @chartH FLOAT = 8.0;
DECLARE @originX FLOAT = 1.0;
DECLARE @originY FLOAT = 1.0;
IF @maxX = 0
    SET @maxX = 1;
IF @maxWait = 0
    SET @maxWait = 1;
IF @maxCount = 0
    SET @maxCount = 1; -- -- DIGIT FONT (declared before cursor, used for SPID labels) ----------------
DECLARE @Digits TABLE
(
    ch NCHAR(1),
    r TINYINT,
    c TINYINT
);
INSERT INTO @Digits
VALUES
(N'0', 0, 1),
(N'0', 0, 2),
(N'0', 0, 3),
(N'0', 1, 0),
(N'0', 1, 4),
(N'0', 2, 0),
(N'0', 2, 4),
(N'0', 3, 0),
(N'0', 3, 4),
(N'0', 4, 0),
(N'0', 4, 4),
(N'0', 5, 0),
(N'0', 5, 4),
(N'0', 6, 1),
(N'0', 6, 2),
(N'0', 6, 3),
(N'1', 0, 2),
(N'1', 1, 1),
(N'1', 1, 2),
(N'1', 2, 2),
(N'1', 3, 2),
(N'1', 4, 2),
(N'1', 5, 2),
(N'1', 6, 1),
(N'1', 6, 2),
(N'1', 6, 3),
(N'2', 0, 1),
(N'2', 0, 2),
(N'2', 0, 3),
(N'2', 1, 0),
(N'2', 1, 4),
(N'2', 2, 4),
(N'2', 3, 2),
(N'2', 3, 3),
(N'2', 4, 1),
(N'2', 5, 0),
(N'2', 6, 0),
(N'2', 6, 1),
(N'2', 6, 2),
(N'2', 6, 3),
(N'2', 6, 4),
(N'3', 0, 1),
(N'3', 0, 2),
(N'3', 0, 3),
(N'3', 1, 0),
(N'3', 1, 4),
(N'3', 2, 4),
(N'3', 3, 2),
(N'3', 3, 3),
(N'3', 4, 4),
(N'3', 5, 0),
(N'3', 5, 4),
(N'3', 6, 1),
(N'3', 6, 2),
(N'3', 6, 3),
(N'4', 0, 0),
(N'4', 0, 4),
(N'4', 1, 0),
(N'4', 1, 4),
(N'4', 2, 0),
(N'4', 2, 4),
(N'4', 3, 0),
(N'4', 3, 1),
(N'4', 3, 2),
(N'4', 3, 3),
(N'4', 3, 4),
(N'4', 4, 4),
(N'4', 5, 4),
(N'4', 6, 4),
(N'5', 0, 0),
(N'5', 0, 1),
(N'5', 0, 2),
(N'5', 0, 3),
(N'5', 0, 4),
(N'5', 1, 0),
(N'5', 2, 0),
(N'5', 3, 0),
(N'5', 3, 1),
(N'5', 3, 2),
(N'5', 3, 3),
(N'5', 4, 4),
(N'5', 5, 4),
(N'5', 6, 0),
(N'5', 6, 1),
(N'5', 6, 2),
(N'5', 6, 3),
(N'6', 0, 1),
(N'6', 0, 2),
(N'6', 0, 3),
(N'6', 1, 0),
(N'6', 2, 0),
(N'6', 3, 0),
(N'6', 3, 1),
(N'6', 3, 2),
(N'6', 3, 3),
(N'6', 4, 0),
(N'6', 4, 4),
(N'6', 5, 0),
(N'6', 5, 4),
(N'6', 6, 1),
(N'6', 6, 2),
(N'6', 6, 3),
(N'7', 0, 0),
(N'7', 0, 1),
(N'7', 0, 2),
(N'7', 0, 3),
(N'7', 0, 4),
(N'7', 1, 4),
(N'7', 2, 3),
(N'7', 3, 2),
(N'7', 4, 2),
(N'7', 5, 1),
(N'7', 6, 1),
(N'8', 0, 1),
(N'8', 0, 2),
(N'8', 0, 3),
(N'8', 1, 0),
(N'8', 1, 4),
(N'8', 2, 0),
(N'8', 2, 4),
(N'8', 3, 1),
(N'8', 3, 2),
(N'8', 3, 3),
(N'8', 4, 0),
(N'8', 4, 4),
(N'8', 5, 0),
(N'8', 5, 4),
(N'8', 6, 1),
(N'8', 6, 2),
(N'8', 6, 3),
(N'9', 0, 1),
(N'9', 0, 2),
(N'9', 0, 3),
(N'9', 1, 0),
(N'9', 1, 4),
(N'9', 2, 0),
(N'9', 2, 4),
(N'9', 3, 1),
(N'9', 3, 2),
(N'9', 3, 3),
(N'9', 3, 4),
(N'9', 4, 4),
(N'9', 5, 4),
(N'9', 6, 1),
(N'9', 6, 2),
(N'9', 6, 3); -- -- LETTER FONT (HEAD BLOCKER label only) ------------------------------------
DECLARE @Font TABLE
(
    ch NCHAR(1),
    r TINYINT,
    c TINYINT
);
INSERT INTO @Font
VALUES
(N'H', 0, 0),
(N'H', 0, 4),
(N'H', 1, 0),
(N'H', 1, 4),
(N'H', 2, 0),
(N'H', 2, 4),
(N'H', 3, 0),
(N'H', 3, 1),
(N'H', 3, 2),
(N'H', 3, 3),
(N'H', 3, 4),
(N'H', 4, 0),
(N'H', 4, 4),
(N'H', 5, 0),
(N'H', 5, 4),
(N'H', 6, 0),
(N'H', 6, 4),
(N'E', 0, 0),
(N'E', 0, 1),
(N'E', 0, 2),
(N'E', 0, 3),
(N'E', 0, 4),
(N'E', 1, 0),
(N'E', 2, 0),
(N'E', 3, 0),
(N'E', 3, 1),
(N'E', 3, 2),
(N'E', 3, 3),
(N'E', 4, 0),
(N'E', 5, 0),
(N'E', 6, 0),
(N'E', 6, 1),
(N'E', 6, 2),
(N'E', 6, 3),
(N'E', 6, 4),
(N'A', 0, 1),
(N'A', 0, 2),
(N'A', 0, 3),
(N'A', 1, 0),
(N'A', 1, 4),
(N'A', 2, 0),
(N'A', 2, 4),
(N'A', 3, 0),
(N'A', 3, 1),
(N'A', 3, 2),
(N'A', 3, 3),
(N'A', 3, 4),
(N'A', 4, 0),
(N'A', 4, 4),
(N'A', 5, 0),
(N'A', 5, 4),
(N'A', 6, 0),
(N'A', 6, 4),
(N'D', 0, 0),
(N'D', 0, 1),
(N'D', 0, 2),
(N'D', 0, 3),
(N'D', 1, 0),
(N'D', 1, 4),
(N'D', 2, 0),
(N'D', 2, 4),
(N'D', 3, 0),
(N'D', 3, 4),
(N'D', 4, 0),
(N'D', 4, 4),
(N'D', 5, 0),
(N'D', 5, 4),
(N'D', 6, 0),
(N'D', 6, 1),
(N'D', 6, 2),
(N'D', 6, 3),
(N'B', 0, 0),
(N'B', 0, 1),
(N'B', 0, 2),
(N'B', 0, 3),
(N'B', 1, 0),
(N'B', 1, 4),
(N'B', 2, 0),
(N'B', 2, 4),
(N'B', 3, 0),
(N'B', 3, 1),
(N'B', 3, 2),
(N'B', 3, 3),
(N'B', 4, 0),
(N'B', 4, 4),
(N'B', 5, 0),
(N'B', 5, 4),
(N'B', 6, 0),
(N'B', 6, 1),
(N'B', 6, 2),
(N'B', 6, 3),
(N'L', 0, 0),
(N'L', 1, 0),
(N'L', 2, 0),
(N'L', 3, 0),
(N'L', 4, 0),
(N'L', 5, 0),
(N'L', 6, 0),
(N'L', 6, 1),
(N'L', 6, 2),
(N'L', 6, 3),
(N'L', 6, 4),
(N'O', 0, 1),
(N'O', 0, 2),
(N'O', 0, 3),
(N'O', 1, 0),
(N'O', 1, 4),
(N'O', 2, 0),
(N'O', 2, 4),
(N'O', 3, 0),
(N'O', 3, 4),
(N'O', 4, 0),
(N'O', 4, 4),
(N'O', 5, 0),
(N'O', 5, 4),
(N'O', 6, 1),
(N'O', 6, 2),
(N'O', 6, 3),
(N'C', 0, 1),
(N'C', 0, 2),
(N'C', 0, 3),
(N'C', 0, 4),
(N'C', 1, 0),
(N'C', 2, 0),
(N'C', 3, 0),
(N'C', 4, 0),
(N'C', 5, 0),
(N'C', 6, 1),
(N'C', 6, 2),
(N'C', 6, 3),
(N'C', 6, 4),
(N'K', 0, 0),
(N'K', 0, 4),
(N'K', 1, 0),
(N'K', 1, 3),
(N'K', 2, 0),
(N'K', 2, 2),
(N'K', 3, 0),
(N'K', 3, 1),
(N'K', 4, 0),
(N'K', 4, 2),
(N'K', 5, 0),
(N'K', 5, 3),
(N'K', 6, 0),
(N'K', 6, 4),
(N'R', 0, 0),
(N'R', 0, 1),
(N'R', 0, 2),
(N'R', 0, 3),
(N'R', 1, 0),
(N'R', 1, 4),
(N'R', 2, 0),
(N'R', 2, 4),
(N'R', 3, 0),
(N'R', 3, 1),
(N'R', 3, 2),
(N'R', 3, 3),
(N'R', 4, 0),
(N'R', 4, 2),
(N'R', 5, 0),
(N'R', 5, 3),
(N'R', 6, 0),
(N'R', 6, 4);
--------------------------------------------------------------------------------

-- 3) AXES + GRID
--------------------------------------------------------------------------------  
INSERT INTO #graph
VALUES
(geometry::STGeomFromText(
                             CONCAT(
                                       'LINESTRING(',
                                       @originX,
                                       ' ',
                                       @originY,
                                       ', ',
                                       @originX + @chartW + 0.5,
                                       ' ',
                                       @originY,
                                       ')'
                                   ),
                             0
                         ).STBuffer(0.03).MakeValid(), 'axis'),
(geometry::STGeomFromText(
                             CONCAT(
                                       'LINESTRING(',
                                       @originX,
                                       ' ',
                                       @originY,
                                       ', ',
                                       @originX,
                                       ' ',
                                       @originY + @chartH + 0.5,
                                       ')'
                                   ),
                             0
                         ).STBuffer(0.03).MakeValid(), 'axis'),
(geometry::STGeomFromText(
                             CONCAT(
                                       'LINESTRING(',
                                       @originX + @chartW,
                                       ' ',
                                       @originY,
                                       ', ',
                                       @originX + @chartW,
                                       ' ',
                                       @originY + @chartH + 0.5,
                                       ')'
                                   ),
                             0
                         ).STBuffer(0.03).MakeValid(), 'axis');
DECLARE @g INT = 1;
WHILE @g <= 4
BEGIN
    INSERT INTO #graph
    VALUES
    (geometry::STGeomFromText(
                                 CONCAT(
                                           'LINESTRING(',
                                           @originX,
                                           ' ',
                                           @originY + (@g * 2.0),
                                           ', ',
                                           @originX + @chartW,
                                           ' ',
                                           @originY + (@g * 2.0),
                                           ')'
                                       ),
                                 0
                             ).STBuffer(0.02).MakeValid(), 'axis');
    SET @g += 1;
END;
--------------------------------------------------------------------------------
-- 4) WAIT TIME LINE + DOTS + SPID PIN MARKERS
--------------------------------------------------------------------------------  
DECLARE @prevX FLOAT,
        @prevY FLOAT,
        @currX FLOAT,
        @currY FLOAT,
        @prevX2 FLOAT,
        @prevY2 FLOAT;
DECLARE @spid INT,
        @bspid INT,
        @wait FLOAT,
        @px INT,
        @isBlocked INT,
        @isHead INT;
DECLARE @dotColour VARCHAR(30);
DECLARE line_cur CURSOR FAST_FORWARD FOR
SELECT spid,
       blocking_spid,
       wait_secs,
       plot_x,
       is_blocked,
       is_head_blocker
FROM #blocking
ORDER BY plot_x;
SET @prevX = NULL;
OPEN line_cur;
FETCH NEXT FROM line_cur
INTO @spid,
     @bspid,
     @wait,
     @px,
     @isBlocked,
     @isHead;
WHILE @@FETCH_STATUS = 0
BEGIN
    SET @currX = @originX + (CAST(@px AS FLOAT) / @maxX) * @chartW;
    SET @currY = @originY + (@wait / @maxWait) * @chartH; -- Line segment connecting dots
    IF @prevX IS NOT NULL
        INSERT INTO #graph
        VALUES
        (geometry::STGeomFromText(CONCAT('LINESTRING(', @prevX, ' ', @prevY, ', ', @currX, ' ', @currY, ')'), 0).STBuffer(0.05).MakeValid(),
         'waitline'); -- Dot colour based on status
    SET @dotColour = CASE
                         WHEN @isHead = 1 THEN
                             'headblocker_dot'
                         WHEN @isBlocked = 1 THEN
                             'blocked_dot'
                         ELSE
                             'dot'
                     END; -- Main dot
    INSERT INTO #graph
    VALUES
    (geometry::Point(@currX, @currY, 0).STBuffer(0.22).MakeValid(), @dotColour); -- Vertical blocking marker line for blocked / head blocker
    IF @isBlocked = 1
       OR @isHead = 1
        INSERT INTO #graph
        VALUES
        (   geometry::STGeomFromText(
                                        CONCAT(
                                                  'LINESTRING(',
                                                  @currX,
                                                  ' ',
                                                  @originY,
                                                  ', ',
                                                  @currX,
                                                  ' ',
                                                  @originY + @chartH,
                                                  ')'
                                              ),
                                        0
                                    ).STBuffer(0.04).MakeValid(), CASE
                                                                      WHEN @isHead = 1 THEN
                                                                          'headblocker_dot'
                                                                      ELSE
                                                                          'blocked_dot'
                                                                  END); -- SPID number only shown for blocked SPIDs and head blockers
    IF @isBlocked = 1
       OR @isHead = 1
    BEGIN
        DECLARE @dScale DECIMAL(6, 3) = 0.12;
        DECLARE @dStep DECIMAL(6, 3) = (5 * @dScale) + @dScale;
        DECLARE @spidStr VARCHAR(10) = CAST(@spid AS VARCHAR(10));
        DECLARE @dX DECIMAL(8, 4) = CAST(@currX - (LEN(@spidStr) * @dStep / 2.0) AS DECIMAL(8, 4));
        DECLARE @dY DECIMAL(8, 4) = CAST(@currY + 0.55 AS DECIMAL(8, 4)); -- above dot, below label
        DECLARE @di INT = 1;
        DECLARE @dch NCHAR(1);
        WHILE @di <= LEN(@spidStr)
        BEGIN
            SET @dch = SUBSTRING(@spidStr, @di, 1);
            INSERT INTO #graph
            (
                shape,
                colour
            )
            SELECT s,
                   @dotColour
            FROM
            (
                SELECT geometry::STGeomFromText(
                                                   CONCAT(
                                                             'POLYGON((',
                                                             CAST(ROUND(@dX + ((@di - 1) * @dStep) + (c * @dScale), 4) AS VARCHAR(14)),
                                                             ' ',
                                                             CAST(ROUND(@dY + ((6 - r) * @dScale), 4) AS VARCHAR(14)),
                                                             ',',
                                                             CAST(ROUND(
                                                                           @dX + ((@di - 1) * @dStep)
                                                                           + ((c + 1) * @dScale),
                                                                           4
                                                                       ) AS VARCHAR(14)),
                                                             ' ',
                                                             CAST(ROUND(@dY + ((6 - r) * @dScale), 4) AS VARCHAR(14)),
                                                             ',',
                                                             CAST(ROUND(
                                                                           @dX + ((@di - 1) * @dStep)
                                                                           + ((c + 1) * @dScale),
                                                                           4
                                                                       ) AS VARCHAR(14)),
                                                             ' ',
                                                             CAST(ROUND(@dY + ((6 - r + 1) * @dScale), 4) AS VARCHAR(14)),
                                                             ',',
                                                             CAST(ROUND(@dX + ((@di - 1) * @dStep) + (c * @dScale), 4) AS VARCHAR(14)),
                                                             ' ',
                                                             CAST(ROUND(@dY + ((6 - r + 1) * @dScale), 4) AS VARCHAR(14)),
                                                             ',',
                                                             CAST(ROUND(@dX + ((@di - 1) * @dStep) + (c * @dScale), 4) AS VARCHAR(14)),
                                                             ' ',
                                                             CAST(ROUND(@dY + ((6 - r) * @dScale), 4) AS VARCHAR(14)),
                                                             '))'
                                                         ),
                                                   0
                                               ).MakeValid() AS s
                FROM @Digits
                WHERE ch = @dch
            ) q
            WHERE s IS NOT NULL
                  AND s.STIsValid() = 1
                  AND s.STGeometryType() <> 'Point'
                  AND s.STArea() > 0.00001;
            SET @di += 1;
        END;
    END; -- end IF @isBlocked / @isHead  SET @prevX = @currX; SET @prevY = @currY;
    FETCH NEXT FROM line_cur
    INTO @spid,
         @bspid,
         @wait,
         @px,
         @isBlocked,
         @isHead;
END;
CLOSE line_cur;
DEALLOCATE line_cur;
--------------------------------------------------------------------------------
-- 5) BLOCKED COUNT LINE (right Y axis)
--------------------------------------------------------------------------------  
DECLARE @runCount FLOAT = 0;
DECLARE @cpx INT,
        @cblocked INT;
DECLARE count_cur CURSOR FAST_FORWARD FOR
SELECT plot_x,
       is_blocked
FROM #blocking
ORDER BY plot_x;
SET @prevX2 = NULL;
OPEN count_cur;
FETCH NEXT FROM count_cur
INTO @cpx,
     @cblocked;
WHILE @@FETCH_STATUS = 0
BEGIN
    SET @runCount += @cblocked;
    SET @currX = @originX + (CAST(@cpx AS FLOAT) / @maxX) * @chartW;
    SET @currY = @originY + (@runCount / @maxCount) * @chartH;
    IF @prevX2 IS NOT NULL
        INSERT INTO #graph
        VALUES
        (geometry::STGeomFromText(CONCAT('LINESTRING(', @prevX2, ' ', @prevY2, ', ', @currX, ' ', @currY, ')'), 0).STBuffer(0.05).MakeValid(),
         'countline');
    SET @prevX2 = @currX;
    SET @prevY2 = @currY;
    FETCH NEXT FROM count_cur
    INTO @cpx,
         @cblocked;
END;
CLOSE count_cur;
DEALLOCATE count_cur;
--------------------------------------------------------------------------------
-- 6) HEAD BLOCKER DIAMOND + PIXEL "HEAD BLOCKER" LABEL
--------------------------------------------------------------------------------  
DECLARE @lbScale DECIMAL(6, 3) = 0.10;
DECLARE @lbStep DECIMAL(6, 3) = (5 * @lbScale) + @lbScale;
DECLARE @lbX DECIMAL(8, 4),
        @lbY1 DECIMAL(8, 4),
        @lbY2 DECIMAL(8, 4);
DECLARE @li INT,
        @lch NCHAR(1);
DECLARE @lbWord1 NVARCHAR(10) = N'HEAD';
DECLARE @lbWord2 NVARCHAR(10) = N'BLOCKER';
DECLARE @hspid INT,
        @hpx INT,
        @hwait FLOAT;
DECLARE hb_cur CURSOR FAST_FORWARD FOR
SELECT spid,
       plot_x,
       wait_secs
FROM #blocking
WHERE is_head_blocker = 1;
OPEN hb_cur;
FETCH NEXT FROM hb_cur
INTO @hspid,
     @hpx,
     @hwait;
WHILE @@FETCH_STATUS = 0
BEGIN
    SET @currX = @originX + (CAST(@hpx AS FLOAT) / @maxX) * @chartW;
    SET @currY = @originY + (@hwait / @maxWait) * @chartH; -- Diamond marker
    INSERT INTO #graph
    VALUES
    (geometry::STGeomFromText(
                                 CONCAT(
                                           'POLYGON((',
                                           @currX,
                                           ' ',
                                           @currY + 0.5,
                                           ',',
                                           @currX + 0.45,
                                           ' ',
                                           @currY,
                                           ',',
                                           @currX,
                                           ' ',
                                           @currY - 0.5,
                                           ',',
                                           @currX - 0.45,
                                           ' ',
                                           @currY,
                                           ',',
                                           @currX,
                                           ' ',
                                           @currY + 0.5,
                                           '))'
                                       ),
                                 0
                             ).MakeValid(), 'headblocker_dot');
    SET @lbX = CAST(@currX - 0.3 AS DECIMAL(8, 4));
    SET @lbY1 = CAST(@currY + 1.8 AS DECIMAL(8, 4)); -- "HEAD" higher up
    SET @lbY2 = CAST(@currY + 1.8 + (@lbScale * 9) AS DECIMAL(8, 4)); -- "BLOCKER" below "HEAD"  -- Stamp "HEAD"
    SET @li = 1;
    WHILE @li <= LEN(@lbWord1)
    BEGIN
        SET @lch = SUBSTRING(@lbWord1, @li, 1);
        INSERT INTO #graph
        (
            shape,
            colour
        )
        SELECT s,
               'hb_label'
        FROM
        (
            SELECT geometry::STGeomFromText(
                                               CONCAT(
                                                         'POLYGON((',
                                                         CAST(ROUND(@lbX + ((@li - 1) * @lbStep) + (c * @lbScale), 4) AS VARCHAR(14)),
                                                         ' ',
                                                         CAST(ROUND(@lbY1 + ((6 - r) * @lbScale), 4) AS VARCHAR(14)),
                                                         ',',
                                                         CAST(ROUND(
                                                                       @lbX + ((@li - 1) * @lbStep)
                                                                       + ((c + 1) * @lbScale),
                                                                       4
                                                                   ) AS VARCHAR(14)),
                                                         ' ',
                                                         CAST(ROUND(@lbY1 + ((6 - r) * @lbScale), 4) AS VARCHAR(14)),
                                                         ',',
                                                         CAST(ROUND(
                                                                       @lbX + ((@li - 1) * @lbStep)
                                                                       + ((c + 1) * @lbScale),
                                                                       4
                                                                   ) AS VARCHAR(14)),
                                                         ' ',
                                                         CAST(ROUND(@lbY1 + ((6 - r + 1) * @lbScale), 4) AS VARCHAR(14)),
                                                         ',',
                                                         CAST(ROUND(@lbX + ((@li - 1) * @lbStep) + (c * @lbScale), 4) AS VARCHAR(14)),
                                                         ' ',
                                                         CAST(ROUND(@lbY1 + ((6 - r + 1) * @lbScale), 4) AS VARCHAR(14)),
                                                         ',',
                                                         CAST(ROUND(@lbX + ((@li - 1) * @lbStep) + (c * @lbScale), 4) AS VARCHAR(14)),
                                                         ' ',
                                                         CAST(ROUND(@lbY1 + ((6 - r) * @lbScale), 4) AS VARCHAR(14)),
                                                         '))'
                                                     ),
                                               0
                                           ).MakeValid() AS s
            FROM @Font
            WHERE ch = @lch
        ) q
        WHERE s IS NOT NULL
              AND s.STIsValid() = 1
              AND s.STGeometryType() <> 'Point'
              AND s.STArea() > 0.00001;
        SET @li += 1;
    END; -- Stamp "BLOCKER"
    SET @li = 1;
    WHILE @li <= LEN(@lbWord2)
    BEGIN
        SET @lch = SUBSTRING(@lbWord2, @li, 1);
        INSERT INTO #graph
        (
            shape,
            colour
        )
        SELECT s,
               'hb_label'
        FROM
        (
            SELECT geometry::STGeomFromText(
                                               CONCAT(
                                                         'POLYGON((',
                                                         CAST(ROUND(@lbX + ((@li - 1) * @lbStep) + (c * @lbScale), 4) AS VARCHAR(14)),
                                                         ' ',
                                                         CAST(ROUND(@lbY2 + ((6 - r) * @lbScale), 4) AS VARCHAR(14)),
                                                         ',',
                                                         CAST(ROUND(
                                                                       @lbX + ((@li - 1) * @lbStep)
                                                                       + ((c + 1) * @lbScale),
                                                                       4
                                                                   ) AS VARCHAR(14)),
                                                         ' ',
                                                         CAST(ROUND(@lbY2 + ((6 - r) * @lbScale), 4) AS VARCHAR(14)),
                                                         ',',
                                                         CAST(ROUND(
                                                                       @lbX + ((@li - 1) * @lbStep)
                                                                       + ((c + 1) * @lbScale),
                                                                       4
                                                                   ) AS VARCHAR(14)),
                                                         ' ',
                                                         CAST(ROUND(@lbY2 + ((6 - r + 1) * @lbScale), 4) AS VARCHAR(14)),
                                                         ',',
                                                         CAST(ROUND(@lbX + ((@li - 1) * @lbStep) + (c * @lbScale), 4) AS VARCHAR(14)),
                                                         ' ',
                                                         CAST(ROUND(@lbY2 + ((6 - r + 1) * @lbScale), 4) AS VARCHAR(14)),
                                                         ',',
                                                         CAST(ROUND(@lbX + ((@li - 1) * @lbStep) + (c * @lbScale), 4) AS VARCHAR(14)),
                                                         ' ',
                                                         CAST(ROUND(@lbY2 + ((6 - r) * @lbScale), 4) AS VARCHAR(14)),
                                                         '))'
                                                     ),
                                               0
                                           ).MakeValid() AS s
            FROM @Font
            WHERE ch = @lch
        ) q
        WHERE s IS NOT NULL
              AND s.STIsValid() = 1
              AND s.STGeometryType() <> 'Point'
              AND s.STArea() > 0.00001;
        SET @li += 1;
    END;
    FETCH NEXT FROM hb_cur
    INTO @hspid,
         @hpx,
         @hwait;
END;
CLOSE hb_cur;
DEALLOCATE hb_cur;
--------------------------------------------------------------------------------
-- 7) DUMMY + OUTPUT
--------------------------------------------------------------------------------  
-- Dummy absorbs slot 4 (purple) — off-screen
INSERT INTO #graph
VALUES
(geometry::Point(-0.5, -0.5, 0).STBuffer(0.01).MakeValid(), 'dummy');
SELECT colour,
       geometry::UnionAggregate(shape).Reduce(0.001).MakeValid() AS shape
FROM #graph
WHERE shape IS NOT NULL
      AND shape.STIsValid() = 1
      AND shape.STGeometryType() <> 'Point'
      AND shape.STGeometryType() <> 'MultiPoint'
      AND shape.STArea() > 0.00001
GROUP BY colour
ORDER BY CASE colour
             WHEN 'axis' THEN
                 1
             WHEN 'waitline' THEN
                 2
             WHEN 'dot' THEN
                 3
             WHEN 'dummy' THEN
                 4 -- absorbs purple slot
             WHEN 'blocked_dot' THEN
                 5
             WHEN 'countline' THEN
                 6
             WHEN 'headblocker_dot' THEN
                 7
             WHEN 'hb_label' THEN
                 8 -- HEAD BLOCKER text separate slot
             ELSE
                 9
         END;
DROP TABLE IF EXISTS #blocking;
DROP TABLE IF EXISTS #graph;
If it works it looks like this

Run on 

Credit Where It's Due

This started with Brent Ozar's How to Draw Line Graphs in SSMS — if you haven't read it, go and find it. He showed that SQL Server's spatial viewer could do practical data visualisation. I just took it sideways into blocking diagnostics via a slight detour through shamrocks and Easter eggs.

Thanks Brent. I think.

If you've made it this far through the SQL spatial art series — shamrocks, Guinness, Easter eggs, Christmas trees, and now a blocking visualiser — I genuinely don't know what to say other than: you are my kind of person, and your production server is probably fine.

Probably.

Resources

Rate

You rated this post out of 5. Change rating

Share

Share

Rate

You rated this post out of 5. Change rating