¿Cómo puedo llenar una columna con números aleatorios en SQL? Obtengo el mismo valor en cada fila

84
UPDATE CattleProds
SET SheepTherapy=(ROUND((RAND()* 10000),0))
WHERE SheepTherapy IS NULL

Si luego hago un SELECCIONAR, veo que mi número aleatorio es idéntico en cada fila . ¿Alguna idea de cómo generar números aleatorios únicos?

NibblyPig
fuente

Respuestas:

167

En lugar de rand(), use newid(), que se vuelve a calcular para cada fila del resultado. La forma habitual es utilizar el módulo de la suma de comprobación. Tenga en cuenta que checksum(newid())puede producir -2,147,483,648 y provocar un desbordamiento de enteros abs(), por lo que debemos usar módulo en el valor de retorno de la suma de comprobación antes de convertirlo en valor absoluto.

UPDATE CattleProds
SET    SheepTherapy = abs(checksum(NewId()) % 10000)
WHERE  SheepTherapy IS NULL

Esto genera un número aleatorio entre 0 y 9999.

Andomar
fuente
1
Esta pregunta / respuesta también puede ser útil: stackoverflow.com/a/9039661/47226
Aaron Hoffman
Esto no me funciona en absoluto. ¿La columna tiene que ser INT? Error # 1064 cada vez. Alcanzando las píldoras locas ...
freeworlder
1
¡Esto es una cosa de belleza! Bien hecho. Quiéralo. Un rendimiento un poco lento, pero excelente.
Arvin Amir
25

Si está en SQL Server 2008, también puede usar

 CRYPT_GEN_RANDOM(2) % 10000

Lo que parece algo más simple (también se evalúa una vez por fila como newidse muestra a continuación)

DECLARE @foo TABLE (col1 FLOAT)

INSERT INTO @foo SELECT 1 UNION SELECT 2

UPDATE @foo
SET col1 =  CRYPT_GEN_RANDOM(2) % 10000

SELECT *  FROM @foo

Devuelve (2 números aleatorios probablemente diferentes )

col1
----------------------
9693
8573

Reflexionando sobre el voto negativo inexplicable, la única razón legítima que puedo pensar es que debido a que el número aleatorio generado está entre 0-65535, que no es divisible de manera uniforme por 10,000, algunos números estarán ligeramente sobre representados. Una forma de evitar esto sería envolverlo en una UDF escalar que deseche cualquier número superior a 60.000 y se llame a sí mismo de forma recursiva para obtener un número de reemplazo.

CREATE FUNCTION dbo.RandomNumber()
RETURNS INT
AS
  BEGIN
      DECLARE @Result INT

      SET @Result = CRYPT_GEN_RANDOM(2)

      RETURN CASE
               WHEN @Result < 60000
                     OR @@NESTLEVEL = 32 THEN @Result % 10000
               ELSE dbo.RandomNumber()
             END
  END  
Martin Smith
fuente
1
@downvoter - ¿Alguna razón en particular? Tal vez quisiste presionar la flecha hacia arriba, esta respuesta funciona bien.
Martin Smith
Lo que todos parecen estar perdiendo es que este método es MUCHO MUCHO mejor para el rendimiento. He estado buscando una alternativa a NEWID () y esta es perfecta, ¡gracias!
Excavaciones
Cualquier rango deseado se maneja fácilmente. Por ejemplo ABS (CAST (CRYPT_GEN_RANDOM (8) AS BIGINT)% 10001) arroja un número de 0-10000 que es el rango que el código de OP habría generado si hubiera funcionado de la manera que esperaban.
bielawski
¿Qué 'mismo' problema? La fórmula genera nuevos valores por fila (problema de operación resuelto) y el resultado está dentro del rango, pero no estarán sesgados porque hay 64 bits de semilla y solo 14 bits de resultado, por lo que cualquier sesgo potencial sería indetectable. Incluso si generó 10 ^ 15 resultados, cualquier desviación que pueda pensar que está detectando todavía estaría dentro del margen de error. Lo que significa que necesitaría generar 2 ^ 19 resultados para demostrar que el sesgo realmente existió.
bielawski
9

