Como faço para gerar um número aleatório para cada linha em um TSQL Select?

328

Preciso de um número aleatório diferente para cada linha da minha tabela. O código aparentemente óbvio a seguir usa o mesmo valor aleatório para cada linha.

SELECT table_name, RAND() magic_number 
FROM information_schema.tables 

Eu gostaria de obter um INT ou um FLOAT com isso. O resto da história é que vou usar esse número aleatório para criar um deslocamento aleatório de data a partir de uma data conhecida, por exemplo, um deslocamento de 1 a 14 dias a partir de uma data inicial.

Isso é para o Microsoft SQL Server 2000.

MatthewMartin
fonte
4
Existe uma solução para isso que não usa NEWID ()? Eu quero ser capaz de gerar a mesma sequência de números aleatórios para uma determinada semente.
Rory MacLeod
@Rory Faça isso como nova pergunta, ele receberá mais atenção. (Minha resposta seria usar tabelas fixas de números aleatórios, por exemplo. Por exemplo, este famoso conjunto padrão de números aleatórios: rand.org/pubs/monograph_reports/MR1418/index.html )
MatthewMartin
2
Look @ RAND (Transact-SQL)
AminM 30/03
O RAND foi introduzido em 2005, essa pergunta foi feita em 2009, que organizações ainda usavam o SQL 2000 porque essa era a primeira versão boa o suficiente para ser usada para sempre.
MatthewMartin
Rory MacLeod perguntou: "Existe uma solução para isso que não use NEWID ()? Quero poder gerar a mesma sequência de números aleatórios para uma determinada semente". A resposta é sim, mas é um pouco complicado. 1. Crie uma visualização que retorne selecione rand () 2. Crie uma UDF que selecione o valor da visualização. 3. Antes de selecionar seus dados, propague a função rand (). 4. Use o UDF na sua instrução de seleção. Vou postar um exemplo completo abaixo
Mitselplik

Respostas:

516

Dê uma olhada no SQL Server - Defina números aleatórios baseados em uma explicação muito detalhada.

Para resumir, o código a seguir gera um número aleatório entre 0 e 13, inclusive com uma distribuição uniforme:

ABS(CHECKSUM(NewId())) % 14

Para alterar seu intervalo, basta alterar o número no final da expressão. Tenha muito cuidado se precisar de um intervalo que inclua números positivos e negativos. Se você errar, é possível contar duas vezes o número 0.

Um pequeno aviso para as porcas matemáticas da sala: há um pequeno viés nesse código. CHECKSUM()resulta em números uniformes em todo o intervalo do tipo de dados sql Int, ou pelo menos o mais próximo possível para o meu teste (do editor). No entanto, haverá algum viés quando CHECKSUM () produzir um número na extremidade superior desse intervalo. Sempre que você obtém um número entre o número máximo máximo possível e o último múltiplo exato exato do tamanho do intervalo desejado (14 neste caso) antes desse número máximo máximo, esses resultados são favorecidos sobre a parte restante do intervalo que não pode ser produzida a partir de esse último múltiplo de 14.

Como um exemplo, imagine que todo o intervalo do tipo Int seja apenas 19. 19 é o maior número possível possível. Quando CHECKSUM () resulta em 14-19, eles correspondem aos resultados 0-5. Esses números seriam altamente favorecidos entre 6-13, porque CHECKSUM () tem duas vezes mais chances de gerá-los. É mais fácil demonstrar isso visualmente. Abaixo está todo o conjunto possível de resultados para o nosso intervalo inteiro imaginário:

Número inteiro de soma de verificação: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Resultado do intervalo: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 0 1 2 3 4 5

Você pode ver aqui que há mais chances de produzir alguns números do que outros: viés. Felizmente, o alcance real do tipo Int é muito maior ... tanto que, na maioria dos casos, o viés é quase indetectável. No entanto, é algo a ter em atenção se você se encontra fazendo isso por um código de segurança sério.

SQLMenace
fonte
28
Esta página vinculada tinha a solução: ABS (CHECKSUM (NewId ()))% 14
MatthewMartin
7
% 14 voltaria números entre 0 e 13
CoderDennis
7
@Dennis Palmer, basta adicionar 1
KM.
59
Acabamos de descobrir um bug genial com isso. Como a soma de verificação retorna um int e o intervalo de um int é -2 ^ 31 (-2.147.483.648) a 2 ^ 31-1 (2.147.483.647), a função abs () pode retornar um erro de estouro se o resultado for exatamente -2.147.483.648 ! As chances são obviamente muito baixas, cerca de 1 em 4 bilhões, no entanto, estávamos rodando sobre uma tabela de linhas de ~ 1.8b todos os dias, então isso acontecia uma vez por semana! Correção é converter a soma de verificação em bigint antes do abs.
precisa saber é o seguinte
17
Eu acho que isso deveria dizer "uma distribuição uniforme" e não "distribuição normalizada" - cada número é igualmente provável, não é uma curva em sino. "Normalizado" tem um significado matemático específico.
AnotherParker
95

