いくつかの方法があります:
- 事前に乱数を含むテーブルを生成し、必要に応じて使用します。または、信頼できる情報源からこのデータを取得します。
NEWID
function を使用して のシードを提供するさまざまな組み合わせRAND
。NEWID 値の分布については保証されていないため、注意して使用する必要があります。多かれ少なかれ均一に分散させるための最良の方法の1つは、CHECKSUM
:を使用することですRAND(CHECKSUM(NEWID()))
。この方法の良いところは、SQL Server 2000 以降で NEWID 関数が利用できることです。
NEWID
たとえば、ある列の MD5 をRAND
:RAND(CHECKSUM(HASHBYTES('MD5', CAST(SomeID AS varbinary(4)))))
または単に行番号:のシードとして使用する代わりにRAND(CHECKSUM(HASHBYTES('MD5', CAST(ROW_NUMBER() OVER(ORDER BY ...) AS varbinary(4)))))
。この方法は、少なくとも SQL Server 2005 以降で使用できます。NEWID
方法との主な違いは、ランダム シーケンスを完全に制御できることです。返されるものを制御することはNEWID
できず、同じ番号からランダム シーケンスを再開することはできません。を使用して同じ行番号のセットを指定すると、PARTITION BY
同じ乱数のセットが得られます。同じ乱数列を数回使用する必要がある場合に役立ちます。2 つの異なるシードに対して同じ乱数を取得することは可能です。1 から 1,000,000 までの行番号でテストしました。MD5
それらのすべてが異なります。CHECKSUM
のMD5
結果、122 回の衝突が発生します。RAND
このCHECKSUM
結果、246 回の衝突が発生します。1 から 100,000 までの行番号でテストすると、CHECKSUM
1 回の衝突があり、RAND
3 回の衝突がありました。
- もう 1 つの可能性は、好みのアルゴリズムを使用して乱数を生成する独自のユーザー定義関数を T-SQL に単純に実装することです。この場合、すべてを完全に制御できます。通常、疑似乱数ジェネレーターは呼び出しの間に内部状態を保存する必要があるため、このデータを保存する専用のテーブルが必要になる場合があります。
- CLR を使用してユーザー定義関数を作成できます。
Random
この場合、独自のジェネレーターを実装するか、 classやRNGCryptoServiceProvider
classなどの .NET に組み込まれている関数を使用できます。
- 最後に、SQL Server 2008 以降、組み込み関数があります
CRYPT_GEN_RANDOM
。
最後の方法について詳しく説明します。これは、SQL Server 2008 以降では非常に優れたソリューションだと思うからです。CRYPT_GEN_RANDOM
は、一度だけ呼び出される とは対照的に、結果セットの各行に対して呼び出されRAND
ます。
CRYPT_GEN_RANDOM (Transact-SQL)
Crypto API (CAPI) によって生成された暗号乱数を返します。出力は、指定されたバイト数の 16 進数です。
さらに、CRYPT_GEN_RANDOM
よりもはるかに優れたランダム値を提供する必要がありますRAND
。分散と暗号強度の点で優れています。例:
(CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5)
これにより、4 つのランダム バイトが として生成されvarbinary
ます。最初に明示的にキャストする必要がありint
ます。次に、結果が 0 から 1 の間の浮動小数点数に変換されます。
したがって、元のクエリは次のようになります。
SELECT ID AS [ID]
, MyFunction.dbo.AddWorkDays(14, S.CREATED_DATE) AS [New Date]
, CONVERT(VARCHAR, DATEADD(MILLISECOND,
CAST(43200000 * (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) as int),
CONVERT(TIME, '08:00')), 114) AS [New Time]
FROM RandomTable
これは、簡単にコピーして貼り付けて試すことができるスタンドアロンの例です(@Steve Fordによる別の回答のクエリを使用しました):
SELECT DATEADD(millisecond,
CAST(43200000 * (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) as int),
CAST('08:00:00' AS Time)) AS [RandomTime]
FROM
( VALUES (1), (2), (3), (4), (5)
) Y(A)
CROSS JOIN
( VALUES (1), (2), (3), (4), (5)
) Z(A)
結果は次のとおりです。
RandomTime
10:58:24.7200000
19:40:06.7220000
11:04:29.0530000
08:57:31.6130000
15:03:14.9470000
09:15:34.9380000
13:46:43.1250000
11:27:00.8940000
14:42:23.6100000
15:07:56.2120000
11:39:09.8830000
08:16:44.3960000
14:23:38.4820000
17:28:31.7440000
16:29:31.4320000
09:09:15.0210000
12:31:09.8370000
11:23:09.8430000
15:35:45.5480000
17:42:49.3390000
08:07:05.4930000
18:17:16.2980000
11:49:08.2010000
10:20:21.7620000
15:56:58.6110000
添加
元の質問を読んだとき、生成されたすべての乱数が一意であることを確認する必要があるとは思いませんでした。質問の「異なる」という言葉は、単純なSELECT RAND()
. 多くの場合、衝突する乱数が少なくても構わないと思います。多くの場合、それは実際には正しい動作です。
したがって、一意の乱数のシーケンスが必要な場合は、ある意味で次のタスクと同等であると理解しています。いくつかの値/行のセットがあります。たとえば、一意の ID のセット、1 日の 86400 秒すべて、または特定の日の 2800 行などです。これらの値/行をシャッフルします。これらの行をランダムな順序で再配置します。
指定された行のセットをシャッフルするには、単純にORDER BY
乱数を使用する必要があります (これらの乱数には、妥当な量の衝突が発生する可能性があります)。乱数は、任意の方法で生成できます。このようなもの:
ROW_NUMBER() OVER ([optional PARTITION BY ...] ORDER BY CRYPT_GEN_RANDOM(4))
または文字通り
SELECT ...
FROM ...
ORDER BY CRYPT_GEN_RANDOM(4)
場所と使い方次第。