Waiting for PostgreSQL 16 – Invent random_normal() to provide normally-distributed random numbers.

On 9th of January 2023, Tom Lane committed patch:

Invent random_normal() to provide normally-distributed random numbers.
 
There is already a version of this in contrib/tablefunc, but it
seems sufficiently widely useful to justify having it in core.
 
Paul Ramsey
 
Discussion: https://postgr.es/m/CACowWR0DqHAvOKUCNxTrASFkWsDLqKMd6WiXvVvaWg4pV1BMnQ@mail.gmail.com

Description tells is all, but let's see how that works.

Let's generate som random_normal() values, which can be done using:

=$ SELECT random_normal() AS v FROM generate_series(1,10);
          v           
----------------------
   2.0769129968970774
   0.4215059076426097
    1.085988642195048
  -0.5816049253906204
   0.8020449529886824
 -0.28712441889530904
   0.3608747983496689
   1.9320722370416128
   1.4822705736995292
  -1.8661221359997575
(10 ROWS)

and let's see the distribution of values. Let's say that we want to group them in ranges of 0.1, for example like this:

=$ SELECT round(random_normal()::NUMERIC, 1) AS v FROM generate_series(1,10)
  v   
------
 -2.2
  0.2
 -0.4
 -1.3
  0.3
 -2.5
 -0.5
  1.4
  0.3
  0.0
(10 ROWS)

and now let's make lots of them, group by the rounded value, get count, and perhaps use this to generate “a graph":

=$ SELECT
    round(
        random_normal()::pg_catalog.numeric,
        1
    ) AS v,
    COUNT(*),
    repeat('#', (COUNT(*) / 100)::int4)
FROM
    generate_series( 1, 100000 )
GROUP BY
    1
ORDER BY
    1;
  v   | COUNT |                  repeat                  
------+-------+------------------------------------------
 -4.6 |     1 | 
 -4.0 |     1 | 
 -3.9 |     6 | 
 -3.7 |     5 | 
 -3.6 |     5 | 
 -3.5 |     9 | 
 -3.4 |    12 | 
 -3.3 |     9 | 
 -3.2 |    23 | 
 -3.1 |    36 | 
 -3.0 |    50 | 
 -2.9 |    49 | 
 -2.8 |    90 | 
 -2.7 |   125 | #
 -2.6 |   115 | #
 -2.5 |   181 | #
 -2.4 |   217 | ##
 -2.3 |   286 | ##
 -2.2 |   360 | ###
 -2.1 |   436 | ####
 -2.0 |   521 | #####
 -1.9 |   682 | ######
 -1.8 |   777 | #######
 -1.7 |   963 | #########
 -1.6 |  1085 | ##########
 -1.5 |  1317 | #############
 -1.4 |  1531 | ###############
 -1.3 |  1718 | #################
 -1.2 |  1982 | ###################
 -1.1 |  2137 | #####################
 -1.0 |  2425 | ########################
 -0.9 |  2652 | ##########################
 -0.8 |  2949 | #############################
 -0.7 |  3090 | ##############################
 -0.6 |  3395 | #################################
 -0.5 |  3529 | ###################################
 -0.4 |  3759 | #####################################
 -0.3 |  3827 | ######################################
 -0.2 |  3916 | #######################################
 -0.1 |  3898 | ######################################
  0.0 |  3952 | #######################################
  0.1 |  4017 | ########################################
  0.2 |  3924 | #######################################
  0.3 |  3822 | ######################################
  0.4 |  3656 | ####################################
  0.5 |  3601 | ####################################
  0.6 |  3391 | #################################
  0.7 |  3223 | ################################
  0.8 |  2891 | ############################
  0.9 |  2688 | ##########################
  1.0 |  2317 | #######################
  1.1 |  2195 | #####################
  1.2 |  1897 | ##################
  1.3 |  1685 | ################
  1.4 |  1389 | #############
  1.5 |  1272 | ############
  1.6 |  1054 | ##########
  1.7 |   932 | #########
  1.8 |   750 | #######
  1.9 |   619 | ######
  2.0 |   538 | #####
  2.1 |   386 | ###
  2.2 |   375 | ###
  2.3 |   285 | ##
  2.4 |   250 | ##
  2.5 |   183 | #
  2.6 |   140 | #
  2.7 |    94 | 
  2.8 |    78 | 
  2.9 |    57 | 
  3.0 |    48 | 
  3.1 |    35 | 
  3.2 |    19 | 
  3.3 |    22 | 
  3.4 |    11 | 
  3.5 |    12 | 
  3.6 |     8 | 
  3.7 |     5 | 
  3.8 |     4 | 
  3.9 |     3 | 
  4.0 |     2 | 
  4.2 |     1 | 
(82 ROWS)

We can, of course, use the params to move result set a bit, or make it wider/narrower:

=$ SELECT
    round(
        random_normal(10, 0.3)::pg_catalog.numeric,
        1
    ) AS v,
    COUNT(*),
    repeat('#', (COUNT(*) / 100)::int4)
FROM
    generate_series( 1, 100000 )
GROUP BY
    1
ORDER BY
    1;
  v   | COUNT |                                                                repeat                                                                 
------+-------+---------------------------------------------------------------------------------------------------------------------------------------
  8.6 |     1 | 
  8.7 |     3 | 
  8.8 |     5 | 
  8.9 |    16 | 
  9.0 |    44 | 
  9.1 |   152 | #
  9.2 |   353 | ###
  9.3 |   895 | ########
  9.4 |  1818 | ##################
  9.5 |  3305 | #################################
  9.6 |  5397 | #####################################################
  9.7 |  8177 | #################################################################################
  9.8 | 10542 | #########################################################################################################
  9.9 | 12642 | ##############################################################################################################################
 10.0 | 13300 | #####################################################################################################################################
 10.1 | 12559 | #############################################################################################################################
 10.2 | 10542 | #########################################################################################################
 10.3 |  8051 | ################################################################################
 10.4 |  5464 | ######################################################
 10.5 |  3388 | #################################
 10.6 |  1861 | ##################
 10.7 |   824 | ########
 10.8 |   392 | ###
 10.9 |   191 | #
 11.0 |    49 | 
 11.1 |    19 | 
 11.2 |     7 | 
 11.3 |     3 | 
(28 ROWS)

Looks pretty awesome. It will definitely help with building some test datasets. Thanks to all involved.