Генерация случайной и уникальной 8-символьной строки с использованием MySQL

111

Я работаю над игрой, в которой в какой-то момент задействованы машины. У меня есть таблица MySQL с именем «cars», содержащая данные о транспортных средствах, включая столбец «plate», в котором хранятся номерные знаки для транспортных средств.

А вот часть, с которой у меня проблемы. Перед созданием нового автомобиля мне нужно найти неиспользуемый номерной знак - это должна быть случайная буквенно-цифровая строка из 8 символов. Как я добился этого, я использовал цикл while в Lua, языке, на котором я программирую, для генерации строк и запроса БД, чтобы узнать, используется ли он. Однако по мере увеличения количества автомобилей я ожидаю, что это станет еще более неэффективным, чем сейчас. Поэтому я решил попробовать решить эту проблему с помощью запроса MySQL.

Требуемый мне запрос должен просто генерировать 8-символьную буквенно-цифровую строку, которой еще нет в таблице. Я снова подумал о подходе создания и проверки цикла, но я не ограничиваю этот вопрос только на тот случай, если есть более эффективный. Мне удалось сгенерировать строки, определив строку, содержащую все разрешенные символы, и случайным образом подстроку, и не более того.

Любая помощь приветствуется.

Funstein
источник
Насколько они вам нужны? Если кто-то получает конкретный номерной знак, важно или нет, смогут ли они определить следующий или предыдущий номерной знак, который вы раздали?
Damien_The_Unbeliever
@YaK См. Мой ответ о том, как избежать даже крошечной возможности столкновения
Ойген Рик,

Ответы:

87

Эта проблема состоит из двух очень разных подзадач:

  • строка должна быть случайной
  • строка должна быть уникальной

В то время как случайность довольно легко достигается, уникальность без цикла повтора - нет. Это заставляет нас в первую очередь сосредоточиться на уникальности. Неслучайной уникальности легко добиться с помощью AUTO_INCREMENT. Таким образом, можно использовать псевдослучайное преобразование, сохраняющее уникальность:

  • Хеш был предложен @paul
  • AES-шифрование также подходит
  • Но есть одно приятное: RAND(N)сам!

Последовательность случайных чисел, созданная одним и тем же семенем, гарантированно будет

  • воспроизводимый
  • разные для первых 8 итераций
  • если семя INT32

Итак, мы используем подход @ AndreyVolk или @ GordonLinoff, но с засеянным RAND :

например, Ассумин id- это AUTO_INCREMENTстолбец:

INSERT INTO vehicles VALUES (blah); -- leaving out the number plate
SELECT @lid:=LAST_INSERT_ID();
UPDATE vehicles SET numberplate=concat(
  substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand(@seed:=round(rand(@lid)*4294967296))*36+1, 1),
  substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand(@seed:=round(rand(@seed)*4294967296))*36+1, 1),
  substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand(@seed:=round(rand(@seed)*4294967296))*36+1, 1),
  substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand(@seed:=round(rand(@seed)*4294967296))*36+1, 1),
  substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand(@seed:=round(rand(@seed)*4294967296))*36+1, 1),
  substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand(@seed:=round(rand(@seed)*4294967296))*36+1, 1),
  substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand(@seed:=round(rand(@seed)*4294967296))*36+1, 1),
  substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand(@seed)*36+1, 1)
)
WHERE id=@lid;
Ойген Рик
источник
Очень интересный подход, но вы, вероятно, имели в виду RAND(LAST_INSERT_ID()); UPDATE vehicles (...) , rand()*36+1, (...)(иначе он возвращает 8 раз один и тот же символ). Как мы можем быть уверены, что 8 последовательных вызовов к rand()гарантированно вернут другую последовательность, если они инициализированы другим семенем?
RandomSeed
8
Мне просто интересно. Почему вы используете эти числа ..4294967296)) * 36 + 1?
Mick
7
Это немного устарело, но я хотел бы отметить, что мне пришлось добавить FLOOR()параметры второй подстроки: в substring('ABC … 789', floor(rand(@seed:= … )*36+1), 1), некоторых случаях подстрока пыталась выбрать символ 36.9, что при округлении до 37 не приводило к выбору символа.
Филлип Додсон
4
Вы не можете назвать строку случайной, если она воспроизводима. И дубликаты также возможны, потому что вы используете floor(). Этот sqlfiddle показывает, что дубликаты создаются для трехсимвольных строк.
Пол Шпигель
6
@EugenRieck Я не понимаю, как вы получаете свои числа («первые 2 ^ 32 итерации»). Но мне нужен только один пример дубликатов, чтобы опровергнуть эту концепцию. Для идентификаторов 193844и 775771вашего алгоритма будет сгенерирована одна и та же строка T82X711( демонстрация ).
Пол Шпигель
114

