Generando una cadena de 8 caracteres aleatoria y única usando MySQL


110

Estoy trabajando en un juego que involucra vehículos en algún momento. Tengo una tabla de MySQL llamada "vehículos" que contiene los datos sobre los vehículos, incluida la columna "placa" que almacena las placas de los vehículos.

Ahora aquí viene la parte con la que estoy teniendo problemas. Necesito encontrar una matrícula sin usar antes de crear un nuevo vehículo; debe ser una cadena aleatoria alfanumérica de 8 caracteres. Cómo logré esto fue usando un bucle while en Lua, que es el lenguaje en el que estoy programando, para generar cadenas y consultar la base de datos para ver si se usa. Sin embargo, a medida que aumenta el número de vehículos, espero que esto se vuelva aún más ineficiente que en este momento. Por lo tanto, decidí intentar resolver este problema utilizando una consulta MySQL.

La consulta que necesito debería simplemente generar una cadena alfanumérica de 8 caracteres que aún no está en la tabla. Pensé en el enfoque de generar y verificar bucle nuevamente, pero no estoy limitando esta pregunta a eso en caso de que haya una más eficiente. He podido generar cadenas definiendo una cadena que contiene todos los caracteres permitidos y subcadenando aleatoriamente, y nada más.

Se agradece cualquier ayuda.


¿Qué tan aleatorios necesitas que sean? Si alguien recibe una matrícula en particular, ¿es importante o no que pueda determinar la matrícula anterior o la siguiente que le entregó?
Damien_The_Unbeliever

@YaK Vea mi respuesta sobre cómo evitar incluso la mínima posibilidad de colisión
Eugen Rieck

Respuestas:


87

Este problema consta de dos subproblemas muy diferentes:

  • la cadena debe ser aparentemente aleatoria
  • la cadena debe ser única

Si bien la aleatoriedad se logra con bastante facilidad, la singularidad sin un bucle de reintento no lo es. Esto nos lleva a concentrarnos primero en la singularidad. La unicidad no aleatoria se puede lograr trivialmente con AUTO_INCREMENT. Entonces, usar una transformación pseudoaleatoria que preserve la singularidad estaría bien:

  • Hash ha sido sugerido por @paul
  • El cifrado AES también se adapta
  • Pero hay uno bonito: RAND(N)¡sí mismo!

Se garantiza que una secuencia de números aleatorios creada por la misma semilla

  • reproducible
  • diferente para las primeras 8 iteraciones
  • si la semilla es un INT32

Entonces usamos el enfoque de @ AndreyVolk o @ GordonLinoff, pero con un sembrado RAND :

por ejemplo, Assumin ides una AUTO_INCREMENTcolumna:

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;

Enfoque muy interesante, pero probablemente quiso decir RAND(LAST_INSERT_ID()); UPDATE vehicles (...) , rand()*36+1, (...)(o de lo contrario devuelve 8 veces el mismo carácter). ¿Cómo podemos estar seguros de que se garantiza que 8 llamadas sucesivas rand()devuelvan una secuencia diferente si se inicializan con una semilla diferente?
RandomSeed

8
Me estaba preguntando. ¿Por qué usa esos números ..4294967296)) * 36 + 1?
Mick

7
Esto es un poco antiguo, pero me gustaría señalar que tuve que agregar FLOOR()alrededor de los parámetros de la segunda subcadena: substring('ABC … 789', floor(rand(@seed:= … )*36+1), 1), en algunas ocasiones, la subcadena estaba tratando de elegir el carácter 36.9, que cuando se redondea a 37, no se elige ningún carácter.
Phillip Dodson

4
No puede llamar a una cadena aleatoria si es reproducible. Y los duplicados también son posibles, porque está utilizando floor(). Este sqlfiddle muestra que los duplicados se crean para cadenas de tres caracteres.
Paul Spiegel

6
@EugenRieck No entiendo cómo obtienes tus números ("primeras 2 ^ 32 iteraciones"). Pero solo necesito un ejemplo de duplicados para refutar este concepto. Para los ID 193844y 775771su algoritmo generará la misma cadena T82X711( demostración ).
Paul Spiegel

113

Como dije en mi comentario, no me molestaría en la posibilidad de colisión. Simplemente genere una cadena aleatoria y verifique si existe. Si es así, inténtelo de nuevo y no debería necesitar hacerlo más de un par de veces a menos que ya tenga una gran cantidad de placas asignadas.

Otra solución para generar una cadena pseudoaleatoria de 8 caracteres en SQL puro (My):

SELECT LEFT(UUID(), 8);

Puede probar lo siguiente (pseudocódigo):

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

Dado que esta publicación ha recibido un nivel de atención inesperado, permítanme resaltar el comentario de ADTC : la pieza de código anterior es bastante tonta y produce dígitos secuenciales.

Para una aleatoriedad un poco menos estúpida, intente algo como esto en su lugar:

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

