Обновить n случайных строк в SQL - PullRequest
6 голосов
/ 25 августа 2011

У меня есть таблица, которая имеет около 1000 строк. Мне нужно обновить столбец («X») в таблице до «Y» для n рядов ramdom. Для этого у меня может быть следующий запрос

update xyz set X='Y' when m in (
'SELECT m FROM (SELECT m
FROM xyz
order by dbms_random.value
) RNDM 
where rownum < n+1);

Есть ли другой эффективный способ написания этого запроса. Таблица не имеет индекса. Пожалуйста, помогите?

Ответы [ 3 ]

9 голосов
/ 25 августа 2011

Я бы использовал ROWID:

UPDATE xyz SET x='Y' WHERE rowid IN (
    SELECT r FROM (
        SELECT ROWID r FROM xyz ORDER BY dbms_random.value
    ) RNDM WHERE rownum < n+1
)

Фактическая причина, по которой я бы использовал ROWID, заключается не в эффективности (хотя он все равно выполнит полное сканирование таблицы) - ваш SQL может не обновлять желаемое количество строк, если столбец m не уникален.

Имея только 1000 строк, вам не стоит беспокоиться об эффективности (возможно, с сотней миллионов строк). Без какого-либо индекса в этой таблице вы застряли при полном просмотре таблицы, чтобы выбрать случайные записи.

[EDIT:] "Но что, если есть 100 000 строк"

Ну, это на 3 порядка меньше, чем 100 миллионов.

Я запустил следующее:

create table xyz as select * from all_objects;

[создал около 50 000 строк в моей системе - не индексируется, как ваша таблица]

UPDATE xyz SET owner='Y' WHERE rowid IN (
     SELECT r FROM (
          SELECT ROWID r FROM xyz ORDER BY dbms_random.value
     ) RNDM WHERE rownum < 10000
);
commit;

Это заняло приблизительно 1,5 секунды. Может быть, это была 1 секунда, может быть, до 3 секунд (формально это не время, просто потребовалось достаточно времени, чтобы мигнуть).

7 голосов
/ 26 августа 2011

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

Первая проблема, с которой вы столкнулись, заключается в том, что вы не можете использовать SAMPLE в подзапросе DML, ORA-30560: SAMPLE clause not allowed. Но по логике это то, что нужно:

UPDATE xyz SET x='Y' WHERE rowid IN (
    SELECT r FROM (
        SELECT ROWID r FROM xyz sample(0.15) ORDER BY dbms_random.value
    ) RNDM WHERE rownum < 100/*n*/+1
);

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

declare
    type rowid_nt is table of rowid;
    rowids rowid_nt;
begin
    --Get the rowids
    SELECT r bulk collect into rowids
    FROM (
        SELECT ROWID r
        FROM xyz sample(0.15)
        ORDER BY dbms_random.value
    ) RNDM WHERE rownum < 100/*n*/+1;

    --update the table
    forall i in 1 .. rowids.count
        update xyz set x = 'Y'
        where rowid = rowids(i);
end;
/

Я выполнил простой тест с 100 000 строк (для таблицы, содержащей только два столбца), и N = 100. Первоначальная версия заняла 0,85 секунды, ответ @ Gerrat занял 0,7 секунды, а версия PL / SQL - 0,015 секунды.

Но это только один сценарий, у меня недостаточно информации, чтобы сказать, что мой ответ всегда будет лучше. По мере увеличения N преимущество выборки теряется, и запись будет более значимой, чем чтение. Если у вас очень маленький объем данных, издержки переключения контекста PL / SQL в моем ответе могут сделать его медленнее, чем решение @ Gerrat.

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

Вот некоторые проблемы, которые следует рассмотреть с моим ответом:

  1. Выборка не всегда возвращает именно тот процент, который вы просили. При 100 000 строк и размере выборки 0,15% количество возвращаемых строк составило 147, а не 150. Именно поэтому я использовал 0,15 вместо 0,10. Вам нужно немного перепроверять, чтобы убедиться, что вы получите больше, чем N. Сколько нужно перепроверять? Я понятия не имею, вам, вероятно, придется проверить его и выбрать безопасный номер.
  2. Вам нужно знать приблизительное количество строк, чтобы выбрать процент.
  3. Процент должен быть литералом, поэтому при изменении количества строк и N вам потребуется использовать динамический SQL для изменения процента.
2 голосов
/ 17 января 2018

Следующее решение работает просто отлично. Он эффективен и, похоже, похож на sample():

create table t1 as 
    select level id, cast ('item'||level as varchar2(32)) item 
    from dual connect by level<=100000; 

Table T1 created.

update t1 set item='*'||item 
where exists (
    select rnd from (
        select dbms_random.value() rnd
        from t1
    ) t2 where t2.rowid = t1.rowid and rnd < 0.15
);

14,858 rows updated.

Elapsed: 00:00:00.717 

Учтите, что псевдоним rnd должен быть включен в предложение select. В противном случае изменяет предикат фильтра omptimizer с RND<0.1 на DBMS_RANDOM.VALUE()<0.1. В этом случае dbms_random.value будет выполняться только один раз.

Как уже упоминалось в ответе @JonHeller, лучшим решением остается блок кода pl / sql, поскольку он позволяет избежать полного сканирования таблицы. Вот мое предложение:

create or replace type rowidListType is table of varchar(18);  
/
create or replace procedure updateRandomly (prefix varchar2 := '*') is
    rowidList rowidListType;  
begin  
    select rowidtochar (rowid) bulk collect into rowidList
    from t1 sample(15)
    ;
    update t1 set item=prefix||item 
    where exists (
        select 1 from table (rowidList) t2
        where chartorowid(t2.column_value) = t1.rowid
    );
    dbms_output.put_line ('updated '||sql%rowcount||' rows.'); 
end;
/
begin  updateRandomly; end;
/ 

Elapsed: 00:00:00.293
updated 14892 rows.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...