MySQL: SUM() of trailing number in VARCHAR [duplicate] - mysql

I'm looking to find records in a table that match a specific number that the user enters. So, the user may enter 12345, but this could be 123zz4-5 in the database.
I imagine something like this would work, if PHP functions worked in MySQL.
SELECT * FROM foo WHERE preg_replace("/[^0-9]/","",bar) = '12345'
What's the equivalent function or way to do this with just MySQL?
Speed is not important.

I realise that this is an ancient topic but upon googling this problem I couldn't find a simple solution (I saw the venerable agents but think this is a simpler solution) so here's a function I wrote, seems to work quite well.
DROP FUNCTION IF EXISTS STRIP_NON_DIGIT;
DELIMITER $$
CREATE FUNCTION STRIP_NON_DIGIT(input VARCHAR(255))
RETURNS VARCHAR(255)
BEGIN
DECLARE output VARCHAR(255) DEFAULT '';
DECLARE iterator INT DEFAULT 1;
WHILE iterator < (LENGTH(input) + 1) DO
IF SUBSTRING(input, iterator, 1) IN ( '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ) THEN
SET output = CONCAT(output, SUBSTRING(input, iterator, 1));
END IF;
SET iterator = iterator + 1;
END WHILE;
RETURN output;
END
$$

You can easily do what you want with REGEXP_REPLACE (compatible with MySQL 8+ and MariaDB 10.0.5+)
REGEXP_REPLACE(expr, pat, repl[, pos[, occurrence[, match_type]]])
Replaces occurrences in the string expr that match the regular expression specified by the pattern pat with the replacement string repl, and returns the resulting string. If expr, pat, or repl is NULL, the return value is NULL.
Go to REGEXP_REPLACE doc: MySQL or MariaDB
Try it:
SELECT REGEXP_REPLACE('123asd12333', '[a-zA-Z]+', '');
Output:
12312333

Updated 2022: as per Marlom's answer, you can now use REGEX_REPLACE - which will perform even better than my historical answer here.
Most upvoted answer above isn't the fastest.
Full kudos to them for giving a working proposal to bounce off!
This is an improved version:
DELIMITER ;;
DROP FUNCTION IF EXISTS `STRIP_NON_DIGIT`;;
CREATE DEFINER=`root`#`localhost` FUNCTION `STRIP_NON_DIGIT`(input VARCHAR(255)) RETURNS VARCHAR(255) CHARSET utf8
READS SQL DATA
BEGIN
DECLARE output VARCHAR(255) DEFAULT '';
DECLARE iterator INT DEFAULT 1;
DECLARE lastDigit INT DEFAULT 1;
DECLARE len INT;
SET len = LENGTH(input) + 1;
WHILE iterator < len DO
-- skip past all digits
SET lastDigit = iterator;
WHILE ORD(SUBSTRING(input, iterator, 1)) BETWEEN 48 AND 57 AND iterator < len DO
SET iterator = iterator + 1;
END WHILE;
IF iterator != lastDigit THEN
SET output = CONCAT(output, SUBSTRING(input, lastDigit, iterator - lastDigit));
END IF;
WHILE ORD(SUBSTRING(input, iterator, 1)) NOT BETWEEN 48 AND 57 AND iterator < len DO
SET iterator = iterator + 1;
END WHILE;
END WHILE;
RETURN output;
END;;
Testing 5000 times on a test server:
-- original
Execution Time : 7.389 sec
Execution Time : 7.257 sec
Execution Time : 7.506 sec
-- ORD between not string IN
Execution Time : 4.031 sec
-- With less substrings
Execution Time : 3.243 sec
Execution Time : 3.415 sec
Execution Time : 2.848 sec

While it's not pretty and it shows results that don't match, this helps:
SELECT * FROM foo WHERE bar LIKE = '%1%2%3%4%5%'
I would still like to find a better solution similar to the item in the original question.

There's no regexp replace, only a plain string REPLACE().
MySQL has the REGEXP operator, but it's only a match tester not a replacer, so you would have to turn the logic inside-out:
SELECT * FROM foo WHERE bar REGEXP '[^0-9]*1[^0-9]*2[^0-9]*3[^0-9]*4[^0-9]*5[^0-9]*';
This is like your version with LIKE but matches more accurately. Both will perform equally badly, needing a full table scan without indexes.

On MySQL 8.0+ there is a new native function called REGEXP_REPLACE. A clean solution to this question would be:
SELECT * FROM foo WHERE REGEXP_REPLACE(bar,'[^0-9]+',"") = '12345'

The simplest way I can think to do it is to use the MySQL REGEXP operator a la:
WHERE foo LIKE '1\D*2\D*3\D*4\D*5'
It's not especially pretty but MySQL doesn't have a preg_replace function so I think it's the best you're going to get.
Personally, if this only-numeric data is so important, I'd keep a separate field just to contain the stripped data. It'll make your lookups a lot faster than with the regular expression search.

This blog post details how to strip non-numeric characters from a string via a MySQL function:
SELECT NumericOnly("asdf11asf");
returns 11
http://venerableagents.wordpress.com/2011/01/29/mysql-numeric-functions/

There's no regex replace as far as I'm concerned, but I found this solution;
--Create a table with numbers
DROP TABLE IF EXISTS ints;
CREATE TABLE ints (i INT UNSIGNED NOT NULL PRIMARY KEY);
INSERT INTO ints (i) VALUES
( 1), ( 2), ( 3), ( 4), ( 5), ( 6), ( 7), ( 8), ( 9), (10),
(11), (12), (13), (14), (15), (16), (17), (18), (19), (20);
--Then extract the numbers from the specified column
SELECT
bar,
GROUP_CONCAT(SUBSTRING(bar, i, 1) ORDER BY i SEPARATOR '')
FROM foo
JOIN ints ON i BETWEEN 1 AND LENGTH(bar)
WHERE
SUBSTRING(bar, i, 1) IN ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
GROUP BY bar;
It works for me and I use MySQL 5.0
Also I found this place that could help.

I have a similar situation, matching products to barcodes where the barcode doesn't store none alpha numerics sometimes, so 102.2234 in the DB needs to be found when searching for 1022234.
In the end I just added a new field, reference_number to the products tables, and have php strip out the none alpha numerics in the product_number to populate reference_number whenever a new products is added.
You'd need to do a one time scan of the table to create all the reference_number fields for existing products.
You can then setup your index, even if speed is not a factor for this operation, it is still a good idea to keep the database running well so this query doesn't bog it down and slow down other queries.

I came across this solution. The top answer by user1467716 will work in phpMyAdmin with a small change: add a second delimiter tag to the end of the code.
phpMyAdmin version is 4.1.14; MySQL version 5.6.20
I also added a length limiter using
DECLARE count INT DEFAULT 0; in the declarations
AND count < 5 in the WHILE statement
SET COUNT=COUNT+1; in the IF statement
Final form:
DROP FUNCTION IF EXISTS STRIP_NON_DIGIT;
DELIMITER $$
CREATE FUNCTION STRIP_NON_DIGIT(input VARCHAR(255))
RETURNS VARCHAR(255)
BEGIN
DECLARE output VARCHAR(255) DEFAULT '';
DECLARE iterator INT DEFAULT 1;
DECLARE count INT DEFAULT 0;
WHILE iterator < (LENGTH(input) + 1) AND count < 5 DO --limits to 5 chars
IF SUBSTRING(input, iterator, 1) IN ( '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ) THEN
SET output = CONCAT(output, SUBSTRING(input, iterator, 1));
SET COUNT=COUNT+1;
END IF;
SET iterator = iterator + 1;
END WHILE;
RETURN output;
END
$$
DELIMITER $$ --added this

How big is table with foo? If it is small, and speed really doesn't matter, you might pull the row ID and foo, loop over it using the PHP replace functions to compare, and then pull the info you want by row number.
Of course, if the table is too big, this won't work well.

try this example. this is used for phone numbers, however you can modify it for your needs.
-- function removes non numberic characters from input
-- returne only the numbers in the string
CREATE DEFINER =`root`#`localhost` FUNCTION `remove_alpha`(inputPhoneNumber VARCHAR(50))
RETURNS VARCHAR(50)
CHARSET latin1
DETERMINISTIC
BEGIN
DECLARE inputLenght INT DEFAULT 0;
-- var for our iteration
DECLARE counter INT DEFAULT 1;
-- if null is passed, we still return an tempty string
DECLARE sanitizedText VARCHAR(50) DEFAULT '';
-- holder of each character during the iteration
DECLARE oneChar VARCHAR(1) DEFAULT '';
-- we'll process only if it is not null.
IF NOT ISNULL(inputPhoneNumber)
THEN
SET inputLenght = LENGTH(inputPhoneNumber);
WHILE counter <= inputLenght DO
SET oneChar = SUBSTRING(inputPhoneNumber, counter, 1);
IF (oneChar REGEXP ('^[0-9]+$'))
THEN
SET sanitizedText = Concat(sanitizedText, oneChar);
END IF;
SET counter = counter + 1;
END WHILE;
END IF;
RETURN sanitizedText;
END
to use this user defined function (UDF).
let's say you have a column of phone numbers:
col1
(513)983-3983
1-838-338-9898
phone983-889-8383
select remove_alpha(col1) from mytable
The result would be;
5139833983
18383389898
9838898383

thought I would share this since I built it off the function from here. I rearranged just so I can read it easier (I'm just server side).
You call it by passing in a table name and column name to have it strip all existing non-numeric characters from that column. I inherited a lot of bad table structures that put a ton of int fields as varchar so I needed a way to clean these up quickly before I can modify the column to an integer.
drop procedure if exists strip_non_numeric_characters;
DELIMITER ;;
CREATE PROCEDURE `strip_non_numeric_characters`(
tablename varchar(100)
,columnname varchar(100)
)
BEGIN
-- =============================================
-- Author: <Author,,David Melton>
-- Create date: <Create Date,,2/26/2019>
-- Description: <Description,,loops through data and strips out the bad characters in whatever table and column you pass it>
-- =============================================
#this idea was generated from the idea STRIP_NON_DIGIT function
#https://stackoverflow.com/questions/287105/mysql-strip-non-numeric-characters-to-compare
declare input,output varchar(255);
declare iterator,lastDigit,len,counter int;
declare date_updated varchar(100);
select column_name
into date_updated
from information_schema.columns
where table_schema = database()
and extra rlike 'on update CURRENT_TIMESTAMP'
and table_name = tablename
limit 1;
#only goes up to 255 so people don't run this for a longtext field
#just to be careful, i've excluded columns that are part of keys, that could potentially mess something else up
set #find_column_length =
concat("select character_maximum_length
into #len
from information_schema.columns
where table_schema = '",database(),"'
and column_name = '",columnname,"'
and table_name = '",tablename,"'
and length(ifnull(character_maximum_length,100)) < 255
and data_type in ('char','varchar')
and column_key = '';");
prepare stmt from #find_column_length;
execute stmt;
deallocate prepare stmt;
set counter = 1;
set len = #len;
while counter <= ifnull(len,1) DO
#this just removes it by putting all the characters before and after the character i'm looking at
#you have to start at the end of the field otherwise the lengths don't stay in order and you have to run it multiple times
set #update_query =
concat("update `",tablename,"`
set `",columnname,"` = concat(substring(`",columnname,"`,1,",len - counter,"),SUBSTRING(`",columnname,"`,",len - counter,",",counter - 1,"))
",if(date_updated is not null,concat(",`",date_updated,"` = `",date_updated,"`
"),''),
"where SUBSTRING(`",columnname,"`,",len - counter,", 1) not REGEXP '^[0-9]+$';");
prepare stmt from #update_query;
execute stmt;
deallocate prepare stmt;
set counter = counter + 1;
end while;
END ;;
DELIMITER ;

To search for numbers that match a particular numeric pattern in a string, first remove all the alphabets and special characters in a similar manner as below then convert the value to an integer and then search
SELECT *
FROM foo
WHERE Convert(Regexp_replace(bar, '[a-zA-Z]+', ''), signed) = 12345

I think you don't need complicated functions for that.
I've found a REGEXP_REPLACE solution using built-in mysql character class names. You can read about them in a table in the docs. Basically, they are mysql-specific names for commonly matched groups of characters like [:alnum:] for alpha-numeric characters, [:alpha:] for only alphabetic characters and so on.
So my version of the REGEXP_REPLACE:
REGEXP_REPLACE('My number is: +59 (29) 889-23-56', '[[:alpha:][:blank:][:punct:][:cntrl:]]', '')
will yield 59298892356 as per requirements.

Being someone that has version 5.7, doesn't have the privilege to create functions, and it's not practical to bring my data down into my code, I found Nelson Miranda's Answer amazing. I wanted to share it in a subquery version which I found more useful.
DROP TABLE IF EXISTS ints;
CREATE TABLE ints (i INT UNSIGNED NOT NULL PRIMARY KEY);
INSERT INTO ints (i) VALUES
( 1), ( 2), ( 3), ( 4), ( 5), ( 6), ( 7), ( 8), ( 9), (10),
(11), (12), (13), (14), (15), (16), (17), (18), (19), (20);
SELECT * FROM foo f
WHERE (SELECT GROUP_CONCAT(SUBSTRING(f.bar, i, 1) ORDER BY i SEPARATOR '')
FROM ints
WHERE i BETWEEN 1 AND LENGTH(f.bar)
AND SUBSTRING(f.bar, i, 1) IN ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')) = '12345'
Note: The ints table can be a temporary table.

If you are on MySQL 5.7 or below, and you just need something quick and dirty without having to define a new function, and you have a small number of known non-numerics to filter out, something like this can work well...
Select replace(replace(replace(replace(replace(phone, '+1', ''), '(', ''), ')', ''), '-', ''), ' ', '') from customers;

Related

What's the simplest way to generate a unique string in MySQL? [duplicate]

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')

MySQL Query to insert random numbers into a column [duplicate]

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')

sql - select column but remove non alphanumeric in result [duplicate]

I'm working on a routine that compares strings, but for better efficiency I need to remove all characters that are not letters or numbers.
I'm using multiple REPLACE functions now, but maybe there is a faster and nicer solution ?
Using MySQL 8.0 or higher
Courtesy of michal.jakubeczy's answer below, replacing by Regex is now supported by MySQL:
UPDATE {table} SET {column} = REGEXP_REPLACE({column}, '[^0-9a-zA-Z ]', '')
Using MySQL 5.7 or lower
Regex isn't supported here. I had to create my own function called alphanum which stripped the chars for me:
DROP FUNCTION IF EXISTS alphanum;
DELIMITER |
CREATE FUNCTION alphanum( str CHAR(255) ) RETURNS CHAR(255) DETERMINISTIC
BEGIN
DECLARE i, len SMALLINT DEFAULT 1;
DECLARE ret CHAR(255) DEFAULT '';
DECLARE c CHAR(1);
IF str IS NOT NULL THEN
SET len = CHAR_LENGTH( str );
REPEAT
BEGIN
SET c = MID( str, i, 1 );
IF c REGEXP '[[:alnum:]]' THEN
SET ret=CONCAT(ret,c);
END IF;
SET i = i + 1;
END;
UNTIL i > len END REPEAT;
ELSE
SET ret='';
END IF;
RETURN ret;
END |
DELIMITER ;
Now I can do:
select 'This works finally!', alphanum('This works finally!');
and I get:
+---------------------+---------------------------------+
| This works finally! | alphanum('This works finally!') |
+---------------------+---------------------------------+
| This works finally! | Thisworksfinally |
+---------------------+---------------------------------+
1 row in set (0.00 sec)
Hurray!
From a performance point of view,
(and on the assumption that you read more than you write)
I think the best way would be to pre calculate and store a stripped version of the column,
This way you do the transform less.
You can then put an index on the new column and get the database to do the work for you.
SELECT teststring REGEXP '[[:alnum:]]+';
SELECT * FROM testtable WHERE test REGEXP '[[:alnum:]]+';
See: http://dev.mysql.com/doc/refman/5.1/en/regexp.html
Scroll down to the section that says: [:character_class:]
If you want to manipulate strings the fastest way will be to use a str_udf, see:
https://github.com/hholzgra/mysql-udf-regexp
Since MySQL 8.0 you can use regular expression to remove non alphanumeric characters from a string. There is method REGEXP_REPLACE
Here is the code to remove non-alphanumeric characters:
UPDATE {table} SET {column} = REGEXP_REPLACE({column}, '[^0-9a-zA-Z ]', '')
Straight and battletested solution for latin and cyrillic characters:
DELIMITER //
CREATE FUNCTION `remove_non_numeric_and_letters`(input TEXT)
RETURNS TEXT
BEGIN
DECLARE output TEXT DEFAULT '';
DECLARE iterator INT DEFAULT 1;
WHILE iterator < (LENGTH(input) + 1) DO
IF SUBSTRING(input, iterator, 1) IN
('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ж', 'З', 'И', 'Й', 'К', 'Л', 'М', 'Н', 'О', 'П', 'Р', 'С', 'Т', 'У', 'Ф', 'Х', 'Ц', 'Ч', 'Ш', 'Щ', 'Ъ', 'Ы', 'Ь', 'Э', 'Ю', 'Я', 'а', 'б', 'в', 'г', 'д', 'е', 'ж', 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы', 'ь', 'э', 'ю', 'я')
THEN
SET output = CONCAT(output, SUBSTRING(input, iterator, 1));
END IF;
SET iterator = iterator + 1;
END WHILE;
RETURN output;
END //
DELIMITER ;
Usage:
-- outputs "hello12356"
SELECT remove_non_numeric_and_letters('hello - 12356-привет ""]')
The fastest way I was able to find (and using ) is with convert().
from Doc. CONVERT() with USING is used to convert data between different character sets.
Example:
convert(string USING ascii)
In your case the right character set will be self defined
NOTE from Doc. The USING form of CONVERT() is available as of 4.1.0.
Based on the answer by Ryan Shillington, modified to work with strings longer than 255 characters and preserving spaces from the original string.
FYI there is lower(str) in the end.
I used this to compare strings:
DROP FUNCTION IF EXISTS spacealphanum;
DELIMITER $$
CREATE FUNCTION `spacealphanum`( str TEXT ) RETURNS TEXT CHARSET utf8
BEGIN
DECLARE i, len SMALLINT DEFAULT 1;
DECLARE ret TEXT DEFAULT '';
DECLARE c CHAR(1);
SET len = CHAR_LENGTH( str );
REPEAT
BEGIN
SET c = MID( str, i, 1 );
IF c REGEXP '[[:alnum:]]' THEN
SET ret=CONCAT(ret,c);
ELSEIF c = ' ' THEN
SET ret=CONCAT(ret," ");
END IF;
SET i = i + 1;
END;
UNTIL i > len END REPEAT;
SET ret = lower(ret);
RETURN ret;
END $$
DELIMITER ;
Be careful, characters like ’ or » are considered as alpha by MySQL.
It better to use something like :
IF c BETWEEN 'a' AND 'z' OR c BETWEEN 'A' AND 'Z' OR c BETWEEN '0' AND
'9' OR c = '-' THEN
I have written this UDF. However, it only trims special characters at the beginning of the string. It also converts the string to lower case. You can update this function if desired.
DELIMITER //
DROP FUNCTION IF EXISTS DELETE_DOUBLE_SPACES//
CREATE FUNCTION DELETE_DOUBLE_SPACES ( title VARCHAR(250) )
RETURNS VARCHAR(250) DETERMINISTIC
BEGIN
DECLARE result VARCHAR(250);
SET result = REPLACE( title, ' ', ' ' );
WHILE (result <> title) DO
SET title = result;
SET result = REPLACE( title, ' ', ' ' );
END WHILE;
RETURN result;
END//
DROP FUNCTION IF EXISTS LFILTER//
CREATE FUNCTION LFILTER ( title VARCHAR(250) )
RETURNS VARCHAR(250) DETERMINISTIC
BEGIN
WHILE (1=1) DO
IF( ASCII(title) BETWEEN ASCII('a') AND ASCII('z')
OR ASCII(title) BETWEEN ASCII('A') AND ASCII('Z')
OR ASCII(title) BETWEEN ASCII('0') AND ASCII('9')
) THEN
SET title = LOWER( title );
SET title = REPLACE(
REPLACE(
REPLACE(
title,
CHAR(10), ' '
),
CHAR(13), ' '
) ,
CHAR(9), ' '
);
SET title = DELETE_DOUBLE_SPACES( title );
RETURN title;
ELSE
SET title = SUBSTRING( title, 2 );
END IF;
END WHILE;
END//
DELIMITER ;
SELECT LFILTER(' !##$%^&*()_+1a b');
Also, you could use regular expressions but this requires installing a MySql extension.
This can be done with a regular expression replacer function I posted in another answer and have blogged about here. It may not be the most efficient solution possible and might look overkill for the job in hand - but like a Swiss army knife, it may come in useful for other reasons.
It can be seen in action removing all non-alphanumeric characters in this Rextester online demo.
SQL (excluding the function code for brevity):
SELECT txt,
reg_replace(txt,
'[^a-zA-Z0-9]+',
'',
TRUE,
0,
0
) AS `reg_replaced`
FROM test;
I had a similar problem with trying to match last names in our database that were slightly different. For example, sometimes people entered the same person's name as "McDonald" and also as "Mc Donald", or "St John" and "St. John".
Instead of trying to convert the Mysql data, I solved the problem by creating a function (in PHP) that would take a string and create an alpha-only regular expression:
function alpha_only_regex($str) {
$alpha_only = str_split(preg_replace('/[^A-Z]/i', '', $str));
return '^[^a-zA-Z]*'.implode('[^a-zA-Z]*', $alpha_only).'[^a-zA-Z]*$';
}
Now I can search the database with a query like this:
$lastname_regex = alpha_only_regex($lastname);
$query = "SELECT * FROM my_table WHERE lastname REGEXP '$lastname_regex';
So far, the only alternative approach less complicated than the other answers here is to determine the full set of special characters of the column, i.e. all the special characters that are in use in that column at the moment, and then do a sequential replace of all those characters, e.g.
update pages set slug = lower(replace(replace(replace(replace(name, ' ', ''), '-', ''), '.', ''), '&', '')); # replacing just space, -, ., & only
.
This is only advisable on a known set of data, otherwise it's
trivial for some special characters to slip past with a
blacklist approach instead of a whitelist approach.
Obviously, the simplest way is to pre-validate the data outside of sql due to the lack of robust built-in whitelisting (e.g. via a regex replace).
I needed to get only alphabetic characters of a string in a procedure, and did:
SET #source = "whatever you want";
SET #target = '';
SET #i = 1;
SET #len = LENGTH(#source);
WHILE #i <= #len DO
SET #char = SUBSTRING(#source, #i, 1);
IF ((ORD(#char) >= 65 && ORD(#char) <= 90) || (ORD(#char) >= 97 && ORD(#char) <= 122)) THEN
SET #target = CONCAT(#target, #char);
END IF;
SET #i = #i + 1;
END WHILE;
Needed to replace non-alphanumeric characters rather than remove non-alphanumeric characters so I have created this based on Ryan Shillington's alphanum. Works for strings up to 255 characters in length
DROP FUNCTION IF EXISTS alphanumreplace;
DELIMITER |
CREATE FUNCTION alphanumreplace( str CHAR(255), d CHAR(32) ) RETURNS CHAR(255)
BEGIN
DECLARE i, len SMALLINT DEFAULT 1;
DECLARE ret CHAR(32) DEFAULT '';
DECLARE c CHAR(1);
SET len = CHAR_LENGTH( str );
REPEAT
BEGIN
SET c = MID( str, i, 1 );
IF c REGEXP '[[:alnum:]]' THEN SET ret=CONCAT(ret,c);
ELSE SET ret=CONCAT(ret,d);
END IF;
SET i = i + 1;
END;
UNTIL i > len END REPEAT;
RETURN ret;
END |
DELIMITER ;
Example:
select 'hello world!',alphanum('hello world!'),alphanumreplace('hello world!','-');
+--------------+--------------------------+-------------------------------------+
| hello world! | alphanum('hello world!') | alphanumreplace('hello world!','-') |
+--------------+--------------------------+-------------------------------------+
| hello world! | helloworld | hello-world- |
+--------------+--------------------------+-------------------------------------+
You'll need to add the alphanum function seperately if you want that, I just have it here for the example.
I tried a few solutions but at the end used replace. My data set is part numbers and I fairly know what to expect. But just for sanity, I used PHP to build the long query:
$dirty = array(' ', '-', '.', ',', ':', '?', '/', '!', '&', '#');
$query = 'part_no';
foreach ($dirty as $dirt) {
$query = "replace($query,'$dirt','')";
}
echo $query;
This outputs something I used to get a headache from:
replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(part_no,' ',''),'-',''),'.',''),',',''),':',''),'?',''),'/',''),'!',''),'&',''),'#','')
if you are using php then....
try{
$con = new PDO ("mysql:host=localhost;dbname=dbasename","root","");
}
catch(PDOException $e){
echo "error".$e-getMessage();
}
$select = $con->prepare("SELECT * FROM table");
$select->setFetchMode(PDO::FETCH_ASSOC);
$select->execute();
while($data=$select->fetch()){
$id = $data['id'];
$column = $data['column'];
$column = preg_replace("/[^a-zA-Z0-9]+/", " ", $column); //remove all special characters
$update = $con->prepare("UPDATE table SET column=:column WHERE id='$id'");
$update->bindParam(':column', $column );
$update->execute();
// echo $column."<br>";
}
the alphanum function (self answered) have a bug, but I don't know why.
For text "cas synt ls 75W140 1L" return "cassyntls75W1401", "L" from the end is missing some how.
Now I use
delimiter //
DROP FUNCTION IF EXISTS alphanum //
CREATE FUNCTION alphanum(prm_strInput varchar(255))
RETURNS VARCHAR(255)
DETERMINISTIC
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE v_char VARCHAR(1);
DECLARE v_parseStr VARCHAR(255) DEFAULT ' ';
WHILE (i <= LENGTH(prm_strInput) ) DO
SET v_char = SUBSTR(prm_strInput,i,1);
IF v_char REGEXP '^[A-Za-z0-9]+$' THEN
SET v_parseStr = CONCAT(v_parseStr,v_char);
END IF;
SET i = i + 1;
END WHILE;
RETURN trim(v_parseStr);
END
//
(found on google)
Probably a silly suggestion compared to others:
if(!preg_match("/^[a-zA-Z0-9]$/",$string)){
$sortedString=preg_replace("/^[a-zA-Z0-9]+$/","",$string);
}

