MySQLを使用してランダムで一意の8文字の文字列を生成する


110

私はある時点で車両を含むゲームに取り組んでいます。「vehicles」という名前のMySQLテーブルがあります。これには、車両のナンバープレートを格納する「plate」列を含む、車両に関するデータが含まれています。

ここで私が問題を抱えている部分があります。新しい車両を作成する前に、未使用のナンバープレートを見つける必要があります。これは、8文字の英数字のランダムな文字列でなければなりません。これを実現する方法は、Luaでwhileループを使用していたことです。これは、プログラミングしている言語で、文字列を生成し、DBにクエリを実行して、それが使用されているかどうかを確認します。しかし、車の数が増えるにつれ、これは今よりも非効率になると思います。したがって、MySQLクエリを使用してこの問題を解決することにしました。

必要なクエリは、まだテーブルにない8文字の英数字の文字列を生成するだけです。ループの生成とチェックのアプローチをもう一度考えましたが、より効率的なものがある場合に備えて、この質問をそれに限定することはしません。許可されているすべての文字を含む文字列を定義し、それをランダムに部分文字列化することで、文字列を生成できました。

どんな助けでもありがたいです。


これらはどれくらいランダムである必要がありますか?誰かが特定のナンバープレートを受け取った場合、彼らがあなたが配った次または前のナンバープレートを解決できるかどうかは重要ですか?
Damien_The_Unbeliever 2013年

@YaK衝突の可能性を
少し

回答:


87

この問題は、2つの非常に異なるサブ問題で構成されています。

  • 文字列は一見ランダムである必要があります
  • 文字列は一意である必要があります

ランダム性は非常に簡単に実現できますが、再試行ループなしの一意性は実現できません。これにより、最初に独自性に集中することができます。非ランダムな一意性は、で簡単に達成できますAUTO_INCREMENT。そのため、一意性を維持した疑似ランダム変換を使用すると問題ありません。

  • ハッシュは@paulによって提案されました
  • AES暗号化も適合
  • しかし、すばらしいものがあります。RAND(N)それ自体です。

同じシードによって作成された一連の乱数は、

  • 再現可能
  • 最初の8回の反復で異なる
  • 種子が INT32

したがって、@ AndreyVolkまたは@GordonLinoffのアプローチを使用しますが、シードは RAND次のとおりです。

例:アスミンidAUTO_INCREMENTカラムです:

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;

非常に興味深いアプローチですが、おそらく意味がありましたRAND(LAST_INSERT_ID()); UPDATE vehicles (...) , rand()*36+1, (...)(または、同じ文字を8回返します)。rand()異なるシードで初期化された場合、8回の連続する呼び出しが異なるシーケンスを返すことが保証されていることをどのように確認できますか?
RandomSeed 2013年

8
ただ疑問に思っていました。なぜあなたはそれらの数字..4294967296))* 36 + 1を使うのですか?
ミック

7
これは少し古いですがFLOOR()、2番目の部分文字列パラメーターを追加する必要があったことに注意したいと思います。 substring('ABC … 789', floor(rand(@seed:= … )*36+1), 1), 場合によっては、部分文字列が文字36.9を選択しようとしました。
Phillip Dodson、2015

4
再現可能な場合、文字列をランダムに呼び出すことはできません。また、を使用してfloor()いるため、重複する可能性もあります。このsqlfiddleは、3文字の長い文字列に対して重複が作成されることを示しています。
Paul Spiegel

6
@EugenRieck数値を取得する方法がわかりません( "最初の2 ^ 32反復")。しかし、私はこの概念を反証するために重複の1つの例しか必要としません。ID 193844775771アルゴリズムについては、同じ文字列T82X711demo)が生成されます。
Paul Spiegel

113

私のコメントで述べたように、衝突の可能性については気にしませんでした。ランダムな文字列を生成し、それが存在するかどうかを確認するだけです。表示されている場合は、もう一度試してください。すでに割り当てられているプレートの数が非常に多い場合を除いて、数回以上実行する必要はありません。

純粋な(My)SQLで8文字の長い擬似ランダム文字列を生成する別のソリューション:

SELECT LEFT(UUID(), 8);

以下を試すことができます(疑似コード):

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

この投稿は予想外のレベルの注目を集めたので、ADTCのコメントを強調しておきます。上記のコードは非常に馬鹿げており、連続した数字を生成します。

少しばかばかしいランダムさについては、代わりにこのようなものを試してください:

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

そして、真の(暗号学的に安全な)ランダム性については、RANDOM_BYTES()ではなくを使用しますRAND()(ただし、このロジックをアプリケーション層に移動することを検討します)。


あなたの解決策をありがとう私はUUIDに関して1つの質問があります。8文字を生成するIDが再び繰り返される可能性のある回数。
TR-Ahmed

1
@ user3099183 公式には、「非常に低い」。16 ^ 8は約40億の可能な文字列です。
RandomSeed 2014

23
UUIDの最初の8文字はランダムではなく連続していることに注意してください。これは、タイムスタンプに基づいているためです。
ADTC

9文字のランダムな文字列を生成したいのです9が、コードSELECT LEFT(UUID(), 9);で使用すると、-生成された文字列の最後に9番目の文字が常に存在します。一定です。どうして?
マーティンAJ

3
@MartinAJは、文字列がuuidであるためです。ハイフンは簡単に置き換えることができます。例SELECT LEFT(REPLACE(UUID(), '-', ''), 16);
jchook

53

連続した整数のMD5(またはその他の)ハッシュを計算し、最初の8文字を取得するのはどうですか。

すなわち

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

注意:衝突前にいくつ割り当てることができるかはわかりません(ただし、既知の定数値です)。

編集:これは今では古い答えですが、時間をかけて手でもう一度見たので、観察から...

すべての数値の確率= 2.35%

すべての文字の確率= 0.05%

MD5(82945)= "7b763dcb ..."の場合の最初の衝突(MD5(25302)と同じ結果)


2
良い考えです。主キーで使用できます。今後のプロジェクトのためにこれを覚えておいてください、ありがとう!
funstein 2013年

2
これは数字のみで生じる可能性があります
Mladen Janjetovic '27 / 12/27

9
それはまったくランダムではありません。
ポール、

1
自動インクリメンタルIDを使用する代わりに、挿入が行われた日時も使用する場合は、ランダムにすることができます。これもユニークです。
Javier La Banca

35

ランダムな文字列を作成する

これは、指定された長さのランダムな文字列を作成するMySQL関数です。

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

SELECT RANDSTRING(8)8文字の文字列を返すための使用法。

をカスタマイズできます@allowedChars

一意性は保証されません-他のソリューションへのコメントでわかるように、これは不可能です。代わりに、文字列を生成し、それがすでに使用されているかどうかを確認し、使用されている場合は再試行する必要があります。


ランダムな文字列がすでに使用されているかどうかを確認します

衝突チェックコードをアプリから除外したい場合は、トリガーを作成できます。

DELIMITER $$

CREATE TRIGGER Vehicle_beforeInsert
  BEFORE INSERT ON `Vehicle`
  FOR EACH ROW
  BEGIN
    SET @vehicleId = 1;
    WHILE (@vehicleId IS NOT NULL) DO 
      SET NEW.plate = RANDSTRING(8);
      SET @vehicleId = (SELECT id FROM `Vehicle` WHERE `plate` = NEW.plate);
    END WHILE;
  END;$$
DELIMITER ;

6
これは受け入れられた答えであり、明確で要点があります。@ paddy-mannに感謝します
Saif

これは私が考える最良の解決策です。ありがとう!
Pronoy999

23

有効な文字として英数字を使用する1つの方法を次に示します。

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;

一意性の保証はありません。個別に確認する必要があります。


7
代わりに、floor(rand()* 36 + 1)を使用してください。そうしないと、一部の結果が「短く」なります。
Fraggle

2
36 + 1ではなく35 + 1が必要です!そうしないと、8文字の文字列と7文字の文字列の一部が表示されます
BIOHAZARD

23

ランダムな文字列を生成する別の方法を次に示します。

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


16

MySQLのrand()およびchar()関数を使用できます。