Y para una verdadera aleatoriedad (criptográficamente segura), use en RANDOM_BYTES()lugar de RAND()(pero luego consideraría mover esta lógica a la capa de aplicación).


Gracias por su solución, tengo una pregunta sobre UUID. cuántas posibilidades de que la identificación que genere 8 caracteres se repita nuevamente.
TR-Ahmed

1
@ user3099183 Oficialmente , "muy bajo". 16 ^ 8 son aproximadamente 4 mil millones de cadenas posibles.
RandomSeed

23
Tenga en cuenta que los primeros 8 caracteres de UUID no son aleatorios, sino secuenciales, ya que se basan en la marca de tiempo.
ADTC

Quiero generar una cadena aleatoria de 9 caracteres de largo, y cuando la uso 9en su código SELECT LEFT(UUID(), 9);, siempre aparece -al final de la cadena generada como el noveno carácter. Es constante. ¿Por qué?
Martin AJ

3
@MartinAJ porque la cadena es un uuid . Puede reemplazar fácilmente los guiones, por ejemploSELECT LEFT(REPLACE(UUID(), '-', ''), 16);
jchook

53

¿Qué hay de calcular el hash MD5 (u otro) de enteros secuenciales y luego tomar los primeros 8 caracteres?

es decir

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

etc.

advertencia: no tengo idea de cuántos podría asignar antes de una colisión (pero sería un valor conocido y constante).

editar: Esta es ahora una respuesta antigua, pero la vi de nuevo con tiempo en mis manos, así que, desde la observación ...

Probabilidad de todos los números = 2,35%

Probabilidad de todas las letras = 0.05%

Primera colisión cuando MD5 (82945) = "7b763dcb ..." (mismo resultado que MD5 (25302))


2
Buena idea, podría usarlo en la clave principal. Tendré esto en cuenta para futuros proyectos, ¡gracias!
funstein

2
Existe la posibilidad de que esto pueda resultar con solo números
Mladen Janjetovic

9
No es aleatorio en absoluto.
paul

1
Podría hacerlo más "aleatorio" si en lugar de usar la identificación incremental automática, usa la fecha y hora en la que se realizó la inserción, que también es única.
Javier La Banca

35

Crea una cadena aleatoria

Aquí hay una función de MySQL para crear una cadena aleatoria de una longitud determinada.

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

Uso SELECT RANDSTRING(8)para devolver una cadena de 8 caracteres.

Puede personalizar el @allowedChars.

La singularidad no está garantizada; como verá en los comentarios de otras soluciones, esto simplemente no es posible. En su lugar, deberá generar una cadena, verificar si ya está en uso y volver a intentarlo si lo está.


Compruebe si la cadena aleatoria ya está en uso

Si queremos mantener el código de verificación de colisiones fuera de la aplicación, podemos crear un disparador:

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
esta debería ser la respuesta aceptada, clara y al grano, funcionó bien para mí, gracias @ paddy-mann
Saif

Creo que esta es la mejor solución. ¡Gracias hombre!
Pronoy999

23

Aquí hay una forma, usando alfanuméricos como caracteres válidos:

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;

Tenga en cuenta que no hay garantía de unicidad. Tendrá que comprobarlo por separado.


7
use floor (rand () * 36 + 1) en su lugar. De lo contrario, algunos resultados serán "cortos".
Fraggle

2
¡debería haber 35 + 1 no 36 + 1! De lo contrario, obtendrá algunas cadenas con 8 caracteres y algunas de ellas con 7
BIOHAZARD

23

Aquí hay otro método para generar una cadena aleatoria:

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


16

Puede usar la función rand () y 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;

14

Puede generar una cadena alfanumérica aleatoria con:

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

Puede usarlo en un BEFORE INSERTdisparador y verificar si hay un duplicado en un ciclo 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 ;

Ahora solo inserta tus datos como

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

Y el disparador generará un valor para la platecolumna.

( demostración de sqlfiddle )

Eso funciona de esta manera si la columna permite NULL. Si desea que NO sea NULO, deberá definir un valor predeterminado

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

También puede usar cualquier otro algoritmo de generación de cadenas aleatorias en el activador si las letras alfanuméricas en mayúsculas no son lo que desea. Pero el disparador se encargará de la singularidad.


¡Asombroso! Esto es exactamente lo que quería. Solo me gustaría entender cómo se convierte en una cadena. Por qué hay un pow, qué hacer, sumar números, etc. De todos modos, gracias.
Akxe

@Akxe conv () se puede utilizar para convertir un número en una cadena alfanumérica. pow(36,8)-1es la representación numérica de ZZZZZZZZ. Entonces, generamos un número entero aleatorio entre 0y '36 ^ 8-1 '(de 0a 2821109907455) y lo convertimos en una cadena alfanumérica entre 0y ZZZZZZZZunsing conv(). lapad () llenará la cadena con ceros hasta que tenga una longitud de 8.
Paul Spiegel

