MySQL 生成隨機(jī)數(shù)字、字符串、日期、驗(yàn)證碼以及 UUID

作者: 不剪發(fā)的Tony老師
畢業(yè)于北京航空航天大學(xué),十多年數(shù)據(jù)庫(kù)管理與開(kāi)發(fā)經(jīng)驗(yàn),目前在一家全球性的金融公司從事數(shù)據(jù)庫(kù)架構(gòu)設(shè)計(jì)。CSDN學(xué)院簽約講師以及GitChat專欄作者。csdn上的博客收藏于以下地址:https://tonydong.blog.csdn.net


文章目錄

        生成隨機(jī)數(shù)字
            生成 0 到 1 之間的隨機(jī)數(shù)
            生成指定范圍內(nèi)的隨機(jī)數(shù)
            生成 6 位數(shù)字手機(jī)驗(yàn)證碼
            生成遵循正態(tài)分布的隨機(jī)數(shù)
        生成隨機(jī)字符串
            生成固定長(zhǎng)度的隨機(jī)字符串
            生成可變長(zhǎng)度的隨機(jī)字符串
        生成隨機(jī)日期和時(shí)間
        獲取表中的隨機(jī)記錄
        生成 UUID
        總結(jié)

大家好,我是只談技術(shù)不剪發(fā)的 Tony 老師。

上一篇介紹了如何在 Oracle 生成隨機(jī)數(shù)字、字符串、日期、驗(yàn)證碼以及 UUID,今天我們繼續(xù)討論在 MySQL 中生成各種隨機(jī)數(shù)據(jù)的方法。

    ??計(jì)算機(jī)生成的都是偽隨機(jī)數(shù),并不是真正的物理隨機(jī)數(shù)。

生成隨機(jī)數(shù)字
生成 0 到 1 之間的隨機(jī)數(shù)

MySQL 中的 RAND 函數(shù)可以用于生成一個(gè)大于等于 0 小于 1 的隨機(jī)數(shù)字。例如:

SELECT rand();
rand()            |
------------------|
0.7245639057127423|

SELECT rand();
rand()             |
-------------------|
0.01697599982561171|


該函數(shù)返回的數(shù)據(jù)類型為 double,包含 16 位小數(shù);每次調(diào)用都會(huì)返回不同的數(shù)據(jù)。

如果想要重現(xiàn)某些場(chǎng)景,需要確保每次運(yùn)行時(shí)生成相同的隨機(jī)數(shù)。這種情況下可以為 RAND 函數(shù)傳遞一個(gè)輸入?yún)?shù),設(shè)置一個(gè)隨機(jī)數(shù)種子。例如:

SELECT rand(1);
rand(1)            |
-------------------|
0.40540353712197724|

SELECT rand(1);
rand(1)            |
-------------------|
0.40540353712197724|


從結(jié)果可以看出,相同的種子返回了相同的隨機(jī)數(shù)。
生成指定范圍內(nèi)的隨機(jī)數(shù)

基于 RAND 函數(shù)和數(shù)學(xué)運(yùn)算,可以返回任意兩個(gè)數(shù)字之間的隨機(jī)數(shù):

low + RAND() * (high ? low)


以上表達(dá)式將會(huì)返回一個(gè)大于等于 low,小于 high 的隨機(jī)數(shù)。例如:

SELECT 10 + rand(1) * 10;
10 + rand(1) * 10 |
------------------|
14.054035371219772|



以上示例返回了一個(gè)大于等于 10 且小于 20 的隨機(jī)數(shù)字。

如果想要生成某個(gè)范圍內(nèi)的隨機(jī)整數(shù),可以加上 FLOOR 函數(shù)。例如:

SELECT floor(10 + rand(1) * (10)) AS rd;
rd  |
----|
14.0|



該語(yǔ)句返回了一個(gè)大于等于 10,小于等于 19(不是 20)的隨機(jī)整數(shù)。
生成 6 位數(shù)字手機(jī)驗(yàn)證碼

我們已經(jīng)獲得了指定范圍內(nèi)的隨機(jī)整數(shù),加上 LPAD 函數(shù)就可以生成由 6 位數(shù)字字符組成的手機(jī)驗(yàn)證碼。例如:

SELECT lpad(floor(rand(999) * 1000000), 6, '0') AS captcha;
captcha|
-------|
088146 |



其中,lpad 函數(shù)可以確保數(shù)據(jù)不夠 6 位時(shí)在前面補(bǔ)足 0。
生成遵循正態(tài)分布的隨機(jī)數(shù)

