ฉันจะสร้างหมายเลขสุ่มสำหรับแต่ละแถวใน TSQL Select ได้อย่างไร


328

ฉันต้องการหมายเลขสุ่มที่แตกต่างกันสำหรับแต่ละแถวในตารางของฉัน รหัสที่ชัดเจนปรากฏขึ้นต่อไปนี้ใช้ค่าสุ่มที่เหมือนกันสำหรับแต่ละแถว

SELECT table_name, RAND() magic_number 
FROM information_schema.tables 

ฉันต้องการเอา INT หรือ FLOAT ออกจากนี้ ส่วนที่เหลือของเรื่องนี้คือฉันจะใช้หมายเลขสุ่มนี้เพื่อสร้างการชดเชยวันที่แบบสุ่มจากวันที่ที่รู้จักเช่น 1-14 วันชดเชยจากวันที่เริ่มต้น

นี่สำหรับ Microsoft SQL Server 2000


4
มีวิธีแก้ปัญหาสำหรับสิ่งนี้ที่ไม่ได้ใช้ NEWID () หรือไม่? ฉันต้องการที่จะสร้างตัวเลขสุ่มลำดับเดียวกันสำหรับเมล็ดที่กำหนด
Rory MacLeod

@ โรรี่ถามว่าเป็นคำถามใหม่มันจะได้รับความสนใจมากขึ้น (คำตอบของฉันจะใช้ตารางคงที่ของตัวเลขสุ่มเช่นตัวอย่างนี้ชุดมาตรฐานที่มีชื่อเสียงของตัวเลขสุ่ม:. rand.org/pubs/monograph_reports/MR1418/index.html )
MatthewMartin


RAND ถูกนำมาใช้ในปี 2005 คำถามนี้ถูกถามในปี 2009 ซึ่งองค์กรยังคงใช้ SQL 2000 เพราะนั่นเป็นรุ่นที่ 1 ดีพอที่จะใช้ตลอดไป
MatthewMartin

Rory MacLeod ถามว่า "มีวิธีแก้ปัญหานี้ที่ไม่ได้ใช้ NEWID () หรือไม่ฉันต้องการที่จะสร้างตัวเลขสุ่มตามลำดับสำหรับเมล็ดที่กำหนด" คำตอบคือใช่ แต่มันค่อนข้างซับซ้อน 1. สร้างมุมมองที่ส่งคืน select rand () 2. สร้าง UDF ที่เลือกค่าจากมุมมอง 3. ก่อนที่จะเลือกข้อมูลของคุณให้เพาะฟังก์ชัน () 4. ใช้ UDF ในข้อความสั่งที่คุณเลือก ฉันจะโพสต์ตัวอย่างแบบเต็มด้านล่าง
Mitselplik

คำตอบ:


516

ลองดูที่SQL Server - ตั้งค่าตัวเลขสุ่มซึ่งมีคำอธิบายอย่างละเอียด

เพื่อสรุปรหัสต่อไปนี้สร้างตัวเลขสุ่มระหว่าง 0 และ 13 รวมกับการกระจายสม่ำเสมอ:

ABS(CHECKSUM(NewId())) % 14

หากต้องการเปลี่ยนช่วงของคุณเพียงแค่เปลี่ยนจำนวนที่ส่วนท้ายของนิพจน์ ระมัดระวังเป็นพิเศษหากคุณต้องการช่วงที่มีทั้งจำนวนบวกและลบ หากคุณทำผิดอาจเป็นไปได้ที่จะนับจำนวน 0 เป็นสองเท่า

