Bagaimana saya bisa menetapkan nilai acak yang berbeda untuk setiap baris dalam pernyataan SELECT?

11

Silakan lihat kode ini:

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

Sekarang, setiap kali Anda menjalankan ini

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

Anda akan mendapatkan hasil dengan semua baris memiliki nilai acak yang sama. misalnya

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

Saya tahu cara menggunakan kursor untuk mengulang lemparan baris dan mendapatkan nilai acak yang berbeda, tetapi itu bukan pemain.

Solusi cerdas untuk ini adalah

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 

Tapi saya menyederhanakan kueri. Permintaan yang sebenarnya lebih mirip

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

dan solusi sederhana tidak cocok. Saya mencari cara untuk memaksa evaluasi berulang

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

tanpa menggunakan kursor.

Edit: Output yang diinginkan:

mungkin 1 panggilan

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

dan panggilan kedua

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

Nilai untuk setiap baris harus berupa nilai acak yang independen dari baris lainnya

Ini adalah versi kode kursor:

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
sumber
Apa yang akan menjadi hasil sempurna Anda? mungkin saya kehilangan sesuatu
gbn
Saya sedang menyiapkan versi kursor untuk membuatnya lebih jelas
bernd_k
Jadi rnd dan val selalu berbeda di setiap baris? Jika itu "acak", maka kadang-kadang mereka akan sama. Juga, dalam 2 panggilan Anda disebutkan apakah penting bahwa rnd tidak memiliki semua nilai pada kolom?
gbn
Ini digunakan untuk menghasilkan demonstrasi acak kecil hingga menengah dari kumpulan besar data nyata. Ya, repletions diperbolehkan.
bernd_k

Jawaban:

11

Subquery dievaluasi satu kali jika memungkinkan. Saya tidak dapat mengingat apa yang disebut "fitur" (lipat?) Maaf.

Hal yang sama berlaku untuk fungsi GETDATE dan RAND. NEWID dievaluasi baris demi baris karena secara intrinsik merupakan nilai acak dan tidak boleh menghasilkan nilai yang sama dua kali.

Teknik yang biasa digunakan adalah menggunakan NEWID sebagai input untuk CHECKSUM atau sebagai seed untuk RAND

Untuk nilai acak per baris:

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

Jika Anda ingin pesanan acak:

SELECT
   co1l, col2
FROM
   MyTable
ORDER BY
   NEWID()

Jika Anda ingin pesanan acak dengan urutan baris juga. Pesanan ActualOrder di sini dipertahankan tanpa memperhatikan urutan resultset

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

Edit:

Dalam hal ini, kita dapat menyatakan persyaratan sebagai:

  1. mengembalikan nilai acak apa pun dari set untuk setiap baris di set
  2. nilai acak akan berbeda dari nilai aktual di baris mana pun

Ini berbeda dengan apa yang saya tawarkan di atas yang hanya memesan ulang baris dengan berbagai cara

Jadi, saya akan mempertimbangkan LINTAS BERLAKU. Klausa WHERE klausa mengevaluasi baris demi baris dan menghindari masalah "melipat" dan memastikan bahwa val dan rnd selalu berbeda. CROSS APPLY juga bisa menskalakan dengan cukup baik

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
sumber
BERLAKU adalah SQL Server 2005 dan atas
bernd_k
1
@bernd_k: ya, tetapi harus realistis untuk mengabaikan pengguna SQL Server 2000 di 2011 ...
gbn