Oracle SQL - 首先选择不同的行

时间:2017-04-20 23:29:10

标签: sql oracle

在Oracle 12中,我们最终有一个限制功能,所以我们可以简单地

select distinct columnname from mytable fetch first n rows only;

但是,我目前仍然坚持使用以前的版本,并想知道如何实现这一结果。

理想情况下,查询应该立即返回行 ,即使对于一个巨大的表也是如此。它应该在找到N distinct后立即返回结果,而不是处理所有行。

2 个答案:

答案 0 :(得分:2)

我认为使用

.. where rownum < XXX

应该有效

也许

select * from (select distinct columnname from mytable) where  rownum < XXX

答案 1 :(得分:2)

没有任何版本的Oracle具有本机语法,可以以最佳方式返回不同的前N个。必须使用PL / SQL流水线函数手动创建此功能。

示例架构

此脚本创建一个包含一列的表,大约有1亿行,占用大约1GB的空间。

--drop table mytable purge;

create table mytable(columnname number not null) nologging;

insert /*+ append */ into mytable
select level from dual connect by level <= 100000;
commit;

begin
    for i in 1 .. 10 loop
        insert /*+ append */ into mytable select * from mytable;
        commit;
    end loop;
end;
/

begin
    dbms_stats.gather_table_stats(user, 'MYTABLE');
end;
/

--1.25GB.
select bytes/1024/1024/1024 gb from dba_segments where segment_name = 'MYTABLE';

Oracle 12c行限制子句与distinct不兼容。

新的12c语法在大约20秒内持续运行以返回少量行:

select distinct columnname from mytable fetch first 10 rows only;

该语句读取整个表,散列整个表,然后抓取前N行:

explain plan for
select distinct columnname from mytable fetch first 10 rows only;

select * from table(dbms_xplan.display(format => 'basic'));

Plan hash value: 239985407

------------------------------------------
| Id  | Operation              | Name    |
------------------------------------------
|   0 | SELECT STATEMENT       |         |
|   1 |  VIEW                  |         |
|   2 |   WINDOW NOSORT STOPKEY|         |
|   3 |    VIEW                |         |
|   4 |     HASH UNIQUE        |         |
|   5 |      TABLE ACCESS FULL | MYTABLE |
------------------------------------------

由Ed Heal创建的Oracle 11g版本令人惊讶地工作更好!它运行大约12秒钟。

select * from (select distinct columnname from mytable) where  rownum < 10;

即使速度更快,12秒仍然很糟糕。无论我的CPU或I / O性能如何,如果算法需要几秒而不是几毫秒,那么算法必定是错误的。

事实上,这个计划看起来好一点。它在计划中有SORT GROUP BY STOPKEY个低点。这会在处理所有内容之前停止查询。但它仍然停止太晚了。 (也许Oracle仍在阅读整个表格,但只对它进行排序?)

explain plan for
select * from (select distinct columnname from mytable) where  rownum < 10;

select * from table(dbms_xplan.display(format => 'basic'));

Plan hash value: 3842480186

-------------------------------------------
| Id  | Operation               | Name    |
-------------------------------------------
|   0 | SELECT STATEMENT        |         |
|   1 |  COUNT STOPKEY          |         |
|   2 |   VIEW                  |         |
|   3 |    SORT GROUP BY STOPKEY|         |
|   4 |     TABLE ACCESS FULL   | MYTABLE |
-------------------------------------------

流水线功能

由于几个原因,这是一个丑陋的解决方案。它需要不同结果集的新代码和对象。它可能无法很好地扩展 - 该函数有一个集合来存储以前的结果,如果该集合变得庞大会发生什么?

每种不同的结果类型都需要新对象:

--Create an object to hold a record with the result columns.
--(Not necessary for this simple example since there's only one column, but will
-- be necessary if there are multiple columns.)
create or replace type columnname_rec is object
(
    columnname number
);

--Create an object to hold a table of the records.
create or replace type columnname_tab is table of columnname_rec;

另一个返回不同类型的函数:

--Function that returns the distinct Top N as soon as they are found.
create or replace function fast_distinct_top_n(p_n number, p_cursor in sys_refcursor) return columnname_tab pipelined is
    v_columnname number;
    v_distinct_count number := 0;

    type previous_values_type is table of varchar2(4000) index by varchar2(4000);
    v_previous_values previous_values_type;
begin
    loop
        --Get new value.
        fetch p_cursor into v_columnname;

        --If the new value does not exist...
        if not v_previous_values.exists(v_columnname) then
            --Save the new value.
            v_previous_values(v_columnname) := v_columnname;
            --Increment the counter.
            v_distinct_count := v_distinct_count + 1;
            --Return the value
            pipe row(columnname_rec(v_columnname));
            --Exit if the counter is more than the top N.
            exit when v_distinct_count >= p_n;
        end if;
    end loop;
end;
/

但最后我们创建了一个查询,以毫秒为单位返回不同的前N个结果。

select * from table(fast_distinct_top_n(10, cursor(select * from mytable)));

如果您无法创建如此多的对象,则可能有一种方法可以使用Method4使其成为通用对象。但是这个解决方案仍然很复杂。