คำเตือนเล็กน้อยสำหรับคณิตศาสตร์ถั่วในห้อง: มีอคติเล็กน้อยในรหัสนี้ CHECKSUM()ผลลัพธ์เป็นตัวเลขที่มีความเหมือนกันในทุกช่วงของประเภทข้อมูล sql Int หรืออย่างน้อยก็ใกล้เคียงดังนั้นการทดสอบของฉัน (ตัวแก้ไข) สามารถแสดงได้ อย่างไรก็ตามจะมีอคติเมื่อ CHECKSUM () สร้างตัวเลขที่ปลายบนสุดของช่วงนั้น เมื่อใดก็ตามที่คุณได้รับตัวเลขระหว่างจำนวนเต็มสูงสุดที่เป็นไปได้และคูณกับขนาดของช่วงที่คุณต้องการ (14 ในกรณีนี้) ก่อนจำนวนเต็มที่สูงสุดผลลัพธ์เหล่านั้นจะได้รับการสนับสนุนเหนือส่วนที่เหลือของช่วงที่คุณไม่สามารถสร้างได้ ผลคูณของ 14

ยกตัวอย่างเช่นสมมติว่าช่วงทั้งหมดของชนิด Int มีค่าเพียง 19. 19 เป็นจำนวนเต็มที่เป็นไปได้ที่คุณสามารถเก็บได้ เมื่อ CHECKSUM () ส่งผลใน 14-19 สิ่งเหล่านี้จะสอดคล้องกับผลลัพธ์ 0-5 ตัวเลขเหล่านั้นจะได้รับความนิยมอย่างมากในช่วงอายุ 6-13 ปีเพราะ CHECKSUM () เป็นสองเท่าที่จะสร้าง มันง่ายกว่าที่จะแสดงให้เห็นด้วยสายตา ด้านล่างเป็นชุดผลลัพธ์ที่เป็นไปได้ทั้งหมดสำหรับช่วงจำนวนเต็มในจินตนาการของเรา:

Checksum จำนวนเต็ม: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
ช่วงผลลัพธ์: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 0 1 2 3 4 5

คุณจะเห็นได้ว่ามีโอกาสที่จะสร้างตัวเลขได้มากกว่าคนอื่น ๆ : อคติ โชคดีที่ช่วงที่แท้จริงของชนิด int เป็นมากขนาดใหญ่ ... มากดังนั้นในกรณีส่วนใหญ่อคติเกือบ undetectable อย่างไรก็ตามมันเป็นสิ่งที่ควรระวังหากคุณพบว่าตัวเองทำเช่นนี้เพื่อความปลอดภัย


28
หน้าเชื่อมโยงนี้มีวิธีแก้ไข: ABS (เช็ค (ใหม่รหัส)) 14%
MatthewMartin

7
% 14 จะส่งคืนตัวเลขระหว่าง 0 ถึง 13
CoderDennis

7
@Dennis Palmer เพียงเพิ่ม 1
KM

59
เราเพิ่งค้นพบข้อบกพร่องอัจฉริยะด้วยสิ่งนี้ เนื่องจาก checksum ส่งกลับค่า int และช่วงของ int คือ -2 ^ 31 (-2,147,483,648) ถึง 2 ^ 31-1 (2,147,483,647) ฟังก์ชัน abs () สามารถส่งคืนข้อผิดพลาดล้นหากผลลัพธ์ที่ได้คือ -2,147,483,648 ! เห็นได้ชัดว่ามีโอกาสต่ำมากประมาณ 1 ใน 4 พันล้านอย่างไรก็ตามเราเรียกใช้มันมากกว่าตารางแถว ~ 1.8b ทุกวันดังนั้นมันจึงเกิดขึ้นประมาณสัปดาห์ละครั้ง! แก้ไขคือการโยนการตรวจสอบเพื่อใหญ่ก่อนหน้าท้อง
EvilPuppetMaster

17
ฉันคิดว่าสิ่งนี้ควรพูดว่า "การกระจายตัวแบบสม่ำเสมอ" ไม่ใช่ "การกระจายแบบปกติ" - แต่ละหมายเลขมีแนวโน้มเท่ากันมันไม่ใช่เส้นโค้งแบบเบลล์ "Normalized" มีความหมายทางคณิตศาสตร์ที่เฉพาะเจาะจง
AnotherParker

95

เมื่อเรียกหลายครั้งในชุดเดียว rand () จะส่งคืนหมายเลขเดียวกัน

ฉันขอแนะนำให้ใช้ convert ( varbinary, newid()) เป็นอาร์กิวเมนต์ของเมล็ดพันธุ์:

SELECT table_name, 1.0 + floor(14 * RAND(convert(varbinary, newid()))) magic_number 
FROM information_schema.tables

newid() มีการรับประกันว่าจะส่งคืนค่าที่แตกต่างกันในแต่ละครั้งที่มีการเรียกใช้แม้จะอยู่ในชุดเดียวกันดังนั้นการใช้เป็นเมล็ดจะพรอมต์ rand () เพื่อให้ค่าแตกต่างกันในแต่ละครั้ง

แก้ไขเพื่อรับจำนวนเต็มแบบสุ่มจาก 1 ถึง 14


คุณจะได้ตัวเลขจาก guid หรือ varbinary ได้อย่างไร? ฉันจะอัปเดตคำถามเพื่อระบุว่าฉันหวังว่าจะเป็นจำนวนเต็ม
MatthewMartin

1
คุณคูณมันด้วยตัวเลขและกำหนดให้ชั้น :) ดังนั้นหากคุณต้องการห้าหลักคูณด้วย 100,000 และแปลงเป็น int น่าเกลียด แต่ก็ง่ายพอที่จะทำ
Jeremy Smyth

1
ภาคผนวกเพิ่มเติม - ที่จะให้คุณสูงสุดห้าหลัก - หากคุณต้องการ zero-pad คุณจะต้องใช้ประเภทข้อมูลถ่านและใช้การทำซ้ำเพื่อ zero-pad ถึง 5 หลัก
Jeremy Smyth

ถ้าคุณใช้ฟังก์ชั่นเพดานแทนพื้นคุณไม่ต้องเพิ่ม 1
PopeDarren

แม้เมื่อฉันใช้สิ่งนี้มีบางครั้งที่ RAND () จะให้ผลลัพธ์เหมือนกันเสมอ แม้แต่คนแปลกหน้ามีบางครั้งที่มันกระโดดจากพฤติกรรมที่ไม่ถูกต้องไปยังพฤติกรรมที่ไม่ถูกต้องขึ้นอยู่กับจำนวนครั้งที่ฉันใช้มัน ฉันพยายามที่จะใช้การเข้าร่วมภายในแบบสุ่มและถ้าฉันขอแถวมากกว่า 19 (!!!) มันจะเริ่มให้ผลเหมือนกันเสมอ ...
Johannes Wentu

72
RAND(CHECKSUM(NEWID()))

ด้านบนจะสร้างหมายเลขสุ่ม (หลอก) ระหว่าง 0 ถึง 1 พิเศษ หากใช้ในการเลือกเนื่องจากค่าเมล็ดจะเปลี่ยนไปสำหรับแต่ละแถวมันจะสร้างหมายเลขสุ่มใหม่สำหรับแต่ละแถว (ไม่รับประกันว่าจะสร้างหมายเลขเฉพาะต่อแถวอย่างไรก็ตาม)

ตัวอย่างเมื่อรวมกับขีด จำกัด สูงสุด 10 (สร้างหมายเลข 1 - 10):

CAST(RAND(CHECKSUM(NEWID())) * 10 as INT) + 1

เอกสาร Transact-SQL:

  1. CAST(): https://docs.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql
  2. RAND(): http://msdn.microsoft.com/en-us/library/ms177610.aspx
  3. CHECKSUM(): http://msdn.microsoft.com/en-us/library/ms189788.aspx
  4. NEWID(): https://docs.microsoft.com/en-us/sql/t-sql/functions/newid-transact-sql

39

การสร้างตัวเลขสุ่มระหว่าง 1,000 ถึง 9999 รวม:

FLOOR(RAND(CHECKSUM(NEWID()))*(9999-1000+1)+1000)

"+1" - เพื่อรวมค่าขอบเขตบน (9999 สำหรับตัวอย่างก่อนหน้า)


ผูกพันบนเป็นพิเศษด้วยวิธีนี้ดังนั้นหากคุณต้องการที่จะรวมถึงตัวเลขด้านบนที่คุณจะต้องทำFLOOR(RAND(CHECKSUM(NEWID()))*(10000-1000)+1000)
vaindil

20