Si bien me encanta usar CHECKSUM, creo que una mejor manera de hacerlo es usar NEWID(), solo porque no tiene que pasar por una matemática complicada para generar números simples.

ROUND( 1000 *RAND(convert(varbinary, newid())), 0)

Puede reemplazar el 1000con el número que desee establecer como límite, y siempre puede usar un signo más para crear un rango, digamos que desea un número aleatorio entre 100y 200, puede hacer algo como:

100 + ROUND( 100 *RAND(convert(varbinary, newid())), 0)

Poniéndolo junto en su consulta:

UPDATE CattleProds 
SET SheepTherapy= ROUND( 1000 *RAND(convert(varbinary, newid())), 0)
WHERE SheepTherapy IS NULL
Segev -CJ- Shmueli
fuente
1

Probé 2 métodos de aleatorización basados ​​en conjuntos contra RAND () generando 100,000,000 filas con cada uno. Para nivelar el campo, la salida es un valor flotante entre 0-1 para imitar RAND (). La mayor parte del código está probando la infraestructura, así que resumo los algoritmos aquí:

-- Try #1 used
(CAST(CRYPT_GEN_RANDOM(8) AS BIGINT)%500000000000000000+500000000000000000.0)/1000000000000000000 AS Val
-- Try #2 used
RAND(Checksum(NewId()))
-- and to have a baseline to compare output with I used
RAND() -- this required executing 100000000 separate insert statements

El uso de CRYPT_GEN_RANDOM fue claramente el más aleatorio, ya que solo hay un .000000001% de probabilidad de ver incluso 1 duplicado al extraer 10 ^ 8 números DE un conjunto de 10 ^ 18 números. ¡OIA no deberíamos haber visto ningún duplicado y este no tenía ninguno! Este conjunto tardó 44 segundos en generarse en mi computadora portátil.

Cnt     Pct
-----   ----
 1      100.000000  --No duplicates

Tiempos de ejecución de SQL Server: tiempo de CPU = 134795 ms, tiempo transcurrido = 39274 ms.

IF OBJECT_ID('tempdb..#T0') IS NOT NULL DROP TABLE #T0;
GO
WITH L0   AS (SELECT c FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS D(c))  -- 2^4  
    ,L1   AS (SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B)    -- 2^8  
    ,L2   AS (SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B)    -- 2^16  
    ,L3   AS (SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B)    -- 2^32  
