SQL Server - корреляция со случайными значениями? - PullRequest
1 голос
/ 21 октября 2019

Упрощенный пример моей ситуации:

У меня есть таблица с тремя столбцами: ID, CATEGORY и TIMESTAMP. Каждый ID и TIMESTAMP уникален, но CATEGORY не уникален.

Я сделал этот запрос, чтобы получить псевдослучайный список строк в таблице (по одной на CATEGORY).

SELECT b.*
FROM
(
  SELECT MIN(RAND(ID)*100000-FLOOR(RAND(ID)*100000)) [RandomID] -- Select random identifier for each row
  FROM MYTABLE
  GROUP BY CATEGORY
) a
INNER JOIN
MYTABLE b
ON a.RandomID = (RAND(b.ID)*100000-FLOOR(RAND(b.ID)*100000))

Кажется, это работает нормально, но я обеспокоен тем, что два (или более) разных ID могут соответствовать одному и тому же RandomID. Если это произойдет, то запрос выдаст неточные результаты, поскольку таблицы JOIN редактируются на основе RandomID.

Является ли это действительной проблемой? Если да, то как это можно преодолеть?


PS Некоторый контекст:

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

Для ясности, мне не важно , какие строки выбраны, если гарантированнобыть одним для каждого CATEGORY, с одним условием: я хочу, чтобы выбранные строки были «примерно» равномерно распределены на основе TIMESTAMP. Вот почему я коррелирую данные из случайных значений, а не из TIMESTAMP. (Например, корреляция через MIN(TIMESTAMP) приведет к более высокой плотности строк в начале месяца.) Учитывая, что у меня есть тысячи категорий в месяц, выбор строк псевдослучайно должен, как правило, приводить к равномерному распределению TIMESTAMPS (чтомоя цель).

Еще одно примечание: я хочу, чтобы запрос был повторяемым (т.е. я хочу, чтобы он всегда выбирал одни и те же псевдослучайные значения.) Поэтому решения, включающие такие вещи, какnewid() не подходит.


В соответствии с запросом, вот примерные данные.

TIMESTAMP       | ID | CATEGORY
-------------------------------
10/21/19 1:00AM | 1  | A
10/21/19 2:00AM | 2  | B
10/21/19 3:00AM | 3  | A
10/21/19 4:00AM | 4  | B
10/21/19 5:00AM | 5  | A
10/21/19 6:00AM | 6  | B

Один из возможных выходных данных (зависит от того, что именно RAND() выбирает):

TIMESTAMP       | ID | CATEGORY
-------------------------------
10/21/19 3:00AM | 3  | A
10/21/19 6:00AM | 6  | B

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

Ответы [ 3 ]

2 голосов
/ 21 октября 2019

Производительность может быть проблемой с этим методом.

declare @mytable table (timestamp datetime, ID int, category varchar(150))

insert into @mytable
values ('10/21/19 1:00AM', 1, 'A'),
    ('10/21/19 2:00AM', 2, 'B'),
    ('10/21/19 3:00AM', 3, 'A'),
    ('10/21/19 4:00AM', 4, 'B'),
    ('10/21/19 5:00AM', 5, 'A'),
    ('10/21/19 6:00AM', 6, 'A'),
    ('10/21/19 7:00AM', 7, 'A'),
    ('10/21/19 8:00AM', 8, 'A'),
    ('10/21/19 9:00AM', 9, 'A'),
    ('10/21/19 10:00AM', 10, 'A'),
    ('10/21/19 11:00AM', 11, 'A'),
    ('10/21/19 12:00AM', 12, 'A'),
    ('10/21/19 1:00PM', 13, 'A'),
    ('10/21/19 2:00PM', 14, 'A'),
    ('10/21/19 3:00PM', 15, 'A'),
    ('10/21/19 4:00PM', 16, 'A'),
    ('10/21/19 5:00PM', 17, 'A'),
    ('10/21/19 6:00PM', 18, 'A'),
    ('10/21/19 7:00PM', 19, 'A'),
    ('10/21/19 8:00PM', 20, 'A'),
    ('10/21/19 6:00PM', 21, 'B')

select timestamp, id, category
from (
   select *, row_number() over (partition by category order by newid()) rown
   from @mytable
) a
where rown=1

Я думаю, вы также можете использовать свой случайный код. Я не знаю, как 2 метода будут сравниваться для распространения. РЕДАКТИРОВАТЬ Я добавил ID к заказу. Это делает результат повторяемым даже в (очень) маловероятном случае, когда случайный код столкнулся.

...
select timestamp, id, category
from (
   select *, row_number() over (partition by category order by RAND(ID)*100000-FLOOR(RAND(ID)*100000),ID) rown
   from @mytable
) a
where rown=1
0 голосов
/ 21 октября 2019

(отвечая на мой вопрос)

Через пару часов я нашел какое-то странное решение, но оно решает проблему, перечисленную в вопросе.

Решениечтобы соединить случайно сгенерированное число с идентификатором, то после выполнения функции агрегирования вычеркнуть часть строки, содержащую случайное число, чтобы получить исходный идентификатор, т. е.

SELECT b.*
FROM
(
  SELECT
  MIN(
    RIGHT(
     CAST(
      CAST(
       RAND(ID)                   -- 1. Get pseudo-random number   (e.g. 0.01234)
      AS decimal(10,10))          -- 2. Get 10 decimal places      (e.g. 0.0123456789)
     AS varchar(20)),             -- 3. Cast it to varchar         (e.g. '0.0123456789')
    4)                            -- 4. Get only the last 4 digits (e.g. '6789')
   + '_' + CAST(ID as varchar(3)) -- 5. Append underscore and ID   (e.g. '6789_1')
  ) [RandomID]
  FROM MYTABLE
  GROUP BY CATEGORY
) a
INNER JOIN
MYTABLE b ON b.ID = 
CAST(SUBSTRING(a.RandomID,6,100) as int) -- Strip away first 5 chars to get ID back

. Это решаетпроблема обоих:

  1. Выбор псевдослучайных строк с помощью GROUP BY CATEGORY

  2. Гарантирует, что ID в JOIN коррелируетвернуться к оригиналу ID, соответствующему RandomID.

0 голосов
/ 21 октября 2019

Это не ответ на ваш вопрос. Просто ваш подход не выглядит многообещающим.

rand() в SQL Server не особенно хорош с последовательными идентификаторами. Рассмотрим этот код:

 select id, floor(RAND(ID)*100000)
 from (values (1), (2), (3), (4), (5), (6), (7), (8), (9)) v(id);

Возвращает:

id  (No column name)
1   71359
2   71361
3   71362
4   71364
5   71366
6   71368
7   71370
8   71372
9   71374

( Здесь - это db <> скрипка).

Это неточно дублируетНо они не являются версией большинства людей "случайным". Я спросил, что вы действительно пытаетесь сделать, потому что может быть решение вашей проблемы. Ваш вопрос, однако, не дает четкого объяснения цели кода.

...