select concat( 
    char(round(rand()*25)+97),
    char(round(rand()*25)+97),
    char(round(rand()*25)+97),
    char(round(rand()*25)+97),
    char(round(rand()*25)+97),
    char(round(rand()*25)+97),
    char(round(rand()*25)+97),
    char(round(rand()*25)+97)
) as name;

14

あなたはランダムな英数字の文字列を生成することができます:

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

これをBEFORE INSERTトリガーで使用して、whileループで重複をチェックできます。

CREATE TABLE `vehicles` (
    `plate` CHAR(8) NULL DEFAULT NULL,
    `data` VARCHAR(50) NOT NULL,
    UNIQUE INDEX `plate` (`plate`)
);

DELIMITER //
CREATE TRIGGER `vehicles_before_insert` BEFORE INSERT ON `vehicles`
FOR EACH ROW BEGIN

    declare str_len int default 8;
    declare ready int default 0;
    declare rnd_str text;
    while not ready do
        set rnd_str := lpad(conv(floor(rand()*pow(36,str_len)), 10, 36), str_len, 0);
        if not exists (select * from vehicles where plate = rnd_str) then
            set new.plate = rnd_str;
            set ready := 1;
        end if;
    end while;

END//
DELIMITER ;

次のようにデータを挿入します

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

そして、トリガーはplate列の値を生成します。

sqlfiddleデモ

列がNULLを許可する場合、これはこのように機能します。NOT NULLにしたい場合は、デフォルト値を定義する必要があります

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

大文字の英数字が適切でない場合は、トリガーで他のランダム文字列生成アルゴリズムを使用することもできます。しかし、トリガーは一意性を処理します。


すごい!これはまさに私が欲しかったものです。それが文字列に変換される方法を理解したいだけです。なぜ、捕虜がいるのか、何をするのか、数字を追加するのかなどです。とにかくありがとうございます。
Akxe 2017

@Akxe conv()を使用して、数値を英数字の文字列に変換できます。pow(36,8)-1の数値表現ですZZZZZZZZ。間のランダムな整数を生成我々はそう0(からと'36 ^ 8-1' 0への2821109907455)との間で、英数字の文字列に変換0し、ZZZZZZZZunsing conv()ラパッド()は、それが8の長さになるまでゼロで文字列を記入します
ポールシュピーゲル

あなたは天才です。文字が連続していないため、小文字を追加することは不可能だと思いますか?(91-96)必要なわけではありません、好奇心が強い...
Akxe

@Akxe conv()は、36までのベース(10桁+ 26の大文字)のみをサポートします。小文字を含める場合は、数値を文字列に変換する別の方法が必要になります。
Paul Spiegel

警告:str_len> 13では機能しません。14以降では、常に「3W5E11264SGSF」が表示されます。;-)
Gerard H. Pille

6

ランダムな文字列を生成するには、以下を使用できます。

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

あなたはそのようなsmthを受け取ります:

353E50CC


5

8つの乱数と大文字と小文字で構成される文字列の場合、これが私の解決策です。

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

裏から説明:

  1. RAND 0と1の間の乱数を生成します
  2. MD5 (1)のMD5合計を計算し、afと0-9から32文字
  3. UNHEX (2)を00からFFの値を持つ16バイトに変換します
  4. TO_BASE64 (3)をbase64としてエンコードします。azとAZからの22文字と0-9に「/」と「+」を加え、その後に2つの「=」を続けます。
  5. 3つREPLACEのsは(4)から「/」、「+」、「=」の文字を削除します
  6. LEFT (5)の最初の8文字を取得します。ランダムな文字列に必要な文字数が多ければ少ない場合は、8を別の文字に変更します
  7. LPAD長さが8文字未満の場合、(6)の先頭にゼロを挿入します。再度、必要に応じて8を別の値に変更します

MySQLでトークンのようなIDをネイティブに作成するためにまさに探していたもの
rabudde '28 / 11/17


4

アルファベットから8文字-すべて大文字:

UPDATE `tablename` SET `tablename`.`randomstring`= concat(CHAR(FLOOR(65 + (RAND() * 25))),CHAR(FLOOR(65 + (RAND() * 25))),CHAR(FLOOR(65 + (RAND() * 25))),CHAR(FLOOR(65 + (RAND() * 25)))CHAR(FLOOR(65 + (RAND() * 25))),CHAR(FLOOR(65 + (RAND() * 25))),CHAR(FLOOR(65 + (RAND() * 25))),CHAR(FLOOR(65 + (RAND() * 25))));

3

挿入の値リストのように、IDまたはシードがない場合:

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

2

大文字と小文字および数字を含むランダムな10文字の文字列を取得するシンプルで効率的なソリューション:

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

1

「ランダム」ではあるが完全に予測可能なナンバープレートに問題がない場合は、線形フィードバックシフトレジスタを使用して次のプレート番号を選択できます-繰り返す前にすべての番号を通過することが保証されています。ただし、複雑な計算を行わないと、8文字の英数字文字列をすべて通過することはできません(可能な36 ^ 8(78%)のプレートから2 ^ 41が得られます)。これでスペースをよりよく埋めるために、プレートから文字を除外することができます(Oの可能性があります)。


1

必要な文字の総数を考慮すると、2つの正確に類似したナンバープレートが生成される可能性は非常に低くなります。したがって、おそらくLUAでの数値の生成を回避できます。

36 ^ 8種類のユニークなナンバープレートがあります(2,821,109,907,456、それはたくさんあります)。すでに100万のナンバープレートがあったとしても、すでに持っているものを生成する可能性は非常に少なく、約0.000035%です。

もちろん、それはすべて、最終的に作成するナンバープレートの数に依存します。


確かに、SQLの代わりに実際のゲームでそれを続けます。どうもありがとうございました。
funstein 2013年

1

この関数は、入力の長さと次のような許可された文字に基づいてランダム文字列を生成します。

SELECT str_rand(8, '23456789abcdefghijkmnpqrstuvwxyz');

機能コード:

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 ;

このコードは、「Ross Smith II」によるシャッフル文字列関数の送信に基づいています


この関数はランダムな一意の値を生成しません。
ファイサル

1

似た文字01oOlIを除いて、ランダムな10桁の英数字を作成するには:

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)

これは、私がバウチャーコードを作成するために必要なものです。紛らわしい文字は削除され、バウチャーコードフォームに入力する際のエラーを減らします。

Jan Uhligの素晴らしい答えに基づいて、これが誰かの役に立つことを願っています

このコードの仕組みの内訳については、Janの回答をご覧ください。


0
DELIMITER $$

USE `temp` $$

DROP PROCEDURE IF EXISTS `GenerateUniqueValue`$$

CREATE PROCEDURE `GenerateUniqueValue`(IN tableName VARCHAR(255),IN columnName VARCHAR(255)) 
BEGIN
    DECLARE uniqueValue VARCHAR(8) DEFAULT "";
    WHILE LENGTH(uniqueValue) = 0 DO
        SELECT CONCAT(SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1)
                ) INTO @newUniqueValue;
        SET @rcount = -1;
        SET @query=CONCAT('SELECT COUNT(*) INTO @rcount FROM  ',tableName,' WHERE ',columnName,'  like ''',@newUniqueValue,'''');
        PREPARE stmt FROM  @query;
        EXECUTE stmt;
        DEALLOCATE PREPARE stmt;
    IF @rcount = 0 THEN
            SET uniqueValue = @newUniqueValue ;
        END IF ;
    END WHILE ;
    SELECT uniqueValue;
    END$$

DELIMITER ;

このストアドプロシージャを使用して、次のように毎回使用します

Call GenerateUniqueValue('tableName','columnName')

0

一意の番号を生成する簡単な方法

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


0

私は似たようなものを探していたので、必要に応じて別のシード(文字のリスト)をパラメーターとして指定できる独自のバージョンを作成することにしました。

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

次のように使用できます:

SELECT random_string(10, '')

これは、大文字と小文字+数字の組み込みシードを使用します。NULLも ''の代わりに値になります。

ただし、呼び出し中にカスタムシードを指定することもできます。

SELECT random_string(10, '1234')
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.