ตอบคำถามเก่า แต่คำตอบนี้ไม่ได้ให้ไว้ก่อนหน้านี้และหวังว่าจะเป็นประโยชน์สำหรับผู้ที่ค้นหาผลลัพธ์ผ่านเครื่องมือค้นหา

กับ SQL Server 2008, ฟังก์ชั่นใหม่ที่ได้รับการแนะนำให้รู้จักCRYPT_GEN_RANDOM(8)ซึ่งใช้ CryptoAPI การผลิตที่มีตัวเลขที่แข็งแกร่ง, VARBINARY(8000)กลับมาเป็น นี่คือหน้าเอกสาร: https://docs.microsoft.com/en-us/sql/t-sql/functions/crypt-gen-random-transact-sql

ดังนั้นเพื่อให้ได้ตัวเลขสุ่มคุณสามารถเรียกใช้ฟังก์ชันและส่งไปยังประเภทที่จำเป็น:

select CAST(CRYPT_GEN_RANDOM(8) AS bigint)

หรือรับfloatระหว่าง -1 ถึง +1 คุณสามารถทำสิ่งนี้:

select CAST(CRYPT_GEN_RANDOM(8) AS bigint) % 1000000000 / 1000000000.0

13

ฟังก์ชัน Rand () จะสร้างหมายเลขสุ่มที่เหมือนกันหากใช้ในแบบสอบถามแบบใช้เลือกตาราง เช่นเดียวกันถ้าคุณใช้เมล็ดพันธุ์ในฟังก์ชั่น Rand อีกทางเลือกหนึ่งในการใช้คือ:

SELECT ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT)) AS [RandomNumber]

รับข้อมูลจากที่นี่ซึ่งอธิบายปัญหาได้เป็นอย่างดี


5

คุณมีค่าจำนวนเต็มในแต่ละแถวที่คุณสามารถส่งเป็น seed ไปยังฟังก์ชัน RAND ได้หรือไม่?

รับจำนวนเต็มระหว่าง 1 ถึง 14 ฉันเชื่อว่าจะใช้งานได้:

FLOOR( RAND(<yourseed>) * 14) + 1

ผลงานในทฤษฎีนี้ แต่ในทางปฏิบัติฉันได้พบไม่ปรากฏจะสุ่มมากสำหรับการเปลี่ยนแปลงเล็กน้อยในRAND(<seed>) <seed>ตัวอย่างเช่นการทดสอบอย่างรวดเร็วฉัน: <seed>184380, 184383, 184386 และRAND(<seed>)ค่าที่เกี่ยวข้องคือ: 0.14912, 0.14917, 0.14923
ImaginaryHuman072889

อาจจะได้รับผลการสุ่ม "ดูเหมือน" มากขึ้นลองดังนี้:RAND(<seed>)*100000) - FLOOR(RAND(<seed>)*100000)
ImaginaryHuman072889

5

หากคุณต้องการเก็บรักษาเมล็ดของคุณเพื่อให้สร้างข้อมูลสุ่ม "เหมือนกัน" ทุกครั้งคุณสามารถทำสิ่งต่อไปนี้:

1. สร้างมุมมองที่ส่งกลับเลือก rand ()

if object_id('cr_sample_randView') is not null
begin
    drop view cr_sample_randView
end
go

create view cr_sample_randView
as
select rand() as random_number
go

2. สร้าง UDF ที่เลือกค่าจากมุมมอง

if object_id('cr_sample_fnPerRowRand') is not null
begin
    drop function cr_sample_fnPerRowRand
end
go

create function cr_sample_fnPerRowRand()
returns float
as
begin
    declare @returnValue float
    select @returnValue = random_number from cr_sample_randView
    return @returnValue
end
go

3. ก่อนที่จะเลือกข้อมูลของคุณให้เมล็ดฟังก์ชัน rand () จากนั้นใช้ UDF ในคำสั่ง select ของคุณ

select rand(200);   -- see the rand() function
with cte(id) as
(select row_number() over(order by object_id) from sys.all_objects)
select 
    id,
    dbo.cr_sample_fnPerRowRand()
