web-dev-qa-db-fra.com

Générer une chaîne aléatoire et unique de 8 caractères avec MySQL

Je travaille sur un jeu qui implique des véhicules à un moment donné. J'ai une table MySQL nommée "véhicules" contenant les données sur les véhicules, y compris la colonne "plaque" qui stocke les plaques d'immatriculation des véhicules.

Maintenant, voici la partie avec laquelle j'ai des problèmes. Je dois trouver une plaque d'immatriculation inutilisée avant de créer un nouveau véhicule. Ce doit être une chaîne alphanumérique aléatoire de 8 caractères. Pour y parvenir, j'ai utilisé une boucle while dans Lua, le langage dans lequel je programme, pour générer des chaînes et interroger la base de données pour voir si elle est utilisée. Cependant, à mesure que le nombre de véhicules augmente, je pense que cela deviendra encore plus inefficace que maintenant. Par conséquent, j'ai décidé d'essayer de résoudre ce problème en utilisant une requête MySQL.

La requête dont j'ai besoin devrait simplement générer une chaîne alphanumérique de 8 caractères qui ne figure pas déjà dans la table. J'ai repensé à l'approche de la boucle de génération et de vérification, mais je ne me limite pas à cette question au cas où il y en aurait une plus efficace. J'ai été capable de générer des chaînes en définissant une chaîne contenant tous les caractères autorisés et en la sous-chaîne de manière aléatoire, et rien de plus. 

Toute aide est appréciée.

73
funstein

Ce problème consiste en deux sous-problèmes très différents:

  • la chaîne doit être apparemment aléatoire
  • la chaîne doit être unique

Bien que le caractère aléatoire soit assez facile à obtenir, l'unicité sans boucle de nouvelle tentative ne l'est pas. Cela nous amène à nous concentrer d'abord sur l'unicité. Unicité non aléatoire peut être réalisée de manière triviale avec AUTO_INCREMENT. Donc, utiliser une transformation pseudo-aléatoire préservant l'unicité serait bien:

  • Hash a été suggéré par @paul
  • AES-encrypt convient également
  • Mais il y a un Nice: Rand(N) lui-même!

Une séquence de nombres aléatoires créés par la même graine est garantie 

  • reproductible 
  • différent pour les 8 premières itérations
  • si la graine est un INT32

Nous utilisons donc l'approche de @ AndreyVolk ou de GordonLinoff, mais avec un seeded Rand:

par exemple. Assumin id est un AUTO_INCREMENT col: 

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;
69
Eugen Rieck

Comme je l'ai dit dans mon commentaire, je ne m'inquiéterais pas du risque de collision. Il suffit de générer une chaîne aléatoire et de vérifier si elle existe. Si c'est le cas, essayez à nouveau et vous ne devriez pas avoir besoin de le faire plusieurs fois, à moins que vous n'ayez déjà un grand nombre de plaques.

Une autre solution pour générer une chaîne pseudo-aléatoire longue de 8 caractères en pur (My) SQL:

SELECT LEFT(UUID(), 8);

Vous pouvez essayer ce qui suit (pseudo-code):

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
73
RandomSeed

Qu'en est-il du calcul du hachage MD5 (ou autre) d'entiers séquentiels, en prenant ensuite les 8 premiers caractères.

c'est à dire 

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

etc.

mise en garde: je ne sais pas combien vous pouvez en affecter avant une collision (mais ce serait une valeur connue et constante).

edit: Ceci est maintenant une ancienne réponse, mais je l'ai vue à nouveau avec le temps sur mes mains, alors, de l'observation ...

Chance of all numbers = 2,35%

Chances de toutes les lettres = 0.05%

Première collision lorsque MD5 (82945) = "7b763dcb ..." (même résultat que MD5 (25302))

48
paul

Crée une chaîne aléatoire

Voici une fonction MySQL pour créer une chaîne aléatoire d’une longueur donnée.

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

Utilisation SELECT RANDSTRING(8) pour renvoyer une chaîne de 8 caractères.

Vous pouvez personnaliser le @allowedChars.

L'unicité n'est pas garantie - comme vous le verrez dans les commentaires sur d'autres solutions, cela n'est tout simplement pas possible Au lieu de cela, vous devrez générer une chaîne, vérifier si elle est déjà utilisée et réessayer.


Vérifie si la chaîne aléatoire est déjà utilisée

Si nous voulons garder le code de contrôle de collision en dehors de l'application, nous pouvons créer un déclencheur:

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 ;
24
Paddy Mann