RAND 函數(shù)生成的是一個(gè)遵循均勻分布的隨機(jī)數(shù),MySQL 沒(méi)有提供生成遵循正態(tài)分布(normal distribution)的隨機(jī)數(shù)。我們可以創(chuàng)建一個(gè)存儲(chǔ)函數(shù)來(lái)模擬正態(tài)分布的隨機(jī)數(shù):

delimiter //
create function normal_distrib(mean double, stdev double)
returns double no sql
begin
set @x = rand(), @y = rand();
set @nd = (sqrt(-2 * log(@x)) * cos(2 * pi() * @y)) * stdev + mean;
return @nd;
end
//
delimiter ;



以上函數(shù)利用 Box-Muller 變換算法通過(guò)兩個(gè)平均分布的隨機(jī)數(shù)生成正態(tài)分布的隨機(jī)數(shù)。

以下語(yǔ)句通過(guò) normal_distrib 函數(shù)生成了一個(gè)期望值為 0,標(biāo)準(zhǔn)差為 1 的正態(tài)分布隨機(jī)數(shù):

SELECT normal_distrib(0,1);
normal_distrib(0,1)|
-------------------|
 1.4930564399841173|



以下語(yǔ)句可以用于驗(yàn)證 normal_distrib 函數(shù)是否遵循正態(tài)分布:

with recursive temp(val) as (
  select normal_distrib(0,1)
  union all
  select normal_distrib(0,1)
  from temp
  limit 1000000
)
select /*+ set_var(cte_max_recursion_depth = 1m) */avg(val),std(val)
from temp;
avg(val)             |std(val)          |
---------------------|------------------|
-0.002340136192616743|0.9994844557755181|



通過(guò)運(yùn)行 1000000 次,計(jì)算這些數(shù)據(jù)的平均值和標(biāo)準(zhǔn)差,返回結(jié)果非常接近 0 和 1。
生成隨機(jī)字符串
生成固定長(zhǎng)度的隨機(jī)字符串

除了隨機(jī)數(shù)字之外,有時(shí)候我們也需要生成一些隨機(jī)的字符串。MySQL 沒(méi)有提供專門(mén)生成隨機(jī)字符串的函數(shù),但是可以通過(guò)其他函數(shù)進(jìn)行模擬。例如:

SELECT char(floor(rand() * 26)+65) as rand_char;
rand_char|
---------|
T        |


以上查詢返回了一個(gè)隨機(jī)的大寫(xiě)字母,char 函數(shù)用于將 ASCII 碼轉(zhuǎn)換為對(duì)應(yīng)的字符。我們可以基于該查詢進(jìn)一步創(chuàng)建一個(gè)存儲(chǔ)函數(shù):

delimiter //
create function rand_string(len integer)
returns text no sql
begin
  declare counter int default 1;
  declare str text default '';
 
  if len < 1 then
    return null;
  end if;
 
  while counter <= len do
    set str = concat(str, char(floor(rand() * 94) + 33));
    set counter = counter + 1;
  end while;
 
  return str;
end
//
delimiter ;



rand_string 函數(shù)可以返回由任意可打印字符(ASCII 碼從 33 到 126)組成的隨機(jī)字符串。例如:

rand_string(8)|
--------------|
7j5dz[58      |



以上示例返回了一個(gè)長(zhǎng)度為 8,由可打印字符組成的隨機(jī)字符串。

另外,MySQL 中的 elt 函數(shù)也可以用于返回指定位置中的元素。例如:

SELECT elt(1 + floor(rand() * 36),
           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') as val;
val|
---|
B  |



以上語(yǔ)句返回了一個(gè)隨機(jī)的數(shù)字或者大寫(xiě)字母,將其替換到 rand_string 函數(shù)可以返回一個(gè)由數(shù)字和大寫(xiě)字母組成的隨機(jī)字符串。
生成可變長(zhǎng)度的隨機(jī)字符串

那么,怎么返回一個(gè)長(zhǎng)度可變的隨機(jī)字符串呢?很簡(jiǎn)單,為 rand_string 函數(shù)指定一個(gè)隨機(jī)的長(zhǎng)度參數(shù)即可。例如:

SELECT rand_string(floor(10 + rand() * 11));
rand_string(floor(10 + rand() * 11))|
------------------------------------|
4U13MjO+($}k"cO@5%[                 |



以上示例返回了一個(gè)長(zhǎng)度大于等于 10 且小于等于 20,由可打印字符組成的隨機(jī)字符串。
生成隨機(jī)日期和時(shí)間

將指定日期增加一個(gè)隨機(jī)的數(shù)字,就可以得到隨機(jī)的日期。例如:

SELECT date_add('2020-01-01', interval rand() * 31 day) rand_date;
rand_date |
----------|
2020-01-19|


以上示例返回了 2020 年 1 月中的某個(gè)隨機(jī)日期。以下語(yǔ)句則返回了一天中的某個(gè)隨機(jī)時(shí)間:

SELECT sec_to_time(rand() * 3600) rand_time;
rand_time         |
------------------|
00:05:29.546878000|



其中,sec_to_time 函數(shù)用于將秒數(shù)轉(zhuǎn)換為時(shí)間數(shù)據(jù)。
獲取表中的隨機(jī)記錄

對(duì)于返回多行數(shù)據(jù)的查詢語(yǔ)句,RAND 函數(shù)每次都會(huì)返回不同的隨機(jī)數(shù)據(jù)。例如:

SELECT rand(1) FROM employee;
rand(1)             |
--------------------|
 0.40540353712197724|
  0.8716141803857071|
  0.1418603212962489|
...


利用這個(gè)特性,我們可以從表中返回隨機(jī)的數(shù)據(jù)行。例如:

SELECT emp_id,emp_name
FROM employee
ORDER BY rand(1)
LIMIT 5;
emp_id|emp_name  |
------|----------|
     6|魏延      |
    14|張苞      |
    16|周倉(cāng)      |
    15|趙統(tǒng)      |
     1|劉備      |


以上示例從 employee 表中返回了 5 行隨機(jī)記錄。該方法需要為表中的每行數(shù)據(jù)都生成一個(gè)隨機(jī)數(shù),然后進(jìn)行排序;所以會(huì)隨著表中的數(shù)據(jù)量增加而逐漸變慢。

如果表中存在自增主鍵,也可以基于主鍵生成一個(gè)隨機(jī)數(shù)據(jù)。例如:

SELECT round(rand() * (SELECT max(emp_id) FROM employee)) AS id;
id  |
----|
10.0|



然后基于這個(gè)隨機(jī)數(shù)返回一條隨機(jī)的記錄:

SELECT e.emp_id, e.emp_name
FROM employee e
INNER JOIN (SELECT round(rand() * (SELECT max(emp_id) FROM employee)) AS id
     ) AS t
WHERE e.emp_id >= t.id
LIMIT 1;
emp_id|emp_name|
------|--------|
     9|趙云     |



這種方法一次只能返回一條隨機(jī)記錄,而且只有當(dāng)自增字段的值沒(méi)有間隙時(shí)才會(huì)返回均勻分布的隨機(jī)記錄。
生成 UUID

UUID(Universal Unique Identifier)或者 GUID(Globally Unique Identifier)是一個(gè) 128 比特的數(shù)字,可以用于唯一標(biāo)識(shí)每個(gè)網(wǎng)絡(luò)對(duì)象或資源。由于它的生成機(jī)制,一個(gè) UUID 可以保證幾乎不會(huì)與其他 UUID 重復(fù),因此常常用于生成數(shù)據(jù)庫(kù)中的主鍵值。

MySQL 提供了一個(gè)系統(tǒng)函數(shù) UUID,可以用于生成 UUID。例如:

SELECT uuid();
uuid()                              |
------------------------------------|
35f67fde-e0e9-11ea-9d25-0800272142b1|



如果想要生成沒(méi)有中劃線(-)的 UUID,可以使用 REPLACE 函數(shù):

SELECT replace(uuid(),'-','');
replace(uuid(),'-','')          |
--------------------------------|
8505290be0ea11ea9d250800272142b1|



除此之外,MySQL 還提供了一個(gè) UUID_SHORT 函數(shù),它可以返回一個(gè) 64 比特的無(wú)符號(hào)整數(shù)。例如:

SELECT uuid_short();
uuid_short()     |
-----------------|
98862025337208832|



該函數(shù)返回的是一個(gè)“短的”唯一標(biāo)識(shí)符,只有滿足以下條件時(shí)才具有唯一性:

    當(dāng)前服務(wù)器的 server_id 位于 0 到 255 之間,并且在復(fù)制結(jié)構(gòu)中具有唯一性;
    重啟 mysqld 前后沒(méi)有將服務(wù)器主機(jī)的系統(tǒng)時(shí)間往回調(diào)整;
    每秒鐘的平均調(diào)用次數(shù)少于 1600 萬(wàn)次。

總結(jié)

本文介紹了在 MySQL 數(shù)據(jù)庫(kù)中生成隨機(jī)數(shù)據(jù)的方法,包括隨機(jī)數(shù)字、驗(yàn)證碼、隨機(jī)字符串以及隨機(jī)日期和時(shí)間等,同時(shí)還介紹了如何從表中返回隨機(jī)記錄,以及如何生成 UUID。

如果覺(jué)得文章對(duì)你有用,歡迎關(guān)注??、評(píng)論??、點(diǎn)贊??!