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.
Ce problème consiste en deux sous-problèmes très différents:
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:
Rand(N)
lui-même!Une séquence de nombres aléatoires créés par la même graine est garantie
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;
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
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))
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 ;
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.
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;
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é.
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 )
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))));
Si vous n'avez pas d'identifiant ou de graine, comme pour sa liste de valeurs dans insert:
REPLACE(Rand(), '.', '')
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:
Rand
génère un nombre aléatoire compris entre 0 et 1MD5
calcule la somme MD5 de (1), 32 caractères de a-f et 0-9UNHEX
traduit (2) en 16 octets avec des valeurs de 00 à FFTO_BASE64
code (3) en base64, 22 caractères de a-z et A-Z et 0-9 plus "/" et "+", suivis de deux "="REPLACE
s suppriment les caractères "/", "+" et "=" de (4)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éatoireLPAD
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écessaireSi 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%.
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"
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.
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();
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?