Problem
Random number generation is vital in computer science, supporting fields like optimization, simulation, robotics, and gaming. The quality, speed, and sometimes security of the generator can directly affect an algorithm’s correctness, performance, and competitiveness. In Python, random number generation is well-supported and widely used. In this article, we will look how to we can use SQL to do this.
Solution
Following are several methods that can be used to generate random numbers.
Permuted Congruential Generator (PCG) Function
The PCG takes a simple and fast random Congruential base generator and combine with Permuted logic. The Permuted logic is used to the scramble the numbers. A permutation math trick is used to make the numbers look more random or evenly spread out. It is hard to guess the next number compared to other generators, but it is still not secure enough for serious cryptography.
Algorithms are used to produce a stream of numbers to match statistical expectations and I choose to work with PCG64.
Let´s create a function to generate the PCG 64 bits that will generate random number between @low and @high values. The @int1 and @int2 are random generated numbers that needs to be supplied outside the function.
-- =============================================
-- Author: SCP - MSSQLTips
-- Create date: 20250813
-- Description: Permuted congruential generator
-- =============================================
CREATE OR ALTER FUNCTION [dbo].[ufnRndGenPCG64]
(@int1 bigint
,@int2 bigint
,@low float
,@high float)
RETURNS float
AS
BEGIN
DECLARE @low32 bigint = (@int1 + 2147483648);
DECLARE @high32 bigint = (@int2 + 2147483648);
DECLARE @u float = CONVERT(float, @high32 * 4294967296 + @low32) / 18446744073709551616.0;
DECLARE @result float = @low + (@high - @low) * @u;
RETURN @result;
END
GOBox-Muller Transformation Function
The Box-Muller Transformation function generates pairs of independent normally standard distributed random numbers. You can find more information at WIKIPEDIA – Box-Muller transform.
-- =============================================
-- Author: SCP - MSSQLTips
-- Create date: 20250813
-- Description: Box-Muller transformation
-- =============================================
CREATE OR ALTER FUNCTION [dbo].[ufnRndGenBoxMuller]
(@u1 float
,@u2 float
,@mean float
,@std float)
RETURNS float
AS
BEGIN
DECLARE @z0 float = SQRT(-2 * LOG(@U1)) * COS(2 * PI() * @U2);
DECLARE @result float = @mean + @std * @z0;
RETURN @result;
END
GOSpiral Shape
Spiral is a curve which emanates from a point, moving farther away as it revolves around the point, where
, y
, and ![]()
The variable a is the initial starting radius, and the variable b is the radius grow rate, so the spiral starts at the origin when r = 0 and grows linearly with theta, and a larger b makes the spiral expand faster. When b = 0 the spiral degenerates to a circle.
-- =============================================
-- Author: SCP - MSSQLTips
-- Create date: 20250815
-- Description: Spiral shaped-bounded random
-- =============================================
CREATE OR ALTER PROCEDURE [dbo].[uspRandGenShapeSpiral]
(@points int
,@a float
,@b float
,@l float
,@cx float
,@cy float
,@fx bit
,@fy bit
,@j float
,@r float)
WITH EXECUTE AS CALLER
AS
BEGIN;
/*
@a initial radius
@b radius increment per angle
@l number of full loops
@cx x axis offset
@cy y axis offset
@fx x flip direction where 1 means left
@fy y flip direction where 1 means vertical inversion
@j random radius jitter
@r spatial point ratio
*/
DECLARE @Values
TABLE (id int
,r float
,theta float
,x float
,y float);
DECLARE @i int = 1;
WHILE @i <= @points BEGIN
DECLARE @theta float = @l * 2 * PI() * (CONVERT(float, @i) / (@points - 1));
INSERT INTO @Values
(id
,r
,theta)
SELECT @i
,@a + @b * @theta +
(([dbo].[ufnRndGenPCG64] (CHECKSUM(NEWID()),CHECKSUM(NEWID()),0,1) - 0.5) * @j) AS r
,@theta AS theta;
SET @i += 1;
END
UPDATE @Values
SET x = CASE WHEN @fx = 1
THEN -1
ELSE 1
END * (@a + @b * theta + r) * COS(theta) + @cx
,y = CASE WHEN @fy = 1
THEN -1
ELSE 1
END * (@a + @b * theta + r) * SIN(theta) + @cy;
SELECT id
,x
,y
,([geometry]::Point([x],[y],(0))).STBuffer(@r)
FROM @Values
RETURN;
END
GOExample – Play with the numbers below to see its effects. To visualize the points, it is necessary to adjust the geometry feature @r which is the radius of each point to be plotted at the Spatial results tab.
-- MSSQLTips (TSQL)
DECLARE @points int = 1000
,@a float = 0.7
,@b float = 0.2
,@l float = 2
,@cx float = 0
,@cy float = 0
,@fx bit = 0
,@fy bit = 0
,@j float = .5
,@r float = 0.08;
EXECUTE [dbo].[uspRandGenShapeSpiral] @points,@a,@b,@l,@cx,@cy,@fx,@fy,@j,@r;
GOResulting in:

Polygonal Shape
First, we need to create a table to hold the polygon vertices. It is very important that the start vertices must be the same as the end vertices to be considered a closed polygonal. See more details at MICROSOFT – Polygon.
CREATE TABLE [dbo].[Polygon](
[vertice] [int] IDENTITY(1,1) NOT NULL,
[x] [float] NULL,
[y] [float] NULL,
CONSTRAINT [PK_Polygon] PRIMARY KEY CLUSTERED
(
[vertice] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GOThis is the store procedure to generate the random values inside the polygon.
-- =============================================
-- Author: SCP - MSSQLTips
-- Create date: 20250818
-- Description: Polygon shaped-bounded random
-- =============================================
CREATE OR ALTER PROCEDURE [dbo].[uspRandGenShapePolygon]
(@Points int
,@r float)
WITH EXECUTE AS CALLER
AS
BEGIN;
SET NOCOUNT ON;
IF (SELECT COUNT(*) FROM [dbo].[Polygon]) < 3 AND
(SELECT CASE
WHEN (SELECT TOP 1 x FROM [dbo].[Polygon] ORDER BY [vertice] ASC) =
(SELECT TOP 1 x FROM [dbo].[Polygon] ORDER BY [vertice] DESC) AND
(SELECT TOP 1 y FROM [dbo].[Polygon] ORDER BY [vertice] ASC) =
(SELECT TOP 1 y FROM [dbo].[Polygon] ORDER BY [vertice] DESC)
THEN 1
ELSE 0
END AS IsClosed) = 0 BEGIN
PRINT ('Polygon must be closed!')
RETURN;
END;
BEGIN TRY
DECLARE @Values
TABLE (id int
,x float
,y float);
DECLARE @poly geometry
,@x float
,@xMax float
,@yMax float
,@y float
,@xMin float
,@yMin float
,@w nvarchar(MAX)
,@p int = 1;
SELECT @w = 'POLYGON((' + STRING_AGG(CAST(x AS nvarchar(10)) + ' ' + CAST(y AS nvarchar(10)), ', ') + '))'
FROM [dbo].[Polygon];
SET @poly = geometry::STGeomFromText(@w, 0);
SELECT @xMax = MAX(x)
,@xMin = MIN(x)
,@yMax = MAX(y)
,@yMin = MIN(y)
FROM [dbo].[Polygon];
WHILE @p <= @Points BEGIN
SET @x = [dbo].[ufnRndGenPCG64] (CHECKSUM(NEWID()),CHECKSUM(NEWID()),@xMin,@xMax);
SET @y = [dbo].[ufnRndGenPCG64] (CHECKSUM(NEWID()),CHECKSUM(NEWID()),@yMin,@yMax);
IF @poly.STContains((geometry::Point(@x, @y, 0))) = 1 BEGIN
INSERT INTO @Values
VALUES (@p
,@x
,@y);
SET @p += 1;
END
END
SELECT 'Polygon' AS Id
,NULL AS x
,NULL AS y
,@poly.STBoundary() AS geom
UNION ALL
SELECT CONVERT(nvarchar(10),id)
,x
,y
,([geometry]::Point([x],[y],(0))).STBuffer(@r)
FROM @Values;
RETURN;
END TRY
BEGIN CATCH
PRINT 'Error: ' + CONVERT(varchar(50), ERROR_NUMBER()) +
', Severity: ' + CONVERT(varchar(5), ERROR_SEVERITY()) +
', State: ' + CONVERT(varchar(5), ERROR_STATE()) +
', Procedure: ' + ISNULL(ERROR_PROCEDURE(), '-') +
', Line: ' + CONVERT(varchar(5), ERROR_LINE());
PRINT ERROR_MESSAGE();
END CATCH;
END
GOTRIANGLE
-- MSSQLTips (TSQL)
TRUNCATE TABLE [dbo].[Polygon];
INSERT INTO [dbo].[Polygon]
VALUES (1,1),(4,3),(8,0),(1,1);
EXEC [dbo].[uspRandGenShapePolygon] 1000,0.03;
GOResulting in

RECTANGLE / SQUARE
-- MSSQLTips (TSQL)
TRUNCATE TABLE [dbo].[Polygon];
INSERT INTO [dbo].[Polygon]
VALUES (0,0),(0,3),(3,3),(3,0),(0,0);
EXEC [dbo].[uspRandGenShapePolygon] 1000,0.03;
GOResulting in:

POLYGON
-- MSSQLTips (TSQL)
TRUNCATE TABLE [dbo].[Polygon];
INSERT INTO [dbo].[Polygon]
VALUES (0,1),(2,0.5),(3,1),(2,2.5),(0.5,2),(0,1);
EXEC [dbo].[uspRandGenShapePolygon] 1000,0.03;
GOResulting in:

Combining Two or More Polygons
To change the location of a polygon you can change its offset and scale.
DECLARE @Values
TABLE ([Id] nvarchar(20)
,[x] float
,[y] float
,[geom] geometry
,[src] smallint);
TRUNCATE TABLE [dbo].[Polygon];
INSERT INTO [dbo].[Polygon]
VALUES (0,1),(2,0.5),(3,1),(2,2.5),(0.5,2),(0,1);
INSERT INTO @Values
(Id,x,y,geom)
EXEC [dbo].[uspRandGenShapePolygon] 1000,0.03;
-- changing the offset to the left
UPDATE @Values
SET src = 1
,x = -2 + x
,y = -2 + y
WHERE src IS NULL;
TRUNCATE TABLE [dbo].[Polygon];
INSERT INTO [dbo].[Polygon]
VALUES (1,1),(4,3),(8,0),(1,1);
INSERT INTO @Values
(Id,x,y,geom)
EXEC [dbo].[uspRandGenShapePolygon] 1000,0.03;
DELETE FROM @Values WHERE x IS NULL;
UPDATE @Values
SET geom = ([geometry]::Point([x],[y],(0))).STBuffer(0.05);
SELECT * FROM @Values;
GOResulting in:

Happy Face Shape
-- MSSQLTips (TSQL)
SET NOCOUNT ON;
DECLARE @face int = 500
,@eye int = 50
,@eye_radius float = 0.08
,@smile int = 200
,@smile_radius float = 0.6
,@smile_start float = PI() / 6 -- 30 degrees
,@smile_end float = 5 * PI() / 6 --150 degrees
,@theta float
,@r float
,@mean float = 0.0
,@std float = 0.02
,@low float = 0
,@high float = 2 * PI()
,@i int = 1;
DECLARE @Values
TABLE (id int IDENTITY
,r float
,theta float
,src smallint
,x float
,y float);
-- face =========================================
WHILE @i <= @face BEGIN
INSERT INTO @Values
(r
,theta
,src)
VALUES (1 + [dbo].[ufnRndGenBoxMuller] (RAND(CHECKSUM(NEWID())),RAND(CHECKSUM(NEWID())),@mean,@std)
,[dbo].[ufnRndGenPCG64] (CHECKSUM(NEWID()),CHECKSUM(NEWID()),@low,@high)
,0);
SET @i += 1;
END
UPDATE @Values
SET x = r * COS(theta)
,y = r * SIN(theta)
WHERE src = 0;
-- left eye =====================================
SET @i = 1;
WHILE @i <= @eye BEGIN
INSERT INTO @Values
(r
,theta
,src)
VALUES ([dbo].[ufnRndGenBoxMuller] (RAND(CHECKSUM(NEWID())),RAND(CHECKSUM(NEWID())),@mean,@std / 4)
,[dbo].[ufnRndGenPCG64] (CHECKSUM(NEWID()),CHECKSUM(NEWID()),@low,@high)
,1);
SET @i += 1;
END
UPDATE @Values
SET x = r - 0.35 + @eye_radius * COS(theta)
,y = r + 0.35 + @eye_radius * SIN(theta)
WHERE src = 1;
-- right eye ====================================
SET @i = 1;
WHILE @i <= @eye BEGIN
INSERT INTO @Values
(r
,theta
,src)
VALUES ([dbo].[ufnRndGenBoxMuller] (RAND(CHECKSUM(NEWID())),RAND(CHECKSUM(NEWID())),@mean,@std / 4)
,[dbo].[ufnRndGenPCG64] (CHECKSUM(NEWID()),CHECKSUM(NEWID()),@low,@high)
,2);
SET @i += 1;
END
UPDATE @Values
SET x = r + 0.35 + @eye_radius * COS(theta)
,y = r + 0.35 + @eye_radius * SIN(theta)
WHERE src = 2;
-- smile ========================================
SET @i = 1;
WHILE @i <= @smile BEGIN
INSERT INTO @Values
(r
,theta
,src)
VALUES ([dbo].[ufnRndGenBoxMuller] (RAND(CHECKSUM(NEWID())),RAND(CHECKSUM(NEWID())),@mean,@std)
,[dbo].[ufnRndGenPCG64] (CHECKSUM(NEWID()),CHECKSUM(NEWID()),@smile_start,@smile_end)
--,@smile_start + (CAST(@i - 1 AS FLOAT) / (@smile - 1)) * (@smile_end - @smile_start)
,3);
SET @i += 1;
END
UPDATE @Values
SET x = -(r + @smile_radius * COS(theta))
,y = -(r + 0.15 + 0.3 * SQUARE(SIN(theta)))
WHERE src = 3;
SELECT x
,y
,([geometry]::Point([x],[y],(0))).STBuffer(0.03) AS geom
FROM @Values;
GOResulting in:

For a sad face just remove the negative signal of x and y for src = 3.
Observe the fact that working with the values r and theta you can obtain the format that you wish when applying to x and y values.
Next Steps

Sebastião Pereira has over 40 years of experience in database development including T-SQL, algorithm design, machine learning and bringing innovative mathematical formulas to SQL Server. He started his career at a transnational fast-moving consumer goods (FMCG) company as an employee then later transitioning into a consultant role. He eventually founded his own company to develop software solutions for the healthcare industry. Sebastião is a respected award-winning author on MSSQLTips.com extending SQL Server capabilities beyond traditional workloads.
- MSSQLTips Awards
- Author of the Year – 2025
- Trendsetter (25+ tips) – 2025
- Rookie of the Year – 2024


