Bir TSQL Seçimi'nde her satır için rasgele sayı nasıl oluştururum?


328

Masamdaki her satır için farklı bir rastgele sayıya ihtiyacım var. Aşağıdaki açıkça görünen kod, her satır için aynı rastgele değeri kullanır.

SELECT table_name, RAND() magic_number 
FROM information_schema.tables 

Bundan bir INT veya FLOAT almak istiyorum. Hikayenin geri kalanı, bilinen bir tarihten rastgele bir tarih ofseti oluşturmak için bu rastgele sayıyı kullanacağım, örneğin bir başlangıç ​​tarihinden 1-14 gün ofset.

Bu Microsoft SQL Server 2000 içindir.


4
NEWID () kullanmayan bir çözüm var mı? Belirli bir tohum için aynı rasgele sayılar dizisini oluşturabilmek istiyorum.
Rory MacLeod

@Rory Yeni bir soru olarak daha fazla dikkat çekeceğini sorun. (Cevabım sabit rastgele sayı tabloları kullanmak olacaktır, örneğin. Örneğin bu ünlü standart rastgele sayı kümesi: rand.org/pubs/monograph_reports/MR1418/index.html )
MatthewMartin


RAND 2005 yılında tanıtıldı, bu soru 2009 yılında, hangi kuruluşların hala SQL 2000 kullandığını sordu, çünkü bu sonsuza kadar kullanmak için yeterince iyi olan ilk versiyondu.
MatthewMartin

Rory MacLeod, "NEWID () kullanmayan bir çözüm var mı? Belirli bir tohum için aynı rasgele sayılar dizisini oluşturmak istiyorum." Cevap evet, ama biraz kıvrık. 1. select rand () döndüren bir görünüm oluşturun 2. Görünümden değeri seçen bir UDF oluşturun. 3. Verilerinizi seçmeden önce rand () işlevini tohumlayın. 4. Seçim ifadenizde UDF'yi kullanın. Aşağıda tam bir örnek
vereceğim

Yanıtlar:


516

SQL Server'a bir göz atın - Çok ayrıntılı bir açıklaması olan tabanlı rasgele sayılar ayarlayın .

Özetlemek gerekirse, aşağıdaki kod, eşit dağılımlı 0 ile 13 arasında rastgele bir sayı üretir:

ABS(CHECKSUM(NewId())) % 14

Aralıkınızı değiştirmek için, ifadenin sonundaki sayıyı değiştirmeniz yeterlidir. Hem pozitif hem de negatif sayılar içeren bir aralığa ihtiyacınız varsa çok dikkatli olun. Yanlış yaparsanız, 0 sayısını iki kez saymak mümkündür.

Odadaki matematik kuruyemişleri için küçük bir uyarı: bu kodda çok hafif bir önyargı var. CHECKSUM()Sonuç olarak, tüm sql Int veri tipinde eşit olan veya en azından benim (editör) testimin gösterebileceği en yakın sayılar elde edersiniz. Ancak, CHECKSUM () bu aralığın en üst ucunda bir sayı ürettiğinde bazı yanlılıklar olacaktır. Bu maksimum tamsayıdan önce, mümkün olan maksimum tamsayı ile istediğiniz aralığın boyutunun son tam katı (bu durumda 14) arasında bir sayı elde ettiğinizde, bu sonuçlar aralığınızın üretilemeyen geri kalan kısmı üzerinde tercih edilir 14'ün katları.

Örnek olarak, Int türünün tüm aralığının sadece 19 olduğunu düşünün. 19, tutabileceğiniz mümkün olan en büyük tam sayıdır. CHECKSUM () 14-19 ile sonuçlandığında, bunlar 0-5 sonuçlarına karşılık gelir. Bu sayılar 6-13 üzerinde yoğun bir şekilde tercih edilir, çünkü CHECKSUM () 'un onları üretme olasılığı iki kat daha yüksektir. Bunu görsel olarak göstermek daha kolaydır. Hayali tam sayı aralığımız için olası sonuçların tamamı aşağıdadır:

Sağlama Toplamı Tamsayı: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Aralık Sonuç: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 0 1 2 3 4 5

Burada bazı sayıları üretme şansının diğerlerinden daha fazla olduğunu görebilirsiniz: önyargı. Neyse ki, Int türünün gerçek aralığı çok daha büyük ... o kadar ki çoğu durumda önyargı neredeyse tespit edilemez. Ancak, ciddi bir güvenlik kodu için bunu yaparken kendinizi fark ederseniz farkında olmak bir şeydir.


28
Bu bağlantılı sayfanın çözümü vardı: ABS (CHECKSUM (NewId ()))% 14
MatthewMartin

7
% 14, 0 ile 13 arasında sayılar döndürür
CoderDennis

7
@Dennis Palmer, sadece 1
KM

