18

I’m using MySql 5.5.37. I have a table with the following columns

+------------------+------------------+------+-----+---------+-------+
| Field            | Type             | Null | Key | Default | Extra |
+------------------+------------------+------+-----+---------+-------+
| ID               | varchar(32)      | NO   | PRI | NULL    |       |
| CODE             | varchar(6)       | NO   | UNI | NULL    |       |

The code column is unique and my ID column is a GUID. I have a number of rows that I would like to update, subject to some criteria in the above table (e.g. WHERE COLUMN1 = 0). How do I generate random, unique 6-character codes (ideally letters and numbers) for my CODE column such that they don’t violate the unique constraint in my table? Note that the columns in the table that do not meet the criteria (e.g. Where COLUMN1 <> 0) already have unique values for the CODE column.

Edit: This is different than this question -- Generating a random & unique 8 character string using MySQL because that link deals with IDs taht are numeric. My IDs are 32-character strings. Also their solution does not take into account the fact that there may values in the table prior to running the statements I want to run that will generate a unique values for the column in question.

Community
  • 1
  • 1
Dave
  • 15,639
  • 133
  • 442
  • 830
  • See my edit. The link you listed deals with a table that has numeric IDs, which mine does not have. – Dave Aug 31 '16 at 20:00
  • 1
    Must the values be random? Why? Is not "unique" sufficient? – Bohemian Sep 03 '16 at 02:06
  • 3
    If the values aren't random, people can figure out the algorithm and guess them. This code is used to grant access a part of the application. – Dave Sep 04 '16 at 14:36

6 Answers6

25

BEFORE UPDATE trigger solution:

You can create a 6 character random alphanumeric uppercase string with:

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

In order to not create an already existing string you can use a BEFORE UPDATE trigger.

DELIMITER //
CREATE TRIGGER `unique_codes_before_update`
BEFORE UPDATE ON `unique_codes` FOR EACH ROW 
BEGIN
    declare ready int default 0;
    declare rnd_str text;
    if new.CODE is null then
        while not ready do
            set rnd_str := lpad(conv(floor(rand()*pow(36,6)), 10, 36), 6, 0);
            if not exists (select * from unique_codes where CODE = rnd_str) then
                set new.CODE = rnd_str;
                set ready := 1;
            end if;
        end while;
    end if;
END//
DELIMITER ;

Every time you set your CODE column to NULL in an UPDATE statement, the trigger will create a new random string in a loop until no match has been found in the table.

Now you can replace all NULL values with:

update unique_codes set CODE = NULL where code is NULL;

In the SQLFiddle demo here i use a one character random string to demonstrate that no value is duplicated.

You can also use the same code in a BEFORE INSERT trigger. This way you can just insert new rows with CODE=NULL and the trigger will set it to a new unique random string. And you will never need to update it again.

Original answer (32 character strings):

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

-- output example: 3AHX44TF

will generate an 8-character alphanumeric uppercase random string. Concatenate four of them to get 32 characters:

select concat(
    lpad(conv(floor(rand()*pow(36,8)), 10, 36), 8, 0),
    lpad(conv(floor(rand()*pow(36,8)), 10, 36), 8, 0),
    lpad(conv(floor(rand()*pow(36,8)), 10, 36), 8, 0),
    lpad(conv(floor(rand()*pow(36,8)), 10, 36), 8, 0)
) as rnd_str_32;

-- output example: KGC8A8EGKE7E4MGD4M09U9YWXVF6VDDS

http://sqlfiddle.com/#!9/9eecb7d/76933

So what about uniqness? Well - try to generate duplicates ;-)

Paul Spiegel
  • 30,925
  • 5
  • 44
  • 53
  • You are generating strings of 32 characters but my unique column is only 6 characters wide. I'm not making the leap of how I get from your answer to what I need to insert in my column. – Dave Sep 01 '16 at 12:05
  • Sorry, I thought you want a 32 character string. For 6 characters i would probably create a trigger, wich will check for duplicates in a loop. Or precalculate a table with unique strings. – Paul Spiegel Sep 01 '16 at 16:42
  • What I'm asking is that I have a group fo existing rows that require unique values (they are all set to NULL at the moment) and I want to figure out how to populate those . Triggers apply to adds, not updates, as I understand them. – Dave Sep 01 '16 at 21:36
  • You're setting the CODE column to NULL, when I have a NOT NULL constraint on that column. – Dave Sep 07 '16 at 14:13
  • @Dave - How can you have a NOT NULL constraint and NULL values in the column at same time? However try `update unique_codes set ID = ID where code is NULL;` That should also work. – Paul Spiegel Sep 07 '16 at 14:42
  • Lol, yeah good call. I removed my NOT NULL constraint and now everything is working. – Dave Sep 07 '16 at 15:40
1

This one is tricky but I think I've reached a nice solution:

DROP FUNCTION IF EXISTS getRandomAlphaNumeric;

DELIMITER $$

CREATE FUNCTION getRandomAlphaNumeric() RETURNS CHAR(6)
DETERMINISTIC
BEGIN

    SELECT 
    CONCAT (
        CHAR(FLOOR(RAND()*10)+48), CHAR(FLOOR(RAND()*26)+65), CHAR(FLOOR(RAND()*26)+97),
        CHAR(FLOOR(RAND()*10)+48), CHAR(FLOOR(RAND()*26)+65), CHAR(FLOOR(RAND()*26)+97)
    ) INTO @code
    ;

    RETURN @code;
