Come posso assegnare valori casuali diversi a ciascuna riga in un'istruzione SELECT?


11

Si prega di guardare questo codice:

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');

Ora, ogni volta che esegui questo

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

otterrai un risultato in cui tutte le righe hanno lo stesso valore casuale. per esempio

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

Conosco un modo per usare un cursore per lanciare le righe in loop e ottenere diversi valori casuali, ma non è performante.

Una soluzione intelligente a questo è

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 

Ma ho semplificato la query. La vera query sembra più simile

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

e la soluzione semplice non si adatta. Sto cercando un modo per forzare una valutazione ripetuta di

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

senza l'uso di cursori.

Modifica: output desiderato:

forse 1 chiamata

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

e una seconda chiamata

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

Il valore per ogni riga dovrebbe essere un valore casuale indipendente dalle altre righe

Ecco la versione del cursore del codice:

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

Quale sarebbe il tuo risultato perfetto per favore? forse mi manca qualcosa
gbn

Sto preparando una versione del cursore per chiarire
bernd_k

Quindi rnd e val sono sempre diversi in ogni riga? Se fosse "casuale", di tanto in tanto farebbero lo stesso. Inoltre, nelle tue 2 chiamate menzionate importa che rnd non abbia tutti i valori nella colonna?
gbn

Viene utilizzato per generare una dimostrazione casuale da piccola a media da un grande pool di dati reali. Sì, sono ammesse ripetizioni.
bernd_k

Risposte:


11

Una sottoquery viene valutata una volta, se possibile. Non riesco a ricordare ciò che la "caratteristica" si chiama (pieghevole?) Mi dispiace.

Lo stesso vale per le funzioni GETDATE e RAND. NEWID viene valutato riga per riga perché è intrinsecamente un valore casuale e non dovrebbe mai generare lo stesso valore due volte.

Le solite tecniche sono di usare NEWID come input per CHECKSUM o come seed per RAND

Per valori casuali per riga:

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

Se vuoi un ordine casuale:

SELECT
   co1l, col2
FROM
   MyTable
ORDER BY
   NEWID()

Se vuoi un ordine casuale anche con un ordine di riga. L'ordine ActualOrder qui viene conservato indipendentemente dall'ordine del set di risultati

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

Modificare:

In questo caso, possiamo dichiarare il requisito come:

  1. restituisce qualsiasi valore casuale dall'insieme per ogni riga nell'insieme
  2. il valore casuale sarà diverso dal valore effettivo in qualsiasi riga

Questo è diverso da quello che ho offerto sopra che riordina semplicemente le righe in vari modi

Quindi, prenderei in considerazione CROSS APPLY. La clausola WHERE forza la valutazione riga per riga ed evita il problema della "piegatura" e garantisce che val e rnd siano sempre diversi. CROSS APPLY può anche ridimensionare abbastanza bene

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

APPLY è SQL Server 2005 e superiore
bernd_k

1
@bernd_k: sì, ma dovrebbe essere realistico ignorare gli utenti di SQL Server 2000 nel 2011 ...
gbn
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.