Comment attribuer différentes valeurs aléatoires à chaque ligne d'une instruction SELECT?

11

Veuillez regarder ce code:

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

Maintenant, chaque fois que vous exécutez cette

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

vous obtiendrez un résultat avec où toutes les lignes ont la même valeur aléatoire. par exemple

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

Je connais un moyen d'utiliser un curseur pour lancer en boucle les lignes et obtenir différentes valeurs aléatoires, mais ce n'est pas performant.

Une solution intelligente à cela est

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 

Mais j'ai simplifié la requête. La vraie requête ressemble plus à

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

et la solution simple ne convient pas. Je cherche un moyen de forcer une évaluation répétée de

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

sans l'utilisation de curseurs.

Edit: Sortie souhaitée:

peut-être 1 appel

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

et un deuxième appel

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

La valeur de chaque ligne doit simplement être une valeur aléatoire indépendante des autres lignes

Voici la version curseur du code:

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
la source
Quelle serait votre sortie parfaite s'il vous plaît? peut-être que je manque quelque chose
gbn
Je prépare une version curseur pour que ce soit clair
bernd_k
Donc, rnd et val sont toujours différents dans chaque rangée? Si c'était "aléatoire", alors de temps en temps ils seraient pareils. De plus, dans vos 2 appels mentionnés, est-il important que rnd n'ait pas toutes les valeurs dans la colonne?
gbn
Il est utilisé pour générer une démonstration aléatoire petite à moyenne à partir d'un grand pool de données réelles. Oui, les répétitions sont autorisées.
bernd_k

Réponses:

11

Une sous-requête est évaluée une fois si possible. Je ne me souviens plus de ce que la "fonction" est appelée (pliage?)

Il en va de même pour les fonctions GETDATE et RAND. NEWID est évalué ligne par ligne car il est intrinsèquement une valeur aléatoire et ne doit jamais générer deux fois la même valeur.

Les techniques habituelles sont d'utiliser use NEWID comme entrée pour CHECKSUM ou comme graine pour RAND

Pour les valeurs aléatoires par ligne:

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

Si vous voulez un ordre aléatoire:

SELECT
   co1l, col2
FROM
   MyTable
ORDER BY
   NEWID()

Si vous souhaitez également un ordre aléatoire avec un ordre de ligne. L'ordre Ordre réel ici est conservé quel que soit l'ordre du jeu de résultats

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

Éditer:

Dans ce cas, nous pouvons énoncer l'exigence comme suit:

  1. retourner toute valeur aléatoire de l'ensemble pour chaque ligne de l'ensemble
  2. la valeur aléatoire sera différente de la valeur réelle dans n'importe quelle ligne

Ceci est différent de ce que j'ai proposé ci-dessus qui réorganise simplement les lignes de diverses manières

Donc, je considérerais CROSS APPLY. La clause WHERE force l'évaluation ligne par ligne et évite le problème de "pliage" et garantit que val et rnd sont toujours différents. CROSS APPLY peut aussi très bien évoluer

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
la source
APPLY est SQL Server 2005 et supérieur
bernd_k
1
@bernd_k: oui, mais il devrait être réaliste d'ignorer les utilisateurs de SQL Server 2000 en 2011 ...
gbn