Функция для получения случайной строки заданного размера

Вначале определим вспомогательную функцию для получения случайного целого числа в заданном диапазоне. Такую функцию легко написать на чистом SQL, но здесь представлен вариант на PL/pgSQL:

=> CREATE FUNCTION rnd_integer(min_value integer, max_value integer) RETURNS integer
AS $$
DECLARE
    retval integer;
BEGIN
    IF max_value <= min_value THEN 
       RETURN NULL; 
    END IF;

    retval := floor((max_value+1 - min_value)*random())::integer + min_value;
    RETURN retval;
END;
$$ STRICT LANGUAGE plpgsql;
CREATE FUNCTION

Проверяем работу:

=> SELECT rnd_integer(0,1) as "0 - 1",
          rnd_integer(1,365) as "1 - 365",
          rnd_integer(-30,30) as "-30 - +30"
   FROM generate_series(1,10);
 0 - 1 | 1 - 365 | -30 - +30 
-------+---------+-----------
     0 |     343 |        -9
     0 |     229 |       -13
     1 |     301 |       -18
     0 |     273 |        15
     0 |       1 |       -28
     1 |     315 |        15
     1 |     337 |       -10
     1 |     318 |        27
     0 |      90 |       -14
     0 |      70 |        30
(10 rows)

Функция гарантирует равномерное распределение случайных значений по всему диапазону, включая граничные значения:

=> SELECT rnd_value, count(*) FROM (
               SELECT rnd_integer(1,5) as rnd_value 
               FROM generate_series(1,100000)
   ) as t GROUP by rnd_value ORDER BY rnd_value;
 rnd_value | count 
-----------+-------
         1 | 19993
         2 | 20102
         3 | 20086
         4 | 19856
         5 | 19963
(5 rows)

Теперь можно приступить к функции для получения случайной строки заданного размера. Будем использовать функцию rnd_integer для получения случайного символа из списка.

=> CREATE FUNCTION rnd_text(
   len int,
   list_of_chars text DEFAULT 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюяABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789'
) RETURNS text
AS $$
DECLARE
    len_of_list CONSTANT integer = length(list_of_chars);
    i integer;
    retval text = '';
BEGIN
    FOR i IN 1 .. len LOOP
        -- добавляем к строке случайный символ
        retval := retval || substr(list_of_chars, rnd_integer(1,len_of_list),1);
    END LOOP;
    RETURN retval;
END;
$$ STRICT LANGUAGE plpgsql;
CREATE FUNCTION

Проверяем:

=> SELECT rnd_text(rnd_integer(1,30)) FROM generate_series(1,10);
           rnd_text            
-------------------------------
 TDcЬrу5MУвДчjЛИzzmЩEp
 Ъ33SWХ4ZsNЮKrКАmYФЙcMupWj
 ю8гйnюдhТ63НVmb0сIБтпZH
 чЦкгtПВнОёY2m8a
 5TЗRъkHtоJвfеqЬeВHГwYЁЯoл3hФ2
 ОzСЧFM8ОымYeО9K
 eM_hэOneшХX
 йQYщErR
 шQЫ
 А_WП_выTvсцоВЯОъхnXgшрЫ9ЭuЛX
(10 rows)

Игра в наперстки

Для загадывания и угадывания наперстка используем rnd_integer(1,3)

=> DO $$
DECLARE
    x integer;
    choice integer;
    new_choice integer;
    remove integer;
    total_games integer := 1000;
    old_choice_win_counter integer = 0;
    new_choice_win_counter integer = 0;
BEGIN
    FOR i IN 1 .. total_games LOOP
        -- Загадываем выигрышный наперсток
        x := rnd_integer(1,3);
    
        -- Игрок делает выбор
        choice := rnd_integer(1,3);
        
        -- Убираем один неверный ответ, кроме выбора игрока
        FOR i IN 1 .. 3 LOOP
            IF i NOT IN (x, choice) THEN 
                remove := i; 
                EXIT;
            END IF;
        END LOOP;
    
        -- Нужно ли игроку менять свой выбор?    
        -- Что лучше оставить choice или заменить его на оставшийся?
    
        -- Измененный выбор
        FOR i IN 1 .. 3 LOOP
            IF i NOT IN (remove, choice) THEN 
                new_choice := i; 
                EXIT;
            END IF;
        END LOOP;
    
        -- Или начальный, или новый выбор обязательно выиграют
        IF choice = x THEN
            old_choice_win_counter := old_choice_win_counter + 1;
        ELSIF new_choice = x THEN
            new_choice_win_counter := new_choice_win_counter + 1;
        END IF;
    END LOOP;

    RAISE NOTICE 'Выиграл начальный выбор : % из %', 
        old_choice_win_counter, total_games;
    RAISE NOTICE 'Выиграл измененный выбор: % из %', 
        new_choice_win_counter, total_games;
END;
$$;
NOTICE:  Выиграл начальный выбор : 330 из 1000
NOTICE:  Выиграл измененный выбор: 670 из 1000
DO

В начале мы выбираем 1 из 3, поэтому вероятность начального выбора 1/3. Если же выбор изменить, то изменится и вероятность на противоположные 2/3.

Таким образом, вероятность выиграть при смене выбора выше. Поэтому есть смысл выбор поменять.