¿Cómo genero un número aleatorio para cada fila en un TSQL Select?

328

Necesito un número aleatorio diferente para cada fila de mi tabla. El siguiente código aparentemente obvio usa el mismo valor aleatorio para cada fila.

SELECT table_name, RAND() magic_number 
FROM information_schema.tables 

Me gustaría obtener un INT o un FLOTADOR de esto. El resto de la historia es que voy a usar este número aleatorio para crear un desplazamiento de fecha aleatorio a partir de una fecha conocida, por ejemplo, un desplazamiento de 1-14 días desde una fecha de inicio.

Esto es para Microsoft SQL Server 2000.

MatthewMartin
fuente
44
¿Hay alguna solución para esto que no use NEWID ()? Quiero poder generar la misma secuencia de números aleatorios para una semilla dada.
Rory MacLeod
@Rory Pregunte eso como una nueva pregunta, obtendrá más atención. (Mi respuesta sería usar tablas fijas de números aleatorios, por ejemplo. Por ejemplo, este famoso conjunto estándar de números aleatorios: rand.org/pubs/monograph_reports/MR1418/index.html )
MatthewMartin
2
Look @ RAND (Transact-SQL)
AminM
RAND se introdujo en 2005, esta pregunta se hizo en 2009, qué organizaciones todavía usaban SQL 2000 porque esa era la primera versión lo suficientemente buena como para usarla para siempre.
MatthewMartin
Rory MacLeod preguntó: "¿Hay alguna solución para esto que no use NEWID ()? Quiero poder generar la misma secuencia de números aleatorios para una semilla dada". La respuesta es sí, pero es un poco complicado. 1. Cree una vista que devuelva select rand () 2. Cree un UDF que seleccione el valor de la vista. 3. Antes de seleccionar sus datos, siembre la función rand (). 4. Use el UDF en su declaración de selección. Publicaré un ejemplo completo a continuación
Mitselplik

Respuestas:

516

Eche un vistazo a SQL Server: establezca números aleatorios basados que tienen una explicación muy detallada.

Para resumir, el siguiente código genera un número aleatorio entre 0 y 13 inclusive con una distribución uniforme:

ABS(CHECKSUM(NewId())) % 14

Para cambiar su rango, simplemente cambie el número al final de la expresión. Tenga mucho cuidado si necesita un rango que incluya números positivos y negativos. Si lo haces mal, es posible contar dos veces el número 0.

Una pequeña advertencia para las matemáticas en la sala: hay un sesgo muy leve en este código. CHECKSUM()da como resultado números que son uniformes en todo el rango del tipo de datos sql Int, o al menos tan cerca como pueden mostrar mis pruebas (del editor). Sin embargo, habrá algún sesgo cuando CHECKSUM () produzca un número en el extremo superior de ese rango. Cada vez que obtenga un número entre el número entero máximo posible y el último múltiplo exacto del tamaño de su rango deseado (14 en este caso) antes de ese número entero máximo, esos resultados se verán favorecidos sobre la porción restante de su rango que no se puede producir a partir de ese último múltiplo de 14.

Como ejemplo, imagine que todo el rango del tipo Int es solo 19. 19 es el mayor entero posible que puede tener. Cuando CHECKSUM () da como resultado 14-19, estos corresponden a los resultados 0-5. Esos números serían muy favorecidos sobre 6-13, porque CHECKSUM () tiene el doble de probabilidades de generarlos. Es más fácil demostrar esto visualmente. A continuación se muestra todo el conjunto de resultados posibles para nuestro rango entero imaginario:

Suma de comprobación Entero: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Rango Resultado: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 0 1 2 3 4 5

Puede ver aquí que hay más posibilidades de producir algunos números que otros: sesgo. Afortunadamente, el rango real del tipo Int es mucho mayor ... tanto que en la mayoría de los casos el sesgo es casi indetectable. Sin embargo, es algo a tener en cuenta si alguna vez te encuentras haciendo esto por un código de seguridad serio.

SQLMenace
fuente
28
Esta página vinculada tenía la solución: ABS (CHECKSUM (NewId ()))% 14
MatthewMartin
77
% 14 devolvería números entre 0 y 13
CoderDennis
77
@Dennis Palmer, solo agregue 1
KM.
59
Acabamos de descubrir un error genial con esto. Debido a que la suma de comprobación devuelve un int, y el rango de un int es -2 ^ 31 (-2,147,483,648) a 2 ^ 31-1 (2,147,483,647), la función abs () puede devolver un error de desbordamiento si el resultado es exactamente -2,147,483,648 ! Obviamente, las posibilidades son muy bajas, alrededor de 1 en 4 mil millones, sin embargo, lo estábamos ejecutando en una tabla de filas de ~ 1.8b todos los días, ¡así que sucedía aproximadamente una vez por semana! La solución es lanzar la suma de comprobación a bigint antes de los abdominales.
EvilPuppetMaster
17
Creo que esto debería decir "una distribución uniforme" no "distribución normalizada": cada número es igualmente probable, no es una curva de campana. "Normalizado" tiene un significado matemático específico.
AnotherParker
95

