Related
I'm working on a game which involves vehicles at some point. I have a MySQL table named "vehicles" containing the data about the vehicles, including the column "plate" which stores the License Plates for the vehicles.
Now here comes the part I'm having problems with. I need to find an unused license plate before creating a new vehicle - it should be an alphanumeric 8-char random string. How I achieved this was using a while loop in Lua, which is the language I'm programming in, to generate strings and query the DB to see if it is used. However, as the number of vehicles increases, I expect this to become even more inefficient it is right now. Therefore, I decided to try and solve this issue using a MySQL query.
The query I need should simply generate a 8-character alphanumeric string which is not already in the table. I thought of the generate&check loop approach again, but I'm not limiting this question to that just in case there's a more efficient one. I've been able to generate strings by defining a string containing all the allowed chars and randomly substringing it, and nothing more.
Any help is appreciated.
I woudn't bother with the likelihood of collision. Just generate a random string and check if it exists. If it does, try again and you shouldn't need to do it more that a couple of times unless you have a huge number of plates already assigned.
Another solution for generating an 8-character long pseudo-random string in pure (My)SQL:
SELECT LEFT(UUID(), 8);
You can try the following (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
Since this post has received a unexpected level of attention, let me highlight ADTC's comment : the above piece of code is quite dumb and produces sequential digits.
For slightly less stupid randomness try something like this instead :
SELECT LEFT(MD5(RAND()), 8)
And for true (cryptograpically secure) randomness, use RANDOM_BYTES() rather than RAND() (but then I would consider moving this logic up to the application layer).
This problem consists of two very different sub-problems:
the string must be seemingly random
the string must be unique
While randomness is quite easily achieved, the uniqueness without a retry loop is not. This brings us to concentrate on the uniqueness first. Non-random uniqueness can trivially be achieved with AUTO_INCREMENT. So using a uniqueness-preserving, pseudo-random transformation would be fine:
Hash has been suggested by #paul
AES-encrypt fits also
But there is a nice one: RAND(N) itself!
A sequence of random numbers created by the same seed is guaranteed to be
reproducible
different for the first 8 iterations
if the seed is an INT32
So we use #AndreyVolk's or #GordonLinoff's approach, but with a seeded RAND:
e.g. Assumin id is an AUTO_INCREMENT column:
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;
What about calculating the MD5 (or other) hash of sequential integers, then taking the first 8 characters.
i.e
MD5(1) = c4ca4238a0b923820dcc509a6f75849b => c4ca4238
MD5(2) = c81e728d9d4c2f636f067f89cc14862c => c81e728d
MD5(3) = eccbc87e4b5ce2fe28308fd9f2a7baf3 => eccbc87e
etc.
caveat: I have no idea how many you could allocate before a collision (but it would be a known and constant value).
edit: This is now an old answer, but I saw it again with time on my hands, so, from observation...
Chance of all numbers = 2.35%
Chance of all letters = 0.05%
First collision when MD5(82945) = "7b763dcb..." (same result as MD5(25302))
Create a random string
Here's a MySQL function to create a random string of a given length.
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
DELIMITER ;
Usage SELECT RANDSTRING(8) to return an 8 character string.
You can customize the #allowedChars.
Uniqueness isn't guaranteed - as you'll see in the comments to other solutions, this just isn't possible. Instead you'll need to generate a string, check if it's already in use, and try again if it is.
Check if the random string is already in use
If we want to keep the collision checking code out of the app, we can create a trigger:
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 ;
Here is one way, using alpha numerics as valid characters:
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;
Note there is no guarantee of uniqueness. You'll have to check for that separately.
Here's another method for generating a random string:
SELECT SUBSTRING(MD5(RAND()) FROM 1 FOR 8) AS myrandomstring
You may use MySQL's rand() and char() function:
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;
You can generate a random alphanumeric string with:
lpad(conv(floor(rand()*pow(36,8)), 10, 36), 8, 0);
You can use it in a BEFORE INSERT trigger and check for a duplicate in a while loop:
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 ;
Now just insert your data like
insert into vehicles(col1, col2) values ('value1', 'value2');
And the trigger will generate a value for the plate column.
(sqlfiddle demo)
That works this way if the column allows NULLs. If you want it to be NOT NULL you would need to define a default value
`plate` CHAR(8) NOT NULL DEFAULT 'default',
You can also use any other random string generating algorithm in the trigger if uppercase alphanumerics isn't what you want. But the trigger will take care of uniqueness.
For a String consisting of 8 random numbers and upper- and lowercase letters, this is my solution:
LPAD(LEFT(REPLACE(REPLACE(REPLACE(TO_BASE64(UNHEX(MD5(RAND()))), "/", ""), "+", ""), "=", ""), 8), 8, 0)
Explained from inside out:
RAND generates a random number between 0 and 1
MD5 calculates the MD5 sum of (1), 32 characters from a-f and 0-9
UNHEX translates (2) into 16 bytes with values from 00 to FF
TO_BASE64 encodes (3) as base64, 22 characters from a-z and A-Z and 0-9 plus "/" and "+", followed by two "="
the three REPLACEs remove the "/", "+" and "=" characters from (4)
LEFT takes the first 8 characters from (5), change 8 to something else if you need more or less characters in your random string
LPAD inserts zeroes at the beginning of (6) if it is less than 8 characters long; again, change 8 to something else if needed
For generate random string, you can use:
SUBSTRING(MD5(RAND()) FROM 1 FOR 8)
You recieve smth like that:
353E50CC
I Use data from another column to generate a "hash" or unique string
UPDATE table_name SET column_name = Right( MD5(another_column_with_data), 8 )
8 letters from the alphabet - All caps:
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))));
If you dont have a id or seed, like its its for a values list in insert:
REPLACE(RAND(), '.', '')
Taking into account the total number of characters that you require, you would have a very small chance of generating two exactly similar number plates. Thus you could probably get away with generating the numbers in LUA.
You have 36^8 different unique numberplates (2,821,109,907,456, that's a lot), even if you already had a million numberplates already, you'd have a very small chance of generating one you already have, about 0.000035%
Of course, it all depends on how many numberplates you will end up creating.
This function generates a Random string based on your input length and allowed characters like this:
SELECT str_rand(8, '23456789abcdefghijkmnpqrstuvwxyz');
function code:
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 ;
This code is based on shuffle string function sends by "Ross Smith II"
To create a random 10 digit alphanumeric, excluding lookalike chars 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)
This is exactly what I needed to create a voucher code. Confusing characters are removed to reduce errors when typing it into a voucher code form.
Hopes this helps somebody, based on Jan Uhlig's brilliant answer.
Please see Jan's answer for a breakdown on how this code works.
Simple and efficient solution to get a random 10 characters string with uppercase and lowercase letters and digits :
select substring(base64_encode(md5(rand())) from 1+rand()*4 for 10);
UPPER(HEX(UUID_SHORT()))
gives you a 16-character alphanumeric string that is unique. It has some unlikely caveats, see https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_uuid-short
The "next" value is often predictable:
mysql> SELECT UPPER(HEX(UUID_SHORT()));
+--------------------------+
| UPPER(HEX(UUID_SHORT())) |
+--------------------------+
| 161AA3FA5000006 |
+--------------------------+
mysql> SELECT UPPER(HEX(UUID_SHORT()));
+--------------------------+
| UPPER(HEX(UUID_SHORT())) |
+--------------------------+
| 161AA3FA5000007 |
+--------------------------+
Converting to BASE64 can get the string down to 11 characters:
https://dev.mysql.com/doc/refman/8.0/en/string-functions.html#function_to-base64
mysql> SELECT TO_BASE64(UNHEX(HEX(UUID_SHORT())));
+-------------------------------------+
| TO_BASE64(UNHEX(HEX(UUID_SHORT()))) |
+-------------------------------------+
| AWGqP6UAABA= |
+-------------------------------------+
That's 12 chars, stripping off the '=' gives you 11.
These may make it unsuitable for your use: The "next" plate is somewhat predictable. There can be some punctuation marks (+,/) in the string. Lower case letters are likely to be included.
This work form me, generate 6 digit number and update in MySQL:
Generate:
SELECT SUBSTRING(MD5(RAND()) FROM 1 FOR 6)
Update:
UPDATE table_name
SET column_name = SUBSTRING(MD5(RAND()) FROM 1 FOR 6)
WHERE id = x12
If you're OK with "random" but entirely predictable license plates, you can use a linear-feedback shift register to choose the next plate number - it's guaranteed to go through every number before repeating. However, without some complex math, you won't be able to go through every 8 character alphanumeric string (you'll get 2^41 out of the 36^8 (78%) possible plates). To make this fill your space better, you could exclude a letter from the plates (maybe O), giving you 97%.
Generate 8 characters key
lpad(conv(floor(rand()*pow(36,6)), 10, 36), 8, 0);
How do I generate a unique, random string for one of my MySql table columns?
SQL Triggers are complex and resource-intensive. Against a MySQL "Trigger"-based solutions, here is a simpler solution.
Create a UNIQUE INDEX on the MySQL table column which will hold the vehicle registration plate string. This will ensure only unique values go in.
Simply generate the standard alphanumeric random string in Lua (or any other programming language like ASP, PHP, Java, etc.)
Execute the INSERT statement with the generated string, and have error-catching code to parse the failure (in case of the UNIQUE INDEX violation)
If the INSERT fails, generate a new random string and re-insert. Length of 8 chars in itself is pretty difficult to repeat, and once found in table generating another one will be next to impossible to be another repeat.
This will be lighter and more efficient on DB Server.
Here's a sample (pseudo-) code in PHP:
function refercode()
{
$string = '';
$characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$max = strlen($characters) - 1;
for ($i = 0; $i < 8; $i++) {
$string .= $characters[mt_rand(0, $max)];
}
$refer = "select * from vehicles where refer_code = '".$string."' ";
$coderefertest = mysqli_query($con,$refer);
if(mysqli_num_rows($coderefertest)>0)
{
return refercode();
}
else
{
return $string;
}
}
$refer_by = refercode();
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 ;
Use this stored procedure and use it everytime like
Call GenerateUniqueValue('tableName','columnName')
An easy way that generate a unique number
set #i = 0;
update vehicles set plate = CONCAT(#i:=#i+1, ROUND(RAND() * 1000))
order by rand();
I was looking for something similar and I decided to make my own version where you can also specify a different seed if wanted (list of characters) as parameter:
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
Can be used as:
SELECT random_string(10, '')
Which would use the built-in seed of upper- and lowercase characters + digits.
NULL would also be value instead of ''.
But one could specify a custom seed while calling:
SELECT random_string(10, '1234')
I'm working on a game which involves vehicles at some point. I have a MySQL table named "vehicles" containing the data about the vehicles, including the column "plate" which stores the License Plates for the vehicles.
Now here comes the part I'm having problems with. I need to find an unused license plate before creating a new vehicle - it should be an alphanumeric 8-char random string. How I achieved this was using a while loop in Lua, which is the language I'm programming in, to generate strings and query the DB to see if it is used. However, as the number of vehicles increases, I expect this to become even more inefficient it is right now. Therefore, I decided to try and solve this issue using a MySQL query.
The query I need should simply generate a 8-character alphanumeric string which is not already in the table. I thought of the generate&check loop approach again, but I'm not limiting this question to that just in case there's a more efficient one. I've been able to generate strings by defining a string containing all the allowed chars and randomly substringing it, and nothing more.
Any help is appreciated.
I woudn't bother with the likelihood of collision. Just generate a random string and check if it exists. If it does, try again and you shouldn't need to do it more that a couple of times unless you have a huge number of plates already assigned.
Another solution for generating an 8-character long pseudo-random string in pure (My)SQL:
SELECT LEFT(UUID(), 8);
You can try the following (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
Since this post has received a unexpected level of attention, let me highlight ADTC's comment : the above piece of code is quite dumb and produces sequential digits.
For slightly less stupid randomness try something like this instead :
SELECT LEFT(MD5(RAND()), 8)
And for true (cryptograpically secure) randomness, use RANDOM_BYTES() rather than RAND() (but then I would consider moving this logic up to the application layer).
This problem consists of two very different sub-problems:
the string must be seemingly random
the string must be unique
While randomness is quite easily achieved, the uniqueness without a retry loop is not. This brings us to concentrate on the uniqueness first. Non-random uniqueness can trivially be achieved with AUTO_INCREMENT. So using a uniqueness-preserving, pseudo-random transformation would be fine:
Hash has been suggested by #paul
AES-encrypt fits also
But there is a nice one: RAND(N) itself!
A sequence of random numbers created by the same seed is guaranteed to be
reproducible
different for the first 8 iterations
if the seed is an INT32
So we use #AndreyVolk's or #GordonLinoff's approach, but with a seeded RAND:
e.g. Assumin id is an AUTO_INCREMENT column:
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;
What about calculating the MD5 (or other) hash of sequential integers, then taking the first 8 characters.
i.e
MD5(1) = c4ca4238a0b923820dcc509a6f75849b => c4ca4238
MD5(2) = c81e728d9d4c2f636f067f89cc14862c => c81e728d
MD5(3) = eccbc87e4b5ce2fe28308fd9f2a7baf3 => eccbc87e
etc.
caveat: I have no idea how many you could allocate before a collision (but it would be a known and constant value).
edit: This is now an old answer, but I saw it again with time on my hands, so, from observation...
Chance of all numbers = 2.35%
Chance of all letters = 0.05%
First collision when MD5(82945) = "7b763dcb..." (same result as MD5(25302))
Create a random string
Here's a MySQL function to create a random string of a given length.
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
DELIMITER ;
Usage SELECT RANDSTRING(8) to return an 8 character string.
You can customize the #allowedChars.
Uniqueness isn't guaranteed - as you'll see in the comments to other solutions, this just isn't possible. Instead you'll need to generate a string, check if it's already in use, and try again if it is.
Check if the random string is already in use
If we want to keep the collision checking code out of the app, we can create a trigger:
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 ;
Here is one way, using alpha numerics as valid characters:
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;
Note there is no guarantee of uniqueness. You'll have to check for that separately.
Here's another method for generating a random string:
SELECT SUBSTRING(MD5(RAND()) FROM 1 FOR 8) AS myrandomstring
You may use MySQL's rand() and char() function:
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;
You can generate a random alphanumeric string with:
lpad(conv(floor(rand()*pow(36,8)), 10, 36), 8, 0);
You can use it in a BEFORE INSERT trigger and check for a duplicate in a while loop:
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 ;
Now just insert your data like
insert into vehicles(col1, col2) values ('value1', 'value2');
And the trigger will generate a value for the plate column.
(sqlfiddle demo)
That works this way if the column allows NULLs. If you want it to be NOT NULL you would need to define a default value
`plate` CHAR(8) NOT NULL DEFAULT 'default',
You can also use any other random string generating algorithm in the trigger if uppercase alphanumerics isn't what you want. But the trigger will take care of uniqueness.
For a String consisting of 8 random numbers and upper- and lowercase letters, this is my solution:
LPAD(LEFT(REPLACE(REPLACE(REPLACE(TO_BASE64(UNHEX(MD5(RAND()))), "/", ""), "+", ""), "=", ""), 8), 8, 0)
Explained from inside out:
RAND generates a random number between 0 and 1
MD5 calculates the MD5 sum of (1), 32 characters from a-f and 0-9
UNHEX translates (2) into 16 bytes with values from 00 to FF
TO_BASE64 encodes (3) as base64, 22 characters from a-z and A-Z and 0-9 plus "/" and "+", followed by two "="
the three REPLACEs remove the "/", "+" and "=" characters from (4)
LEFT takes the first 8 characters from (5), change 8 to something else if you need more or less characters in your random string
LPAD inserts zeroes at the beginning of (6) if it is less than 8 characters long; again, change 8 to something else if needed
For generate random string, you can use:
SUBSTRING(MD5(RAND()) FROM 1 FOR 8)
You recieve smth like that:
353E50CC
I Use data from another column to generate a "hash" or unique string
UPDATE table_name SET column_name = Right( MD5(another_column_with_data), 8 )
8 letters from the alphabet - All caps:
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))));
If you dont have a id or seed, like its its for a values list in insert:
REPLACE(RAND(), '.', '')
Taking into account the total number of characters that you require, you would have a very small chance of generating two exactly similar number plates. Thus you could probably get away with generating the numbers in LUA.
You have 36^8 different unique numberplates (2,821,109,907,456, that's a lot), even if you already had a million numberplates already, you'd have a very small chance of generating one you already have, about 0.000035%
Of course, it all depends on how many numberplates you will end up creating.
This function generates a Random string based on your input length and allowed characters like this:
SELECT str_rand(8, '23456789abcdefghijkmnpqrstuvwxyz');
function code:
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 ;
This code is based on shuffle string function sends by "Ross Smith II"
To create a random 10 digit alphanumeric, excluding lookalike chars 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)
This is exactly what I needed to create a voucher code. Confusing characters are removed to reduce errors when typing it into a voucher code form.
Hopes this helps somebody, based on Jan Uhlig's brilliant answer.
Please see Jan's answer for a breakdown on how this code works.
Simple and efficient solution to get a random 10 characters string with uppercase and lowercase letters and digits :
select substring(base64_encode(md5(rand())) from 1+rand()*4 for 10);
UPPER(HEX(UUID_SHORT()))
gives you a 16-character alphanumeric string that is unique. It has some unlikely caveats, see https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_uuid-short
The "next" value is often predictable:
mysql> SELECT UPPER(HEX(UUID_SHORT()));
+--------------------------+
| UPPER(HEX(UUID_SHORT())) |
+--------------------------+
| 161AA3FA5000006 |
+--------------------------+
mysql> SELECT UPPER(HEX(UUID_SHORT()));
+--------------------------+
| UPPER(HEX(UUID_SHORT())) |
+--------------------------+
| 161AA3FA5000007 |
+--------------------------+
Converting to BASE64 can get the string down to 11 characters:
https://dev.mysql.com/doc/refman/8.0/en/string-functions.html#function_to-base64
mysql> SELECT TO_BASE64(UNHEX(HEX(UUID_SHORT())));
+-------------------------------------+
| TO_BASE64(UNHEX(HEX(UUID_SHORT()))) |
+-------------------------------------+
| AWGqP6UAABA= |
+-------------------------------------+
That's 12 chars, stripping off the '=' gives you 11.
These may make it unsuitable for your use: The "next" plate is somewhat predictable. There can be some punctuation marks (+,/) in the string. Lower case letters are likely to be included.
This work form me, generate 6 digit number and update in MySQL:
Generate:
SELECT SUBSTRING(MD5(RAND()) FROM 1 FOR 6)
Update:
UPDATE table_name
SET column_name = SUBSTRING(MD5(RAND()) FROM 1 FOR 6)
WHERE id = x12
If you're OK with "random" but entirely predictable license plates, you can use a linear-feedback shift register to choose the next plate number - it's guaranteed to go through every number before repeating. However, without some complex math, you won't be able to go through every 8 character alphanumeric string (you'll get 2^41 out of the 36^8 (78%) possible plates). To make this fill your space better, you could exclude a letter from the plates (maybe O), giving you 97%.
Generate 8 characters key
lpad(conv(floor(rand()*pow(36,6)), 10, 36), 8, 0);
How do I generate a unique, random string for one of my MySql table columns?
SQL Triggers are complex and resource-intensive. Against a MySQL "Trigger"-based solutions, here is a simpler solution.
Create a UNIQUE INDEX on the MySQL table column which will hold the vehicle registration plate string. This will ensure only unique values go in.
Simply generate the standard alphanumeric random string in Lua (or any other programming language like ASP, PHP, Java, etc.)
Execute the INSERT statement with the generated string, and have error-catching code to parse the failure (in case of the UNIQUE INDEX violation)
If the INSERT fails, generate a new random string and re-insert. Length of 8 chars in itself is pretty difficult to repeat, and once found in table generating another one will be next to impossible to be another repeat.
This will be lighter and more efficient on DB Server.
Here's a sample (pseudo-) code in PHP:
function refercode()
{
$string = '';
$characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$max = strlen($characters) - 1;
for ($i = 0; $i < 8; $i++) {
$string .= $characters[mt_rand(0, $max)];
}
$refer = "select * from vehicles where refer_code = '".$string."' ";
$coderefertest = mysqli_query($con,$refer);
if(mysqli_num_rows($coderefertest)>0)
{
return refercode();
}
else
{
return $string;
}
}
$refer_by = refercode();
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 ;
Use this stored procedure and use it everytime like
Call GenerateUniqueValue('tableName','columnName')
An easy way that generate a unique number
set #i = 0;
update vehicles set plate = CONCAT(#i:=#i+1, ROUND(RAND() * 1000))
order by rand();
I was looking for something similar and I decided to make my own version where you can also specify a different seed if wanted (list of characters) as parameter:
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
Can be used as:
SELECT random_string(10, '')
Which would use the built-in seed of upper- and lowercase characters + digits.
NULL would also be value instead of ''.
But one could specify a custom seed while calling:
SELECT random_string(10, '1234')
I'm looking for a single query that's purely MySQL. The goal of this query is to utilize things such as SUBSTRING_INDEX, CONCAT, or whatever it needs to, in order to find a value in a string.
Let's say that the string looks something like this:
{"name":34,"otherName":55,"moreNames":12,"target":26,"hello":56,"hi":26,"asd":552,"p":3722,"bestName":11,"cc":6,"dd":10,}
My goal is to get the value of target, in this case, 26. However, "target":26 might not always be in that location in the string. Neither would any of the other properties. On top of that, the value might not always be 26. I need some way to check what number comes after "target": but before the , after "target":. Is there any way of doing this?
This one ?
create table sandbox (id integer, jsoncolumn varchar(255));
insert into sandbox values (1,'{"name":34,"otherName":55,"moreNames":12,"target":26,"hello":56,"hi":26,"asd":552,"p":3722,"bestName":11,"cc":6,"dd":10}');
mysql root#localhost:sandbox> SELECT jsoncolumn->'$.target' from sandbox;
+--------------------------+
| jsoncolumn->'$.target' |
|--------------------------|
| 26 |
+--------------------------+
https://dev.mysql.com/doc/refman/5.7/en/json-search-functions.html
Please try this function to get value from JSON string in MYSQL
DROP FUNCTION IF EXISTS CAP_FIRST_CHAR;
DELIMITER $$
CREATE FUNCTION getValueFromJsonSring(jsonStr VARCHAR(250), getKey VARCHAR(250))
RETURNS VARCHAR(250) deterministic
BEGIN
DECLARE output VARCHAR(250); -- Holds the final value.
DECLARE data VARCHAR(250); -- Holds the exctracted value from JSON
SET getKey=CONCAT('"',getKey,'"');
SET data= TRIM(LEADING ':' FROM
substring_index(
substring_index(
substring_index(
substring_index(
SUBSTRING(jsonStr, 2, LENGTH(jsonStr)-2)
, getKey , '2'),
getKey,
-1
)
, ',', '1'),
',',
-1
)
);
SET output =SUBSTRING(data, 2, LENGTH(data)-2);
RETURN output;
END;
$$
DELIMITER ;
SELECT getValueFromJsonSring('{"amount":"400.34","departmentId":"7","date":"2017-06-02","PONumber":"0000064873","vendor":"44"}',"departmentId");
I'm working on a game which involves vehicles at some point. I have a MySQL table named "vehicles" containing the data about the vehicles, including the column "plate" which stores the License Plates for the vehicles.
Now here comes the part I'm having problems with. I need to find an unused license plate before creating a new vehicle - it should be an alphanumeric 8-char random string. How I achieved this was using a while loop in Lua, which is the language I'm programming in, to generate strings and query the DB to see if it is used. However, as the number of vehicles increases, I expect this to become even more inefficient it is right now. Therefore, I decided to try and solve this issue using a MySQL query.
The query I need should simply generate a 8-character alphanumeric string which is not already in the table. I thought of the generate&check loop approach again, but I'm not limiting this question to that just in case there's a more efficient one. I've been able to generate strings by defining a string containing all the allowed chars and randomly substringing it, and nothing more.
Any help is appreciated.
I woudn't bother with the likelihood of collision. Just generate a random string and check if it exists. If it does, try again and you shouldn't need to do it more that a couple of times unless you have a huge number of plates already assigned.
Another solution for generating an 8-character long pseudo-random string in pure (My)SQL:
SELECT LEFT(UUID(), 8);
You can try the following (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
Since this post has received a unexpected level of attention, let me highlight ADTC's comment : the above piece of code is quite dumb and produces sequential digits.
For slightly less stupid randomness try something like this instead :
SELECT LEFT(MD5(RAND()), 8)
And for true (cryptograpically secure) randomness, use RANDOM_BYTES() rather than RAND() (but then I would consider moving this logic up to the application layer).
This problem consists of two very different sub-problems:
the string must be seemingly random
the string must be unique
While randomness is quite easily achieved, the uniqueness without a retry loop is not. This brings us to concentrate on the uniqueness first. Non-random uniqueness can trivially be achieved with AUTO_INCREMENT. So using a uniqueness-preserving, pseudo-random transformation would be fine:
Hash has been suggested by #paul
AES-encrypt fits also
But there is a nice one: RAND(N) itself!
A sequence of random numbers created by the same seed is guaranteed to be
reproducible
different for the first 8 iterations
if the seed is an INT32
So we use #AndreyVolk's or #GordonLinoff's approach, but with a seeded RAND:
e.g. Assumin id is an AUTO_INCREMENT column:
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;
What about calculating the MD5 (or other) hash of sequential integers, then taking the first 8 characters.
i.e
MD5(1) = c4ca4238a0b923820dcc509a6f75849b => c4ca4238
MD5(2) = c81e728d9d4c2f636f067f89cc14862c => c81e728d
MD5(3) = eccbc87e4b5ce2fe28308fd9f2a7baf3 => eccbc87e
etc.
caveat: I have no idea how many you could allocate before a collision (but it would be a known and constant value).
edit: This is now an old answer, but I saw it again with time on my hands, so, from observation...
Chance of all numbers = 2.35%
Chance of all letters = 0.05%
First collision when MD5(82945) = "7b763dcb..." (same result as MD5(25302))
Create a random string
Here's a MySQL function to create a random string of a given length.
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
DELIMITER ;
Usage SELECT RANDSTRING(8) to return an 8 character string.
You can customize the #allowedChars.
Uniqueness isn't guaranteed - as you'll see in the comments to other solutions, this just isn't possible. Instead you'll need to generate a string, check if it's already in use, and try again if it is.
Check if the random string is already in use
If we want to keep the collision checking code out of the app, we can create a trigger:
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 ;
Here is one way, using alpha numerics as valid characters:
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;
Note there is no guarantee of uniqueness. You'll have to check for that separately.
Here's another method for generating a random string:
SELECT SUBSTRING(MD5(RAND()) FROM 1 FOR 8) AS myrandomstring
You may use MySQL's rand() and char() function:
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;
You can generate a random alphanumeric string with:
lpad(conv(floor(rand()*pow(36,8)), 10, 36), 8, 0);
You can use it in a BEFORE INSERT trigger and check for a duplicate in a while loop:
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 ;
Now just insert your data like
insert into vehicles(col1, col2) values ('value1', 'value2');
And the trigger will generate a value for the plate column.
(sqlfiddle demo)
That works this way if the column allows NULLs. If you want it to be NOT NULL you would need to define a default value
`plate` CHAR(8) NOT NULL DEFAULT 'default',
You can also use any other random string generating algorithm in the trigger if uppercase alphanumerics isn't what you want. But the trigger will take care of uniqueness.
For a String consisting of 8 random numbers and upper- and lowercase letters, this is my solution:
LPAD(LEFT(REPLACE(REPLACE(REPLACE(TO_BASE64(UNHEX(MD5(RAND()))), "/", ""), "+", ""), "=", ""), 8), 8, 0)
Explained from inside out:
RAND generates a random number between 0 and 1
MD5 calculates the MD5 sum of (1), 32 characters from a-f and 0-9
UNHEX translates (2) into 16 bytes with values from 00 to FF
TO_BASE64 encodes (3) as base64, 22 characters from a-z and A-Z and 0-9 plus "/" and "+", followed by two "="
the three REPLACEs remove the "/", "+" and "=" characters from (4)
LEFT takes the first 8 characters from (5), change 8 to something else if you need more or less characters in your random string
LPAD inserts zeroes at the beginning of (6) if it is less than 8 characters long; again, change 8 to something else if needed
For generate random string, you can use:
SUBSTRING(MD5(RAND()) FROM 1 FOR 8)
You recieve smth like that:
353E50CC
I Use data from another column to generate a "hash" or unique string
UPDATE table_name SET column_name = Right( MD5(another_column_with_data), 8 )
8 letters from the alphabet - All caps:
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))));
If you dont have a id or seed, like its its for a values list in insert:
REPLACE(RAND(), '.', '')
Taking into account the total number of characters that you require, you would have a very small chance of generating two exactly similar number plates. Thus you could probably get away with generating the numbers in LUA.
You have 36^8 different unique numberplates (2,821,109,907,456, that's a lot), even if you already had a million numberplates already, you'd have a very small chance of generating one you already have, about 0.000035%
Of course, it all depends on how many numberplates you will end up creating.
This function generates a Random string based on your input length and allowed characters like this:
SELECT str_rand(8, '23456789abcdefghijkmnpqrstuvwxyz');
function code:
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 ;
This code is based on shuffle string function sends by "Ross Smith II"
To create a random 10 digit alphanumeric, excluding lookalike chars 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)
This is exactly what I needed to create a voucher code. Confusing characters are removed to reduce errors when typing it into a voucher code form.
Hopes this helps somebody, based on Jan Uhlig's brilliant answer.
Please see Jan's answer for a breakdown on how this code works.
Simple and efficient solution to get a random 10 characters string with uppercase and lowercase letters and digits :
select substring(base64_encode(md5(rand())) from 1+rand()*4 for 10);
UPPER(HEX(UUID_SHORT()))
gives you a 16-character alphanumeric string that is unique. It has some unlikely caveats, see https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_uuid-short
The "next" value is often predictable:
mysql> SELECT UPPER(HEX(UUID_SHORT()));
+--------------------------+
| UPPER(HEX(UUID_SHORT())) |
+--------------------------+
| 161AA3FA5000006 |
+--------------------------+
mysql> SELECT UPPER(HEX(UUID_SHORT()));
+--------------------------+
| UPPER(HEX(UUID_SHORT())) |
+--------------------------+
| 161AA3FA5000007 |
+--------------------------+
Converting to BASE64 can get the string down to 11 characters:
https://dev.mysql.com/doc/refman/8.0/en/string-functions.html#function_to-base64
mysql> SELECT TO_BASE64(UNHEX(HEX(UUID_SHORT())));
+-------------------------------------+
| TO_BASE64(UNHEX(HEX(UUID_SHORT()))) |
+-------------------------------------+
| AWGqP6UAABA= |
+-------------------------------------+
That's 12 chars, stripping off the '=' gives you 11.
These may make it unsuitable for your use: The "next" plate is somewhat predictable. There can be some punctuation marks (+,/) in the string. Lower case letters are likely to be included.
This work form me, generate 6 digit number and update in MySQL:
Generate:
SELECT SUBSTRING(MD5(RAND()) FROM 1 FOR 6)
Update:
UPDATE table_name
SET column_name = SUBSTRING(MD5(RAND()) FROM 1 FOR 6)
WHERE id = x12
If you're OK with "random" but entirely predictable license plates, you can use a linear-feedback shift register to choose the next plate number - it's guaranteed to go through every number before repeating. However, without some complex math, you won't be able to go through every 8 character alphanumeric string (you'll get 2^41 out of the 36^8 (78%) possible plates). To make this fill your space better, you could exclude a letter from the plates (maybe O), giving you 97%.
Generate 8 characters key
lpad(conv(floor(rand()*pow(36,6)), 10, 36), 8, 0);
How do I generate a unique, random string for one of my MySql table columns?
SQL Triggers are complex and resource-intensive. Against a MySQL "Trigger"-based solutions, here is a simpler solution.
Create a UNIQUE INDEX on the MySQL table column which will hold the vehicle registration plate string. This will ensure only unique values go in.
Simply generate the standard alphanumeric random string in Lua (or any other programming language like ASP, PHP, Java, etc.)
Execute the INSERT statement with the generated string, and have error-catching code to parse the failure (in case of the UNIQUE INDEX violation)
If the INSERT fails, generate a new random string and re-insert. Length of 8 chars in itself is pretty difficult to repeat, and once found in table generating another one will be next to impossible to be another repeat.
This will be lighter and more efficient on DB Server.
Here's a sample (pseudo-) code in PHP:
function refercode()
{
$string = '';
$characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$max = strlen($characters) - 1;
for ($i = 0; $i < 8; $i++) {
$string .= $characters[mt_rand(0, $max)];
}
$refer = "select * from vehicles where refer_code = '".$string."' ";
$coderefertest = mysqli_query($con,$refer);
if(mysqli_num_rows($coderefertest)>0)
{
return refercode();
}
else
{
return $string;
}
}
$refer_by = refercode();
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 ;
Use this stored procedure and use it everytime like
Call GenerateUniqueValue('tableName','columnName')
An easy way that generate a unique number
set #i = 0;
update vehicles set plate = CONCAT(#i:=#i+1, ROUND(RAND() * 1000))
order by rand();
I was looking for something similar and I decided to make my own version where you can also specify a different seed if wanted (list of characters) as parameter:
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
Can be used as:
SELECT random_string(10, '')
Which would use the built-in seed of upper- and lowercase characters + digits.
NULL would also be value instead of ''.
But one could specify a custom seed while calling:
SELECT random_string(10, '1234')
I find that find_in_set only search by a single string :-
find_in_set('a', 'a,b,c,d')
In the above example, 'a' is the only string used for search.
Is there any way to use find_in_set kind of functionality and search by multiple strings, like :-
find_in_set('a,b,c', 'a,b,c,d')
In the above example, I want to search by three strings 'a,b,c'.
One way I see is using OR
find_in_set('a', 'a,b,c,d') OR find_in_set('b', 'a,b,c,d') OR find_in_set('b', 'a,b,c,d')
Is there any other way than this?
there is no native function to do it, but you can achieve your aim using following trick
WHERE CONCAT(",", `setcolumn`, ",") REGEXP ",(val1|val2|val3),"
The MySQL function find_in_set() can search only for one string in a set of strings.
The first argument is a string, so there is no way to make it parse your comma separated string into strings (you can't use commas in SET elements at all!). The second argument is a SET, which in turn is represented by a comma separated string hence your wish to find_in_set('a,b,c', 'a,b,c,d') which works fine, but it surely can't find a string 'a,b,c' in any SET by definition - it contains commas.
You can also use this custom function
CREATE FUNCTION SPLIT_STR(
x VARCHAR(255),
delim VARCHAR(12),
pos INT
)
RETURNS VARCHAR(255)
RETURN REPLACE(SUBSTRING(SUBSTRING_INDEX(x, delim, pos),
LENGTH(SUBSTRING_INDEX(x, delim, pos -1)) + 1),
delim, '');
DELIMITER $$
CREATE FUNCTION `FIND_SET_EQUALS`(`s1` VARCHAR(200), `s2` VARCHAR(200))
RETURNS TINYINT(1)
LANGUAGE SQL
BEGIN
DECLARE a INT Default 0 ;
DECLARE isEquals TINYINT(1) Default 0 ;
DECLARE str VARCHAR(255);
IF s1 IS NOT NULL AND s2 IS NOT NULL THEN
simple_loop: LOOP
SET a=a+1;
SET str= SPLIT_STR(s2,",",a);
IF str='' THEN
LEAVE simple_loop;
END IF;
#Do check is in set
IF FIND_IN_SET(str, s1)=0 THEN
SET isEquals=0;
LEAVE simple_loop;
END IF;
SET isEquals=1;
END LOOP simple_loop;
END IF;
RETURN isEquals;
END;
$$
DELIMITER ;
SELECT FIND_SET_EQUALS('a,c,b', 'a,b,c')- 1
SELECT FIND_SET_EQUALS('a,c', 'a,b,c')- 0
SELECT FIND_SET_EQUALS(null, 'a,b,c')- 0
Wow, I'm surprised no one ever mentioned this here.In a nutshell, If you know the order of your members, then just query in a single bitwise operation.
SELECT * FROM example_table WHERE (example_set & mbits) = mbits;
Explanation:
If we had a set that has members in this order: "HTML", "CSS", "PHP", "JS"... etc.
That's how they're interpreted in MySQL:
"HTML" = 0001 = 1
"CSS" = 0010 = 2
"PHP" = 0100 = 4
"JS" = 1000 = 16
So for example, if you want to query all rows that have "HTML" and "CSS" in their sets, then you'll write
SELECT * FROM example_table WHERE (example_set & 3) = 3;
Because 0011 is 3 which is both 0001 "HTML" and 0010 "CSS".
Your sets can still be queried using the other methods like REGEXP , LIKE, FIND_IN_SET(), and so on. Use whatever you need.
Amazing answer by #Pavel Perminov! - And also nice comment by #doru for dynamically check..
From there what I have made for PHP code CONCAT(',','" . $country_lang_id . "', ',') REGEXP CONCAT(',(', REPLACE(YourColumnName, ',', '|'), '),') this below query may be useful for someone who is looking for ready code for PHP.
$country_lang_id = "1,2";
$sql = "select a.* from tablename a where CONCAT(',','" . $country_lang_id . "', ',') REGEXP CONCAT(',(', REPLACE(a.country_lang_id, ',', '|'), '),') ";
You can also use the like command for instance:
where setcolumn like '%a,b%'
or
where 'a,b,c,d' like '%b,c%'
which might work in some situations.
you can use in to find match values from two values
SELECT * FROM table WHERE myvals in (a,b,c,d)