Bagaimana cara menghasilkan nomor acak untuk setiap baris dalam TSQL Select?


328

Saya perlu nomor acak yang berbeda untuk setiap baris di meja saya. Kode yang tampaknya jelas berikut ini menggunakan nilai acak yang sama untuk setiap baris.

SELECT table_name, RAND() magic_number 
FROM information_schema.tables 

Saya ingin mendapatkan INT atau FLOAT dari ini. Kisah selanjutnya adalah saya akan menggunakan nomor acak ini untuk membuat offset tanggal acak dari tanggal yang diketahui, misalnya 1-14 hari dari tanggal mulai.

Ini untuk Microsoft SQL Server 2000.


4
Apakah ada solusi untuk ini yang tidak menggunakan NEWID ()? Saya ingin dapat menghasilkan urutan nomor acak yang sama untuk seed yang diberikan.
Rory MacLeod

@Rory Tanyakan itu sebagai pertanyaan baru, itu akan mendapat lebih banyak perhatian. (Jawaban saya adalah dengan menggunakan tabel angka acak yang tetap, misalnya. Misalnya set standar angka acak yang terkenal ini: rand.org/pubs/monograph_reports/MR1418/index.html )
MatthewMartin


RAND diperkenalkan pada 2005, pertanyaan ini diajukan pada 2009, organisasi mana yang masih menggunakan SQL 2000 karena itu adalah versi pertama yang cukup baik untuk digunakan selamanya.
MatthewMartin

Rory MacLeod bertanya, "Apakah ada solusi untuk ini yang tidak menggunakan NEWID ()? Saya ingin dapat menghasilkan urutan nomor acak yang sama untuk seed yang diberikan." Jawabannya adalah ya, tapi agak berbelit-belit. 1. Buat tampilan yang mengembalikan pilih rand () 2. Buat UDF yang memilih nilai dari tampilan. 3. Sebelum memilih data Anda, seed fungsi rand (). 4. Gunakan UDF dalam pernyataan pilihan Anda. Saya akan memposting contoh lengkap di bawah ini
Mitselplik

Jawaban:


516

Lihatlah SQL Server - Tetapkan nomor acak berbasis yang memiliki penjelasan yang sangat rinci.

Untuk meringkas, kode berikut menghasilkan angka acak antara 0 dan 13 termasuk dengan distribusi seragam:

ABS(CHECKSUM(NewId())) % 14

Untuk mengubah rentang Anda, cukup ubah angka di akhir ekspresi. Berhati-hatilah jika Anda membutuhkan rentang yang mencakup angka positif dan negatif. Jika Anda salah melakukannya, Anda dapat menghitung dua kali angka 0.

Peringatan kecil untuk kacang matematika di ruangan: ada sedikit bias dalam kode ini. CHECKSUM()menghasilkan angka yang seragam di seluruh jajaran datatype sql Int, atau setidaknya sedekat yang dapat ditunjukkan oleh pengujian (editor) saya. Namun, akan ada beberapa bias ketika CHECKSUM () menghasilkan angka di bagian paling atas dari rentang itu. Setiap kali Anda mendapatkan angka antara bilangan bulat maksimum yang mungkin dan kelipatan tepat terakhir dari ukuran rentang yang Anda inginkan (14 dalam kasus ini) sebelum bilangan bulat maksimum itu, hasil-hasil itu disukai daripada bagian sisa rentang Anda yang tidak dapat dihasilkan dari kelipatan terakhir dari 14.

Sebagai contoh, bayangkan seluruh rentang tipe Int hanya 19. 19 adalah bilangan bulat terbesar yang bisa Anda pegang. Ketika CHECKSUM () menghasilkan 14-19, ini sesuai dengan hasil 0-5. Angka-angka itu akan sangat disukai di atas 6-13, karena CHECKSUM () dua kali lebih mungkin menghasilkannya. Lebih mudah untuk menunjukkan ini secara visual. Di bawah ini adalah seluruh rangkaian hasil yang mungkin untuk rentang integer imajiner kami:

Integer Checksum: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Hasil Rentang: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 0 1 2 3 4 5

Anda dapat melihat di sini bahwa ada lebih banyak peluang untuk menghasilkan beberapa angka daripada yang lain: bias. Untungnya, kisaran sebenarnya dari tipe Int jauh lebih besar ... begitu banyak sehingga dalam kebanyakan kasus biasnya hampir tidak terdeteksi. Namun, ini adalah sesuatu yang harus diperhatikan jika Anda menemukan diri Anda melakukan ini untuk kode keamanan serius.


28
Halaman tertaut ini memiliki solusinya: ABS (CHECKSUM (NewId ()))% 14
MatthewMartin

7
% 14 akan mengembalikan angka antara 0 dan 13
CoderDennis

7
@ Dennis Palmer, tambahkan saja 1
KM.

59
Kami baru saja menemukan bug jenius dengan ini. Karena checksum mengembalikan int, dan kisaran int adalah -2 ^ 31 (-2.147.483.648) menjadi 2 ^ 31-1 (2.147.483.647), fungsi abs () dapat mengembalikan kesalahan overflow jika hasilnya persis -2,147.483.648 ! Peluangnya jelas sangat rendah, sekitar 1 banding 4 miliar, namun kami menggunakan tabel baris ~ 1,8b setiap hari, jadi itu terjadi sekali seminggu! Perbaiki adalah dengan melemparkan checksum ke bigint sebelum abs.
EvilPuppetMaster

17
Saya pikir ini harus mengatakan "distribusi yang seragam" bukan "distribusi yang dinormalisasi" - setiap angka sama-sama mungkin, itu bukan kurva lonceng. "Normalisasi" memiliki arti matematika tertentu.
AnotherParker

95

Ketika dipanggil beberapa kali dalam satu batch, rand () mengembalikan nomor yang sama.

Saya sarankan menggunakan convert ( varbinary, newid()) sebagai argumen seed:

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

newid() dijamin untuk mengembalikan nilai yang berbeda setiap kali dipanggil, bahkan dalam batch yang sama, jadi menggunakannya sebagai seed akan meminta rand () untuk memberikan nilai yang berbeda setiap kali.

Diedit untuk mendapatkan bilangan bulat acak dari 1 hingga 14.


Bagaimana Anda mendapatkan nomor dari panduan atau varbinary? Saya akan memperbarui pertanyaan untuk menunjukkan saya berharap untuk bilangan bulat.
MatthewMartin

1
Anda mengalikannya dengan angka dan lantai itu :) jadi jika Anda ingin lima digit, kalikan dengan 100.000, dan konversikan ke int. Jelek, tapi cukup sederhana untuk dilakukan.
Jeremy Smyth

1
Sebagai tambahan tambahan - yang akan memberi Anda hingga lima digit - jika Anda ingin nol-padnya, Anda harus menggunakan datatype char, dan menggunakan replikasi ke nol-pad hingga 5 digit.
Jeremy Smyth

Jika Anda menggunakan fungsi plafon alih-alih lantai, Anda tidak perlu menambahkan 1.
PopeDarren

Bahkan ketika saya menggunakan ini, ada saatnya RAND () selalu memberi saya hasil yang sama. Bahkan lebih aneh lagi, ada kalanya ia melompat dari perilaku yang benar ke perilaku yang salah tergantung pada berapa kali saya menggunakannya. Saya mencoba menerapkan RANDOM INNER GABUNG dan jika saya meminta lebih dari 19 baris (!!!), itu mulai memberi saya hasil yang sama ...
Johannes Wentu

72
RAND(CHECKSUM(NEWID()))

Di atas akan menghasilkan angka acak (pseudo-) antara 0 dan 1, eksklusif. Jika digunakan dalam pilih, karena nilai seed berubah untuk setiap baris, itu akan menghasilkan angka acak baru untuk setiap baris (namun tidak dijamin untuk menghasilkan nomor unik per baris).

