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

Вначале определим вспомогательную функцию для получения случайного целого числа в заданном диапазоне. Такую функцию легко написать на чистом 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 
-------+---------+-----------
     1 |     284 |        25
     0 |     330 |        26
     1 |     112 |       -21
     0 |     179 |       -21
     1 |     168 |       -25
     1 |     140 |         9
     0 |     307 |        17
     0 |      48 |       -14
     1 |      89 |         2
     0 |      69 |       -14
(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 | 19942
         2 | 20000
         3 | 19983
         4 | 19933
         5 | 20142
(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            
--------------------------------
 yыQdфКЩсTCдjИmТUВС6шh0NS4XEЁлц
 а_
 9йhЩdхcДЫmqныtAшзгп12
 Pэп80хЮqНЮЦ
 ч0жЦ4D8lyщTу_8Yё0QQFПГDЖщix
 ГЙшъАhR4ёPgЪЛфPВуи
 й65wЛ9хУшЪ
 ГЭ
 ьYРЯл
 G8s
(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:  Выиграл начальный выбор : 332 из 1000
NOTICE:  Выиграл измененный выбор: 668 из 1000
DO

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

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