30

データセットから選択された行ごとに、午前 8:00 から午後 8:00 までのランダムな時間を生成しようとしていますが、ごとに常に同じランダム値を取得します。各行

テーブルのスキーマとデータ:

╔══════╦════════════════╗
║  ID  ║  CREATED_DATE  ║
╠══════╬════════════════╣
║ ID/1 ║   26/04/2014   ║
║ ID/2 ║   26/04/2014   ║
║ ID/3 ║   26/04/2014   ║
║ ID/4 ║   26/04/2014   ║
║ ID/5 ║   26/04/2014   ║
╚══════╩════════════════╝

現在の SQL ステートメント:

SELECT [ID]
     , MyFunction.dbo.AddWorkDays(14, [CREATED_DATE]) AS [New Date]
     , CONVERT(VARCHAR, DATEADD(MILLISECOND, CAST(43200000 * RAND() AS INT), CONVERT(TIME, '08:00')), 114) AS [New Time]
FROM [RandomTable]

現在の結果 (列の各行で同じ[New Time]時間):

╔══════╦════════════════╦════════════════╗
║  ID  ║    New Date    ║    New Time    ║
╠══════╬════════════════╬════════════════╣
║ ID/1 ║   10/05/2014   ║    09:41:43    ║
║ ID/2 ║   10/05/2014   ║    09:41:43    ║
║ ID/3 ║   10/05/2014   ║    09:41:43    ║
║ ID/4 ║   10/05/2014   ║    09:41:43    ║
║ ID/5 ║   10/05/2014   ║    09:41:43    ║
╚══════╩════════════════╩════════════════╝

望ましい結果 (列の行ごとに異なる[New Time]時間):

╔══════╦════════════════╦════════════════╗
║  ID  ║    New Date    ║    New Time    ║
╠══════╬════════════════╬════════════════╣
║ ID/1 ║   10/05/2014   ║    09:41:43    ║
║ ID/2 ║   10/05/2014   ║    15:05:23    ║
║ ID/3 ║   10/05/2014   ║    10:01:05    ║
║ ID/4 ║   10/05/2014   ║    19:32:45    ║
║ ID/5 ║   10/05/2014   ║    08:43:15    ║
╚══════╩════════════════╩════════════════╝

これを修正する方法についてのアイデアはありますか? 上記はすべて単なるサンプル データです。私の実際のテーブルには約 2800 のレコードがあります (それが誰かの提案に違いをもたらすかどうかはわかりません)。

4

8 に答える 8

7

いくつかの方法があります:

  • 事前に乱数を含むテーブルを生成し、必要に応じて使用します。または、信頼できる情報源からこのデータを取得します。
  • NEWIDfunction を使用して のシードを提供するさまざまな組み合わせ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それらのすべてが異なります。CHECKSUMMD5結果、122 回の衝突が発生します。RANDこのCHECKSUM結果、246 回の衝突が発生します。1 から 100,000 までの行番号でテストすると、CHECKSUM1 回の衝突があり、RAND3 回の衝突がありました。
  • もう 1 つの可能性は、好みのアルゴリズムを使用して乱数を生成する独自のユーザー定義関数を T-SQL に単純に実装することです。この場合、すべてを完全に制御できます。通常、疑似乱数ジェネレーターは呼び出しの間に内部状態を保存する必要があるため、このデータを保存する専用のテーブルが必要になる場合があります。
  • CLR を使用してユーザー定義関数を作成できます。Randomこの場合、独自のジェネレーターを実装するか、 classRNGCryptoServiceProviderclassなどの .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)

場所と使い方次第。

于 2015-01-06T06:10:51.950 に答える
3

これをテストします:

 Declare @t table(ID int,CREATED_DATE datetime)
insert into @t values
 (1 ,  '04/26/2014'),
 (2 ,  '04/26/2014'),
 (3 ,  '04/26/2014'),
 (4 ,  '04/26/2014')

 ;WITH CTE AS
 (
   SELECT *,CONVERT(VARCHAR, DATEADD(SECOND, RAND(CAST(NEWID() AS VARBINARY)) * 43200, 
   CAST('08:00:00' AS TIME)),114) AS [New Time] FROM @t WHERE ID=1
   UNION ALL
   SELECT *,CONVERT(VARCHAR, DATEADD(SECOND, RAND(CAST(NEWID() AS VARBINARY)) * 43200, 
   CAST('08:00:00' AS TIME)), 114)  FROM @t WHERE ID>1 AND ID<=5
 )
 SELECT * FROM CTE
于 2015-01-06T08:40:20.997 に答える
2

これは、時間の生成方法をもう少し制御できる別のオプションです。ランダム時間の間隔を指定できます。また、RAND機能を利用していません。

DECLARE @StartTime  VARCHAR(10) = '08:00',
        @EndTime    VARCHAR(10) = '20:00',
        @Interval   INT = 5 --(In Seconds)

WITH times AS(
    SELECT CONVERT(TIME, @StartTime) AS t
    UNION ALL
    SELECT DATEADD(SECOND, @Interval, t)
    FROM times
    WHERE t < @EndTime
)

SELECT *, 
(SELECT TOP 1 t FROM times WHERE d.Id > 0 ORDER BY NEWID())
FROM #data d
option (maxrecursion 0)

補足:上記のサブクエリ
の句を削除すると( )、すべての行に対して同じ時間値が返されます。つまり、最初に発生したのと同じ問題です。WHEREWHERE d.Id > 0

于 2015-01-10T22:24:19.613 に答える
0

特定の範囲のランダム時間を取得: SQL Server

SELECT
    X.Value,
    RT.RandomTime,
    DateObject = CONVERT(SMALLDATETIME, CONVERT(DATE, GETDATE())) + CONVERT(SMALLDATETIME, RT.RandomTime)
 FROM (VALUES(101),(204),(77),(54),(75),(66)) X(Value)  /* YOUR TABLE */
 CROSS APPLY(SELECT FromTime = '08:20:00', ToTime = '08:33:00') FT
 CROSS APPLY(SELECT MaxSeconds = DATEDIFF(ss, FT.FromTime, FT.ToTime)) MS
 CROSS APPLY(SELECT RandomTime = CONVERT(TIME, DATEADD(SECOND, (MS.MaxSeconds + 1) * RAND(CONVERT(VARBINARY, NEWID() )) , FT.FromTime))) RT

ここに画像の説明を入力

于 2021-06-14T05:35:45.890 に答える