from cte
where id <= 1000    -- limit the results to 1000 random numbers

4

ลองใช้ค่าเมล็ดใน RAND (seedInt) RAND () จะดำเนินการเพียงครั้งเดียวต่อคำสั่งนั่นคือเหตุผลที่คุณเห็นหมายเลขเดิมทุกครั้ง


ที่ง่าย! แม้ว่าค่าดูเหมือนจะกระจัดกระจายมากขึ้นโดยใช้ตัวเลขจากตรงกลางของเช่นRIGHT(CONVERT(BIGINT, RAND(RecNo) * 1000000000000), 2) (หมายเหตุ: ฉันเห็นRIGHTแปลงโดยอ้อมBIGINTไปCHARแต่จะเข้มงวดคุณจะมีอีกCONVERTในนั้น)
Doug_Ivison

4

หากคุณไม่ต้องการให้เป็นจำนวนเต็ม แต่มีตัวระบุเฉพาะใด ๆ แบบสุ่มคุณสามารถใช้ newid()

SELECT table_name, newid() magic_number 
FROM information_schema.tables

4

คุณจะต้องโทรหา RAND () สำหรับแต่ละแถว นี่เป็นตัวอย่างที่ดี

https://web.archive.org/web/20090216200320/http://dotnet.org.za/calmyourself/archive/2007/04/13/sql-rand-trap-same-value-per-row.aspx


