Problem
Generating random numbers from a normal distribution is essential for accuracy and realistic modeling, simulation, inference, and algorithm design for scientific, engineering, statistical, and AI domains. How can we build a random number generator using Marsaglia Polar method in SQL Server without the use of external tools?
Solution
The Marsaglia polar method generates pairs of independent standard normal random variables from uniformly distributed random numbers. It does not use trigonometric functions to calculate angles instead uses a polar transformation.
Calculation Overview
The first step is to generate the number of rows desired with random numbers W1 and W2 varying between minus one and one.
Next calculate the value of s.

If the s value is greater than zero and less than one, we calculate the t value:

From the random number one we calculate the first normally distributed variable, using the formula:

And now the second normally distribute variable using the formula:

Let’s look at how we can build this with T-SQL.
Table to hold generated numbers
-- MSSQLTips (TSQL)
CREATE TABLE [dbo].[RndNormalDist](
[ClusterId] [int] NOT NULL,
[PointId] [int] NOT NULL,
[X] [float] NOT NULL,
[Y] [float] NOT NULL,
[SpatialPoint] AS ([geometry]::Point([x],[y],(0))),
CONSTRAINT [PK_RndNormalDist] PRIMARY KEY CLUSTERED
(
[ClusterId] ASC,
[PointId] 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]
GOMarsaglia Polar Stored Procedure
Here is the SQL code to build the functionality.
-- =============================================
-- Author: SCP - MSSQLTips
-- Create date: 20250716
-- Description: Normal Marsaglia Polar
-- =============================================
CREATE OR ALTER PROCEDURE [dbo].[uspRandGenMarsaglia]
(@Points int
,@X_Offset decimal(18,6)
,@Y_Offset decimal(18,6)
,@Scale decimal(18,6)
,@ClusterId int)
WITH EXECUTE AS CALLER
AS
BEGIN;
BEGIN TRY
DECLARE @X float
,@Y float
,@S float
,@T float
,@Count int = 0;
DECLARE @GenPoints
TABLE ([PointId] int
,[X] decimal(18, 6)
,[Y] decimal(18, 6)
,[S] decimal(18, 6)
,[T] decimal(18, 6));
WHILE @Count < @Points BEGIN
SET @X = (RAND(CHECKSUM(NEWID())) * 2) - 1;
SET @Y = (RAND(CHECKSUM(NEWID())) * 2) - 1;
SET @S = SQRT(SQUARE(@X) + SQUARE(@Y));
IF @S > 0 AND @S < 1 BEGIN
SET @Count += 1;
INSERT INTO @GenPoints
([PointId]
,[X]
,[Y]
,[S]
,[T])
VALUES (@Count
,@X
,@Y
,@S
,SQRT(-2 * LOG(@S) / @S))
END
END
SELECT [PointId]
,@X_Offset + @Scale * ([X] * [T]) AS [X]
,@Y_Offset + @Scale * ([Y] * [T]) AS [Y]
,@ClusterId
FROM @GenPoints;
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
GOIf you need more variables just add more lines for the calculation and also the return point in the SELECT clause for the temporary table.
Generating Random Numbers
Let’s generate a group of three random clusters giving the number of points, the x offset value, the y offset value, the scale, and the cluster number for each one.
-- MSSQLTips (TSQL)
TRUNCATE TABLE [dbo].[RndNormalDist];
-- Cluster 1
INSERT INTO [dbo].[RndNormalDist]
([PointId]
,[X]
,[Y]
,[ClusterId])
EXEC [dbo].[uspRandGenMarsaglia] 250,-3,-3,2,1;
-- Cluster 2
INSERT INTO [dbo].[RndNormalDist]
([PointId]
,[X]
,[Y]
,[ClusterId])
EXEC [dbo].[uspRandGenMarsaglia] 250,3,3,2.5,2;
-- Cluster 3
INSERT INTO [dbo].[RndNormalDist]
([PointId]
,[X]
,[Y]
,[ClusterId])
EXEC [dbo].[uspRandGenMarsaglia] 250,1,-1,2,3;
-- Returning the generated values
SELECT [ClusterId]
,[PointId]
,[X]
,[Y]
FROM [dbo].[RndNormalDist];
GOResulting in:

If you want to have a better visualization using the Spatial Results tab, do the following:
-- MSSQLTips (TSQL)
DECLARE @OverallExtent geometry
,@ProportionalBuffer float;
SELECT @OverallExtent = geometry::EnvelopeAggregate([SpatialPoint])
FROM [dbo].[RndNormalDist];
DECLARE @MinX float = @OverallExtent.STPointN(1).STX;
DECLARE @MinY float = @OverallExtent.STPointN(1).STY;
DECLARE @MaxX float = @OverallExtent.STPointN(3).STX;
DECLARE @MaxY float = @OverallExtent.STPointN(3).STY;
DECLARE @ExtentWidth float = @MaxX - @MinX;
DECLARE @ExtentHeight float = @MaxY - @MinY;
DECLARE @MinDimension float = IIF(@ExtentWidth < @ExtentHeight, @ExtentWidth, @ExtentHeight);
DECLARE @Ratio float = @MinDimension * 0.015;
-- Returning the clusters
SELECT [ClusterId]
,COUNT(*) AS N
,geometry::CollectionAggregate(SpatialPoint) AS geom
FROM (SELECT [ClusterId]
,SpatialPoint.STBuffer(@Ratio) AS SpatialPoint
FROM [dbo].[RndNormalDist]) AS X
GROUP BY [ClusterId];
GOResulting in:

Next Steps
- WIKIPEDIA – Marsaglia polar method
- Check out these related articles:

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



Great idea