Я бы не стал беспокоиться о вероятности столкновения. Просто сгенерируйте случайную строку и проверьте, существует ли она. Если это так, попробуйте еще раз, и вам не нужно будет делать это чаще, чем пару раз, если вам не назначено огромное количество номеров.

Другое решение для генерации псевдослучайной строки длиной 8 символов в чистом (My) SQL:

SELECT LEFT(UUID(), 8);

Вы можете попробовать следующее (псевдокод):

DO 
    SELECT LEFT(UUID(), 8) INTO @plate;
    INSERT INTO plates (@plate);
WHILE there_is_a_unique_constraint_violation
-- @plate is your newly assigned plate number

Поскольку этот пост получил неожиданный уровень внимания, позвольте мне выделить комментарий ADTC : приведенный выше фрагмент кода довольно глуп и выдает последовательные цифры.

Для чуть менее глупой случайности попробуйте вместо этого что-нибудь вроде этого:

SELECT LEFT(MD5(RAND()), 8)

А для истинной (криптографически безопасной) случайности используйте RANDOM_BYTES()вместо RAND()(но тогда я бы подумал о перемещении этой логики на уровень приложения).

Случайное зерно
источник
Спасибо за ваше решение. У меня есть один вопрос по UUID. сколько шансов, что идентификатор, который вы создадите, 8 символов снова повторится.
TR-Ahmed
1
@ user3099183 Официально "очень низкий". 16 ^ 8 - это около 4 миллиардов возможных строк.
RandomSeed
23
Обратите внимание, что первые 8 символов UUID не случайны, а являются последовательными, поскольку они основаны на метке времени.
ADTC
Я хочу сгенерировать случайную строку длиной 9 символов, и когда я использую 9в вашем коде SELECT LEFT(UUID(), 9);, всегда -в конце сгенерированной строки находится девятый символ. Это постоянно. Зачем?
Martin AJ
3
@MartinAJ, потому что строка - это uuid . Вы можете легко заменить дефисы, напримерSELECT LEFT(REPLACE(UUID(), '-', ''), 16);
jchook
53

Как насчет вычисления MD5 (или другого) хэша последовательных целых чисел, а затем взятия первых 8 символов.

т.е.

MD5(1) = c4ca4238a0b923820dcc509a6f75849b => c4ca4238
MD5(2) = c81e728d9d4c2f636f067f89cc14862c => c81e728d
MD5(3) = eccbc87e4b5ce2fe28308fd9f2a7baf3 => eccbc87e

и т.п.

предостережение: я понятия не имею, сколько вы можете выделить до столкновения (но это будет известное и постоянное значение).

edit: Теперь это старый ответ, но я снова увидел его со временем, поэтому из наблюдения ...

Вероятность выпадения всех чисел = 2,35%

Вероятность появления всех букв = 0,05%

Первая коллизия, когда MD5 (82945) = "7b763dcb ..." (тот же результат, что и MD5 (25302))

Павел
источник
2
Хорошая идея, можно использовать его на первичном ключе. Учтите это для будущих проектов, спасибо!
funstein
2
Есть шанс, что это может закончиться только цифрами
Младен Джанжетович
9
Это вовсе не случайно.
Павел
1
Вы можете сделать его более «случайным», если вместо использования автоматического инкрементного идентификатора вы используете дату и время, в которые была сделана вставка, которая также является уникальной.
Хавьер Ла Банкка,
35

Создать случайную строку

Вот функция MySQL для создания случайной строки заданной длины.

DELIMITER $$

CREATE DEFINER=`root`@`%` FUNCTION `RandString`(length SMALLINT(3)) RETURNS varchar(100) CHARSET utf8
begin
    SET @returnStr = '';
    SET @allowedChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    SET @i = 0;

    WHILE (@i < length) DO
        SET @returnStr = CONCAT(@returnStr, substring(@allowedChars, FLOOR(RAND() * LENGTH(@allowedChars) + 1), 1));
        SET @i = @i + 1;
    END WHILE;

    RETURN @returnStr;
END

Использование SELECT RANDSTRING(8)для возврата строки из 8 символов.

Вы можете настроить @allowedChars.

