Как сделать выбор случайных строк в оракуле быстрее с таблицей с миллионами строк

Есть ли способ сделать выбор случайных строк быстрее в оракуле с таблицей с миллионом строк. Я попытался использовать sample (x) и dbms_random.value и потратил на это много времени.

Спасибо!

Ответы

Ответ 1

Использование соответствующих значений sample(x) является самым быстрым способом. Он случайный и случайный случайный в блоках, поэтому, если вам нужна только одна случайная строка:

select dbms_rowid.rowid_relative_fno(rowid) as fileno,
       dbms_rowid.rowid_block_number(rowid) as blockno,
       dbms_rowid.rowid_row_number(rowid) as offset
  from (select rowid from [my_big_table] sample (.01))
 where rownum = 1

Я использую подсеризованную таблицу, и я получаю довольно хорошую случайность, даже хватая несколько строк:

select dbms_rowid.rowid_relative_fno(rowid) as fileno,
       dbms_rowid.rowid_block_number(rowid) as blockno,
       dbms_rowid.rowid_row_number(rowid) as offset
  from (select rowid from [my_big_table] sample (.01))
 where rownum <= 5

    FILENO    BLOCKNO     OFFSET
---------- ---------- ----------
       152    2454936         11
       152    2463140         32
       152    2335208          2
       152    2429207         23
       152    2746125         28

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

Ответ 2

Сначала начните с ответа Адама, но если SAMPLE просто недостаточно быстро, даже с оптимизацией ROWNUM, вы можете использовать образцы блоков:

....FROM [table] SAMPLE BLOCK (0.01)

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

Ответ 3

Здесь тот же вопрос в AskTom:

http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:6075151195522

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

Скопировано из: http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:6075151195522#56174726207861

create or replace function get_random_rowid
( table_name varchar2
) return urowid
as
sql_v varchar2(100);
urowid_t dbms_sql.urowid_table;
cursor_v integer;
status_v integer;
rows_v integer;
begin
  for exp_v in -6..2 loop
    exit when (urowid_t.count > 0);
    if (exp_v < 2) then
      sql_v := 'select rowid from ' || table_name
      || ' sample block (' || power(10, exp_v) || ')';
    else
      sql_v := 'select rowid from ' || table_name;
    end if;
    cursor_v := dbms_sql.open_cursor;
    dbms_sql.parse(cursor_v, sql_v, dbms_sql.native);
    dbms_sql.define_array(cursor_v, 1, urowid_t, 100, 0);
    status_v := dbms_sql.execute(cursor_v);
    loop
      rows_v := dbms_sql.fetch_rows(cursor_v);
      dbms_sql.column_value(cursor_v, 1, urowid_t);
      exit when rows_v != 100;
    end loop;
    dbms_sql.close_cursor(cursor_v);
  end loop;
  if (urowid_t.count > 0) then
    return urowid_t(trunc(dbms_random.value(0, urowid_t.count)));
  end if;
  return null;
exception when others then
  if (dbms_sql.is_open(cursor_v)) then
    dbms_sql.close_cursor(cursor_v);
  end if;
  raise;
end;
/
show errors

Ответ 4

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

Решение:

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

SELECT * FROM (SELECT * FROM table ORDER BY dbms_random.value) WHERE rownum = 1

Итак, если вы установите rownum, как показано ниже, вы можете обойти проблему производительности. Увеличивая rownum, вы можете уменьшить возможности. Но в этом случае вы всегда будете получать строки из тех же 1000 строк. Если вы получите строку из 1000 и обновите свой статус с помощью "USED", вы получите почти каждую строку при каждом запросе с помощью "ACTIVE"

SELECT * FROM
( SELECT * FROM table
where rownum < 1000
  and status = 'ACTIVE'
  ORDER BY dbms_random.value  )
WHERE rownum = 1

обновить статус строк после его выбора. Если вы не можете обновить это, значит, еще одна транзакция уже использовала его. Затем вы должны попытаться получить новую строку и обновить ее статус. Кстати, получение одной и той же строки по двум различным возможностям транзакции составляет 0,001, поскольку rownum - 1000.

Ответ 5

