¿Cómo puedo asignar diferentes valores aleatorios a cada fila en una instrucción SELECT?


11

Por favor mira este código:

create table #t1(
  id int identity (1,1),
  val varchar(10)
);


insert into #t1 values ('a');
insert into #t1 values ('b');
insert into #t1 values ('c');
insert into #t1 values ('d');

Ahora, cada vez que ejecutes esto

select *, 
    ( select top 1 val from #t1 order by NEWID()) rnd 
from #t1 order by 1;

obtendrá un resultado donde todas las filas tienen el mismo valor aleatorio. p.ej

id          val        rnd
----------- ---------- ----------
1           a          b
2           b          b
3           c          b
4           d          b

Conozco una forma de usar un cursor para lanzar en bucle las filas y obtener diferentes valores aleatorios, pero eso no es rentable.

Una solución inteligente para esto es

select t1.id, t1.val, t2.val
from #t1 t1
    join (select *, ROW_NUMBER() over( order by NEWID()) lfd from #t1) as t2 on  t1.id = t2.lfd 

Pero simplifiqué la consulta. La consulta real se parece más a

select *, 
    ( select top 1 val from t2 where t2.x <> t1.y order by NEWID()) rnd 
from t1 order by 1;

y la solución simple no encaja. Estoy buscando una manera de forzar la evaluación repetida de

( select top 1 val from #t1 order by NEWID()) rnd 

sin el uso de cursores.

Editar: Salida deseada:

tal vez 1 llamada

id          val        rnd
----------- ---------- ----------
1           a          c
2           b          c
3           c          b
4           d          a

y una segunda llamada

id          val        rnd
----------- ---------- ----------
1           a          a
2           b          d
3           c          d
4           d          b

El valor para cada fila solo debe ser un valor aleatorio independiente de las otras filas

Aquí está la versión del cursor del código:

CREATE TABLE #res ( id INT, val VARCHAR(10), rnd VARCHAR(10));

DECLARE @id INT
DECLARE @val VARCHAR(10)
DECLARE c CURSOR FOR
SELECT id, val
FROM #t1
OPEN c
FETCH NEXT FROM c INTO @id, @val
WHILE @@FETCH_STATUS = 0
BEGIN
    INSERT INTO #res
    SELECT @id, @val, ( SELECT TOP 1 val FROM #t1 ORDER BY NEWID()) rnd 
    FETCH NEXT FROM c INTO @id, @val
END
CLOSE c
DEALLOCATE c

SELECT * FROM #res

¿Cuál sería su salida perfecta por favor? tal vez me falta algo
gbn

Estoy preparando una versión del cursor para que quede claro
bernd_k

¿Entonces rnd y val son siempre diferentes en cada fila? Si fuera "aleatorio", ocasionalmente harían lo mismo. Además, en sus 2 llamadas mencionadas, ¿importa que rnd no tenga todos los valores en la columna?
gbn

Se utiliza para generar una demostración aleatoria de pequeña a mediana a partir de un gran conjunto de datos reales. Sí, se permiten reposiciones.
bernd_k

Respuestas:


11

Una subconsulta se evalúa una vez si es posible. No recuerdo cómo se llama la "característica" (¿plegado?) Lo siento.

Lo mismo se aplica a las funciones GETDATE y RAND. NEWID se evalúa fila por fila porque es intrínsecamente un valor aleatorio y nunca debe generar el mismo valor dos veces.

Las técnicas habituales son usar NEWID como entrada para CHECKSUM o como semilla para RAND

Para valores aleatorios por fila:

SELECT
   co1l, col2,
   ABS(CHECKSUM(NEWID())) AS Random1,
   RAND(CHECKSUM(NEWID())) AS Random2
FROM
   MyTable

Si quieres orden aleatorio:

SELECT
   co1l, col2
FROM
   MyTable
ORDER BY
   NEWID()

Si desea un orden aleatorio con un orden de fila también. El orden RealOrder aquí se conserva independientemente del orden del conjunto de resultados

SELECT
   id, val,
   ROWNUMBER() OVER (ORDER BY id) AS id
FROM
   #t1
ORDER BY
   NEWID()

Editar:

En este caso, podemos establecer el requisito como:

  1. devolver cualquier valor aleatorio del conjunto para cada fila del conjunto
  2. el valor aleatorio será diferente del valor real en cualquier fila

Esto es diferente a lo que ofrecí anteriormente, que simplemente reordena las filas de varias maneras

Por lo tanto, consideraría la APLICACIÓN CRUZADA. La cláusula WHERE fuerza la evaluación fila por fila y evita el problema de "plegado" y garantiza que val y rnd sean siempre diferentes. CROSS APPLY también puede escalar bastante bien

SELECT
   id, val, R.rnd
FROM
   #t1 t1
   CROSS APPLY
   (SELECT TOP 1 val as rnd FROM #t1 t2 WHERE t1.val <> t2.val ORDER BY NEWID()) R
ORDER BY
   id

APLICAR es SQL Server 2005 y superior
bernd_k

1
@bernd_k: sí, pero debería ser realista ignorar a los usuarios de SQL Server 2000 en 2011 ...
gbn
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.