END
$$

DELIMITER ;


DROP PROCEDURE IF EXISTS generateCodes;

DELIMITER $$
CREATE PROCEDURE generateCodes()
BEGIN

    SET @count = 0;
    SELECT COUNT(1) INTO @count FROM demo.codes;

    SET @i = 0;
    WHILE @i < @count DO

        PREPARE stmt FROM "SELECT @id := id, @itemCode := code FROM demo.codes p LIMIT ?, 1;";
        EXECUTE stmt USING @i;

        SET @code = getRandomAlphaNumeric();

        SELECT COUNT(1) INTO @countRowsWithCode FROM demo.codes WHERE code = @code;

        IF @countRowsWithCode = 0 AND @itemCode IS NULL THEN
            UPDATE demo.codes SET code = @code WHERE id = @id;
        END IF;

        SET @i := @i + 1;   
    END WHILE;
END
$$

DELIMITER ;


CALL generateCodes();

First, I created a function that returns a random string of 6 chars that it's used following to generates the desired codes:

DROP FUNCTION IF EXISTS getRandomAlphaNumeric;

DELIMITER $$

CREATE FUNCTION getRandomAlphaNumeric() RETURNS CHAR(6)
DETERMINISTIC
BEGIN

    SELECT 
    CONCAT (
        CHAR(FLOOR(RAND()*10)+48), CHAR(FLOOR(RAND()*26)+65), CHAR(FLOOR(RAND()*26)+97),
        CHAR(FLOOR(RAND()*10)+48), CHAR(FLOOR(RAND()*26)+65), CHAR(FLOOR(RAND()*26)+97)
    ) INTO @code
    ;

    RETURN @code;
END
$$

Then I created a procedure that is responsible to update the table with random unique codes. The procedure consists in:

  • Count all the records that will be updated with a fresh and random code of 6 characters.

    SELECT COUNT(1) INTO @count FROM demo.codes;

  • Then, foreach row (using WHILE loop):

    • Get the id of the next record to be uptdated

      PREPARE stmt FROM "SELECT @id := id, @itemCode := code FROM demo.codes p LIMIT ?, 1;"; EXECUTE stmt USING @i;

    • Get a new code for the record:

      SET @code = getRandomAlphaNumeric();

    • For last, verify if the new code do not already exists on table and if currently the field column has no value (is NULL), if it's not, update the current record with the random code:

      SELECT COUNT(1) INTO @countRowsWithCode FROM demo.codes WHERE code = @code; IF @countRowsWithCode = 0 AND @itemCode IS NULL THEN UPDATE demo.codes SET code = @code WHERE id = @id; END IF;

    • Finally, CALL the created PROCEDURE in order to populate the fields from code column that are NULL.

      CALL generateCodes();

Cristian Gonçalves
  • 892
  • 2
  • 9
  • 18
  • Hey I'm trying to figure out what's going on before i run this ... will this update all rows in the codes table or will it only update the rows where there is no code (the value is NULL)? – Dave Sep 06 '16 at 14:21
  • Hey Dave. I didn't notice that requirement on your request but I edited my response and now the current script will only generate random codes for fields that currently has no value, i.e, they're `NULL` – Cristian Gonçalves Sep 06 '16 at 15:34
  • Np, thanks for your update. How long will your function take? I have about 30,000 rows that need to be updated and have been running your code but it has been half an hour and it is still running. I think I'm in the midst of an infinite loop. – Dave Sep 06 '16 at 19:30
  • Woow 30k rows... ok, things can get pretty slower... At this moment, the script ran successfully? – Cristian Gonçalves Sep 06 '16 at 21:23
  • I had to stop it after a coupe of hours because the process was draining my battery. I'll try and figure out another way to troubleshoot and determine if it was making progress or if it was hung for some reason. – Dave Sep 06 '16 at 21:38
1
CONV(CONV(( SELECT MAX(CODE) FROM tbl ), 36, 10) + 1, 10, 36)

will get you the next 'number' encoded in base-36 (digits & capital letters).

For example:

SELECT CONV(CONV(( 'A1B2C' ), 36, 10) + 1, 10, 36); --> 'A1B2D'
Rick James
  • 135,179
  • 13
  • 127
  • 222
0

Try this for code

SELECT LEFT(MD5(NOW()), 6) AS CODE;

LEFT(MD5(NOW()), 6) this will return unique code with 6 characters.

Try another way like this

SELECT LEFT(UUID(), 6);

LEFT(UUID(), 6) This will also return unique code

krunal nerikar
  • 436
  • 4
  • 12
  • If I'm updating multiple rows at once in a big update statement, won't NOW() work out to be the same thing for several rows? – Dave Sep 06 '16 at 13:45
  • This does not generate unique ID. `SELECT LEFT(MD5(NOW()), 6) AS CODE union all SELECT LEFT(MD5(NOW()), 6) AS CODE` output -> `9fd589 9fd5891` – TheTechGuy Sep 26 '17 at 15:56
0
DELIMITER $$

USE `db` $$

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 this stored procedure like

Call GenerateUniqueValue('tableName','columnName')

This will give you a unique 8 character string everytime.

0

Simple and efficient solution to get a random 10 characters string with uppercase and lowercase letters and digits, without custom function (works with MySQL >= 5.6) :

select substring(base64_encode(md5(rand())) from 1+rand()*4 for 10);
Antares
  • 177
  • 2
  • 12