Wie kann ich jeder Zeile in einer SELECT-Anweisung unterschiedliche Zufallswerte zuweisen?

11

Bitte schauen Sie sich diesen Code an:

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

Nun, wann immer Sie dies ausführen

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

Sie erhalten ein Ergebnis, bei dem alle Zeilen den gleichen Zufallswert haben. z.B

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

Ich kenne eine Möglichkeit, mit einem Cursor die Zeilen zu schleifen und verschiedene Zufallswerte zu erhalten, aber das ist nicht performant.

Eine clevere Lösung dafür ist

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 

Aber ich habe die Abfrage vereinfacht. Die eigentliche Abfrage sieht eher so aus

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

und die einfache Lösung passt nicht. Ich suche nach einer Möglichkeit, eine wiederholte Bewertung von zu erzwingen

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

ohne die Verwendung von Cursorn.

Bearbeiten: Gewünschte Ausgabe:

vielleicht 1 Anruf

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

und ein zweiter Anruf

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

Der Wert für jede Zeile sollte nur ein zufälliger Wert sein, der von den anderen Zeilen unabhängig ist

Hier ist die Cursorversion des Codes:

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
quelle
Was wäre bitte Ihre perfekte Ausgabe? Vielleicht fehlt mir etwas
gbn
Ich bereite eine Cursor-Version vor, um es klar zu machen
bernd_k
Also sind rnd und val in jeder Reihe immer unterschiedlich? Wenn es "zufällig" wäre, würden sie gelegentlich dasselbe tun. Ist es in Ihren 2 genannten Aufrufen auch wichtig, dass rnd nicht alle Werte in der Spalte enthält?
gbn
Es wird verwendet, um eine kleine bis mittlere zufällige Demonstration aus einem großen Pool realer Daten zu generieren. Ja, Wiederholungen sind erlaubt.
Bernd_k

Antworten:

11

Eine Unterabfrage wird nach Möglichkeit einmal ausgewertet. Ich kann mich nicht erinnern, wie das "Feature" heißt (Falten?).

Gleiches gilt für die Funktionen GETDATE und RAND. NEWID wird zeilenweise ausgewertet, da es sich um einen zufälligen Wert handelt und niemals denselben Wert zweimal generieren sollte.

Die üblichen Techniken sind die Verwendung von NEWID als Eingabe für CHECKSUM oder als Startwert für RAND

Für zufällige Werte pro Zeile:

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

Wenn Sie eine zufällige Reihenfolge wünschen:

SELECT
   co1l, col2
FROM
   MyTable
ORDER BY
   NEWID()

Wenn Sie eine zufällige Reihenfolge mit einer Zeilenreihenfolge wünschen. Die Reihenfolge von ActualOrder bleibt hier unabhängig von der Reihenfolge der Ergebnismenge erhalten

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

Bearbeiten:

In diesem Fall können wir die Anforderung wie folgt angeben:

  1. Gibt einen beliebigen Wert aus der Menge für jede Zeile in der Menge zurück
  2. Der Zufallswert unterscheidet sich vom tatsächlichen Wert in einer beliebigen Zeile

Dies unterscheidet sich von dem, was ich oben angeboten habe, bei dem Zeilen einfach auf verschiedene Arten neu angeordnet werden

Also würde ich CROSS APPLY in Betracht ziehen. Die WHERE-Klausel erzwingt eine zeilenweise Auswertung und vermeidet das Problem des "Faltens" und stellt sicher, dass val und rnd immer unterschiedlich sind. CROSS APPLY kann auch recht gut skaliert werden

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
quelle
APPLY ist SQL Server 2005 und Upper
Bernd_k
1
@bernd_k: Ja, aber es sollte realistisch sein, SQL Server 2000-Benutzer im Jahr 2011 zu ignorieren ...
gbn