Contoh ketika dikombinasikan dengan batas atas 10 (menghasilkan angka 1 - 10):

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

Dokumentasi 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

Pembuatan angka acak antara 1000 dan 9999 inklusif:

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

"+1" - untuk memasukkan nilai batas atas (9999 untuk contoh sebelumnya)


Batas atas eksklusif dengan metode ini, jadi jika Anda ingin memasukkan nomor teratas yang perlu Anda lakukanFLOOR(RAND(CHECKSUM(NEWID()))*(10000-1000)+1000)
vaindil

20

Menjawab pertanyaan lama, tetapi jawaban ini belum pernah diberikan sebelumnya, dan semoga ini akan bermanfaat bagi seseorang yang menemukan hasil ini melalui mesin pencari.

Dengan SQL Server 2008, fungsi baru telah diperkenalkan CRYPT_GEN_RANDOM(8),, yang menggunakan CryptoAPI untuk menghasilkan angka acak kriptografis yang kuat, dikembalikan sebagai VARBINARY(8000). Inilah halaman dokumentasi: https://docs.microsoft.com/en-us/sql/t-sql/functions/crypt-gen-random-transact-sql

Jadi untuk mendapatkan nomor acak, Anda cukup memanggil fungsi dan melemparkannya ke jenis yang diperlukan:

select CAST(CRYPT_GEN_RANDOM(8) AS bigint)

atau untuk mendapatkan floatantara -1 dan +1, Anda dapat melakukan sesuatu seperti ini:

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

13

Fungsi Rand () akan menghasilkan nomor acak yang sama, jika digunakan dalam tabel SELECT query. Hal yang sama berlaku jika Anda menggunakan seed ke fungsi Rand. Cara alternatif untuk melakukannya, adalah menggunakan ini:

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

Dapatkan informasi dari sini , yang menjelaskan masalahnya dengan sangat baik.


5

Apakah Anda memiliki nilai integer di setiap baris yang bisa Anda berikan sebagai seed ke fungsi RAND?

Untuk mendapatkan bilangan bulat antara 1 dan 14, saya yakin ini akan berhasil:

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

Ini berfungsi secara teori, tetapi dalam praktiknya saya menemukan bahwa RAND(<seed>)tampaknya tidak terlalu acak untuk perubahan kecil di <seed>. Sebagai contoh tes cepat yang saya lakukan: Saya membiarkan <seed>184380, 184383, 184386, dan nilai yang sesuai RAND(<seed>)adalah: 0,14912, 0,14917, 0,14923.
ImaginaryHuman072889

Mungkin untuk mendapatkan lebih banyak hasil acak "yang tampaknya", coba sesuatu seperti:RAND(<seed>)*100000) - FLOOR(RAND(<seed>)*100000)
ImaginaryHuman072889

5

Jika Anda perlu menyimpan benih Anda sehingga menghasilkan data acak "sama" setiap kali, Anda dapat melakukan hal berikut:

1. Buat tampilan yang mengembalikan pilih 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. Buat UDF yang memilih nilai dari tampilan.

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. Sebelum memilih data Anda, seed fungsi rand (), dan kemudian gunakan UDF dalam pernyataan pilih Anda.

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

coba gunakan nilai seed di RAND (seedInt). RAND () hanya akan mengeksekusi sekali per pernyataan itu sebabnya Anda melihat nomor yang sama setiap kali.


Paling sederhana! Meskipun nilainya tampak jauh lebih tersebar, menggunakan angka dari tengahnya, seperti RIGHT(CONVERT(BIGINT, RAND(RecNo) * 1000000000000), 2) (catatan: Saya melihat RIGHTsecara implisit mengonversikan BIGINTke CHAR, tetapi untuk lebih teliti, Anda akan memiliki yang lain CONVERTdi sana).
Doug_Ivison

