SQL Art, Part 2: New Year Fireworks in SSMS (Yes, Really)

 Last time we turned SQL Server into a Christmas card.

This time we’re doing something even less sensible: fireworks.

Not charts. Not dashboards. Not “data visualization”.
Actual fireworks, built from SQL Server geometry shapes and rendered in SSMS.

What you’ll get

  • Random firework bursts in the sky

  • Rings, cores, rays, and trails

  • “HAPPY NEW YEAR” stamped in spark pixels

  • One simple way to remix it without understanding all the math

If you want a reminder that SQL is just a programming language with a storage habit, this is it.

Run it

Paste this into SSMS, execute, then view the results in the spatial viewer.

USE tempdb; GO DROP TABLE IF EXISTS #scene; DROP TABLE IF EXISTS #centers; CREATE TABLE #scene (shape geometry); CREATE TABLE #centers ( id int identity(1,1) primary key, x float not null, y float not null, r float not null ); DECLARE @Fireworks int = 6; -- bursts DECLARE @Rays int = 12; -- rays per burst -- Optional ground INSERT INTO #scene(shape) VALUES (geometry::STGeomFromText('LINESTRING(-2 0, 14 0)', 0).STBuffer(0.03)); -- Burst centers (materialized so we can reuse them) INSERT INTO #centers(x,y,r) SELECT TOP (@Fireworks) (ABS(CHECKSUM(NEWID())) / 2147483647.0) * 12.0 + 0.0, -- x in [0,12] (ABS(CHECKSUM(NEWID())) / 2147483647.0) * 5.0 + 6.5, -- y in [6.5,11.5] (ABS(CHECKSUM(NEWID())) / 2147483647.0) * 0.9 + 0.8 -- r in [0.8,1.7] FROM sys.all_objects; -- Core + ring INSERT INTO #scene(shape) SELECT geometry::Point(x, y, 0).STBuffer(r * 0.12) FROM #centers; INSERT INTO #scene(shape) SELECT geometry::Point(x, y, 0).STBuffer(r) .STDifference(geometry::Point(x, y, 0).STBuffer(r * 0.92)) FROM #centers; -- Rays ;WITH Angles AS ( SELECT TOP (@Rays) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 AS k FROM sys.all_objects ) INSERT INTO #scene(shape) SELECT geometry::STGeomFromText( CONCAT( 'LINESTRING(', CAST(c.x AS varchar(32)), ' ', CAST(c.y AS varchar(32)), ',', CAST(c.x + COS((a.k * (2.0*PI() / @Rays))) * (c.r * 1.3) AS varchar(32)), ' ', CAST(c.y + SIN((a.k * (2.0*PI() / @Rays))) * (c.r * 1.3) AS varchar(32)), ')' ), 0 ).STBuffer(0.04) FROM #centers c CROSS JOIN Angles a; -- Trails INSERT INTO #scene(shape) SELECT geometry::STGeomFromText( CONCAT( 'LINESTRING(', CAST(x AS varchar(32)), ' 0,', CAST(x AS varchar(32)), ' ', CAST(y - (r * 0.4) AS varchar(32)), ')' ), 0 ).STBuffer(0.02) FROM #centers; -------------------------------------------------------------------------------- -- Text: HAPPY NEW YEAR (rendered as spark pixels) -------------------------------------------------------------------------------- DECLARE @Scale decimal(10,4) = 0.22; -- pixel size DECLARE @Dot decimal(10,4) = @Scale * 0.30;-- spark radius DECLARE @Gap decimal(10,4) = @Scale; DECLARE @Step decimal(10,4) = (5 * @Scale) + @Gap; DECLARE @Line1 nvarchar(50) = N'HAPPY'; DECLARE @Line2 nvarchar(50) = N'NEW YEAR'; -- Position the text (tweak these to move it) DECLARE @X1 decimal(10,4) = 1.0, @Y1 decimal(10,4) = 4.7; DECLARE @X2 decimal(10,4) = 0.3, @Y2 decimal(10,4) = 3.3; DECLARE @Font TABLE (ch nchar(1), r tinyint, c tinyint); -- 5x7 pixels, r=0 top..6 bottom, c=0 left..4 right -- H INSERT @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); -- A INSERT @Font VALUES (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); -- P INSERT @Font VALUES (N'P',0,0),(N'P',0,1),(N'P',0,2),(N'P',0,3), (N'P',1,0),(N'P',1,4), (N'P',2,0),(N'P',2,4), (N'P',3,0),(N'P',3,1),(N'P',3,2),(N'P',3,3), (N'P',4,0), (N'P',5,0), (N'P',6,0); -- Y INSERT @Font VALUES (N'Y',0,0),(N'Y',0,4), (N'Y',1,0),(N'Y',1,4), (N'Y',2,1),(N'Y',2,3), (N'Y',3,2), (N'Y',4,2), (N'Y',5,2), (N'Y',6,2); -- N INSERT @Font VALUES (N'N',0,0),(N'N',0,4), (N'N',1,0),(N'N',1,1),(N'N',1,4), (N'N',2,0),(N'N',2,2),(N'N',2,4), (N'N',3,0),(N'N',3,3),(N'N',3,4), (N'N',4,0),(N'N',4,4), (N'N',5,0),(N'N',5,4), (N'N',6,0),(N'N',6,4); -- E INSERT @Font VALUES (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); -- W INSERT @Font VALUES (N'W',0,0),(N'W',0,4), (N'W',1,0),(N'W',1,4), (N'W',2,0),(N'W',2,4), (N'W',3,0),(N'W',3,2),(N'W',3,4), (N'W',4,0),(N'W',4,2),(N'W',4,4), (N'W',5,0),(N'W',5,2),(N'W',5,4), (N'W',6,1),(N'W',6,3); -- R INSERT @Font VALUES (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); -- Helper: stamp a line of text as spark dots DECLARE @i int, @len int, @ch nchar(1); -- Line 1 SET @i = 1; SET @len = LEN(@Line1); WHILE @i <= @len BEGIN SET @ch = SUBSTRING(@Line1, @i, 1); IF @ch <> N' ' BEGIN INSERT INTO #scene(shape) SELECT geometry::Point( CONVERT(float, @X1 + ((@i-1) * @Step) + (c * @Scale)), CONVERT(float, @Y1 + ((6-r) * @Scale)), 0 ).STBuffer(CONVERT(float, @Dot)) FROM @Font WHERE ch = @ch; END SET @i += 1; END -- Line 2 SET @i = 1; SET @len = LEN(@Line2); WHILE @i <= @len BEGIN SET @ch = SUBSTRING(@Line2, @i, 1); IF @ch <> N' ' BEGIN INSERT INTO #scene(shape) SELECT geometry::Point( CONVERT(float, @X2 + ((@i-1) * @Step) + (c * @Scale)), CONVERT(float, @Y2 + ((6-r) * @Scale)), 0 ).STBuffer(CONVERT(float, @Dot)) FROM @Font WHERE ch = @ch; END SET @i += 1; END SELECT * FROM #scene; DROP TABLE IF EXISTS #scene; DROP TABLE IF EXISTS #centers;