Уникальность не гарантируется - как вы увидите в комментариях к другим решениям, это просто невозможно. Вместо этого вам нужно будет сгенерировать строку, проверить, используется ли она уже, и повторить попытку, если это так.


Проверьте, не используется ли уже случайная строка

Если мы хотим, чтобы код проверки столкновений не попадал в приложение, мы можем создать триггер:

DELIMITER $$

CREATE TRIGGER Vehicle_beforeInsert
  BEFORE INSERT ON `Vehicle`
  FOR EACH ROW
  BEGIN
    SET @vehicleId = 1;
    WHILE (@vehicleId IS NOT NULL) DO 
      SET NEW.plate = RANDSTRING(8);
      SET @vehicleId = (SELECT id FROM `Vehicle` WHERE `plate` = NEW.plate);
    END WHILE;
  END;$$
DELIMITER ;
Пэдди Манн
источник
6
это должен быть принятый ответ, ясный и по существу, отлично сработал для меня, спасибо @ paddy-mann
Саиф
Думаю, это лучшее решение. Спасибо чувак!
Pronoy999,
23

Вот один из способов использования буквенно-цифровых символов в качестве допустимых символов:

select concat(substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1),
              substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1),
              substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1),
              substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1),
              substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1),
              substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1),
              substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1),
              substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1)
             ) as LicensePlaceNumber;

Обратите внимание, что нет гарантии уникальности. Вам нужно будет проверить это отдельно.

Гордон Линофф
источник
7
используйте вместо него floor (rand () * 36 + 1). В противном случае некоторые результаты будут «короткими».
Fraggle
2
должно быть 35 + 1, а не 36 + 1! В противном случае вы получите несколько строк с 8 символами, а некоторые из них - с 7
BIOHAZARD
23

Вот еще один метод создания случайной строки:

SELECT SUBSTRING(MD5(RAND()) FROM 1 FOR 8) AS myrandomstring

Beingalex
источник
16

Вы можете использовать MySQL функции rand () и char () :

select concat( 
    char(round(rand()*25)+97),
    char(round(rand()*25)+97),
    char(round(rand()*25)+97),
    char(round(rand()*25)+97),
    char(round(rand()*25)+97),
    char(round(rand()*25)+97),
    char(round(rand()*25)+97),
    char(round(rand()*25)+97)
) as name;
Андрей Волк
источник
14

Вы можете создать случайную буквенно-цифровую строку с помощью:

lpad(conv(floor(rand()*pow(36,8)), 10, 36), 8, 0);

Вы можете использовать его в BEFORE INSERTтриггере и проверять наличие дубликата в цикле while:

CREATE TABLE `vehicles` (
    `plate` CHAR(8) NULL DEFAULT NULL,
    `data` VARCHAR(50) NOT NULL,
    UNIQUE INDEX `plate` (`plate`)
);

DELIMITER //
CREATE TRIGGER `vehicles_before_insert` BEFORE INSERT ON `vehicles`
FOR EACH ROW BEGIN

    declare str_len int default 8;
    declare ready int default 0;
    declare rnd_str text;
    while not ready do
        set rnd_str := lpad(conv(floor(rand()*pow(36,str_len)), 10, 36), str_len, 0);
        if not exists (select * from vehicles where plate = rnd_str) then
            set new.plate = rnd_str;
            set ready := 1;
        end if;
    end while;

END//
DELIMITER ;

Теперь просто вставьте свои данные, например

insert into vehicles(col1, col2) values ('value1', 'value2');

И триггер сгенерирует значение для plateстолбца.

( демонстрация sqlfiddle )

Это работает таким образом, если столбец допускает NULL. Если вы хотите, чтобы оно было НЕ NULL, вам нужно будет определить значение по умолчанию

`plate` CHAR(8) NOT NULL DEFAULT 'default',

Вы также можете использовать любой другой алгоритм генерации случайной строки в триггере, если буквенно-цифровые символы в верхнем регистре вам не подходят. Но триггер позаботится об уникальности.

