Oracle 更快的重叠检查

Posted

技术标签:

【中文标题】Oracle 更快的重叠检查【英文标题】:Oracle faster overlap check 【发布时间】:2012-04-12 13:15:56 【问题描述】:

我有一个包含两列 beginrange 和 endrange 的表格。不应允许重叠范围。这些列上有索引,我们尝试了许多 sql 条件,例如

inputBegin between beginRange and endRange or
inputEnd between beginRange and endRange 

not ( inputEnd < beginRange or inputStart > endRange )

等 哪个工作正常,除了它们非常慢,因为表包含超过 500 万条记录。

有没有写一个更有效的重叠检查?

编辑: 我想到了另一种解决方案,oracle 仅当 count() 在具有索引的 NOT NULL 列上完成时才会计算索引。如果 beginRange 和 endRange 不是 NULL 并且都有一个索引,我们可以得到三个和:

count(endRange) where inputBegin > endRange
+
count(beginRange) where inputEnd < beginRange
=
count(beginRange/endRange)

所以使用 UNION ALL 我会得到三行,并且在代码中我需要检查前两个的总和是否等于第三行。当然,我假设只计算索引并且不会访问任何行。还有什么办法吗?

【问题讨论】:

当您说这些列上有索引时,您能否准确指定它们的索引,因为听起来您有 2 个单独的索引。 @Ben 它们只是列上的两个非唯一索引。 您是否尝试在(begin,end) 两列而不是(begin)(end) 每一列上添加索引? @Ben:很好的提示——我们有一个复合索引,它运行良好。我们还按begin 进行分区。 OP 可以发布计划给我们看... 这不是 DBA 问题。它只需要认识到没有一个索引可以帮助这两种情况。而是有两个索引和两个查询,然后将它们全部联合起来。 【参考方案1】:

我不确定你是否愿意:

    检查您要插入的行是否与某些现有行重叠,或者 搜索所有现有行并找出重叠的行?

如果 (1),那么您基本上已经在做的事情......

SELECT *
FROM YOUR_TABLE
WHERE :inputEnd > beginRange AND :inputStart < endRange;

...如果您有一个复合索引,其组件在相反方向:beginRange ASC, endRange DESC


如果 (2),那么您可以像这样使用窗口化:

SELECT *
FROM (
    SELECT
        YOUR_TABLE.*,
        LEAD(beginRange) OVER (ORDER BY beginRange) nextBeginRange
    FROM YOUR_TABLE
)
WHERE endRange > nextBeginRange;

这将为您提供与其下一个范围重叠的每个范围(其中“下一个”的含义在 beginRange 排序的上下文中定义)。

严格来说,这甚至不需要复合索引(除非你想要covering)——只需一个简单的beginRange 索引就可以确保良好的性能。

【讨论】:

一条评论清楚地表明 Op 需要你的答案 1。复合索引会有所帮助,但是,我不确定。第一个谓词将产生一个索引范围查找(表的开头)->(:inputEnd)。然而,第二个谓词很少使用索引中的第二个字段。相对较少的范围将同时开始,这使得第一个字段的选择性如此之高,以至于第二个字段也可能是随机的。需要进行一些额外的优化。 @Dems 我看到endRange 在执行计划中被用作索引扫描的访问(而不是过滤)谓词,我得出的结论是索引是最佳使用的,但是我越想想在这种特殊情况下如何使用索引必须,我认为你是对的。【参考方案2】:

这是一个答案 - 如果可以做出某些断言:

您有一个包含 beginRangeendRange 列的表,其中没有两个现有行重叠 (beginRange, endRange)

您想用(inputStart, inputEnd) 插入一个新行,但请检查它是否与表上的任何现有行重叠。

然后你可以使用这个应该很快的条件 - 在startRange上使用一个简单的索引:

WHERE input_Start <
      ( SELECT endRange
        FROM
          ( SELECT endRange
                 , ROW_NUMBER() OVER(ORDER BY startRange DESC) AS rn 
            FROM tableX
            WHERE startRange < input_End
          ) tmp
        WHERE rn = 1
      )


  --- TRUE  --> Overlaps
  --- FALSE --> No overlap

【讨论】:

在这种情况下,您当然应该在startrange,endrange 上有一个索引,因此您不需要在内部选择中通过rowid 访问表。你只能访问这个索引。 @Ben:是的,但它只会是单行读取 - 使用聪明的优化器。【参考方案3】:

没有一个索引可以满足这个查询。这实际上意味着您最好创建两个索引并运行两个查询,然后对结果进行 UNION...

1) 在 InputBegin 上创建索引 2) 在 InputEnd 上创建一个单独的索引 3)运行以下查询

SELECT * FROM yourTable WHERE InputEnd   < ExclusionPeriodStart 
UNION ALL
SELECT * FROM yourTable WHERE InputBegin > ExclusionPeriodEnd

然后,第一个查询可以在 InputEnd 索引上使用范围查找。 然后第二个查询也可以使用范围搜索,但在不同的索引上。

