2012-04-12 65 views
4

我有一个表有两列beginrange和endrange。没有重叠的范围应该是allowed.There这些列的索引和我们试图像Oracle更快的重叠检查

inputBegin between beginRange and endRange or 
inputEnd between beginRange and endRange 

not (inputEnd < beginRange or inputStart > endRange)

等 其中做工精细,除非他们是作为表含有非常慢许多SQL条件超过5mil的记录。

无论如何写了一个很有效的重叠检查?

编辑: 我想到了一个更的解决方案,甲骨文将在数()上的索引NOT NULL列做只计算指数。如果beginRange范围和结束范围是NOT NULL并且都具有一个索引,我们可以有三个总和:

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

因此与UNION ALL我会得到三排,并在代码中,我需要检查,如果前两项的总和等于第三。当然,我假设只有索引将被计数,并且不会访问任何行。任何其他方式?

+0

当你说这些列有索引时,你可以指定它们是什么索引,因为它听起来像你有2个单独的索引。 – Ben 2012-04-12 13:22:41

+0

@Ben他们只是列上的两个非唯一索引。 – Rnet 2012-04-12 13:26:04

+0

您是否曾尝试在列('begin,end)'而不是每列'(begin)'和'(end)'上添加索引? – Ben 2012-04-12 13:27:57

回答

0

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

1)InputBegin
创建索引 2)InputEnd
3创建单独指数)运行以下查询

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

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

通过保持查询独立,两个不同的要求不会相互干扰,并且可以使用最优化的索引。

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

据我所知,没有办法比这更快地执行此查询。 (在5米记录,它可能更快地只是扫描对小数据集整个表。)


编辑这个回答假设你要查找不所有记录出现在一个固定的范围内。如果你想检查每个记录对每个其他记录,那么你需要一个不同的方法...

检查每个重叠是昂贵的。另外,如果你有这四个范围,制定出其删除是不可能的...

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

你应该删除范围1和3或2和4?

你可以做什么,是找到与他们有另一个范围重叠的所有范围。

,哪些是你不想要的是找到一个为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 

重叠这将necesarrily扫描整个表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 

现在前两个条件形成了一系列探索,并给要扫描最后一个条件的数据要小得多。

你怎么知道的最大lnegth关系吗?你可以扫描整个桌子,但这是一个自我挫败的尝试在优化。

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

+0

该查询也可以在不使用联合的情况下编写为WHERE ExclusionPeriodStart <= InputEnd AND InputBegin <= ExclusionPeriodEnd'。我没有看到分裂成2个子查询和联盟的理由。 – 2012-04-12 14:06:17

+0

@ypercube - 这并没有给出相同的结果。你可以使用WHERE InputEnd ExclusionPeriodStart或InputStart ExclusionPeriodEnd *(假设你的意思是我的第一个答案,在编辑之前)*。但是,这给出了整个表/索引的扫描。没有索引帮助 - 为了一个谓词的好处而进行排序,可以有效地随机地排序第二个谓词。通过分离查询,您可以获得两个范围搜索。所以最重要的是产生SEEK而不是SCAN。 – MatBailie 2012-04-12 14:46:30

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排序)。

严格来说,这甚至不需要复合索引(除非你想covering) - 只要{beginRange}上的简单索引应该可以确保体面的性能。

+0

评论清楚地表明,操作需要您的答案1.然而,综合指数将有所帮助,但我不确定。第一个谓词将产生索引范围查找(表的开始) - >(:inputEnd)。但是,第二个谓词可以很少使用索引中的第二个字段。相对较少的范围将同时开始,这使得第一个字段如此高度选择性以至于第二个字段也可以是随机的。需要一些额外的优化。 – MatBailie 2012-04-12 22:24:37

+0

@Dems我在执行计划中看到'endRange'被用作访问(而不仅仅是过滤器)索引扫描的谓词,并且我跳到了索引被最佳使用的结论,但是我更多地考虑如何使用索引_must_在这个特殊情况下,我认为你是对的。 – 2012-04-13 10:23:39

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 
+0

在这种情况下,你应该在'startrange,endrange'上有一个索引,所以你不需要在内部select中用'rowid'来访问表。那么你只能访问这个索引。 – Ben 2012-04-13 23:41:33

+0

@Ben:是的,但它只会是一行阅读 - 一个聪明的优化器。 – 2012-04-13 23:46:14

0

假设现有范围不重叠,然后{beginRange}应该是一个(主或替代)键并检测是否有新的范围将与一些现有的重叠,可以这样进行:

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

{beginRange}”键下的索引对于效率来说足够了(我们只需要支持“MAX扫描”)。