59
Bununla sadece dahiyane bir hata keşfettik. Sağlama toplamı bir int döndürdüğü ve int aralığı -2 ^ 31 (-2,147,483,648) ile 2 ^ 31-1 (2,147,483,647) olduğu için, sonuç tam olarak -2,147,483,648 olduğunda abs () işlevi bir taşma hatası döndürebilir ! Şanslar çok düşük, yaklaşık 4 milyarda 1, ancak biz her gün ~ 1.8b sıralı masa üzerinde çalıştırıyorduk, bu yüzden haftada bir kez oluyordu! Fix, sağlama toplamını abs'den önce bigint'e dökmektir.
EvilPuppetMaster

17
Bunun "normalleştirilmiş dağılım" değil "tekdüze bir dağılım" demesi gerektiğini düşünüyorum - her sayı eşit derecede olasıdır, bu bir çan eğrisi değildir. "Normalize" nin belirli bir matematiksel anlamı vardır.
AnotherParker

95

Tek bir toplu işte birden çok kez çağrıldığında, rand () aynı sayıyı döndürür.

Tohum argümanı olarak convert ( varbinary, newid()) kullanmanızı öneririm :

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

newid() aynı toplu işte bile çağrıldığında her seferinde farklı bir değer döndürmesi garanti edilir, bu nedenle onu tohum olarak kullanmak rand () 'yi her seferinde farklı bir değer vermeye yönlendirir.

1'den 14'e kadar rastgele bir tam sayı almak için düzenlendi.


Rehberden veya varbinerden nasıl bir sayı elde edersiniz? Bir tamsayı umduğumu belirtmek için soruyu güncelleyeceğim.
MatthewMartin

1
Bir sayıyla çarpıp katlayın :) böylece beş basamak istiyorsanız, 100000 ile çarpın ve bir int'e dönüştürün. Çirkin ama yapacak kadar basit.
Jeremy Smyth

1
Ek bir ek olarak - bu size beş basamağa kadar verecektir - sıfırla doldurmak istiyorsanız, bir char veri türü kullanmanız ve 5 basamağa kadar sıfır yastığına çoğaltmanız gerekir.
Jeremy Smyth

Zemin yerine tavan fonksiyonunu kullanırsanız 1 eklemeniz gerekmez.
PopeDarren

Bunu kullandığımda bile, RAND () 'nin bana her zaman aynı sonucu verdiği zamanlar vardır. Hatta yabancı, onu kaç kez kullandığım sayısına bağlı olarak doğrudan yanlış bir davranışa atladığı zamanlar var. RANDOM INNER JOIN'i uygulamaya çalışıyorum ve 19'dan fazla (!!!) satır
istersem

72
RAND(CHECKSUM(NEWID()))

Yukarıdaki, 0 ile 1 arasında (hariç) rasgele bir sayı üretecektir. Bir seçimde kullanılırsa, tohum değeri her satır için değiştiğinden, her satır için yeni bir rastgele sayı oluşturur (ancak satır başına benzersiz bir sayı üretilmesi garanti edilmez).

Üst sınır 10 ile birleştirildiğinde örnek (1 - 10 arası sayı üretir):

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

Transact-SQL Belgeleri:

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

39

1000 ile 9999 arasında rastgele sayı oluşturma:

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

"+1" - üst sınır değerleri eklemek için (önceki örnek için 9999)


Üst sınır bu yöntemle münhasırdır, bu yüzden üst sayıyı dahil etmek isterseniz yapmanız gerekirFLOOR(RAND(CHECKSUM(NEWID()))*(10000-1000)+1000)
vaindil

20

Eski soruyu cevaplamak, ancak bu cevap daha önce verilmemiştir ve umarım bu, bir arama motoru aracılığıyla bu sonuçları bulan biri için yararlı olacaktır.

SQL Server 2008 ile, CRYPT_GEN_RANDOM(8)kriptografik olarak güçlü bir rasgele sayı üretmek için CryptoAPI kullanan yeni bir işlev tanıtıldı VARBINARY(8000). Doküman sayfası: https://docs.microsoft.com/en-us/sql/t-sql/functions/crypt-gen-random-transact-sql

Rastgele bir sayı elde etmek için işlevi çağırabilir ve gerekli türe atabilirsiniz:

select CAST(CRYPT_GEN_RANDOM(8) AS bigint)

veya float-1 ile +1 arasında bir sayı elde etmek için şöyle bir şey yapabilirsiniz:

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

13

Bir tablo SELECT sorgusunda kullanılırsa, Rand () işlevi aynı rastgele sayıyı oluşturur. Rand işlevinde bir tohum kullanırsanız aynı durum geçerlidir. Bunu yapmanın alternatif bir yolu bunu kullanmaktır:

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

Sorunu çok iyi açıklayan bilgiyi buradan aldım .


5

RAND işlevine tohum olarak iletebileceğiniz her satırda bir tamsayı var mı?

1 ile 14 arasında bir tam sayı elde etmek için bunun işe yarayacağına inanıyorum:

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