通过将查询分开,两种不同的需求不会相互干扰,并且可以使用最优化的索引。

您还已经知道(通过了解您的数据)结果中没有重叠(没有记录可以在完成之前开始,因此两个查询中都不能出现记录)。这意味着可以使用UNION ALL 代替较慢的UNION

据我所知,没有办法比这更快地执行此查询。 (对于 500 万条记录,只扫描小数据集上的整个表可能会更快。)

编辑该答案假定您正在尝试查找未出现在固定范围内的所有记录。如果您想对照其他记录检查每条记录,那么您需要一种不同的方法...

检查每个重叠的成本很高。此外,如果您有这四个范围,则无法确定要删除的范围...

1 -->--> 4
      3 -->--> 6
            5 -->--> 8
                  7 -->--> 9

应该删除范围 1 和 3,还是 2 和 4?

您可以做的是找到所有与另一个范围重叠的范围。

而你不想要的是发现A与B重叠,B与A重叠。

SELECT
  *
FROM
  yourTable   AS first_range
INNER JOIN
  yourTable   AS second_range
    ON  second_range.start_date >= first_range.start_date
    AND second_range.start_date <= first_range.end_date

这将需要扫描整个表的 first_range。但是因为您只检查第二个范围的 start_date,它将能够在 start_date 索引上使用范围搜索来检测任何冲突。

EDIT2:或者您可能需要与第一个答案相反的答案?

如果您希望所有确实与设定范围发生冲突的范围,则可以修改相同的方法。

SELECT * FROM yourTable WHERE InputEnd   >= ExclusionPeriodStart 
INTERSECT
SELECT * FROM yourTable WHERE InputBegin <= ExclusionPeriodEnd

但是,这可能不是很好。您将在 query1 中获取表的一部分,并将其与表的几乎所有其余部分相交。相反,您可以使用简单的方法,然后添加优化...

SELECT
  *
FROM
  yourTable
WHERE
    InputStart <= ExclusionPeriodEnd
AND InputEnd   >= ExclusionPeriodStart

WHERE 子句中的第一个条件可以通过范围查找来解决,然后扫描所有结果记录以测试第二个条件。那么,我们能否缩小需要扫描的范围(currently (start of table) -&gt; (ExclusionPeriodEnd))

我们可以 如果我们知道一条额外的信息:任何一个范围的最大长度...

SELECT
  *
FROM
  yourTable
WHERE
    InputStart <= ExclusionPeriodEnd
AND InputStart >= ExclusionPeriodStart - (maximumLength)
AND InputEnd   >= ExclusionPeriodStart

现在前两个条件形成一个范围搜索,并提供一个小得多的数据集来扫描最后一个条件。

你怎么知道最大长度?您可以扫描整个表,但这是一种自我挫败的优化尝试。

相反,您可以索引一个计算字段;给出范围最大长度的计算。 SELECT MAX(calculatedField) FROM yourTable 然后避免扫描整个表。或者您可以使用触发器跟踪。这对 INSERTS 来说很好,但是当你有一个 DELETE 时有点混乱(如果你删除最长的范围,你是否再次扫描整个表以找到新的最长范围?可能不是,你可能会想保留旧的最大长度而是)。

【讨论】:

查询也可以在没有联合的情况下编写,如WHERE ExclusionPeriodStart &lt;= InputEnd AND InputBegin &lt;= ExclusionPeriodEnd。我认为没有理由拆分为 2 个子查询和一个联合。 @ypercube - 这并没有给出相同的结果。你可以使用WHERE InputEnd &lt; ExclusionPeriodStart OR InputStart &gt; ExclusionPeriodEnd(假设你的意思是我的第一个答案,在编辑之前)。但这会扫描整个表/索引。没有索引有帮助 - 为一个谓词的利益排序会给出关于第二个谓词的有效随机排序。通过分离查询,您可以获得两个范围搜索。所以目的是产生 SEEK 而不是 SCAN。【参考方案4】:

假设现有范围不重叠,那么beginRange 应该是一个(主或备用)键,检测新范围是否与某些现有范围重叠可以这样完成:

SELECT *
FROM YOUR_TABLE
WHERE beginRange = (
    SELECT MAX(beginRange)
    FROM YOUR_TABLE
    WHERE beginRange < :inputEnd
)
AND :inputStart < endRange
如果新范围与某些现有范围重叠,则此查询返回“最高”范围。 如果没有重叠,则返回一个空结果集。

beginRange 键“下”的索引足以提高效率(我们只需要支持“MAX 扫描”即可)。

【讨论】:

以上是关于Oracle 更快的重叠检查的主要内容,如果未能解决你的问题,请参考以下文章

ARCGIS如何检查重叠面呢?

在plsql中的以下数据中查找重叠

arcgis 怎样检查出重叠区

分类重叠类型 - Oracle

如何检查两个小部件是不是在颤动中重叠?

检查表格的时间重叠?