4

Jika Anda tidak perlu menjadi bilangan bulat, tetapi pengidentifikasi unik acak apa pun, Anda dapat menggunakannya newid()

SELECT table_name, newid() magic_number 
FROM information_schema.tables

4

Tautan mati :(
Adakah

Dia menempatkan RAND()ke tampilan, menempatkan SELECTpandangan itu ke dalam fungsi, dan kemudian memanggil fungsi dari mana saja. Pintar.
Doug_Ivison

Saya memposting solusi yang memecahkan masalah dengan cara yang persis sama seperti pada artikel yang ditautkan, tetapi di sini di blog ini langsung sebagai jawaban lima posting yang lalu! Tidak ada yang menyebut saya wajah cemburu yang pintar hehe
Mitselplik

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

Di sini angka acak akan berada di antara 20 dan 30. roundakan memberikan dua tempat desimal maksimum.

Jika Anda ingin angka negatif, Anda dapat melakukannya dengan

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

Maka nilai min akan menjadi -60 dan maks akan menjadi -50.


3

Semudah:

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

Dan ini akan menempatkan angka acak antara 0-99 ke dalam tabel:

CREATE TABLE R
(
    Number int
)

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

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

SELECT * FROM R

2

Masalah yang kadang-kadang saya miliki dengan "Jawaban" yang dipilih adalah bahwa distribusi tidak selalu genap. Jika Anda memerlukan distribusi acak dari 1 - 14 acak di antara banyak baris, Anda dapat melakukan sesuatu seperti ini (database saya memiliki 511 tabel, jadi ini berfungsi. Jika Anda memiliki lebih sedikit baris daripada yang Anda lakukan rentang angka acak, ini tidak berfungsi baik):

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

Jenis ini melakukan kebalikan dari solusi acak normal dalam arti bahwa itu membuat angka diurutkan dan mengacak kolom lainnya.

Ingat, saya memiliki 511 tabel di basis data saya (yang bersangkutan hanya b / c yang kami pilih dari information_schema). Jika saya mengambil kueri sebelumnya dan memasukkannya ke tabel temp #X, lalu jalankan kueri ini pada data yang dihasilkan:

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

Saya mendapatkan hasil ini, menunjukkan kepada saya bahwa nomor acak saya SANGAT merata di antara banyak baris:

masukkan deskripsi gambar di sini


2
select ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT)) as [Randomizer]

selalu bekerja untuk saya



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;

maaf @arnt jika saya tidak menjelaskan dengan baik,
ichak khoury

maaf @arnt, kami memiliki dua fungsi CTDE_GENERATE_32_BIT_KEY yang menghasilkan kunci alfanumerik 32 bit (dapat diperpanjang menjadi lebih atau kurang) dan yang lain disebut CTDE_GENERATE_PUBLIC_KEY yang memanggil fungsi pertama dan mengembalikan kunci publik 32 bit atau Anda dapat kembali kunci pribadi 16 bit ... Anda hanya perlu memanggil select dbo.CTDE_GENERATE_PUBLIC_KEY () sebagai kunci publik; Logikanya di belakang adalah bahwa kita memilih satu karakter dari daftar karakter alfanumerik 32 kali dan menggabungkannya bersama untuk mendapatkan kunci alfanumerik acak. setelah penelitian.
ichak khoury

Bagus. Penjelasan itu membuatnya menjadi jawaban yang jauh lebih baik. (Seseorang menandainya untuk dihapus; saya memilih untuk membiarkannya terbuka dan meninggalkan komentar itu untuk Anda.)
arnt

0

Coba ini:

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

Di mana aangka bawah dan bangka atas


1
Bisakah Anda mencoba menjadi lebih jelas saat menjawab pertanyaan?
Yunus Temurlenk

0
Update my_table set my_field = CEILING((RAND(CAST(NEWID() AS varbinary)) * 10))

Angka antara 1 dan 10.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.