Bu teoride işe yarıyor, ancak pratikte bunun RAND(<seed>)küçük değişiklikler için çok rastgele görünmediğini gördüm <seed>. Örneğin hızlı bir test yaptım: <seed>184380, 184383, 184386 olsun ve karşılık gelen RAND(<seed>)değerler: 0.14912, 0.14917, 0.14923 idi.
ImaginaryHuman072889

Belki biraz daha "görünüşte" rastgele sonuçlar almak için, şöyle bir şey deneyin:RAND(<seed>)*100000) - FLOOR(RAND(<seed>)*100000)
ImaginaryHuman072889

5

Tohumunuzu her seferinde "aynı" rastgele verileri üretecek şekilde korumanız gerekiyorsa, aşağıdakileri yapabilirsiniz:

1. select rand () döndüren bir görünüm oluşturun

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. Görünümden değeri seçen bir UDF oluşturun.

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. Verilerinizi seçmeden önce rand () işlevini tohumlayın ve sonra select deyiminizde UDF'yi kullanın.

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) içinde bir tohum değeri kullanmayı deneyin. RAND () her ifade için yalnızca bir kez yürütülür, bu yüzden her seferinde aynı numarayı görürsünüz.


En basit! Değerler görünüyor olsa çok daha fazla bu işin orta, gelen rakamları kullanılarak, dağınık RIGHT(CONVERT(BIGINT, RAND(RecNo) * 1000000000000), 2) (not: görüyorum RIGHTdönüştürmek dolaylı BIGINTiçin CHAR, ama titiz olmak, başka olurdu CONVERTorada).
Doug_Ivison

4

Tamsayı olması gerekmez, ancak rastgele benzersiz tanımlayıcılar kullanabilirsiniz. newid()

SELECT table_name, newid() magic_number 
FROM information_schema.tables

4

Ölü bağlantı :(
Cevaba eklenebilecek

O koyar RAND(), bir görünüm içine bir koyar SELECTbir işlev haline görüşündedir ve sonra herhangi bir yerden işlevini çağırır. Zeki.
Doug_Ivison

Sorunu bağlantılı makalede olduğu gibi çözen bir çözüm yayınladım, ancak burada bu blogda beş mesaj önce doğrudan bir cevap olarak! Kimse beni zeki kıskançlık yüz hehe çağırdı
Mitselplik

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

Burada rastgele sayı 20 ile 30 arasında roundolacaktır. Maksimum iki ondalık basamak verecektir.

Negatif sayılar istiyorsanız bunu yapabilirsiniz

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

Ardından min değeri -60 ve maks. -50 olacaktır.


3

Bu kadar kolay:

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

Ve bu tabloya 0-99 arasında rastgele bir sayı koyacaktır:

CREATE TABLE R
(
    Number int
)

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

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

SELECT * FROM R

2

Bazen seçilen "Cevapla" ile ilgili sorun dağıtım her zaman eşit değil olmasıdır. Rastgele 1 - 14'ün çok sayıda satır arasında çok eşit bir dağılımına ihtiyacınız varsa, böyle bir şey yapabilirsiniz (veritabanımda 511 tablo var, bu yüzden çalışır. Rastgele sayı açıklığından daha az satırınız varsa, bu çalışmaz iyi):

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

Bu tür sayılar sıralı tutulur ve diğer sütun rastgele randomize anlamında normal rastgele çözümlerin tersini yapar.

Unutmayın, veritabanımda 511 tablo var (bu sadece bilgi / schema'dan seçtiğimiz b / c ile ilgili). Önceki sorguyu alıp bir geçici tablo #X içine koymak ve sonuçta elde edilen verilerde bu sorguyu çalıştırmak:

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

Bu sonucu, rastgele sayımın ÇOK çok sayıda satır arasında eşit olarak dağıtıldığını gösteriyorum:

resim açıklamasını buraya girin


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

her zaman benim için çalıştı



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;

üzgünüm @arnt eğer ben iyi açıklayamadı,
ichak khoury

özür dileriz @arnt, burada CTDE_GENERATE_32_BIT_KEY , 32 bit alfasayısal bir anahtar (daha fazla veya daha az olacak şekilde genişletilebilir) üreten ve CTDE_GENERATE_PUBLIC_KEY adlı ilk işlevi çağıran ve geri dönebilen ilk işlevimiz var. 16 bitlik özel bir anahtar ... açık anahtar olarak select dbo.CTDE_GENERATE_PUBLIC_KEY () öğesini çağırmanız yeterlidir; arkasındaki mantık, alfasayısal karakter listesinden 32 kez bir karakter seçip rastgele alfasayısal anahtarı elde etmek için bunları birleştirmemizdir. araştırmadan sonra.
ichak khoury

Güzel. Bu açıklama onu çok daha iyi bir cevap haline getiriyor. (Birisi silinmek üzere işaretledi; açık bırakmaya oy verdim ve bu yorumu sizin için bıraktım.)
arnt

0

Bunu dene:

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

aDüşük sayı ve büst sayı nerede


1
Bir soruyu cevaplarken daha açık olmaya çalışabilir misiniz?
Yunus Temurlenk

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

1 ile 10 arasında sayı.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.