Quando chamado várias vezes em um único lote, rand () retorna o mesmo número.

Eu sugiro usar convert ( varbinary, newid()) como o argumento de semente:

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

newid() é garantido que retorne um valor diferente cada vez que for chamado, mesmo dentro do mesmo lote, portanto, usá-lo como uma semente solicitará que rand () forneça um valor diferente a cada vez.

Editado para obter um número inteiro aleatório de 1 a 14.

Jeremy Smyth
fonte
Como você obtém um número de um guid ou varbinary? Vou atualizar a pergunta para indicar que estou esperando um número inteiro.
MatthewMartin
1
Você o multiplica por um número e o coloca no piso :), portanto, se você quiser cinco dígitos, multiplique por 100000 e converta para um int. Feio, mas simples o suficiente para fazer.
Jeremy Smyth
1
Como um adendo adicional - que fornecerá até cinco dígitos - se você deseja zerá-lo, precisará usar um tipo de dados char e usar replicar para zerar até 5 dígitos.
Jeremy Smyth
Se você usar a função de teto em vez de andar, você não tem que adicionar 1.
PopeDarren
Mesmo quando eu uso isso, há momentos em que RAND () sempre me dá o mesmo resultado. Ainda mais estranho, há momentos em que ele pula de um comportamento correto para um incorreto, dependendo do número de vezes que o uso. Eu estou tentando implementar um RANDOM INNER JOIN e se eu pedir por mais de 19 (!!!) linhas, ele começa a me dar sempre o mesmo resultado ...
Johannes Wentu
72
RAND(CHECKSUM(NEWID()))

O acima irá gerar um número (pseudo-) aleatório entre 0 e 1, exclusivo. Se usado em uma seleção, porque o valor inicial muda para cada linha, ele gerará um novo número aleatório para cada linha (não é garantido que você gere um número único por linha).

Exemplo quando combinado com um limite superior de 10 (produz os números 1 - 10):

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

Documentação 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
Aaron Hoffman
fonte
39

Geração aleatória de números entre 1000 e 9999, inclusive:

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

"+1" - para incluir valores do limite superior (9999 no exemplo anterior)

Vova
fonte
O limite superior é exclusiva com este método, por isso, se você quiser incluir o número mais alto que você precisa fazerFLOOR(RAND(CHECKSUM(NEWID()))*(10000-1000)+1000)
vaindil
20

Respondendo à pergunta antiga, mas essa resposta não foi fornecida anteriormente, e espero que seja útil para alguém encontrar esses resultados por meio de um mecanismo de pesquisa.

Com o SQL Server 2008, uma nova função foi introduzida CRYPT_GEN_RANDOM(8), que usa o CryptoAPI para produzir um número aleatório criptograficamente forte, retornado como VARBINARY(8000). Aqui está a página da documentação: https://docs.microsoft.com/en-us/sql/t-sql/functions/crypt-gen-random-transact-sql

Portanto, para obter um número aleatório, você pode simplesmente chamar a função e convertê-la no tipo necessário:

select CAST(CRYPT_GEN_RANDOM(8) AS bigint)

ou para obter um valor floatentre -1 e +1, você pode fazer algo assim:

select CAST(CRYPT_GEN_RANDOM(8) AS bigint) % 1000000000 / 1000000000.0
Andrei Tanas
fonte
13

A função Rand () gerará o mesmo número aleatório, se usada em uma consulta SELECT da tabela. O mesmo se aplica se você usar uma semente na função Rand. Uma maneira alternativa de fazer isso é usar o seguinte:

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

Consegui as informações aqui , o que explica muito bem o problema.

MicSim
fonte
5

Você tem um valor inteiro em cada linha que pode passar como uma semente para a função RAND?

Para obter um número inteiro entre 1 e 14, acredito que isso funcionaria:

FLOOR( RAND(<yourseed>) * 14) + 1
CoderDennis
fonte
Isso funciona na teoria, mas, na prática, descobri RAND(<seed>)que não parece ser muito aleatório para pequenas alterações <seed>. Por exemplo, um teste rápido que fiz: deixei <seed>ser 184380, 184383, 184386 e os RAND(<seed>)valores correspondentes foram: 0,14912, 0,14917, 0,14923.
ImaginaryHuman072889
Talvez para obter algumas mais "aparentemente" resultados aleatórios, tente algo como:RAND(<seed>)*100000) - FLOOR(RAND(<seed>)*100000)
ImaginaryHuman072889
5