Generating a random & unique 8 character string using MySQL

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')

MySQL strip non-numeric characters to compare

I'm looking to find records in a table that match a specific number that the user enters. So, the user may enter 12345, but this could be 123zz4-5 in the database.
I imagine something like this would work, if PHP functions worked in MySQL.
SELECT * FROM foo WHERE preg_replace("/[^0-9]/","",bar) = '12345'
What's the equivalent function or way to do this with just MySQL?
Speed is not important.
I realise that this is an ancient topic but upon googling this problem I couldn't find a simple solution (I saw the venerable agents but think this is a simpler solution) so here's a function I wrote, seems to work quite well.
DROP FUNCTION IF EXISTS STRIP_NON_DIGIT;
DELIMITER $$
CREATE FUNCTION STRIP_NON_DIGIT(input VARCHAR(255))
RETURNS VARCHAR(255)
BEGIN
DECLARE output VARCHAR(255) DEFAULT '';
DECLARE iterator INT DEFAULT 1;
WHILE iterator < (LENGTH(input) + 1) DO
IF SUBSTRING(input, iterator, 1) IN ( '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ) THEN
SET output = CONCAT(output, SUBSTRING(input, iterator, 1));
END IF;
SET iterator = iterator + 1;
END WHILE;
RETURN output;
END
$$
You can easily do what you want with REGEXP_REPLACE (compatible with MySQL 8+ and MariaDB 10.0.5+)
REGEXP_REPLACE(expr, pat, repl[, pos[, occurrence[, match_type]]])
Replaces occurrences in the string expr that match the regular expression specified by the pattern pat with the replacement string repl, and returns the resulting string. If expr, pat, or repl is NULL, the return value is NULL.
Go to REGEXP_REPLACE doc: MySQL or MariaDB
Try it:
SELECT REGEXP_REPLACE('123asd12333', '[a-zA-Z]+', '');
Output:
12312333
Updated 2022: as per Marlom's answer, you can now use REGEX_REPLACE - which will perform even better than my historical answer here.
Most upvoted answer above isn't the fastest.
Full kudos to them for giving a working proposal to bounce off!
This is an improved version:
DELIMITER ;;
DROP FUNCTION IF EXISTS `STRIP_NON_DIGIT`;;
CREATE DEFINER=`root`#`localhost` FUNCTION `STRIP_NON_DIGIT`(input VARCHAR(255)) RETURNS VARCHAR(255) CHARSET utf8
READS SQL DATA
BEGIN
DECLARE output VARCHAR(255) DEFAULT '';
DECLARE iterator INT DEFAULT 1;
DECLARE lastDigit INT DEFAULT 1;
DECLARE len INT;
SET len = LENGTH(input) + 1;
WHILE iterator < len DO
-- skip past all digits
SET lastDigit = iterator;
WHILE ORD(SUBSTRING(input, iterator, 1)) BETWEEN 48 AND 57 AND iterator < len DO
SET iterator = iterator + 1;
END WHILE;
IF iterator != lastDigit THEN
SET output = CONCAT(output, SUBSTRING(input, lastDigit, iterator - lastDigit));
END IF;
WHILE ORD(SUBSTRING(input, iterator, 1)) NOT BETWEEN 48 AND 57 AND iterator < len DO
SET iterator = iterator + 1;
END WHILE;
END WHILE;
RETURN output;
END;;
Testing 5000 times on a test server:
-- original
Execution Time : 7.389 sec
Execution Time : 7.257 sec
Execution Time : 7.506 sec
-- ORD between not string IN
Execution Time : 4.031 sec
-- With less substrings
Execution Time : 3.243 sec
Execution Time : 3.415 sec
Execution Time : 2.848 sec
While it's not pretty and it shows results that don't match, this helps:
SELECT * FROM foo WHERE bar LIKE = '%1%2%3%4%5%'
I would still like to find a better solution similar to the item in the original question.
There's no regexp replace, only a plain string REPLACE().
MySQL has the REGEXP operator, but it's only a match tester not a replacer, so you would have to turn the logic inside-out:
SELECT * FROM foo WHERE bar REGEXP '[^0-9]*1[^0-9]*2[^0-9]*3[^0-9]*4[^0-9]*5[^0-9]*';
This is like your version with LIKE but matches more accurately. Both will perform equally badly, needing a full table scan without indexes.
On MySQL 8.0+ there is a new native function called REGEXP_REPLACE. A clean solution to this question would be:
SELECT * FROM foo WHERE REGEXP_REPLACE(bar,'[^0-9]+',"") = '12345'
The simplest way I can think to do it is to use the MySQL REGEXP operator a la:
WHERE foo LIKE '1\D*2\D*3\D*4\D*5'
It's not especially pretty but MySQL doesn't have a preg_replace function so I think it's the best you're going to get.
Personally, if this only-numeric data is so important, I'd keep a separate field just to contain the stripped data. It'll make your lookups a lot faster than with the regular expression search.
This blog post details how to strip non-numeric characters from a string via a MySQL function:
SELECT NumericOnly("asdf11asf");
returns 11
http://venerableagents.wordpress.com/2011/01/29/mysql-numeric-functions/
There's no regex replace as far as I'm concerned, but I found this solution;
--Create a table with numbers
DROP TABLE IF EXISTS ints;
CREATE TABLE ints (i INT UNSIGNED NOT NULL PRIMARY KEY);
INSERT INTO ints (i) VALUES
( 1), ( 2), ( 3), ( 4), ( 5), ( 6), ( 7), ( 8), ( 9), (10),
(11), (12), (13), (14), (15), (16), (17), (18), (19), (20);
--Then extract the numbers from the specified column
SELECT
bar,
GROUP_CONCAT(SUBSTRING(bar, i, 1) ORDER BY i SEPARATOR '')
FROM foo
JOIN ints ON i BETWEEN 1 AND LENGTH(bar)
WHERE
SUBSTRING(bar, i, 1) IN ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
GROUP BY bar;
It works for me and I use MySQL 5.0
Also I found this place that could help.
I have a similar situation, matching products to barcodes where the barcode doesn't store none alpha numerics sometimes, so 102.2234 in the DB needs to be found when searching for 1022234.
In the end I just added a new field, reference_number to the products tables, and have php strip out the none alpha numerics in the product_number to populate reference_number whenever a new products is added.
You'd need to do a one time scan of the table to create all the reference_number fields for existing products.
You can then setup your index, even if speed is not a factor for this operation, it is still a good idea to keep the database running well so this query doesn't bog it down and slow down other queries.
I came across this solution. The top answer by user1467716 will work in phpMyAdmin with a small change: add a second delimiter tag to the end of the code.
phpMyAdmin version is 4.1.14; MySQL version 5.6.20
I also added a length limiter using
DECLARE count INT DEFAULT 0; in the declarations
AND count < 5 in the WHILE statement
SET COUNT=COUNT+1; in the IF statement
Final form:
DROP FUNCTION IF EXISTS STRIP_NON_DIGIT;
DELIMITER $$
CREATE FUNCTION STRIP_NON_DIGIT(input VARCHAR(255))
RETURNS VARCHAR(255)
BEGIN
DECLARE output VARCHAR(255) DEFAULT '';
DECLARE iterator INT DEFAULT 1;
DECLARE count INT DEFAULT 0;
WHILE iterator < (LENGTH(input) + 1) AND count < 5 DO --limits to 5 chars
IF SUBSTRING(input, iterator, 1) IN ( '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ) THEN
SET output = CONCAT(output, SUBSTRING(input, iterator, 1));
SET COUNT=COUNT+1;
END IF;
SET iterator = iterator + 1;
END WHILE;
RETURN output;
END
$$
DELIMITER $$ --added this
How big is table with foo? If it is small, and speed really doesn't matter, you might pull the row ID and foo, loop over it using the PHP replace functions to compare, and then pull the info you want by row number.
Of course, if the table is too big, this won't work well.
try this example. this is used for phone numbers, however you can modify it for your needs.
-- function removes non numberic characters from input
-- returne only the numbers in the string
CREATE DEFINER =`root`#`localhost` FUNCTION `remove_alpha`(inputPhoneNumber VARCHAR(50))
RETURNS VARCHAR(50)
CHARSET latin1
DETERMINISTIC
BEGIN
DECLARE inputLenght INT DEFAULT 0;
-- var for our iteration
DECLARE counter INT DEFAULT 1;
-- if null is passed, we still return an tempty string
DECLARE sanitizedText VARCHAR(50) DEFAULT '';
-- holder of each character during the iteration
DECLARE oneChar VARCHAR(1) DEFAULT '';
-- we'll process only if it is not null.
IF NOT ISNULL(inputPhoneNumber)
THEN
SET inputLenght = LENGTH(inputPhoneNumber);
WHILE counter <= inputLenght DO
SET oneChar = SUBSTRING(inputPhoneNumber, counter, 1);
IF (oneChar REGEXP ('^[0-9]+$'))
THEN
SET sanitizedText = Concat(sanitizedText, oneChar);
END IF;
SET counter = counter + 1;
END WHILE;
END IF;
RETURN sanitizedText;
END
to use this user defined function (UDF).
let's say you have a column of phone numbers:
col1
(513)983-3983
1-838-338-9898
phone983-889-8383
select remove_alpha(col1) from mytable
The result would be;
5139833983
18383389898
9838898383
thought I would share this since I built it off the function from here. I rearranged just so I can read it easier (I'm just server side).
You call it by passing in a table name and column name to have it strip all existing non-numeric characters from that column. I inherited a lot of bad table structures that put a ton of int fields as varchar so I needed a way to clean these up quickly before I can modify the column to an integer.
drop procedure if exists strip_non_numeric_characters;
DELIMITER ;;
CREATE PROCEDURE `strip_non_numeric_characters`(
tablename varchar(100)
,columnname varchar(100)
)
BEGIN
-- =============================================
-- Author: <Author,,David Melton>
-- Create date: <Create Date,,2/26/2019>
-- Description: <Description,,loops through data and strips out the bad characters in whatever table and column you pass it>
-- =============================================
#this idea was generated from the idea STRIP_NON_DIGIT function
#https://stackoverflow.com/questions/287105/mysql-strip-non-numeric-characters-to-compare
declare input,output varchar(255);
declare iterator,lastDigit,len,counter int;
declare date_updated varchar(100);
select column_name
into date_updated
from information_schema.columns
where table_schema = database()
and extra rlike 'on update CURRENT_TIMESTAMP'
and table_name = tablename
limit 1;
#only goes up to 255 so people don't run this for a longtext field
#just to be careful, i've excluded columns that are part of keys, that could potentially mess something else up
set #find_column_length =
concat("select character_maximum_length
into #len
from information_schema.columns
where table_schema = '",database(),"'
and column_name = '",columnname,"'
and table_name = '",tablename,"'
and length(ifnull(character_maximum_length,100)) < 255
and data_type in ('char','varchar')
and column_key = '';");
prepare stmt from #find_column_length;
execute stmt;
deallocate prepare stmt;
set counter = 1;
set len = #len;
while counter <= ifnull(len,1) DO
#this just removes it by putting all the characters before and after the character i'm looking at
#you have to start at the end of the field otherwise the lengths don't stay in order and you have to run it multiple times
set #update_query =
concat("update `",tablename,"`
set `",columnname,"` = concat(substring(`",columnname,"`,1,",len - counter,"),SUBSTRING(`",columnname,"`,",len - counter,",",counter - 1,"))
",if(date_updated is not null,concat(",`",date_updated,"` = `",date_updated,"`
"),''),
"where SUBSTRING(`",columnname,"`,",len - counter,", 1) not REGEXP '^[0-9]+$';");
prepare stmt from #update_query;
execute stmt;
deallocate prepare stmt;
set counter = counter + 1;
end while;
END ;;
DELIMITER ;
To search for numbers that match a particular numeric pattern in a string, first remove all the alphabets and special characters in a similar manner as below then convert the value to an integer and then search
SELECT *
FROM foo
WHERE Convert(Regexp_replace(bar, '[a-zA-Z]+', ''), signed) = 12345
I think you don't need complicated functions for that.
I've found a REGEXP_REPLACE solution using built-in mysql character class names. You can read about them in a table in the docs. Basically, they are mysql-specific names for commonly matched groups of characters like [:alnum:] for alpha-numeric characters, [:alpha:] for only alphabetic characters and so on.
So my version of the REGEXP_REPLACE:
REGEXP_REPLACE('My number is: +59 (29) 889-23-56', '[[:alpha:][:blank:][:punct:][:cntrl:]]', '')
will yield 59298892356 as per requirements.
Being someone that has version 5.7, doesn't have the privilege to create functions, and it's not practical to bring my data down into my code, I found Nelson Miranda's Answer amazing. I wanted to share it in a subquery version which I found more useful.
DROP TABLE IF EXISTS ints;
CREATE TABLE ints (i INT UNSIGNED NOT NULL PRIMARY KEY);
INSERT INTO ints (i) VALUES
( 1), ( 2), ( 3), ( 4), ( 5), ( 6), ( 7), ( 8), ( 9), (10),
(11), (12), (13), (14), (15), (16), (17), (18), (19), (20);
SELECT * FROM foo f
WHERE (SELECT GROUP_CONCAT(SUBSTRING(f.bar, i, 1) ORDER BY i SEPARATOR '')
FROM ints
WHERE i BETWEEN 1 AND LENGTH(f.bar)
AND SUBSTRING(f.bar, i, 1) IN ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')) = '12345'
Note: The ints table can be a temporary table.
If you are on MySQL 5.7 or below, and you just need something quick and dirty without having to define a new function, and you have a small number of known non-numerics to filter out, something like this can work well...
Select replace(replace(replace(replace(replace(phone, '+1', ''), '(', ''), ')', ''), '-', ''), ' ', '') from customers;