Пол Шпигель
источник
Удивительный! Это именно то, что я хотел. Я только хотел бы понять, как он преобразуется в строку. Почему там пауза, что делать, складывать числа и так далее. Во всяком случае, спасибо.
Akxe 02
@Akxe conv () может использоваться для преобразования числа в буквенно-цифровую строку. pow(36,8)-1- это числовое представление ZZZZZZZZ. Таким образом, мы генерируем случайное целое число между 0и '36 ^ 8-1' (от 0до 2821109907455) и преобразуем его в буквенно-цифровую строку между 0и ZZZZZZZZunsing conv(). lapad () заполнит строку нулями, пока она не станет длиной 8.
Пол Шпигель
Вы, сэр, гений. Я предполагаю, что добавление маленьких букв невозможно из-за отсутствия непрерывности символов? (91-96) Не то чтобы мне это нужно, просто любопытно ...
Akxe
@Akxe conv()поддерживает только основание до 36 (10 цифр + 26 заглавных букв). Если вы хотите использовать строчные буквы, вам понадобится другой способ преобразования числа в строку.
Пол Шпигель
Предупреждение: не работает для str_len> 13. Начиная с 14, вы всегда получаете «3W5E11264SGSF». ;-)
Джерард Х. Пилле
6

Для генерации случайной строки вы можете использовать:

SUBSTRING(MD5(RAND()) FROM 1 FOR 8)

Вы получаете что-то подобное:

353E50CC

Никита Г.
источник
5

Для строки, состоящей из 8 случайных чисел и прописных и строчных букв, это мое решение:

LPAD(LEFT(REPLACE(REPLACE(REPLACE(TO_BASE64(UNHEX(MD5(RAND()))), "/", ""), "+", ""), "=", ""), 8), 8, 0)

Объясняется изнутри:

  1. RAND генерирует случайное число от 0 до 1
  2. MD5 вычисляет сумму MD5 (1), 32 символа от af и 0-9
  3. UNHEX переводит (2) в 16 байтов со значениями от 00 до FF
  4. TO_BASE64 кодирует (3) как base64, 22 символа от az и AZ и 0-9 плюс "/" и "+", за которыми следуют два "="
  5. три REPLACEs удаляют символы "/", "+" и "=" из (4)
  6. LEFT берет первые 8 символов из (5), замените 8 на что-то другое, если вам нужно больше или меньше символов в вашей случайной строке
  7. LPADвставляет нули в начало (6), если оно меньше 8 символов; опять же, при необходимости измените 8 на другое
Ян Улиг
источник
Отлично, именно то, что я искал для создания идентификатора, подобного токену, изначально в MySQL
rabudde
4

Я использую данные из другого столбца для создания «хеша» или уникальной строки

UPDATE table_name SET column_name = Right( MD5(another_column_with_data), 8 )
Ричард Фраджакомо
источник
4

8 букв алфавита - все заглавные:

UPDATE `tablename` SET `tablename`.`randomstring`= concat(CHAR(FLOOR(65 + (RAND() * 25))),CHAR(FLOOR(65 + (RAND() * 25))),CHAR(FLOOR(65 + (RAND() * 25))),CHAR(FLOOR(65 + (RAND() * 25)))CHAR(FLOOR(65 + (RAND() * 25))),CHAR(FLOOR(65 + (RAND() * 25))),CHAR(FLOOR(65 + (RAND() * 25))),CHAR(FLOOR(65 + (RAND() * 25))));
TV-C-15
источник
3

Если у вас нет идентификатора или начального числа, например его для списка значений во вставке:

REPLACE(RAND(), '.', '')
Экернер
источник
2

Простое и эффективное решение для получения случайной строки из 10 символов с прописными и строчными буквами и цифрами:

select substring(base64_encode(md5(rand())) from 1+rand()*4 for 10);
Антарес
источник
1

Если вас устраивают «случайные», но полностью предсказуемые номерные знаки, вы можете использовать регистр сдвига с линейной обратной связью, чтобы выбрать следующий номерной знак - он гарантированно пройдется по каждому номеру перед повторением. Однако без сложной математики вы не сможете пройти через каждую 8-символьную буквенно-цифровую строку (вы получите 2 ^ 41 из 36 ^ 8 (78%) возможных табличек). Чтобы это лучше заполняло ваше пространство, вы можете исключить букву из табличек (возможно, O), что даст вам 97%.

τεκ
источник
1

Принимая во внимание общее количество символов, которые вам требуются, у вас будет очень небольшая вероятность создания двух точно таких же номерных знаков. Таким образом, вам, вероятно, удастся создать числа в LUA.

У вас есть 36 ^ 8 различных уникальных номерных знаков (2 821 109 907 456, это много), даже если у вас уже был миллион номерных знаков, у вас был бы очень небольшой шанс создать тот, который у вас уже есть, около 0,000035%