Se você precisar preservar sua semente para que ela gere os mesmos dados aleatórios todas as vezes, faça o seguinte:

1. Crie uma exibição que retorne select 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. Crie um UDF que selecione o valor na visualização.

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. Antes de selecionar seus dados, propague a função rand () e use o UDF na sua instrução 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
Mitselplik
fonte
4

tente usar um valor inicial no RAND (seedInt). RAND () será executado apenas uma vez por instrução, e é por isso que você vê o mesmo número cada vez.

Polo Norte
fonte
Mais simples! Embora os valores pareçam muito mais dispersos, usando dígitos do meio disso, como RIGHT(CONVERT(BIGINT, RAND(RecNo) * 1000000000000), 2) (nota: estou vendo RIGHTimplicitamente converter o BIGINTpara CHAR, mas para ser rigoroso, você terá outro CONVERT).
Doug_Ivison 11/11/2015
4

Se você não precisar que ele seja um número inteiro, mas qualquer identificador exclusivo aleatório, poderá usar newid()

SELECT table_name, newid() magic_number 
FROM information_schema.tables

fonte
4

Você precisaria chamar RAND () para cada linha. Aqui está um bom exemplo

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

David
fonte
Dead link :( Todas as cópias que poderiam ser incluídos na resposta?
jocull
Ele coloca RAND()uma visão, coloca uma SELECTdessa visão em uma função e depois a chama de qualquer lugar. Inteligente.
Doug_Ivison 11/11/2015
Postei uma solução que resolve o problema exatamente da mesma maneira que no artigo vinculado, mas aqui neste blog diretamente como resposta há cinco posts! Ninguém me chamou inteligente inveja rosto hehe
Mitselplik
4
select round(rand(checksum(newid()))*(10)+20,2)

Aqui o número aleatório estará entre 20 e 30. rounddará duas casas decimais no máximo.

Se você quer números negativos, pode fazê-lo com

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

Então o valor mínimo será -60 e max será -50.

Tirthankar
fonte
3

É tão fácil quanto:

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

E isso colocará um número aleatório entre 0-99 em uma tabela:

CREATE TABLE R
(
    Number int
)

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

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

SELECT * FROM R
Rosjier Hall
fonte
2

O problema que às vezes tenho com a "Resposta" selecionada é que a distribuição nem sempre é uniforme. Se você precisar de uma distribuição muito uniforme de 1 a 14 aleatórios entre várias linhas, poderá fazer algo assim (meu banco de dados possui 511 tabelas, portanto isso funcionará. Se você tiver menos linhas do que o intervalo de números aleatórios, isso não funcionará) bem):

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

Isso meio que faz o oposto das soluções aleatórias normais, no sentido de manter os números sequenciados e randomizar a outra coluna.

Lembre-se, eu tenho 511 tabelas no meu banco de dados (que é pertinente apenas porque estamos selecionando no information_schema). Se eu pegar a consulta anterior e colocá-la em uma tabela temporária #X e executar essa consulta nos dados resultantes:

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

Eu recebo esse resultado, mostrando-me que meu número aleatório é MUITO distribuído igualmente entre as muitas linhas:

insira a descrição da imagem aqui

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

sempre trabalhou para mim

theteague
fonte
2

Use newid ()

select newid()

ou possivelmente isso

select binary_checksum(newid())
Chris Klepeis
fonte
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;
ichak khoury
fonte
desculpe @arnt se eu não explicou bem,
Ichak khoury
desculpe @arnt, temos aqui duas funções CTDE_GENERATE_32_BIT_KEY que geram uma chave alfanumérica de 32 bits (pode ser estendida para mais ou menos) e a outra chamada CTDE_GENERATE_PUBLIC_KEY que chama a primeira função e retorna a chave pública de 32 bits ou você pode retornar uma chave privada de 16 bits ... você só precisa chamar select dbo.CTDE_GENERATE_PUBLIC_KEY () como uma chave pública; a lógica por trás disso é que selecionamos um caractere da lista alfanumérica 32 vezes e os concatenamos juntos para obter a chave alfanumérica aleatória. após pesquisa.
khoury Ichak
Agradável. Essa explicação torna uma resposta muito melhor. (Alguém sinalizado para exclusão;. Eu votei para deixá-la aberta e deixou que o comentário para você)
Arnt
0

Tente o seguinte:

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

Onde aé o número mais baixo e bé o número superior

Rutendo
fonte
1
Você pode tentar ser mais claro ao responder uma pergunta?
Yunus Temurlenk
0
Update my_table set my_field = CEILING((RAND(CAST(NEWID() AS varbinary)) * 10))

Número entre 1 e 10.

user3478586
fonte