在SQL中更新n个随机行

时间:2011-08-25 15:23:51

标签: sql oracle plsql

我有一个大约有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 个答案:

答案 0 :(得分:8)

我会使用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的实际原因不是效率(它仍会进行全表扫描) - 如果列m不唯一,您的SQL可能无法更新所需的行数。

只有1000行,你不应该真的担心效率(可能有一亿行)。如果没有此表中的任何索引,您将无法进行全表扫描以选择随机记录。

[编辑:] “但如果有10万行怎么办”

嗯,这仍然是3个数量级,低于1亿。

我运行了以下内容:

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秒钟(没有正式计时,它只花了足够的时间来眨眼)。

答案 1 :(得分:7)

您可以使用样本替换全表扫描来提高性能。

您遇到的第一个问题是您无法在DML子查询ORA-30560: SAMPLE clause not allowed中使用SAMPLE。但从逻辑上讲,这就是所需要的:

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

您可以通过使用集合来存储rowid,然后使用rowid集合更新行来解决此问题。通常将查询分解为单独的部分并将它们与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的解决方案慢。

对于性能问题,表的大小(以字节为单位)通常比行中的大小重要得多。使用1 TB空间的1000行远远超过仅使用1 GB的1亿行。

以下是我的答案需要考虑的一些问题:

  1. 抽样并不总是完全返回您要求的百分比。有100,000行和0.15%的样本大小,返回的行数是147,而不是150.这就是我使用0.15而不是0.10的原因。您需要对样品进行过量采样以确保获得超过N.您需要多少过量样品?我不知道,你可能要测试它并选择一个安全号码。
  2. 您需要知道选择百分比的大致行数。
  3. 百分比必须是文字,因此当行数和N发生变化时,您需要使用动态SQL来更改百分比。

答案 2 :(得分:2)

以下解决方案正常。它具有高性能,似乎与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子句中。否则,将过滤器谓词的优化程序从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.