【问题标题】:Oracle SQL - Efficient Join N Rows Per MatchOracle SQL - 每次匹配高效连接 N 行
【发布时间】:2017-09-13 16:55:15
【问题描述】:

基本思想是连接两个表,我们在字段JOINID 上分别称它们为MYTABLE1MYTABLE2。每个JOINID 会有很多匹配项(MYTABLE1 中的一行对应MYTABLE2 中的很多行,出于测试目的,MYTABLE1 有 50 行),但我们只想选择最多 N每个 JOINID 值匹配。我见过很多低效的解决方案,例如:

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 TABLE1 的每一行,其中有 50 行,总执行时间为 1.5 秒。

【问题讨论】:

  • 您提到 MYTABLE1 有 50 行。 MYTABLE2 中有多少行?
  • 10^8 的顺序。
  • 谢谢阿波利斯。这有助于判断事情。好的,我会尝试做一个实验,也许会添加一些东西。

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


【解决方案1】:

我不会以任何方式将以下方法称为有效的方法,它有点作弊并且有些笨拙,但它低于您提供的 1500 毫秒限制,因此我将添加一些需要考虑的内容。

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

这种方法只是使用匿名 subquery-factoring-clause 函数迭代地探测 MYTABLE2MYTABLE1 中的每个 JOINKEY 并累积结果。

我不知道所涉及的表的真实结构,所以这个例子假设MYTABLE2 有一个额外的CHAR 属性,称为OTHER_DATA,它是SELECT 的目标。

首先,设置测试表:

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

然后添加测试数据。 50 行到 MYTABLE1 和 100M 行到 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;
/

然后运行一个使用匿名子查询因子块函数的查询,该函数对每个 JOINKEY 强制返回一个行数。在第一个示例中,它为每个 JOINKEY 获取两个 MYTABLE2 行:

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

返回:

...
50        K           
50        L           
50        M           
650 rows selected. 

【讨论】:

  • 很好,我也决定尝试一种程序方法;一旦我有时间看看它是否对我有意义,我将不得不阅读你的解决方案。只有一个问题:您认为在非过程 SQL 中是不可能的,还是更难?
  • 感谢@Apollys 我不知道是否不可能在 Oracle 中获得每个加入的停止键来执行这种限制。我从来没有遇到过它,我想不出一种近似它的方法,但这是一个有趣的想法。如果像“INNER JOIN LIMIT 5”之类的东西或任何存在的东西,它会很方便并在这种情况下降低成本。如果对这种方法有任何疑问,请告诉我。我没有看到标记了特定的 oracle 版本,所以我在这个实验中只使用了 12cR1。
  • 好的,感谢您在这个问题上的辛勤工作 :) 干杯 alexgibbs!
【解决方案2】:

您无法比较这两个查询。第二个查询只是返回首先出现的任何行。第二个必须通过所有数据返回任何东西。更恰当的比较是:

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 有另一个键,那也应该在查询中。

【讨论】:

  • 您似乎错过了重点......我正在与第二个查询进行比较,因为它提供了我需要的功能。我可以手动遍历MYTABLE1 的每一行并获得我需要的结果,如何在单个查询中实现相同的结果?
猜你喜欢
  • 1970-01-01
  • 2015-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-19
  • 2016-12-04
  • 2019-10-26
相关资源
最近更新 更多