Oracle SQL - 每次匹配有效加入N行

时间:2017-04-18 01:42:52

标签: sql oracle greatest-n-per-group

基本想法是加入两个表格,让我们在字段MYTABLE1上称他们为MYTABLE2JOINID。每个JOINID会有很多匹配(MYTABLE1中的一行与MYTABLE2中的许多行相对应,为了测试,MYTABLE1有50行),但是我们希望每个JOINID值最多只能选择N个匹配项。我见过很多低效的解决方案,例如:

select t1.*, t2.*
from MYTABLE1 t1 inner join
  (select MYTABLE2.*,
    row_number() over (partition by MYTABLE2.JOINKEY order by 1) as seqnum
  from MYTABLE2) t2
  on t1.joinkey = t2.joinkey and seqnum <= 2;

我需要花费5分钟才能运行并返回少于100个结果,而类似

select t1.*, t2.* 
  from MYTABLE1 t1 inner join MYTABLE2 t2 on t1.JOINKEY = t2.JOINKEY
  where rownum <= 100;

在~60毫秒内返回100个结果。

(为了确保这些结果的有效性,我选择了一个不同的测试表,并在特定的单个JOINKEY上执行了上面的第二个查询,直到我得到的结果集少于100个结果,这意味着它确实实际上搜索所有MYTABLE2。总查询时间是30毫秒。之后,我开始了原始查询,但这次获得了每行MYTABLE1的50个连接,这又花了5分钟到完成)。

我怎样才能以一种非常低效的方式处理这个问题?

看起来很简单,我们需要做的就是浏览MYTABLE1行并将JOINKEY字段与MYTABLE2行匹配,然后转到下一行一旦我们匹配了该行的所需数字,就MYTABLE1

在我的第二个示例的最坏情况下,我们应该花费30毫秒搜索每行TABLE2的{​​{1}},其中有50行,总执行时间为1.5秒。

2 个答案:

答案 0 :(得分:1)

我不会以任何方式将下面的方法称为高效并且它有点欺骗并且有一些笨拙,但它在你提供的1500ms限制之内,所以我将添加作为需要考虑的事情。

这个例子欺骗它编译TYPE,因此它可以表示一个匿名函数。

这种方法只是使用匿名子查询因子子句函数对来自MYTABLE2的每个JOINKEY进行迭代探测MYTABLE1,并在结果中累积结果。

我不知道所涉及的表的真实结构,因此这个示例假装MYTABLE2还有一个名为CHAR的{​​{1}}属性,它是{{OTHER_DATA的目标。 1}}。

首先,设置测试表:

SELECT

然后添加测试数据。 50行到CREATE TABLE MYTABLE1 ( JOINKEY NUMBER NOT NULL ); CREATE TABLE MYTABLE2 ( JOINKEY NUMBER NOT NULL, OTHER_DATA CHAR(1) NOT NULL ); CREATE INDEX MYTABLE2_I ON MYTABLE2 (JOINKEY); 和100M行到MYTABLE1

MYTABLE2

然后收集统计数据......

验证表计数:

INSERT INTO MYTABLE1
  SELECT LEVEL
  FROM DUAL
  CONNECT BY LEVEL < 51;

BEGIN
  <<COMMIT_LOOP>>
  FOR OUTER_POINTER IN 1..4000 LOOP
  <<DATA_LOOP>>
    FOR POINTER IN 1..10 LOOP
      INSERT INTO MYTABLE2
        SELECT
          JOINKEY, OTHER_DATA
        FROM
          (SELECT LEVEL AS JOINKEY FROM DUAL CONNECT BY LEVEL < 51)
          CROSS JOIN
          (SELECT CHR(64 + LEVEL) AS OTHER_DATA FROM DUAL CONNECT BY LEVEL < 51);
    END LOOP DATA_LOOP;
    COMMIT;
  END LOOP COMMIT_LOOP;
END;
/

然后创建一个包含所需数据的SELECT COUNT(*) FROM MYTABLE1; 50 SELECT COUNT(*) FROM MYTABLE2; 100000000

TYPE

然后运行一个使用匿名子查询因子块函数的查询,该函数强制每个CREATE OR REPLACE TYPE JOINKEY_OTHER_DATA IS OBJECT (JOINKEY1 NUMBER, OTHER_DATA CHAR(1)); / CREATE OR REPLACE TYPE JOINKEY_OTHER_DATA_LIST IS TABLE OF JOINKEY_OTHER_DATA; / 返回一个rowcount。在第一个示例中,它按JOINKEY

获取两个MYTABLE2
JOINKEY

结果:

SELECT SYSTIMESTAMP FROM DUAL;

WITH FUNCTION FETCH_N_ROWS
(P_MATCHES_LIMIT IN NUMBER)
              RETURN JOINKEY_OTHER_DATA_LIST
              AS
              V_JOINKEY_OTHER_DATAS JOINKEY_OTHER_DATA_LIST;
BEGIN
  V_JOINKEY_OTHER_DATAS := JOINKEY_OTHER_DATA_LIST();
  FOR JOINKEY_POINTER IN (SELECT MYTABLE1.JOINKEY
                          FROM MYTABLE1)
  LOOP
    DECLARE
      V_MYTABLE2_JOINKEYS JOINKEY_OTHER_DATA_LIST;
    BEGIN
    SELECT JOINKEY_OTHER_DATA(MYTABLE2.JOINKEY, MYTABLE2.OTHER_DATA)
    BULK COLLECT INTO V_MYTABLE2_JOINKEYS
    FROM MYTABLE2 WHERE MYTABLE2.JOINKEY = JOINKEY_POINTER.JOINKEY
    FETCH FIRST P_MATCHES_LIMIT ROWS ONLY;
    V_JOINKEY_OTHER_DATAS := V_JOINKEY_OTHER_DATAS MULTISET UNION ALL V_MYTABLE2_JOINKEYS;
      END;
  END LOOP;
  RETURN V_JOINKEY_OTHER_DATAS;
END;
SELECT *
FROM TABLE (FETCH_N_ROWS(2));
/

SELECT SYSTIMESTAMP FROM DUAL;

通过更改传递给SYSTIMESTAMP 18-APR-17 03.32.10.623056000 PM -06:00 JOINKEY1 OTHER_DATA 1 A 1 B 2 A 2 B 3 A 3 B ... 49 A 49 B 50 A 50 B 100 rows selected. SYSTIMESTAMP 18-APR-17 03.32.11.014554000 PM -06:00 的数字,可以获得具有相当一致性能的不同数据量。

FETCH_N_ROWS

返回:

...
SELECT * FROM TABLE (FETCH_N_ROWS(13));

答案 1 :(得分:0)

您无法比较这两个查询。第二个查询只是返回第一个行。第二个必须通过 all 数据返回任何内容。更合适的比较将使用:

select . . .
from (select t1.*, t2.* 
      from MYTABLE1 t1 inner join
           MYTABLE2 t2
           on t1.JOINKEY = t2.JOINKEY
      order by t1.JOINKEY
     ) t1
where rownum <= 100;

这必须在返回任何内容之前读取所有数据,因此它更类似于使用行号。

但是,从这个查询开始:

select t1.*, t2.*
from MYTABLE1 t1 inner join
     (select MYTABLE2.*,
             row_number() over (partition by t2.JOINKEY order by 1) as seqnum
      from MYTABLE2 t2
     ) t2
     on t1.joinkey = t2.joinkey and seqnum <= 2;

对于此查询,您需要MYTABLE2(JOINKEY)上的索引。如果ORDER BY有另一个密钥,那么它也应该在查询中。