135

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

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

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

どんな助けでも大歓迎です。

4

25 に答える 25

155

衝突の可能性は気にしません。ランダムな文字列を生成し、存在するかどうかを確認するだけです。その場合は、もう一度やり直してください。すでに膨大な数のプレートが割り当てられていない限り、数回以上行う必要はありません。

純粋な (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()(ただし、このロジックをアプリケーション層に移動することを検討します)。

于 2013-05-24T15:16:36.020 に答える
93

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

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

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

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

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

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

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

例えば ​​AssuminidAUTO_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;
于 2013-05-24T15:21:09.457 に答える
57

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

すなわち

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

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

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

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

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

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

于 2013-05-24T15:05:43.077 に答える
45

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

これは、指定された長さのランダムな文字列を作成する 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 ;
于 2016-11-30T10:41:41.483 に答える
28

英数字を有効な文字として使用する方法の 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;

一意性は保証されないことに注意してください。それについては別途確認する必要があります。

于 2013-05-24T15:07:00.793 に答える
17

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;
于 2013-05-24T15:11:36.273 に答える
15

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.

于 2016-09-10T00:02:09.317 に答える
7

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

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

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

353E50CC

于 2018-04-14T05:26:01.353 に答える
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))));
于 2016-10-12T04:29:02.890 に答える
2

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

select substring(base64_encode(md5(rand())) from 1+rand()*4 for 10);
于 2020-03-01T21:18:44.013 に答える
1

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

36^8 の異なる固有のナンバープレートがあります (2,821,109,907,456、これはかなりの数です)。すでに 100 万個のナンバープレートを既に持っていたとしても、すでに持っているナンバープレートを生成する可能性は非常に低く、約 0.000035% です。

もちろん、最終的に作成するナンバープレートの数によって異なります。

于 2013-05-24T15:12:52.877 に答える
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')
于 2019-09-11T09:47:57.377 に答える