How it works

Step 1: Pick the sky coordinates

We generate @Fireworks random centers in #centers using CHECKSUM(NEWID()). That gives us repeatable “random enough” values per row, which is better than RAND() when you want variety without weird loop behaviour.

Each center has:

  • x and y for position

  • r for burst size

Step 2: Build each burst in layers

For each center:

  • Core: a small circle STBuffer(r * 0.12)

  • Ring: big circle minus slightly smaller circle using STDifference

  • Rays: @Rays line segments shot out using COS and SIN around a full circle

  • Trail: a vertical line down to the ground to fake a launch path

It’s basically procedural art, just written in SQL.

Step 3: Draw “HAPPY NEW YEAR” as spark pixels

This is my favourite part.

We define a 5x7 pixel font in a table variable @Font where each character is a set of (row, col) pixels. Then we loop through each letter and stamp little circles as “spark dots”.

It’s a tiny rendering engine, built from a table and two loops.

Remix it in 30 seconds

If you want readers to actually engage, give them these quick edits:

More chaos

DECLARE @Fireworks int = 12; DECLARE @Rays int = 24;

Bigger sparks and thicker rays

-- Rays thickness .STBuffer(0.06) -- Spark dot radius DECLARE @Dot decimal(10,4) = @Scale * 0.45;

Move the text

DECLARE @X1 decimal(10,4) = 0.2, @Y1 decimal(10,4) = 5.2; DECLARE @X2 decimal(10,4) = 0.0, @Y2 decimal(10,4) = 3.8;

One improvement you should make before publishing

Right now you end with SELECT * FROM #scene; which works, but the viewer will show multiple shapes. For a cleaner “single picture” result, output one unioned geometry, like this:

SELECT geometry::UnionAggregate(shape) AS shape FROM #scene WHERE shape IS NOT NULL;

That makes the result feel like a finished artwork, not a bag of parts.

Your next-level version

If you want this series to become something people follow, stop hard-coding glyphs inside every post. Make a reusable font table and a stored procedure like:

  • dbo.GlyphPixels(ch, r, c)

  • dbo.StampText(@text, @x, @y, @scale, @dot)

That turns a holiday gag into a tiny SQL art toolkit.

Comments

Popular posts from this blog

Free Monthly Budget Spreadsheet (UK-Friendly)

Financial Literacy and ADHD – Money, Mistakes, and Learning the Hard Way