Como posso atribuir valores aleatórios diferentes para cada linha em uma instrução SELECT?

11

Por favor, veja 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');

Agora, sempre que você executar isso

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

você obterá um resultado em que todas as linhas têm o mesmo valor aleatório. por exemplo

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

Eu sei como usar um cursor para fazer um loop nas linhas e obter diferentes valores aleatórios, mas isso não é de alto desempenho.

Uma solução inteligente para isso é

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 

Mas eu simplifiquei a consulta. A consulta real se parece mais com

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

e a solução simples não se encaixa. Estou procurando uma maneira de forçar a avaliação repetida de

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

sem o uso de cursores.

Editar: Saída desejada:

talvez 1 chamada

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

e uma segunda chamada

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

O valor para cada linha apenas deve ser um valor aleatório independente das outras linhas

Aqui está a versão do cursor do 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
bernd_k
fonte
Qual seria a sua saída perfeita, por favor? talvez eu estou faltando alguma coisa
GBN
Estou preparando uma versão cursor para que fique claro
bernd_k
Então rnd e val são sempre diferentes em todas as linhas? Se fosse "aleatório", ocasionalmente eles fariam o mesmo. Além disso, nas duas chamadas mencionadas, importa que rnd não tenha todos os valores na coluna?
gbn 8/03/11
É usado para gerar uma demonstração aleatória pequena a média a partir de um grande conjunto de dados reais. Sim, substituições são permitidas.
22811 bernd_k

Respostas:

11

Uma subconsulta é avaliada uma vez, se possível. Não me lembro como o "recurso" é chamado (dobrável?) Desculpe.

O mesmo se aplica às funções GETDATE e RAND. NEWID é avaliado linha por linha porque é intrinsecamente um valor aleatório e nunca deve gerar o mesmo valor duas vezes.

As técnicas comuns são usar NEWID como entrada para CHECKSUM ou como uma semente para RAND

Para valores aleatórios por linha:

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

Se você deseja ordem aleatória:

SELECT
   co1l, col2
FROM
   MyTable
ORDER BY
   NEWID()

Se você quiser ordem aleatória com uma ordem de linha também. A ordem ActualOrder aqui é preservada, independentemente da ordem do conjunto de resultados

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

Editar:

Nesse caso, podemos declarar o requisito como:

  1. retornar qualquer valor aleatório do conjunto para cada linha do conjunto
  2. o valor aleatório será diferente do valor real em qualquer linha

Isso é diferente do que eu ofereci acima, que simplesmente reordena as linhas de várias maneiras

Então, eu consideraria CROSS APPLY. A cláusula WHERE força a avaliação linha a linha e evita o problema de "dobragem" e garante que val e rnd sejam sempre diferentes. O CROSS APPLY também pode ser dimensionado muito bem

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
gbn
fonte
O APPLY é o SQL Server 2005 e superior
bernd_k
1
@bernd_k: sim, mas deve ser realista ignorar os usuários do SQL Server 2000 em 2011 ...
gbn