Кто-то сказал, что образец (x) - это самый быстрый способ. Но для меня этот метод работает немного быстрее, чем метод sample (x). Он должен взять часть второго (0,2 в моем случае) независимо от размера таблицы. Если требуется больше времени использовать подсказки (- + leading (e) use_nl (e t) rowid (t)), может помочь

SELECT *
  FROM My_User.My_Table
 WHERE ROWID = (SELECT MAX(t.ROWID) KEEP(DENSE_RANK FIRST ORDER BY dbms_random.value)
                  FROM (SELECT o.Data_Object_Id,
                               e.Relative_Fno,
                               e.Block_Id + TRUNC(Dbms_Random.Value(0, e.Blocks)) AS Block_Id
                          FROM Dba_Extents e
                          JOIN Dba_Objects o ON o.Owner = e.Owner AND o.Object_Type = e.Segment_Type AND o.Object_Name = e.Segment_Name
                         WHERE e.Segment_Name = 'MY_TABLE'
                           AND(e.Segment_Type, e.Owner, e.Extent_Id) =
                              (SELECT MAX(e.Segment_Type) AS Segment_Type,
                                      MAX(e.Owner)        AS Owner,
                                      MAX(e.Extent_Id) KEEP(DENSE_RANK FIRST ORDER BY Dbms_Random.Value) AS Extent_Id
                                 FROM Dba_Extents e
                                WHERE e.Segment_Name = 'MY_TABLE'
                                  AND e.Owner = 'MY_USER'
                                  AND e.Segment_Type = 'TABLE')) e
                  JOIN My_User.My_Table t
                    ON t.Rowid BETWEEN Dbms_Rowid.Rowid_Create(1, Data_Object_Id, Relative_Fno, Block_Id, 0)
                   AND Dbms_Rowid.Rowid_Create(1, Data_Object_Id, Relative_Fno, Block_Id, 32767))

Ответ 6

Версия с повторениями, когда строки не возвращаются:

WITH gen AS ((SELECT --+ inline leading(e) use_nl(e t) rowid(t)
                     MAX(t.ROWID) KEEP(DENSE_RANK FIRST ORDER BY dbms_random.value) Row_Id
                FROM (SELECT o.Data_Object_Id,
                             e.Relative_Fno,
                             e.Block_Id + TRUNC(Dbms_Random.Value(0, e.Blocks)) AS Block_Id 
                        FROM Dba_Extents e
                        JOIN Dba_Objects o ON o.Owner = e.Owner AND o.Object_Type = e.Segment_Type AND o.Object_Name = e.Segment_Name
                       WHERE e.Segment_Name = 'MY_TABLE'
                         AND(e.Segment_Type, e.Owner, e.Extent_Id) =
                            (SELECT MAX(e.Segment_Type) AS Segment_Type,
                                    MAX(e.Owner)        AS Owner,
                                    MAX(e.Extent_Id) KEEP(DENSE_RANK FIRST ORDER BY Dbms_Random.Value) AS Extent_Id
                               FROM Dba_Extents e
                              WHERE e.Segment_Name = 'MY_TABLE'
                                AND e.Owner = 'MY_USER'
                                AND e.Segment_Type = 'TABLE')) e
                JOIN MY_USER.MY_TABLE t ON t.ROWID BETWEEN Dbms_Rowid.Rowid_Create(1, Data_Object_Id, Relative_Fno, Block_Id, 0)
                                                  AND Dbms_Rowid.Rowid_Create(1, Data_Object_Id, Relative_Fno, Block_Id, 32767))),
  Retries(Cnt, Row_Id) AS (SELECT 1, gen.Row_Id
                             FROM Dual
                             LEFT JOIN gen ON 1=1
                            UNION ALL
                           SELECT Cnt + 1, gen.Row_Id
                             FROM Retries
                             LEFT JOIN gen ON 1=1
                            WHERE Retries.Row_Id IS NULL AND Retries.Cnt < 10)
SELECT *
  FROM MY_USER.MY_TABLE
 WHERE ROWID = (SELECT Row_Id
                  FROM Retries
                 WHERE Row_Id IS NOT NULL)

Ответ 7

Можете ли вы использовать псевдослучайные строки?

select * from (
  select * from ... where... order by ora_hash(rowid)
) where rownum<100