Voici un moyen d'utiliser les caractères alphanumériques comme caractères valides:

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;

Notez qu'il n'y a aucune garantie d'unicité. Vous devrez vérifier cela séparément.

21
Gordon Linoff

Vous pouvez utiliser la fonction Rand () et char () de 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;
13
Andrey Volk

Vous pouvez générer une chaîne alphanumérique aléatoire avec:

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

Vous pouvez l'utiliser dans un déclencheur BEFORE INSERT et rechercher une copie dans une boucle 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 ;

Maintenant, insérez simplement vos données comme

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

Et le déclencheur générera une valeur pour la colonne plate.

( sqlfiddle demo )

Cela fonctionne de cette façon si la colonne autorise les valeurs NULL. Si vous voulez que ce soit NOT NULL, vous devez définir une valeur par défaut.

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

Vous pouvez également utiliser n'importe quel autre algorithme de génération de chaîne aléatoire dans le déclencheur si les caractères alphanumériques majuscules ne vous conviennent pas. Mais la gâchette prendra soin de l'unicité.

11
Paul Spiegel

I Utiliser les données d'une autre colonne pour générer un "hash" ou une chaîne unique

UPDATE table_name SET column_name = Right( MD5(another_column_with_data), 8 )
3

8 lettres de l'alphabet - toutes en majuscules:

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))));
2
TV-C-15

Si vous n'avez pas d'identifiant ou de graine, comme pour sa liste de valeurs dans insert:

REPLACE(Rand(), '.', '')
2
ekerner

Pour une chaîne composée de 8 nombres aléatoires et de lettres majuscules et minuscules, voici ma solution:

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

Expliqué de l'intérieur:

  1. Rand génère un nombre aléatoire compris entre 0 et 1
  2. MD5 calcule la somme MD5 de (1), 32 caractères de a-f et 0-9
  3. UNHEX traduit (2) en 16 octets avec des valeurs de 00 à FF
  4. TO_BASE64 code (3) en base64, 22 caractères de a-z et A-Z et 0-9 plus "/" et "+", suivis de deux "="
  5. les trois REPLACEs suppriment les caractères "/", "+" et "=" de (4)
  6. LEFT prend les 8 premiers caractères de (5), remplacez 8 par autre chose si vous avez besoin de plus ou moins de caractères dans votre chaîne aléatoire
  7. LPAD insère des zéros au début de (6) si sa longueur est inférieure à 8 caractères; encore une fois, remplacez 8 par autre chose si nécessaire
1
Jan Uhlig

Si les plaques d'immatriculation «aléatoires», mais tout à fait prévisibles, vous conviennent, vous pouvez utiliser un registre à décalage linear-feedback pour choisir le numéro de plaque suivant. Cependant, sans calculs complexes, vous ne pourrez pas parcourir toutes les chaînes alphanumériques de 8 caractères (vous obtiendrez 2 ^ 41 sur des plaques possibles sur 36 ^ 8 (78%)). Pour que cela remplisse mieux votre espace, vous pouvez exclure une lettre des plaques (peut-être O), ce qui vous donne 97%.

1
τεκ

Cette fonction génère une chaîne aléatoire basée sur votre longueur et des caractères autorisés comme ceci:

SELECT str_Rand(8, '23456789abcdefghijkmnpqrstuvwxyz');

code de fonction:

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 ;

Ce code est basé sur fonction de lecture aléatoire envoyé par "Ross Smith II"

1
Mahoor13

Compte tenu du nombre total de caractères requis, vous aurez très peu de chance de générer deux plaques d’immatriculation parfaitement similaires. Ainsi, vous pourriez probablement vous en sortir en générant les nombres en LUA.

Vous avez 36 ^ 8 plaques d'immatriculation uniques différentes (2 821 109 907 456, c'est beaucoup), même si vous aviez déjà un million de plaques d'immatriculation déjà, vous auriez une très petite chance d'en générer une, environ 0,000035%

Bien sûr, tout dépend du nombre de plaques que vous finirez par créer.

1
Lawrence Andrews

Un moyen simple de générer un numéro unique 

set @i = 0;
update vehicles set plate = CONCAT(@i:=@i+1, ROUND(Rand() * 1000)) 
order by Rand();
0
Gautier
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 ;

Utilisez cette procédure stockée et utilisez-la à chaque fois comme

Call GenerateUniqueValue('tableName','columnName')

Générer une clé de 8 caractères

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

Comment générer une chaîne aléatoire unique pour l'une de mes colonnes de table MySql?

0
Kaushik shrimali