午前8時から午前8時の間にランダムな時間を生成しようとしていますPM各行はデータセットから選択されますが、常にsameランダムな値を取得します各行–私はそれを異なる各行。
テーブルスキーマとデータ:
╔══════╦════════════════╗
║ 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 ║
╚══════╩════════════════╝
СurrentSQLステートメント:
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レコードがあります(それが誰かの提案に違いをもたらすかどうかはわかりません)。
質問は次のように述べています。
ここで、次の点を考慮に入れてください。
次の領域にはいくつかのあいまいさがあります。
上記の情報を前提として、リクエストを解釈する方法はいくつかあります。
Rand(CAST(NEWID() AS VARBINARY)) * 43200
ABS(CHECKSUM(NewId()) % 43201)
CAST(43200000 * (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) as int)
したがって、私は次のような考えに基づいて答えました。
状況に固有の時間が必要な場合、真にランダムな値を生成する方法ではそれを保証できません。 @Vladimir Baranovによる_CRYPT_GEN_RANDOM
_の使用は本当に好きですが、一意の値のセットを生成することはほぼ不可能です。
_DECLARE @Table TABLE (Col1 BIGINT NOT NULL UNIQUE);
INSERT INTO @Table (Col1)
SELECT CONVERT(BIGINT, CRYPT_GEN_RANDOM(4))
FROM [master].sys.objects so
CROSS JOIN [master].sys.objects so2
CROSS JOIN [master].sys.objects so3;
-- 753,571 rows
_
ランダムな値を8バイトに増やすことはうまくいくようです:
_DECLARE @Table TABLE (Col1 BIGINT NOT NULL UNIQUE);
INSERT INTO @Table (Col1)
SELECT CONVERT(BIGINT, CRYPT_GEN_RANDOM(8))
FROM [master].sys.objects so
CROSS JOIN [master].sys.objects so2
CROSS JOIN [master].sys.objects so3;
-- 753,571 rows
_
もちろん、2番目まで生成している場合、それらは86,400個しかありません。以下が時折機能するため、スコープを縮小すると役立つようです。
_DECLARE @Table TABLE (Col1 BIGINT NOT NULL UNIQUE);
INSERT INTO @Table (Col1)
SELECT TOP (86400) CONVERT(BIGINT, CRYPT_GEN_RANDOM(4))
FROM [master].sys.objects so
CROSS JOIN [master].sys.objects so2
CROSS JOIN [master].sys.objects so3;
_
ただし、一意性が必要な場合は少し注意が必要です毎日(これは、すべての日で一意であるのではなく、このタイプのプロジェクトの合理的な要件のようです)。しかし、乱数ジェネレーターは、新しい日にリセットすることを知りません。
単にランダムに見えるだけでよい場合は、次のことを行わなくても、日付ごとに一意性を保証できます。
Rand()
、NEWID()
、またはCRYPT_GEN_RANDOM()
を使用する次のソリューションでは、この回答で学習した モジュラ逆数 (MMI)の概念を使用しています。 SQL Serverで一見ランダムな一意の数値IDを生成する 。もちろん、その質問には、ここにあるような厳密に定義された値の範囲はなく、1日あたり86,400個しかありませんでした。そこで、86400の範囲(「モジュロ」として)を使用し、 オンライン計算機 でいくつかの「互いに素」の値(「整数」として)を試して、MMIを取得しました。
私はCTEでROW_NUMBER()
を使用し、1日の各秒に値を割り当てる手段として_CREATED_DATE
_でパーティション化(つまりグループ化)しています。
ただし、秒0、1、2、...などで生成された値はランダムに表示されますが、異なる日をまたいで、その特定の秒は同じ値にマップされます。したがって、2番目のCTE( "WhichSecond"という名前)は、日付をINT(1900-01-01からの順次オフセットに変換する)に変換してから、101を掛けることにより、各日付の開始点をシフトします。
_DECLARE @Data TABLE
(
ID INT NOT NULL IDENTITY(1, 1),
CREATED_DATE DATE NOT NULL
);
INSERT INTO @Data (CREATED_DATE) VALUES ('2014-10-05');
INSERT INTO @Data (CREATED_DATE) VALUES ('2014-10-05');
INSERT INTO @Data (CREATED_DATE) VALUES ('2014-10-05');
INSERT INTO @Data (CREATED_DATE) VALUES ('2014-10-05');
INSERT INTO @Data (CREATED_DATE) VALUES ('2014-10-05');
INSERT INTO @Data (CREATED_DATE) VALUES ('2015-03-15');
INSERT INTO @Data (CREATED_DATE) VALUES ('2016-10-22');
INSERT INTO @Data (CREATED_DATE) VALUES ('2015-03-15');
;WITH cte AS
(
SELECT tmp.ID,
CONVERT(DATETIME, tmp.CREATED_DATE) AS [CREATED_DATE],
ROW_NUMBER() OVER (PARTITION BY tmp.CREATED_DATE ORDER BY (SELECT NULL))
AS [RowNum]
FROM @Data tmp
), WhichSecond AS
(
SELECT cte.ID,
cte.CREATED_DATE,
((CONVERT(INT, cte.[CREATED_DATE]) - 29219) * 101) + cte.[RowNum]
AS [ThisSecond]
FROM cte
)
SELECT parts.*,
(parts.ThisSecond % 86400) AS [NormalizedSecond], -- wrap around to 0 when
-- value goes above 86,400
((parts.ThisSecond % 86400) * 39539) % 86400 AS [ActualSecond],
DATEADD(
SECOND,
(((parts.ThisSecond % 86400) * 39539) % 86400),
parts.CREATED_DATE
) AS [DateWithUniqueTime]
FROM WhichSecond parts
ORDER BY parts.ID;
_
戻り値:
_ID CREATED_DATE ThisSecond NormalizedSecond ActualSecond DateWithUniqueTime
1 2014-10-05 1282297 72697 11483 2014-10-05 03:11:23.000
2 2014-10-05 1282298 72698 51022 2014-10-05 14:10:22.000
3 2014-10-05 1282299 72699 4161 2014-10-05 01:09:21.000
4 2014-10-05 1282300 72700 43700 2014-10-05 12:08:20.000
5 2014-10-05 1282301 72701 83239 2014-10-05 23:07:19.000
6 2015-03-15 1298558 2558 52762 2015-03-15 14:39:22.000
7 2016-10-22 1357845 61845 83055 2016-10-22 23:04:15.000
8 2015-03-15 1298559 2559 5901 2015-03-15 01:38:21.000
_
午前8時から午後8時までの時間のみを生成する場合は、いくつかの小さな調整を行うだけで済みます。
28800
_をDATEADD
の2番目のパラメーターに8時間のオフセットとして追加しますその結果、1行だけに変更されます(他の行は診断用であるため)。
_-- second parameter of the DATEADD() call
28800 + (((parts.ThisSecond % 43200) * 39539) % 43200)
_
予測しにくい方法で毎日シフトする別の方法は、「WhichSecond」CTEで_CREATED_DATE
_のINT形式を渡すことによってRand()
を利用することです。 Rand(x)
は渡されたy
の同じ値に対して同じ値x
を返しますが、渡されたy
の異なる値に対して異なる値x
を返すため、これにより各日付ごとに安定したオフセットが得られます。意味:
Rand(1)= y1
Rand(2)= y2
Rand(3)= y3
Rand(2)= y2
2回目にRand(2)
が呼び出されたときでも、最初に呼び出されたときに返したのと同じ_y2
_の値が返されました。
したがって、「WhichSecond」CTEは次のようになります。
_(
SELECT cte.ID,
cte.CREATED_DATE,
(Rand(CONVERT(INT, cte.[CREATED_DATE])) * {some number}) + cte.[RowNum]
AS [ThisSecond]
FROM cte
)
_
Rand()
だけを使用しているときに発生した問題[〜#〜] op [〜#〜]は、評価ごとに1回発生します。クエリ。
ドキュメント :から
seedが指定されていない場合、SQL Serverデータベースエンジンはseed値をランダムに割り当てます。指定されたseed値の場合、返される結果は常に同じです。
以下で説明するアプローチでは、最適化が削除され、この動作が抑制されるため、Rand()
が評価されます行ごとに1回:
_dateadd( second
, Rand(cast(newid() as varbinary)) * 43200
, cast('08:00:00' as time) )
_
newid()
タイプの一意の値を生成します uniqueidentifier
;cast
で変換され、疑似ランダムを生成するための Rand([seed])
関数でseedとして使用されますfloat
0から1までの値、およびseedは常に一意であるため、戻り値も一意です。または、次を使用することもできます。
_SELECT DATEADD(s, ABS(CHECKSUM(NewId()) % 43201), CAST('08:00:00' AS Time))
_
ABS(CHECKSUM(NewId()) % 43201)
は、_0
_と_43200
_の間の乱数を生成します。 ここでの議論 を参照してください。
MS SQL Server 2008スキーマセットアップ:
クエリ1:
_SELECT DATEADD(s, ABS(CHECKSUM(NewId()) % 43201), 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 |
|------------------|
| 16:51:58.0000000 |
| 10:42:44.0000000 |
| 14:01:38.0000000 |
| 13:33:51.0000000 |
| 18:00:51.0000000 |
| 11:29:03.0000000 |
| 10:21:14.0000000 |
| 16:38:27.0000000 |
| 09:55:37.0000000 |
| 13:21:13.0000000 |
| 11:29:37.0000000 |
| 10:57:49.0000000 |
| 14:56:42.0000000 |
| 15:33:11.0000000 |
| 18:49:45.0000000 |
| 16:23:28.0000000 |
| 09:00:05.0000000 |
| 09:20:01.0000000 |
| 11:26:23.0000000 |
| 15:26:23.0000000 |
| 10:38:44.0000000 |
| 11:46:30.0000000 |
| 16:00:59.0000000 |
| 09:29:18.0000000 |
| 09:09:19.0000000 |
_
いくつかの方法があります。
NEWID
関数を使用してRand
のシードを提供するさまざまな組み合わせ。 NEWID値の配布についての保証はないため、注意して使用する必要があります。多かれ少なかれ均一に分散させるための最良の方法の1つは、CHECKSUM
:Rand(CHECKSUM(NEWID()))
を使用することです。この方法の良いところは、SQL Server2000以降で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 Server2005以降で使用できます。NEWID
メソッドとの主な違いは、ランダムシーケンスを完全に制御できることです。 NEWID
が返すものを制御することはできず、同じ番号からランダムシーケンスを再開することもできません。たとえば、_PARTITION BY
_を使用して同じ行番号のセットを指定すると、同じ乱数のセットが得られます。同じ乱数シーケンスを数回使用する必要がある場合に役立つことがあります。 2つの異なるシードに対して同じ乱数を取得することが可能です。 1から1,000,000までの行番号でテストしました。それらの_MD5
_はすべて異なります。 _MD5
_のCHECKSUM
は、122回の衝突を引き起こします。このRand
のCHECKSUM
は、246回の衝突を引き起こします。 1から100,000までの行番号でテストした場合、CHECKSUM
には1回の衝突があり、Rand
には3回の衝突がありました。Random
class や RNGCryptoServiceProvider
class などの.NETに組み込まれている関数を使用できます。 。CRYPT_GEN_RANDOM
_ があります。最後の方法について詳しく説明します。これは、SQL Server2008以降にとって非常に優れたソリューションだと思うからです。 _CRYPT_GEN_RANDOM
_は、1回だけ呼び出される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)
_
どこでどのように使用されるかによって異なります。
これをテストします:
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
これは、時間の生成方法をもう少し制御できる別のオプションです。ランダムな時間の間隔を指定できます。また、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)
補足:
上記のサブクエリでWHERE
句を削除した場合(WHERE d.Id > 0
)、すべての行に同じ時間値が返されます。つまり、最初に使用したのと同じ問題です。
すべて、
私は自分の質問に対する答えを共有したいと思いました。詳細をどこで見つけたか正確に思い出せません-それはsgeddesによって提供されたリンクの1つを経由したものだと思います。
次のクエリを使用して、午前8時から午後7時55分までのランダムな時間を取得しました(おおよそ)
SELECT convert(varchar,CONVERT(varchar, DATEADD(ms, dbo.MyRand(335 ,830) * 86400, 0), 114),114)
MyRand関数は以下のとおりです。
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
CREATE FUNCTION dbo.myRand(@Min INT, @Max INT) RETURNS decimal(18,15) AS
BEGIN
DECLARE @BinaryFloat BINARY(8)
SELECT @BinaryFloat = CAST(Id AS BINARY) FROM vwGuid
DECLARE
@PartValue TINYINT,
@Mask TINYINT,
@Mantissa FLOAT,
@Exponent SMALLINT,
@Bit TINYINT,
@Ln2 FLOAT,
@BigValue BIGINT,
@RandomNumber FLOAT
SELECT
@Mantissa = 1,
@Bit = 1,
@Ln2 = LOG(2),
@BigValue = CAST(@BinaryFloat AS BIGINT),
@Exponent = (@BigValue & 0x7ff0000000000000) / EXP(52 * @Ln2)
WHILE @Part <= 8
BEGIN
SELECT
@PartValue = CAST(SUBSTRING(@BinaryFloat, @Part, 1) AS TINYINT),
@Mask =
WHILE @Mask > 0
BEGIN
IF @PartValue & @Mask > 0
SET @Mantissa = @Mantissa + EXP(-@Bit * @Ln2)
SELECT
@Mask = @Mask / 2
END
END
SET @RandomNumber = CASE @Exponent WHEN 0 THEN 0 ELSE CAST(@Exponent AS FLOAT) / 2047 END
RETURN CAST((@RandomNumber * (@Max - @Min)) + @Min AS DECIMAL(18,15))
END
GO
END
これがお役に立てば幸いです。私は上記の返信の多くを読んでいないので、誰かがより良い答えを持っている場合はお詫びします-これは単に私がそれを解決した方法です。
ありがとう