Створення випадкової та унікальної 8-символьної рядки за допомогою MySQL


110

Я працюю над грою, яка передбачає транспортні засоби в певний момент. У мене є таблиця MySQL під назвою "транспортні засоби", що містить дані про транспортні засоби, включаючи стовпець "табличка", де зберігаються номерні знаки для транспортних засобів.

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

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

Будь-яка допомога вдячна.


Наскільки випадкові вони вам потрібні? Якщо хтось отримує певний номерний знак, важливо чи ні, чи можуть вони опрацювати наступний чи попередній номерний знак, який ви видали?
Damien_The_Unbeliever

@YaK Дивіться мою відповідь, як уникнути навіть крихітної можливості зіткнення
Eugen Rieck,

Відповіді:


87

Ця проблема складається з двох дуже різних підзадач:

  • рядок повинен бути, здавалося б, випадковим
  • рядок повинен бути унікальним

Хоча випадковість досягається досить легко, унікальності без повторного циклу немає. Це змушує спершу зосередитись на унікальності. Невипадкова унікальність може бути досягнута тривіально AUTO_INCREMENT. Отже, використовуючи збереження унікальності, псевдовипадкове перетворення було б добре:

  • Хеш запропонував @paul
  • AES-шифр також підходить
  • Але є приємне: RAND(N)сама!

Гарантована послідовність випадкових чисел, створених тим самим насінням

  • відтворювані
  • різні за перші 8 ітерацій
  • якщо насіння є INT32

Тому ми використовуємо підхід @ AndreyVolk або @ GordonLinoff, але з насінням RAND :

наприклад, Assumin 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?
Мік

7
Це трохи старо, але хотілося б зазначити, що мені довелося додати FLOOR()навколо другого параметра підрядки: substring('ABC … 789', floor(rand(@seed:= … )*36+1), 1), У деяких випадках підрядка намагалася вибрати символ 36.9, що при округленні до 37 не призведе до вибору символу.
Філіп Додсон

4
Ви не можете назвати рядок випадковим, якщо він відтворюваний. Дублікати також можливі, тому що ви використовуєте floor(). Цей sqlfiddle показує, що дублікати створюються для трьох рядків довгих символів.
Пол Шпігель

6
@EugenRieck Я не розумію, як ви отримуєте свої номери ("перші 2 ^ 32 ітерації"). Але мені потрібен лише один приклад дублікатів, щоб спростувати цю концепцію. Для ідентифікаторів 193844та 775771вашого алгоритму буде генеруватися однаковий рядок T82X711( демонстрація ).
Пол Шпігель

113

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

Ще одне рішення для генерації 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-Ахмед

1
@ user3099183 Офіційно "дуже низький". 16 ^ 8 - це близько 4 мільярдів можливих рядків.
RandomSeed

23
Зверніть увагу, що перші 8 символів UUID не є випадковими, а послідовними, оскільки вони засновані на часовій позначці.
ADTC

Я хочу генерувати 9-символьну довгу випадкову рядок, і коли я використовую 9у вашому коді SELECT LEFT(UUID(), 9);, завжди -є кінець згенерованого рядка як дев'ятий символ. Це постійна. Чому?
Мартін 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

тощо.

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

редагувати: Це вже давня відповідь, але я знову побачив це з часом на руках, тож із спостереження ...

Шанс усіх чисел = 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
Saif

Це найкраще рішення, я думаю. Спасибі людино!
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
використовувати замість підлоги (rand () * 36 + 1). Інакше деякі результати будуть "короткими".
Fraggle

2
має бути 35 + 1, а не 36 + 1! Інакше ви отримаєте кілька рядків з 8 символів, а деякі з 7
BIOHAZARD,

23

Ось ще один метод генерації випадкової рядки:

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


16

Ви можете використовувати функцію rand () та char () MySQL :

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тригері та перевіряти наявність дубліката через певний час:

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

@Akxe conv () може використовуватися для перетворення числа в буквено-цифровий рядок. pow(36,8)-1- числове представлення ZZZZZZZZ. Таким чином, ми генеруємо випадкове ціле число між 0та '36 ^ 8-1 '(від 0до 2821109907455) і перетворюємо його в буквено-цифровий рядок між 0і ZZZZZZZZвідкручуванням conv(). lapad () заповнить рядок нулями, поки вона не набере довжину 8.
Пол Шпігель

Ви, сер, геній. Я думаю, додавати маленькі літери неможливо через неперервність символів? (91-96) Не те, що мені це потрібно, просто цікаво ...
Akxe

@Akxe conv()підтримує лише базу до 36 (10 цифр + 26 великих літер). Якщо ви хочете включити малі літери, вам знадобиться інший спосіб перетворення числа в рядок.
Пол Шпігель

Caveat: не працює для 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. три REPLACE s видаліть символи "/", "+" і "=" з (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))));

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 ;

Цей код заснований на функції строкової перестановки, яку надсилає "Ross Smith II"


Ця функція не буде генерувати випадкове унікальне значення.
Faisal

1

Щоб створити випадкову буквено-цифрову випадкову цифру , за винятком символів "lookalike" 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')
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.