Конечно, все зависит от того, сколько номерных знаков вы в итоге создадите.

Лоуренс Эндрюс
источник
Правда, я продолжу делать это в самой игре, а не в SQL. Большое спасибо.
funstein
1

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

SELECT str_rand(8, '23456789abcdefghijkmnpqrstuvwxyz');

код функции:

DROP FUNCTION IF EXISTS str_rand;

DELIMITER //

CREATE FUNCTION str_rand(
    u_count INT UNSIGNED,
    v_chars TEXT
)
RETURNS TEXT
NOT DETERMINISTIC
NO SQL
SQL SECURITY INVOKER
COMMENT ''
BEGIN
    DECLARE v_retval TEXT DEFAULT '';
    DECLARE u_pos    INT UNSIGNED;
    DECLARE u        INT UNSIGNED;

    SET u = LENGTH(v_chars);
    WHILE u_count > 0
    DO
      SET u_pos = 1 + FLOOR(RAND() * u);
      SET v_retval = CONCAT(v_retval, MID(v_chars, u_pos, 1));
      SET u_count = u_count - 1;
    END WHILE;

    RETURN v_retval;
END;
//
DELIMITER ;

Этот код основан на функции перемешивания строки, отправляемой Россом Смитом II.

Махур13
источник
Эта функция не будет генерировать случайное уникальное значение.
Faisal
1

Чтобы создать случайный 10-значный буквенно-цифровой символ, исключая похожие символы 01oOlI:

LPAD(LEFT(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(TO_BASE64(UNHEX(MD5(RAND()))), "/", ""), "+", ""), "=", ""), "O", ""), "l", ""), "I", ""), "1", ""), "0", ""), "o", ""), 10), 10, 0)

Это именно то, что мне нужно для создания кода ваучера . Непонятные символы удалены, чтобы уменьшить количество ошибок при вводе их в форму кода ваучера.

Надеюсь, что это кому-то поможет, основываясь на блестящем ответе Яна Улига .

Пожалуйста, посмотрите ответ Яна, чтобы узнать, как работает этот код.

Пол Харрис
источник
0
DELIMITER $$

USE `temp` $$

DROP PROCEDURE IF EXISTS `GenerateUniqueValue`$$

CREATE PROCEDURE `GenerateUniqueValue`(IN tableName VARCHAR(255),IN columnName VARCHAR(255)) 
BEGIN
    DECLARE uniqueValue VARCHAR(8) DEFAULT "";
    WHILE LENGTH(uniqueValue) = 0 DO
        SELECT CONCAT(SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1)
                ) INTO @newUniqueValue;
        SET @rcount = -1;
        SET @query=CONCAT('SELECT COUNT(*) INTO @rcount FROM  ',tableName,' WHERE ',columnName,'  like ''',@newUniqueValue,'''');
        PREPARE stmt FROM  @query;
        EXECUTE stmt;
        DEALLOCATE PREPARE stmt;
    IF @rcount = 0 THEN
            SET uniqueValue = @newUniqueValue ;
        END IF ;
    END WHILE ;
    SELECT uniqueValue;
    END$$

DELIMITER ;

Используйте эту хранимую процедуру и используйте ее каждый раз, как

Call GenerateUniqueValue('tableName','columnName')
Харирампрасат Нандхагопалан
источник
0

Простой способ сгенерировать уникальный номер

set @i = 0;
update vehicles set plate = CONCAT(@i:=@i+1, ROUND(RAND() * 1000)) 
order by rand();
Готье
источник
0

Я искал что-то подобное и решил создать свою собственную версию, в которой вы также можете указать другое семя, если хотите (список символов) в качестве параметра:

CREATE FUNCTION `random_string`(length SMALLINT(3), seed VARCHAR(255)) RETURNS varchar(255) CHARSET utf8
    NO SQL
BEGIN
    SET @output = '';

    IF seed IS NULL OR seed = '' THEN SET seed = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; END IF;

    SET @rnd_multiplier = LENGTH(seed);

    WHILE LENGTH(@output) < length DO
        # Select random character and add to output
        SET @output = CONCAT(@output, SUBSTRING(seed, RAND() * (@rnd_multiplier + 1), 1));
    END WHILE;

    RETURN @output;
END

Может использоваться как:

SELECT random_string(10, '')

Которая будет использовать встроенное семя из прописных и строчных символов + цифры. NULL также будет значением вместо ''.

Но можно указать собственное семя при вызове:

SELECT random_string(10, '1234')
Маартен Уриль
источник