SELECT TOP 100000000 (CAST(CRYPT_GEN_RANDOM(8) AS BIGINT)%500000000000000000+500000000000000000.0)/1000000000000000000 AS Val
  INTO #T0
  FROM L3;

 WITH x AS (
     SELECT Val,COUNT(*) Cnt
      FROM #T0
     GROUP BY Val
)
SELECT x.Cnt,COUNT(*)/(SELECT COUNT(*)/100 FROM #T0) Pct
  FROM X
 GROUP BY x.Cnt;

Con casi 15 órdenes de magnitud menos aleatorios, este método no fue el doble de rápido, y solo tomó 23 segundos para generar 100 millones de números.

Cnt  Pct
---- ----
1    95.450254    -- only 95% unique is absolutely horrible
2    02.222167    -- If this line were the only problem I'd say DON'T USE THIS!
3    00.034582
4    00.000409    -- 409 numbers appeared 4 times
5    00.000006    -- 6 numbers actually appeared 5 times 

Tiempos de ejecución de SQL Server: tiempo de CPU = 77156 ms, tiempo transcurrido = 24613 ms.

IF OBJECT_ID('tempdb..#T1') IS NOT NULL DROP TABLE #T1;
GO
WITH L0   AS (SELECT c FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS D(c))  -- 2^4  
    ,L1   AS (SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B)    -- 2^8  
    ,L2   AS (SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B)    -- 2^16  
    ,L3   AS (SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B)    -- 2^32  
SELECT TOP 100000000 RAND(Checksum(NewId())) AS Val
  INTO #T1
  FROM L3;

WITH x AS (
    SELECT Val,COUNT(*) Cnt
     FROM #T1
    GROUP BY Val
)
SELECT x.Cnt,COUNT(*)*1.0/(SELECT COUNT(*)/100 FROM #T1) Pct
  FROM X
 GROUP BY x.Cnt;

RAND () solo es inútil para la generación basada en conjuntos, por lo que generar la línea de base para comparar la aleatoriedad tomó más de 6 horas y tuvo que reiniciarse varias veces para finalmente obtener el número correcto de filas de salida. También parece que la aleatoriedad deja mucho que desear, aunque es mejor que usar la suma de comprobación (newid ()) para resembrar cada fila.

Cnt  Pct
---- ----
1    99.768020
2    00.115840
3    00.000100  -- at least there were comparitively few values returned 3 times

Debido a los reinicios, no se pudo capturar el tiempo de ejecución.

IF OBJECT_ID('tempdb..#T2') IS NOT NULL DROP TABLE #T2;
GO
CREATE TABLE #T2 (Val FLOAT);
GO
SET NOCOUNT ON;
GO
INSERT INTO #T2(Val) VALUES(RAND());
GO 100000000

WITH x AS (
    SELECT Val,COUNT(*) Cnt
     FROM #T2
    GROUP BY Val
)
SELECT x.Cnt,COUNT(*)*1.0/(SELECT COUNT(*)/100 FROM #T2) Pct
  FROM X
 GROUP BY x.Cnt;
bielawski
fuente
PD: Pensando que los reinicios podrían haber representado algunos de los duplicados, rápidamente probé solo 3 millones de filas, lo que tomó casi 6-1 / 2 minutos. Obtuve 2101 dups y 2 valores aparecieron 3 veces (.07% y .000067% respectivamente) lo que indica que los reinicios probablemente desempeñaron un papel, pero la aleatoriedad aún está lejos de ser estelar.
bielawski
Habiendo notado otra respuesta simplemente sembrada con newid convertido a varbinary, lo intenté también. No solo no es más rápido que usar la suma de comprobación, sino que un valor aparece 8 veces en esa prueba. Para ser justos, todavía era 95.447319% único, que es apenas peor que el 95.450254% de RAND (Checksum (NewId ())) en mi prueba. Una segunda ejecución arrojó el peor de los casos de 3 números que aparecen 5 veces y 95,452929% distintos, por lo que YMMV incluso cuando se prueban 100 millones de filas.
bielawski
-2
require_once('db/connect.php');

//rand(1000000 , 9999999);

$products_query = "SELECT id FROM products";
$products_result = mysqli_query($conn, $products_query);
$products_row = mysqli_fetch_array($products_result);
$ids_array = [];

do
{
    array_push($ids_array, $products_row['id']);
}
while($products_row = mysqli_fetch_array($products_result));

/*
echo '<pre>';
print_r($ids_array);
echo '</pre>';
*/
$row_counter = count($ids_array);

for ($i=0; $i < $row_counter; $i++)
{ 
    $current_row = $ids_array[$i];
    $rand = rand(1000000 , 9999999);
    mysqli_query($conn , "UPDATE products SET code='$rand' WHERE id='$current_row'");
}
Vaso Nadiradze
fuente
tal vez no sea la forma correcta y fácil, pero funciona)))
Vaso Nadiradze
1
Lea la pregunta detenidamente antes de comenzar a responder. Por cierto, enviar una consulta de ACTUALIZACIÓN para todas y cada una de las filas por separado es una MUY, MUY MALA IDEA cuando uno tiene que ACTUALIZAR incluso un número modesto de filas.
darlove