4

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

inputBegin between beginRange and endRange or
inputEnd between beginRange and endRange 

not ( inputEnd < beginRange or inputStart > endRange )

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

反正有没有写一个非常有效的重叠检查?

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

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

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

4

4 回答 4

1

我不确定您是否要:

  1. 检查您要插入的行是否与某些现有行重叠,或者
  2. 搜索所有现有行并识别那些重叠的行?

如果(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排序的上下文中定义)。

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

于 2012-04-12T19:34:00.630 回答
1

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

您有一个带有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
于 2012-04-13T23:34:03.980 回答
0

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

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

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

于 2012-04-14T05:58:28.243 回答
0

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

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) -> (ExclusionPeriodEnd))

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

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

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

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

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

于 2012-04-12T14:00:22.597 回答