Как я могу назначить разные случайные значения для каждой строки в инструкции SELECT?

11

Пожалуйста, посмотрите на этот код:

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

Теперь, когда вы выполняете это

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

вы получите результат, где все строки имеют одинаковое случайное значение. например

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

Я знаю способ использования курсора для циклического отбрасывания строк и получения разных случайных значений, но это не является производительным.

Умное решение этого

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 

Но я упростил запрос. Реальный запрос больше похож на

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

и простое решение не подходит. Я ищу способ заставить повторную оценку

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

без использования курсоров.

Изменить: Требуемый вывод:

возможно 1 звонок

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

и второй звонок

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

Значение для каждой строки просто должно быть случайным значением, независимым от других строк

Вот версия курсора кода:

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
источник
Какой будет ваш идеальный выход, пожалуйста? может быть, я что-то
упустил
Я готовлю версию курсора, чтобы прояснить
ситуацию
Значит rnd и val всегда разные в каждом ряду? Если бы это было «случайным», то иногда они были бы одинаковыми. Кроме того, в ваших 2 упомянутых вызовах имеет значение, что rnd не имеет всех значений в столбце?
Гбн
Он используется для генерации случайной демонстрации от малого до среднего из большого пула реальных данных. Да, повторы разрешены.
bernd_k

Ответы:

11

Подзапрос оценивается один раз, если это возможно. Я не могу вспомнить, как называется «фальшивка» (сворачивание?).

То же самое относится к функциям GETDATE и RAND. NEWID оценивается построчно, потому что это по сути случайное значение и никогда не должно генерировать одно и то же значение дважды.

Обычные методы - использовать NEWID в качестве входных данных для CHECKSUM или в качестве начального числа для RAND

Для случайных значений в строке:

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

Если вы хотите случайный порядок:

SELECT
   co1l, col2
FROM
   MyTable
ORDER BY
   NEWID()

Если вы хотите случайный порядок с порядком строк тоже. Порядок ActualOrder здесь сохраняется независимо от порядка набора результатов

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

Редактировать:

В этом случае мы можем сформулировать требование как:

  1. вернуть любое случайное значение из набора для каждой строки в наборе
  2. случайное значение будет отличаться от фактического значения в любой строке

Это отличается от того, что я предложил выше, который просто переупорядочивает строки различными способами.

Итак, я бы рассмотрел CROSS APPLY. Предложение WHERE вынуждает вычислять строку за строкой, избегая проблемы «сворачивания» и гарантируя, что val и rnd всегда будут разными. CROSS APPLY тоже может хорошо масштабироваться

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 и выше
bernd_k
1
@bernd_k: да, но было бы реалистично игнорировать пользователей SQL Server 2000 в 2011 году ...
gbn