SQLで列に乱数を入力するにはどうすればよいですか?すべての行で同じ値を取得します


84
UPDATE CattleProds
SET SheepTherapy=(ROUND((RAND()* 10000),0))
WHERE SheepTherapy IS NULL

次にSELECTを実行すると、乱数がすべての行で同一であることがわかります。一意の乱数を生成する方法はありますか?

回答:


167

の代わりにrand()newid()結果の各行に対して再計算されるを使用します。通常の方法は、チェックサムのモジュロを使用することです。にchecksum(newid())-2,147,483,648が生成されabs()、で整数オーバーフローが発生する可能性があるため、チェックサムの戻り値を絶対値に変換する前にモジュロを使用する必要があることに注意してください。

UPDATE CattleProds
SET    SheepTherapy = abs(checksum(NewId()) % 10000)
WHERE  SheepTherapy IS NULL

これにより、0から9999までの乱数が生成されます。


1
この質問/回答も役立つ場合があります:stackoverflow.com/a/9039661/47226
Aaron Hoffman

これは私にはまったく機能していません。列はINTである必要がありますか?毎回エラー#1064。...狂気の薬に手を伸ばす
freeworlder

1
これは美しいものです!よくやった。大好きです。パフォーマンスは少し遅くなりますが、それでも素晴らしいです。
Arvin Amir

25

SQL Server 2008を使用している場合は、次を使用することもできます。

 CRYPT_GEN_RANDOM(2) % 10000

これはやや単純に見えます(newid以下に示すように、行ごとに1回評価されます)

DECLARE @foo TABLE (col1 FLOAT)

INSERT INTO @foo SELECT 1 UNION SELECT 2

UPDATE @foo
SET col1 =  CRYPT_GEN_RANDOM(2) % 10000

SELECT *  FROM @foo

戻り値(2つのランダムなおそらく異なる数)

col1
----------------------
9693
8573

説明のつかない反対票をつぶすと、私が考えることができる唯一の正当な理由は、生成される乱数が0〜65535であり、10,000で割り切れないため、一部の数値がわずかに過剰に表現されるためです。これを回避する方法は、60,000を超える任意の番号を破棄し、それ自体を再帰的に呼び出して置換番号を取得するスカラーUDFでラップすることです。

CREATE FUNCTION dbo.RandomNumber()
RETURNS INT
AS
  BEGIN
      DECLARE @Result INT

      SET @Result = CRYPT_GEN_RANDOM(2)

      RETURN CASE
               WHEN @Result < 60000
                     OR @@NESTLEVEL = 32 THEN @Result % 10000
               ELSE dbo.RandomNumber()
             END
  END  

1
@ downvoter-特別な理由はありますか?たぶん、上矢印を押すつもりだったので、この答えはうまくいきます!
マーティンスミス

誰もが見逃しているように思われるのは、この方法の方がパフォーマンスがはるかに優れているということです。NEWID()に代わるものを探していましたが、これはまさにその通りです。
2013

任意の範囲を簡単に処理できます。たとえば、ABS(CAST(CRYPT_GEN_RANDOM(8)AS BIGINT)%10001)は、OPのコードが期待どおりに機能した場合に生成される範囲である0〜10000の数値を生成します。
bielawski 2018年

どの「同じ」問題?数式は行ごとに新しい値を生成し(opの問題は解決されました)、結果は範囲内ですが、シードが64ビットで結果が14ビットしかないため、スキューが発生する可能性があるため、スキューされません。10 ^ 15の結果を生成した場合でも、検出していると思われるスキューは誤差の範囲内にあります。つまり、スキューが実際に存在したことを証明するには、2 ^ 19の結果を生成する必要があります。
bielawski 2018年

9

私はCHECKSUMを使用するのが大好きNEWID()ですが、単純な数値を生成するために複雑な計算を行う必要がないという理由だけで、を使用する方が良い方法だと思います。

ROUND( 1000 *RAND(convert(varbinary, newid())), 0)

1000制限として設定する任意の数値に置き換えることができ、いつでもプラス記号を使用して範囲を作成できます。たとえば、100との間の乱数200が必要な場合は、次のように実行できます。

100 + ROUND( 100 *RAND(convert(varbinary, newid())), 0)

クエリにまとめる:

UPDATE CattleProds 
SET SheepTherapy= ROUND( 1000 *RAND(convert(varbinary, newid())), 0)
WHERE SheepTherapy IS NULL

1

それぞれ1億行を生成することにより、RAND()に対して2つのセットベースのランダム化メソッドをテストしました。フィールドを平準化するために、出力は0-1からRAND()を模倣するフロートになります。ほとんどのコードはインフラストラクチャをテストしているので、ここでアルゴリズムを要約します。

-- Try #1 used
(CAST(CRYPT_GEN_RANDOM(8) AS BIGINT)%500000000000000000+500000000000000000.0)/1000000000000000000 AS Val
-- Try #2 used
RAND(Checksum(NewId()))
-- and to have a baseline to compare output with I used
RAND() -- this required executing 100000000 separate insert statements

CRYPT_GEN_RANDOMの使用は、10 ^ 18個の数字のセットから10 ^ 8個の数字を抽出するときに、重複が1つでも表示される可能性が.000000001%しかないため、明らかに最もランダムでした。IOW重複は見られないはずでしたが、これには重複がありませんでした。このセットは私のラップトップで生成するのに44秒かかりました。

