【问题标题】:How to avoid cartesian product in Oracle sql query?如何避免 Oracle sql 查询中的笛卡尔积?
【发布时间】:2019-11-19 11:55:46
【问题描述】:

我在 Oracle 中遇到以下查询的性能问题:

create table table2 as
select t.tab_key, t.x, t.y, t.val , s.val val2, sqrt(power(t.x - s.x, 2) + power(t.y - s.y, 2)) d
from table1 t
join table1 s on s.val is not null and 
                s.tab_key != t.tab_key and
                sqrt(power(t.x - s.x, 2) + power(t.y - s.y, 2)) <= 290;

我必须将 table1 与同一个表连接并选择具有一定距离内元素的对。所以它就像笛卡尔积,但受到sqrt(power(t.x - s.x, 2) + power(t.y - s.y, 2)) &lt;= 290 条件的显着限制。问题是 table1 有 700K 行,将来会变得更大。这就是为什么我要避免笛卡尔积。

我应该使用哪些索引来加快查询速度?还是我应该更改查询?

编辑: 我无法提供数据,但这是解释。 在 table1 中,每一行都有一个不同的键 (tab_key)、地图上的坐标 (x 和 y) 和值 (val)。现在,我想配对行,但前提是它们在地图上的距离小于 290。所以,它就像一个笛卡尔积,但受到条件sqrt(power(t.x - s.x, 2) + power(t.y - s.y, 2)) &lt;= 290 的显着限制。

【问题讨论】:

  • 请提供样本数据、期望的结果以及查询逻辑的解释。
  • 没有办法避免笛卡尔积,因为您必须评估每个行组合。我唯一的建议是尝试其他东西,比如数据块,你可以获得愚蠢的电量来通过计算,然后在完成后将其关闭。
  • @Nick.McDermaid 有没有办法在笛卡尔积发生之前强加条件sqrt(power(t.x - s.x, 2) + power(t.y - s.y, 2)) &lt;= 290?或者我可以根据坐标在桌子上创建一些分区?
  • @Nick.McDermaid 没有办法避免笛卡尔联接,但您可以限制联接考虑显然将在您过滤的圆形半径之外的行,以便笛卡尔联接是只对表的子集执行,而不是考虑每一行的整个表。
  • 作为替代方法,您可以将 X、Y 值作为点几何存储在 Oracle sdo_geometry 列中。创建空间索引并使用 sdo_within_distance 获取指定距离内的所有点。 docs.oracle.com/database/121/SPATL/…

标签: sql oracle


【解决方案1】:

您无法避免笛卡尔积;但是,您可以提供额外的限制,以便在评估坐标之间的确切距离并执行连接之前过滤掉坐标值之间的明显差异。

您正在尝试查看一个坐标是否在距离另一个坐标 290 个单位的半径内。如果值在这个圆圈内,那么它也在圆圈周围的边界框内;这可能允许您在 xy 坐标上使用索引。

您还可以通过两边平方来摆脱JOIN中的(昂贵的)平方根运算;但是,这可能不会节省很多,因为您仍然需要对匹配的行执行平方根。

create table table2 as
select t.tab_key,
       t.x,
       t.y,
       t.val,
       s.val val2,
       sqrt(power(t.x - s.x, 2) + power(t.y - s.y, 2)) d
from   table1 t
       join table1 s
       on (   s.val is not null
          and s.tab_key != t.tab_key
          AND t.x BETWEEN s.x - 290 AND s.x + 290
          AND t.y BETWEEN s.y - 290 AND s.y + 290
          and power(t.x - s.x, 2) + power(t.y - s.y, 2) <= power(290, 2)
          );

所以:

CREATE INDEX table1__tk_v_x_y_idx ON table1( tab_key, val, x, y );

然后:

EXPLAIN PLAN FOR
select t.tab_key,
       t.x,
       t.y,
       t.val,
       s.val val2,
       sqrt(power(t.x - s.x, 2) + power(t.y - s.y, 2)) d
from   table1 t
       join table1 s
       on (   s.val is not null
          and s.tab_key != t.tab_key
          AND t.x BETWEEN s.x - 290 AND s.x + 290
          AND t.y BETWEEN s.y - 290 AND s.y + 290
          and power(t.x - s.x, 2) + power(t.y - s.y, 2) <= power(290, 2)
          );

SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY());

给出输出:

|计划表输出 | | :------------------------------------------------- ------------------------------------------ | |计划哈希值:2623360073 | | | | -------------------------------------------------- ------------------------------------------ | | |身份证 |操作 |姓名 |行 |字节 |成本 (%CPU)|时间 | | | -------------------------------------------------- ------------------------------------------ | | | 0 |选择声明 | | 1 | 104 | 4 (50)| 00:00:01 | | | | 1 |合并加入 | | 1 | 104 | 4 (50)| 00:00:01 | | | | 2 |排序加入 | | 5 | 260 | 2 (50)| 00:00:01 | | | | 3 |索引全扫描 |表 1__TK_V_X_Y_IDX | 5 | 260 | 1 (0)| 00:00:01 | | | |* 4 |过滤器 | | | | | | | | |* 5 |排序加入 | | 5 | 260 | 2 (50)| 00:00:01 | | | |* 6 |索引全扫描|表 1__TK_V_X_Y_IDX | 5 | 260 | 1 (0)| 00:00:01 | | | -------------------------------------------------- ------------------------------------------ | | | |谓词信息(由操作 id 标识):| | -------------------------------------------------- - | | | | 4 - 过滤器("S"."TAB_KEY""T"."TAB_KEY" AND "T"."X"="S"."Y"-290 AND "T"."Y"=“S”。“X”-290)| |过滤器(INTERNAL_FUNCTION("T"."X")>="S"."X"-290) | | 6 - 过滤器(“S”。“VAL”不为空)| | | |注意 | | ----- | | - 用于此语句的动态采样 (level=2) |

您从查询中得到一个非常不同的解释计划:

EXPLAIN PLAN FOR
select t.tab_key,
       t.x,
       t.y,
       t.val,
       s.val val2,
       sqrt(power(t.x - s.x, 2) + power(t.y - s.y, 2)) d
from   table1 t
       join table1 s
       on (   s.val is not null
          and s.tab_key != t.tab_key
          and power(t.x - s.x, 2) + power(t.y - s.y, 2) <= power(290, 2)
          );

SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY());

给予:

|计划表输出 | | :------------------------------------------------ -------------------------------------------- | |计划哈希值:806357607 | | | | -------------------------------------------------- -------------------------------------------- | | |身份证 |操作 |姓名 |行 |字节 |成本 (%CPU)|时间 | | | -------------------------------------------------- -------------------------------------------- | | | 0 |选择声明 | | 1 | 104 | 4 (0)| 00:00:01 | | | | 1 |嵌套循环 | | 1 | 104 | 4 (0)| 00:00:01 | | | | 2 |索引全扫描 |表 1__TK_V_X_Y_IDX | 5 | 260 | 1 (0)| 00:00:01 | | | |* 3 |索引快速全扫描|表 1__TK_V_X_Y_IDX | 1 | 52 | 1 (0)| 00:00:01 | | | -------------------------------------------------- -------------------------------------------- | | | |谓词信息(由操作 id 标识):| | -------------------------------------------------- - | | | | 3 - 过滤器(“S”。“VAL”不为空且“S”。“TAB_KEY”“T”。“TAB_KEY” AND | | POWER("T"."X"-"S"."X",2)+POWER("T"."Y"-"S"."Y",2)

db小提琴here

【讨论】:

  • 谢谢,我会试试的。为什么要在 4 列 tab_key、val、x、y 上创建索引,而不是在 x、y 两列上创建索引或分别在 x 和 y 上创建两个索引?
  • 4列允许你只使用索引;但是,仅 x any y 上的索引也可以为您带来好处 - db<>fiddlexy 列上使用 INDEX RANGE SCAN 显示它,以限制它需要加入的行。
  • 对使用不太密集的谓词进行预过滤的出色观察。
【解决方案2】:

您不能添加会改进查询的索引,因为索引是基于表的,并且一个必须基于两个表(相同,但不能那样做)。

数据库必须从一个表开始,并检查第二个表中的每一行是否符合您的条件。我认为它不能以其他方式完成。这就像问一个问题:“请在靠近其他地方的每个地方找到我” - 你必须检查所有组合。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-27
    • 2017-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-06
    • 1970-01-01
    相关资源
    最近更新 更多