Cuando se llama varias veces en un solo lote, rand () devuelve el mismo número.

Sugeriría usar convert ( varbinary, newid()) como argumento inicial:

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

newid() se garantiza que devolverá un valor diferente cada vez que se llame, incluso dentro del mismo lote, por lo que usarlo como semilla hará que rand () otorgue un valor diferente cada vez.

Editado para obtener un número entero aleatorio del 1 al 14.

Jeremy Smyth
fuente
¿Cómo se obtiene un número de un guid o varbinary? Actualizaré la pregunta para indicar que espero un número entero.
MatthewMartin
1
Lo multiplica por un número y lo escribe :) así que si quiere cinco dígitos, multiplíquelo por 100000 y conviértalo en int. Feo, pero lo suficientemente simple como para hacerlo.
Jeremy Smyth
1
Como un apéndice adicional, que le dará hasta cinco dígitos, si desea rellenar con ceros, deberá usar un tipo de datos char y usar replicar para rellenar con ceros hasta 5 dígitos.
Jeremy Smyth
Si usa la función de techo en lugar de piso, no tiene que agregar 1.
PopeDarren
Incluso cuando uso esto, hay veces que RAND () siempre me da el mismo resultado. Aún más extraño, hay veces que salta de un comportamiento correcto a uno incorrecto dependiendo de la cantidad de veces que lo estoy usando. Estoy tratando de implementar una UNIÓN INTERNA ALEATORIA y si solicito más de 19 (!!!) filas, comienza a darme siempre el mismo resultado ...
Johannes Wentu
72
RAND(CHECKSUM(NEWID()))

Lo anterior generará un número (pseudo-) aleatorio entre 0 y 1, exclusivo. Si se usa en una selección, debido a que el valor inicial cambia para cada fila, generará un nuevo número aleatorio para cada fila (sin embargo, no se garantiza que genere un número único por fila).

Ejemplo cuando se combina con un límite superior de 10 (produce números del 1 al 10):

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

Documentación de 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
fuente
39

Generación de números aleatorios entre 1000 y 9999 inclusive:

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

"+1": para incluir valores de límite superior (9999 para el ejemplo anterior)

Vova
fuente
El límite superior es exclusivo con este método, por lo que si desea incluir el número superior, debe hacerloFLOOR(RAND(CHECKSUM(NEWID()))*(10000-1000)+1000)
vaindil el
20

Responde la pregunta anterior, pero esta respuesta no se ha proporcionado previamente, y con suerte esto será útil para alguien que encuentre estos resultados a través de un motor de búsqueda.

Con SQL Server 2008, se ha introducido una nueva función CRYPT_GEN_RANDOM(8), que utiliza CryptoAPI para producir un número aleatorio criptográficamente fuerte, devuelto como VARBINARY(8000). Aquí está la página de documentación: https://docs.microsoft.com/en-us/sql/t-sql/functions/crypt-gen-random-transact-sql

Entonces, para obtener un número aleatorio, simplemente puede llamar a la función y convertirla al tipo necesario:

select CAST(CRYPT_GEN_RANDOM(8) AS bigint)

o para obtener un valor floatentre -1 y +1, podría hacer algo como esto:

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

La función Rand () generará el mismo número aleatorio, si se usa en una tabla SELECT query. Lo mismo se aplica si usa una semilla para la función Rand. Una forma alternativa de hacerlo es usar esto:

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

Obtuve la información de aquí , lo que explica muy bien el problema.

MicSim
fuente
5

¿Tiene un valor entero en cada fila que podría pasar como semilla a la función RAND?

Para obtener un número entero entre 1 y 14, creo que esto funcionaría:

FLOOR( RAND(<yourseed>) * 14) + 1
CodificadorDennis
fuente
Esto funciona en teoría, pero en la práctica he encontrado RAND(<seed>)que no parece ser muy aleatorio para cambios menores en <seed>. Por ejemplo, hice una prueba rápida: dejé <seed>ser 184380, 184383, 184386, y los RAND(<seed>)valores correspondientes fueron: 0.14912, 0.14917, 0.14923.
ImaginaryHuman072889
Tal vez para obtener más resultados aleatorios "aparentemente", intente algo como:RAND(<seed>)*100000) - FLOOR(RAND(<seed>)*100000)
ImaginaryHuman072889
5