ลิงก์ตาย :( สำเนาใด ๆ ที่สามารถรวมอยู่ในคำตอบได้หรือไม่
jocull

เขาใส่RAND()มุมมองวางSELECTมุมมองนั้นลงในฟังก์ชันแล้วเรียกใช้ฟังก์ชันจากที่ใดก็ได้ ฉลาด.
Doug_Ivison

ฉันโพสต์โซลูชันที่แก้ไขปัญหาในลักษณะเดียวกับในบทความที่เชื่อมโยง แต่ที่นี่ในบล็อกนี้โดยตรงเป็นคำตอบห้าโพสต์ที่ผ่านมา! ไม่มีใครเรียกฉันว่าใบหน้าที่อิจฉาอย่างฉลาดhehe
Mitselplik

4
select round(rand(checksum(newid()))*(10)+20,2)

ที่นี่ตัวเลขสุ่มจะมาระหว่าง 20 และ 30 roundจะให้ทศนิยมสูงสุดสองตำแหน่ง

หากคุณต้องการตัวเลขติดลบคุณสามารถทำได้

select round(rand(checksum(newid()))*(10)-60,2)

จากนั้นค่าต่ำสุดจะเป็น -60 และสูงสุดจะเป็น -50


3

ง่ายเหมือน:

DECLARE @rv FLOAT;
SELECT @rv = rand();

และจะใส่ตัวเลขสุ่มระหว่าง 0-99 ลงในตาราง:

CREATE TABLE R
(
    Number int
)

DECLARE @rv FLOAT;
SELECT @rv = rand();

INSERT INTO dbo.R
(Number)
    values((@rv * 100));

SELECT * FROM R

2

บางครั้งปัญหาที่ฉันมีกับ "คำตอบ" ที่เลือกคือการแจกแจงไม่ได้เสมอกัน หากคุณต้องการการกระจายแบบสุ่ม 1 - 14 ในจำนวนแถวคุณสามารถทำสิ่งนี้ได้ (ฐานข้อมูลของฉันมี 511 ตารางดังนั้นมันใช้งานได้ถ้าคุณมีแถวน้อยกว่าที่คุณกระจายจำนวนแบบสุ่มมันไม่ทำงาน ดี):

SELECT table_name, ntile(14) over(order by newId()) randomNumber 
FROM information_schema.tables

ตรงข้ามกับวิธีการสุ่มแบบปกติในแง่ที่ว่ามันเก็บตัวเลขไว้ตามลำดับและสุ่มคอลัมน์อื่น

จำไว้ว่าฉันมี 511 ตารางในฐานข้อมูลของฉัน (ซึ่งเกี่ยวข้องเฉพาะ b / c ที่เราเลือกจาก information_schema) ถ้าฉันใช้แบบสอบถามก่อนหน้านี้และวางลงในตาราง temp # X แล้วเรียกใช้แบบสอบถามนี้กับข้อมูลผลลัพธ์:

select randomNumber, count(*) ct from #X
group by randomNumber

ฉันได้รับผลลัพธ์นี้แสดงให้ฉันเห็นว่าตัวเลขสุ่มของฉันกระจายอย่างสม่ำเสมอในหลาย ๆ แถว:

ป้อนคำอธิบายรูปภาพที่นี่




1
    DROP VIEW IF EXISTS vwGetNewNumber;
    GO
    Create View vwGetNewNumber
    as
    Select CAST(RAND(CHECKSUM(NEWID())) * 62 as INT) + 1 as NextID,
    'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'as alpha_num;

    ---------------CTDE_GENERATE_PUBLIC_KEY -----------------
    DROP FUNCTION IF EXISTS CTDE_GENERATE_PUBLIC_KEY;  
    GO
    create function CTDE_GENERATE_PUBLIC_KEY()
    RETURNS NVARCHAR(32)
    AS 
    BEGIN
        DECLARE @private_key NVARCHAR(32);
        set @private_key = dbo.CTDE_GENERATE_32_BIT_KEY();
        return @private_key;
    END;
    go

---------------CTDE_GENERATE_32_BIT_KEY -----------------
DROP FUNCTION IF EXISTS CTDE_GENERATE_32_BIT_KEY;  
GO
CREATE function CTDE_GENERATE_32_BIT_KEY()
RETURNS NVARCHAR(32)
AS 
BEGIN
    DECLARE @public_key NVARCHAR(32);
    DECLARE @alpha_num NVARCHAR(62);
    DECLARE @start_index INT = 0;
    DECLARE @i INT = 0;
    select top 1 @alpha_num = alpha_num from vwGetNewNumber;
        WHILE @i < 32
        BEGIN
          select top 1 @start_index = NextID from vwGetNewNumber;
          set @public_key = concat (substring(@alpha_num,@start_index,1),@public_key);
          set @i = @i + 1;
        END;
    return @public_key;
END;
    select dbo.CTDE_GENERATE_PUBLIC_KEY() public_key;

ขอโทษ @arnt ถ้าฉันไม่ได้อธิบายอย่างดี
ichak Khoury

ขออภัย @arnt เรามีสองฟังก์ชันที่นี่CTDE_GENERATE_32_BIT_KEYที่สร้างคีย์ตัวอักษรผสมตัวเลขขนาด 32 บิต (สามารถขยายได้มากกว่าหรือน้อยกว่า) และอีกอันเรียกว่าCTDE_GENERATE_PUBLIC_KEYที่เรียกฟังก์ชันแรกและคืนค่าพับลิกของ 32 บิตหรือคุณสามารถคืนค่าคีย์สาธารณะเป็น 32 บิต ไพรเวตคีย์ 16 บิต ... คุณเพียงแค่เรียกselect select dbo.CTDE_GENERATE_PUBLIC_KEY () เป็นคีย์สาธารณะ ตรรกะที่อยู่เบื้องหลังคือเราเลือกอักขระหนึ่งตัวจากรายการตัวอักษรและตัวเลข 32 ครั้งและต่อกันเข้าด้วยกันเพื่อรับคีย์ตัวอักษรและตัวเลขแบบสุ่ม หลังการวิจัย
ichak khoury

ดี คำอธิบายนั้นทำให้มันเป็นคำตอบที่ดีกว่ามาก (มีคนทำเครื่องหมายเพื่อลบ; ฉันโหวตให้เปิดทิ้งไว้และแสดงความคิดเห็นนั้นกับคุณ)
34414

0

ลองสิ่งนี้:

SELECT RAND(convert(varbinary, newid()))*(b-a)+a magic_number 

ที่ไหนaเป็นจำนวนที่ลดลงและbเป็นจำนวนบน


1
คุณพยายามที่จะชัดเจนมากขึ้นในขณะตอบคำถามหรือไม่?
Yunus Temurlenk

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.