Как распределяется получение одной случайной строки в Oracle с помощью этого оператора SQL? - PullRequest
1 голос
/ 19 апреля 2020

Мы пытаемся вытащить полуслучайную строку из Oracle. (Нам не нужна совершенно случайная строка, которая соответствует строгой статистической проверке, но нам хотелось бы, чтобы что-то получило шанс получить любую строку в таблице, даже если она может иметь некоторую степень перекоса.)

Мы используем этот подход:

SELECT PERSON_ID FROM ENCOUNTER SAMPLE(0.0001) WHERE EXTRACT(YEAR FROM REG_DT_TM) = 2020 AND ROWNUM = 1

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

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

Как часто? Если это утверждение верно, то насколько чаще значения берутся из верхней части таблицы? В нашей типичной таблице десятки миллионов строк (иногда миллиарды). Существует ли простой heuristi c или приблизительная оценка, чтобы понять перекос в распределении, которое мы можем ожидать?

Мы просим перекос, потому что другие методы недостаточно быстры для нашего варианта использования. Мы избегаем использования ORDER, потому что исходные таблицы могут быть настолько большими (т.е. миллиарды строк), что сервер отчетов будет работать в течение нескольких часов или может истечь время ожидания, прежде чем мы получим ответ. Таким образом, наше ограничение заключается в том, что нам нужно использовать такие подходы, как SAMPLE, которые отвечают с небольшими накладными расходами базы данных.

Ответы [ 3 ]

1 голос
/ 20 апреля 2020

Просто для забавы, вот альтернативный способ выбрать одну равномерно распределенную строку из (равномерно распределенной) "маленькой" выборки строк из таблицы.

Предположим, что в таблице миллионы или миллиарды строк, и мы используем предложение sample для выбора только небольшой, случайной (и предположительно равномерно распределенной) выборки строк. Допустим, размер выборки составляет 200 строк. Как мы можем выбрать одну строку из этих 200 таким образом, чтобы выбор не был смещен?

Как объяснил ОП, если мы всегда выбираем первую строку, сгенерированную в образце, которая имеет очень высокая вероятность быть предвзятым. Гордон Линофф показал совершенно верный способ исправить это. Здесь я опишу другой подход - который еще более эффективен, поскольку он генерирует только одно случайное число, и ему не нужно упорядочивать 200 строк. (По общему признанию, это не много накладных расходов, но это все равно может иметь значение, если запрос должен выполняться много раз.)

А именно: учитывая любые 200 строк, сгенерировать (надеюсь, равномерно распределенное) одно целое число от 1 до 200. Кроме того, по мере генерирования 200 строк одновременно захватывается ROWNUM. Тогда это так же просто, как выбрать строку, где ROWNUM = <the randomly generated integer>

К сожалению, предложение sample не генерирует фиксированное количество строк, даже если таблица и процент выборки фиксированы (и даже если статистика на столе текут). Таким образом, решение немного сложнее - сначала я генерирую образец, затем подсчитываю, сколько строк в нем содержится, а затем выбираю одну нужную строку.

В вывод будет включен столбец для "random номер строки "; если это проблема, просто перечислите столбцы из базовой таблицы вместо * в конечном запросе. Я предполагаю, что имя базовой таблицы t.

with
  p as ( select t.*, rownum as rn 
         from   t   sample(0.0001)
       )
, r as ( select trunc(dbms_random.value(1, (select count(*) from p) + 1)) as rn
         from   dual
       )
select p.*
from   p join r on p.rn = r.rn
;
1 голос
/ 28 апреля 2020

Неверно говорить «[SAMPLE] дает результаты с начала таблицы гораздо чаще», если вы не используете SAMPLE неправильно. Однако в некоторых необычных случаях предпочтение отдается более ранним строкам, если эти ранние строки намного больше, чем последующие.

SAMPLE Не Это Плохо

Если Если вы используете большой размер выборки, первые возвращенные строки появляются из "первых" строк таблицы. (Но таблицы неупорядочены, и хотя я наблюдаю такое поведение на моей машине, нет гарантии, что вы всегда увидите его.)

Следующий запрос действительно , кажется, хорошо справляется с выбором случайные строки, но только если вы посмотрите только на первые N возвращенных строк:

select * from test1 sample(99);

SAMPLE не идеален либо

В приведенном ниже тестовом примере показано, как может измениться размер строки результаты. Если вы вставите 10000 больших строк, а затем вставите 10000 маленьких строк, маленькая SAMPLE почти всегда будет возвращать только большие строки.

--drop table test1 purge;
create table test1(a varchar2(5), b varchar2(4000));

--Insert 10K large records.
insert into test1 select 'large', lpad('A', 4000, 'A') from dual connect by level <= 10000;

--Insert 10K small records.
insert into test1 select 'small', null from dual connect by level <= 10000;

--Select about 10 rows. Notice that they are almost always a "LARGE" row.
select * from test1 sample (0.1);

Однако перекос полностью исчезнет, ​​если вы вставите маленькие строки перед большими row.

Я думаю, что эти результаты означают, что SAMPLE основан на распределении данных по блокам (8 КБ данных), а не строго случайным образом для каждой строки. Если маленькие строки «спрятаны» в физически небольшой части таблицы, вероятность их появления гораздо ниже. Однако, Oracle всегда проверяет первую часть таблицы, и если там есть небольшие строки, то выборка распределяется равномерно. Строки должны быть очень хорошо скрыты, чтобы их не было.

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

1 голос
/ 19 апреля 2020

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

. Решение состоит в том, чтобы использовать выборку и затем случайную сортировку:

SELECT p.*
FROM (SELECT PERSON_ID
      FROM ENCOUNTER SAMPLE(0.0001)
      WHERE EXTRACT(YEAR FROM REG_DT_TM) = 2020
      ORDER BY dbms_random.value
     ) p
WHERE ROWNUM = 1
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...