32

How can I declare an array like variable with two or three values and get them randomly during execution?

a := [1, 2, 5] -- sample sake
select random(a) -- returns random value

Any suggestion where to start?

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
Mo J. Mughrabi
  • 6,747
  • 16
  • 85
  • 143

6 Answers6

57

Try this one:

select (array['Yes', 'No', 'Maybe'])[floor(random() * 3 + 1)];
Andrey Sarul
  • 1,382
  • 2
  • 16
  • 20
  • 2
    Would this work the same in case there is more than 3 items in the array? Also would you please know how to get 2 random items rather than 1? Thanks! – Alexis.Rolland Jan 20 '22 at 03:24
  • 2
    It would be good to clarify what the magic numbers represent. If I wanted to try this on an array with a different number of items, what magic numbers would I use? I'd imagine the `3` here represents the length of the array, then regardless of the array length we should always `+ 1`? – devklick May 26 '22 at 13:24
  • Yes, the '3' should be the number of elements in the array. The '1' is for making the indexing 1-based so it shouldn't be changed. – Samuel Lindblom Jun 24 '23 at 14:28
19

Updated 2023-01-10 to fix the broken array literal. Made it several times faster while being at it:

CREATE OR REPLACE FUNCTION random_pick()
  RETURNS int
  LANGUAGE sql VOLATILE PARALLEL SAFE AS
$func$
SELECT ('[0:2]={1,2,5}'::int[])[trunc(random() * 3)::int];
$func$;

random() returns a value x where 0.0 <= x < 1.0. Multiply by 3 and truncate it with trunc() (slightly faster than floor()) to get 0, 1, or 2 with exactly equal chance.

Postgres indexes are 1-based by default (as per SQL standard). This would be off-by-1. We could increment by 1 every time, but for efficiency I declare the array index to start with 0 instead. Slightly faster, yet. See:

PARALLEL SAFE for Postgres 9.6 or later. See:

You can use the plain SELECT statement if you don't want to create a function:

SELECT ('[0:2]={1,2,5}'::int[])[trunc(random() * 3)::int];
Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
8

Erwin Brandstetter answered the OP's question well enough. However, for others looking for understanding how to randomly pick elements from more complex arrays (like me some two months ago), I expanded his function:

CREATE OR REPLACE FUNCTION random_pick( a anyarray, OUT x anyelement )
  RETURNS anyelement AS
$func$
BEGIN
  IF a = '{}' THEN
    x := NULL::TEXT;
  ELSE
    WHILE x IS NULL LOOP
      x := a[floor(array_lower(a, 1) + (random()*( array_upper(a, 1) -  array_lower(a, 1)+1) ) )::int];
    END LOOP;
  END IF;
END
$func$ LANGUAGE plpgsql VOLATILE RETURNS NULL ON NULL INPUT;

Few assumptions:

  • this is not only for integer arrays, but for arrays of any type

  • we ignore NULL data; NULL is returned only if the array is empty or if NULL is inserted (values of other non-array types produce an error)

  • the array don't need to be formatted as usual - the array index may start and end anywhere, may have gaps etc.

  • this is for one-dimensional arrays

Other notes:

  • without the first IF statement, empty array would lead to an endless loop

  • without the loop, gaps and NULLs would make the function return NULL

  • omit both array_lower calls if you know that your arrays start at zero

  • with gaps in the index, you will need array_upper instead of array_length; without gaps, it's the same (not sure which is faster, but they shouldn't be much different)

  • the +1 after second array_lower serves to get the last value in the array with the same probability as any other; otherwise it would need the random()'s output to be exactly 1, which never happens

  • this is considerably slower than Erwin's solution, and likely to be an overkill for the your needs; in practice, most people would mix an ideal cocktail from the two

Community
  • 1
  • 1
Pavel V.
  • 2,653
  • 10
  • 43
  • 74
  • Why not use `array_length`? – Bergi Sep 25 '19 at 23:07
  • This causes an infinite loop on arrays that contain only `NULL` values. Might want to do something like `a := ARRAY(SELECT x FROM unnest(a) AS rel(x) WHERE x NOT NULL)`. – Bergi Sep 25 '19 at 23:18
8

Here is another way to do the same thing

WITH arr AS (
    SELECT '{1, 2, 5}'::INT[] a
)
SELECT a[1 + floor((random() * array_length(a, 1)))::int] FROM arr;

You can change the array to any type you would like.

Yaki Klein
  • 3,978
  • 3
  • 37
  • 34
  • this is the simplest answer of all and does not use a fixed value for the index, instead it uses the array length.. for my humble point of view it might be the most correct answer! Thanks for sharing! :D – Victor Feb 26 '21 at 14:56
3
CREATE OR REPLACE FUNCTION pick_random( members anyarray )
RETURNS anyelement AS
$$
BEGIN
  RETURN members[trunc(random() * array_length(members, 1) + 1)];
END
$$ LANGUAGE plpgsql VOLATILE;

or

CREATE OR REPLACE FUNCTION pick_random( members anyarray )
RETURNS anyelement AS
$$
  SELECT (array_agg(m1 order by random()))[1]
  FROM unnest(members) m1;
$$ LANGUAGE SQL VOLATILE;

For bigger datasets, see:

Gajus
  • 69,002
  • 70
  • 275
  • 438
  • I definitely like this one as it uses the array as an argument and then array_length instead of the hard-coded number. I recently became a fan of `width_bucket`, so my version has `RETURN vals[width_bucket(random(), 0, 1, array_length(vals, 1))];` but otherwise it's the same. – virgo47 Nov 27 '20 at 15:45
0
CREATE FUNCTION random_pick(p_items anyarray)
RETURNS anyelement AS
$$
   SELECT unnest(p_items) ORDER BY RANDOM() LIMIT 1;
$$ LANGUAGE SQL;
user1612798
  • 702
  • 1
  • 5
  • 8
  • This no longer seems to work in PostgreSQL 9.6: https://stackoverflow.com/questions/42179012/how-to-shuffle-array-in-postgresql-9-6-and-also-lower-versions – Martin von Wittich Jan 29 '18 at 22:19