Si necesita preservar su semilla para que genere los "mismos" datos aleatorios cada vez, puede hacer lo siguiente:

1. Cree una vista que devuelva 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. Cree un UDF que seleccione el valor de la vista.

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 seleccionar sus datos, siembre la función rand () y luego use el UDF en su instrucción 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
fuente
4

intente usar un valor de inicialización en RAND (seedInt). RAND () solo se ejecutará una vez por declaración, por eso ves el mismo número cada vez.

Polo Norte
fuente
Más simple! Aunque los valores parecen mucho más dispersos, usando dígitos de la mitad de eso, como RIGHT(CONVERT(BIGINT, RAND(RecNo) * 1000000000000), 2) (nota: estoy viendo RIGHTimplícitamente convertir BIGINTa CHAR, pero para ser riguroso, tendrías otro CONVERTallí).
Doug_Ivison
4

Si no necesita que sea un número entero, pero cualquier identificador único aleatorio, puede usar newid()

SELECT table_name, newid() magic_number 
FROM information_schema.tables

fuente
4

Debería llamar a RAND () para cada fila. Aquí hay un buen ejemplo.

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

David
fuente
Enlace inactivo :( ¿Alguna copia que pueda incluirse en la respuesta?
jocull
Él pone RAND()una vista, pone una SELECTde esa vista en una función, y luego llama a la función desde cualquier lugar. Inteligente.
Doug_Ivison
Publiqué una solución que resuelve el problema exactamente de la misma manera que en el artículo vinculado, ¡pero aquí en este blog directamente como respuesta hace cinco publicaciones! Nadie me llamó cara de envidia inteligente jeje
Mitselplik 05 de
4
select round(rand(checksum(newid()))*(10)+20,2)

Aquí el número aleatorio vendrá entre 20 y 30. rounddará un máximo de dos decimales.

Si quieres números negativos puedes hacerlo con

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

Entonces el valor mínimo será -60 y el máximo será -50.

Tirthankar
fuente
3

Es tan fácil como:

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

Y esto pondrá un número aleatorio entre 0-99 en una tabla:

CREATE TABLE R
(
    Number int
)

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

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

SELECT * FROM R
Salón Rosjier
fuente
2

El problema que a veces tengo con la "Respuesta" seleccionada es que la distribución no siempre es pareja. Si necesita una distribución muy uniforme de 1 a 14 al azar entre muchas filas, puede hacer algo como esto (mi base de datos tiene 511 tablas, por lo que funciona. Si tiene menos filas que el intervalo de números aleatorios, esto no funciona bien):

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

Esto hace lo contrario de las soluciones aleatorias normales en el sentido de que mantiene los números secuenciados y aleatoriza la otra columna.

Recuerde, tengo 511 tablas en mi base de datos (que es pertinente solo b / c que estamos seleccionando desde el esquema de información). Si tomo la consulta anterior y la pongo en una tabla temporal #X, y luego ejecuto esta consulta en los datos resultantes:

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

Obtengo este resultado, que me muestra que mi número aleatorio se distribuye MUY uniformemente entre las muchas filas:

ingrese la descripción de la imagen aquí

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

siempre ha trabajado para mi

theteague
fuente
2

Use newid ()

select newid()

o posiblemente esto

select binary_checksum(newid())
Chris Klepeis
fuente
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
fuente
lo siento @arnt si no lo
expliqué
lo siento @arnt, tenemos aquí dos funciones CTDE_GENERATE_32_BIT_KEY que genera una clave alfanumérica de 32 bits (puede extenderse para ser más o menos) y la otra llamada CTDE_GENERATE_PUBLIC_KEY que llama a la primera función y devuelve la clave pública de 32 bits o puede devolver una clave privada de 16 bits ... solo necesita llamar a select dbo.CTDE_GENERATE_PUBLIC_KEY () como clave pública; La lógica detrás es que seleccionamos un carácter de la lista de caracteres alfanuméricos 32 veces y los concatenamos juntos para obtener la clave alfanumérica aleatoria. después de la investigación
ichak khoury
Agradable. Esa explicación hace que sea una respuesta mucho mejor. (Alguien lo marcó para eliminarlo; voté para dejarlo abierto y dejé ese comentario para usted)
Arnt
0

Prueba esto:

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

¿Dónde aestá el número inferior y bes el número superior?

Rutendo
fuente
1
¿Puedes tratar de ser más claro al responder una pregunta?
Yunus Temurlenk
0
Update my_table set my_field = CEILING((RAND(CAST(NEWID() AS varbinary)) * 10))

Número entre 1 y 10.

usuario3478586
fuente