Cnt     Pct
-----   ----
 1      100.000000  --No duplicates

SQL Serverの実行時間:CPU時間= 134795ミリ秒、経過時間= 39274ミリ秒。

IF OBJECT_ID('tempdb..#T0') IS NOT NULL DROP TABLE #T0;
GO
WITH L0   AS (SELECT c FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS D(c))  -- 2^4  
    ,L1   AS (SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B)    -- 2^8  
    ,L2   AS (SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B)    -- 2^16  
    ,L3   AS (SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B)    -- 2^32  
SELECT TOP 100000000 (CAST(CRYPT_GEN_RANDOM(8) AS BIGINT)%500000000000000000+500000000000000000.0)/1000000000000000000 AS Val
  INTO #T0
  FROM L3;

 WITH x AS (
     SELECT Val,COUNT(*) Cnt
      FROM #T0
     GROUP BY Val
)
SELECT x.Cnt,COUNT(*)/(SELECT COUNT(*)/100 FROM #T0) Pct
  FROM X
 GROUP BY x.Cnt;

ランダム性がほぼ15桁少ないため、この方法は2倍の速さではなく、1億個の数値を生成するのに23秒しかかかりませんでした。

Cnt  Pct
---- ----
1    95.450254    -- only 95% unique is absolutely horrible
2    02.222167    -- If this line were the only problem I'd say DON'T USE THIS!
3    00.034582
4    00.000409    -- 409 numbers appeared 4 times
5    00.000006    -- 6 numbers actually appeared 5 times 

SQL Serverの実行時間:CPU時間= 77156ミリ秒、経過時間= 24613ミリ秒。

IF OBJECT_ID('tempdb..#T1') IS NOT NULL DROP TABLE #T1;
GO
WITH L0   AS (SELECT c FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS D(c))  -- 2^4  
    ,L1   AS (SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B)    -- 2^8  
    ,L2   AS (SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B)    -- 2^16  
    ,L3   AS (SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B)    -- 2^32  
SELECT TOP 100000000 RAND(Checksum(NewId())) AS Val
  INTO #T1
  FROM L3;

WITH x AS (
    SELECT Val,COUNT(*) Cnt
     FROM #T1
    GROUP BY Val
)
SELECT x.Cnt,COUNT(*)*1.0/(SELECT COUNT(*)/100 FROM #T1) Pct
  FROM X
 GROUP BY x.Cnt;

RAND()だけではセットベースの生成には役に立たないため、ランダム性を比較するためのベースラインの生成には6時間以上かかり、最終的に正しい数の出力行を取得するには数回再起動する必要がありました。また、checksum(newid())を使用して各行を再シードするよりも優れていますが、ランダム性には多くの要望が残されているようです。

Cnt  Pct
---- ----
1    99.768020
2    00.115840
3    00.000100  -- at least there were comparitively few values returned 3 times

再起動のため、実行時間をキャプチャできませんでした。

IF OBJECT_ID('tempdb..#T2') IS NOT NULL DROP TABLE #T2;
GO
CREATE TABLE #T2 (Val FLOAT);
GO
SET NOCOUNT ON;
GO
INSERT INTO #T2(Val) VALUES(RAND());
GO 100000000

WITH x AS (
    SELECT Val,COUNT(*) Cnt
     FROM #T2
    GROUP BY Val
)
SELECT x.Cnt,COUNT(*)*1.0/(SELECT COUNT(*)/100 FROM #T2) Pct
  FROM X
 GROUP BY x.Cnt;

PS再起動が重複の一部を説明している可能性があると考えて、私はすぐに3M行だけをテストしました。これには約6分半かかりました。2101の重複があり、2つの値が3回表示され(それぞれ.07%と.000067%)、再起動がおそらく役割を果たしたことを示していますが、ランダム性はまだ恒星からはほど遠いです。
bielawski 2017

newidがvarbinaryに変換されたばかりの別の回答に気付いたので、それも試してみました。チェックサムを使用するよりも高速ではないだけでなく、そのテストでは1つの値が8回表示されます。公平を期すために、それはまだ95.447319%の一意であり、私のテストではRAND(Checksum(NewId()))の95.450254%よりもわずかに悪いだけです。2回目の実行では、3つの数値が5回出現し、95.452929%が異なるという最悪のケースが発生したため、1億行をテストした場合でもYMMVが発生しました。
bielawski 2017

-2
require_once('db/connect.php');

//rand(1000000 , 9999999);

$products_query = "SELECT id FROM products";
$products_result = mysqli_query($conn, $products_query);
$products_row = mysqli_fetch_array($products_result);
$ids_array = [];

do
{
    array_push($ids_array, $products_row['id']);
}
while($products_row = mysqli_fetch_array($products_result));

/*
echo '<pre>';
print_r($ids_array);
echo '</pre>';
*/
$row_counter = count($ids_array);

for ($i=0; $i < $row_counter; $i++)
{ 
    $current_row = $ids_array[$i];
    $rand = rand(1000000 , 9999999);
    mysqli_query($conn , "UPDATE products SET code='$rand' WHERE id='$current_row'");
}

多分それは正しくなく、最も簡単な方法ではありませんが、それは機能します)))
Vaso Nadiradze 2017

1
回答を始める前に、質問を注意深くお読みください。ちなみに、行ごとに個別にUPDATEクエリを送信することは、適度な数の行でも更新する必要がある場合、非常に悪いアイデアです。
darlove
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.