Usted señor es un genio. Me imagino que es imposible agregar letras pequeñas debido a la falta de continuidad de los caracteres. (91-96) No es que lo necesite, solo curiosidad ...
Akxe

@Akxe conv()solo admite una base de hasta 36 (10 dígitos + 26 letras mayúsculas). Si desea incluir letras minúsculas, necesitará otra forma de convertir un número en una cadena.
Paul Spiegel

Advertencia: no funciona para str_len> 13. A partir del 14, siempre obtienes '3W5E11264SGSF'. ;-)
Gerard H. Pille

6

Para generar una cadena aleatoria, puede usar:

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

Recibes algo así:

353E50CC


5

Para una cadena que consta de 8 números aleatorios y letras mayúsculas y minúsculas, esta es mi solución:

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

Explicado de adentro hacia afuera:

  1. RAND genera un número aleatorio entre 0 y 1
  2. MD5 calcula la suma MD5 de (1), 32 caracteres de af y 0-9
  3. UNHEX traduce (2) en 16 bytes con valores de 00 a FF
  4. TO_BASE64 codifica (3) como base64, 22 caracteres de az y AZ y 0-9 más "/" y "+", seguidos de dos "="
  5. el tres REPLACE eliminan los caracteres "/", "+" y "=" de (4)
  6. LEFT toma los primeros 8 caracteres de (5), cambie 8 a otra cosa si necesita más o menos caracteres en su cadena aleatoria
  7. LPADinserta ceros al principio de (6) si tiene menos de 8 caracteres; de nuevo, cambie 8 a otra cosa si es necesario

Grande, exactamente lo que estaba buscando para crear un token como identificador de forma nativa en MySQL
rabudde

4

Utilizo datos de otra columna para generar un "hash" o una cadena única

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

4

8 letras del alfabeto - Todo en mayúsculas:

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

Si no tiene una identificación o semilla, como si fuera una lista de valores en la inserción:

REPLACE(RAND(), '.', '')

2

Solución simple y eficiente para obtener una cadena aleatoria de 10 caracteres con letras mayúsculas y minúsculas y dígitos:

select substring(base64_encode(md5(rand())) from 1+rand()*4 for 10);

1

Si está de acuerdo con las matrículas "aleatorias" pero completamente predecibles, puede usar un registro de desplazamiento de retroalimentación lineal para elegir el siguiente número de matrícula; se garantiza que pasará por todos los números antes de repetir. Sin embargo, sin algunas matemáticas complejas, no podrá pasar por cada cadena alfanumérica de 8 caracteres (obtendrá 2 ^ 41 de las 36 ^ 8 (78%) placas posibles). Para que esto llene mejor su espacio, podría excluir una letra de las placas (tal vez O), lo que le da un 97%.


1

Teniendo en cuenta el número total de caracteres que necesita, tendría una probabilidad muy pequeña de generar dos matrículas exactamente similares. Por lo tanto, probablemente podría salirse con la suya generando los números en LUA.

Tiene 36 ^ 8 placas de matrícula únicas diferentes (2.821.109.907.456, eso es mucho), incluso si ya tuviera un millón de placas de matrícula, tendría una posibilidad muy pequeña de generar una que ya tiene, aproximadamente 0.000035%

Por supuesto, todo depende de la cantidad de matrículas que acabes creando.


Es cierto que seguiré haciéndolo en el juego real en lugar de SQL. Muchas gracias.
funstein

1

Esta función genera una cadena aleatoria basada en la longitud de su entrada y los caracteres permitidos como este:

SELECT str_rand(8, '23456789abcdefghijkmnpqrstuvwxyz');

Código de función:

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 ;

Este código se basa en la función de secuencia aleatoria que envía "Ross Smith II"


Esta función no generará un valor único aleatorio.
Faisal

1

Para crear un alfanumérico aleatorio de 10 dígitos , excluyendo los caracteres similares 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)

Esto es exactamente lo que necesitaba para crear un código de cupón. . Los caracteres confusos se eliminan para reducir los errores al escribirlos en un formulario de código de cupón.

Espera que esto ayude a alguien, según la brillante respuesta de Jan Uhlig .

Consulte la respuesta de Jan para obtener un desglose de cómo funciona este código.


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 ;

Utilice este procedimiento almacenado y utilícelo siempre como

Call GenerateUniqueValue('tableName','columnName')

0

Una forma sencilla de generar un número único

set @i = 0;
update vehicles set plate = CONCAT(@i:=@i+1, ROUND(RAND() * 1000)) 
order by rand();


0

Estaba buscando algo similar y decidí hacer mi propia versión donde también se puede especificar una semilla diferente si se desea (lista de caracteres) como parámetro:

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

Puede usarse como:

SELECT random_string(10, '')

Que usaría la semilla incorporada de caracteres en mayúsculas y minúsculas + dígitos. NULL también sería valor en lugar de ''.

Pero se podría especificar una semilla personalizada al llamar:

SELECT random_string(10, '1234')
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.