我有一个包含两列beginrange和endrange的表。不应该允许重叠范围。这些列上有索引,我们尝试了许多sql条件,如
inputBegin between beginRange and endRange or
inputEnd between beginRange and endRange
not ( inputEnd < beginRange or inputStart > endRange )
等 哪个工作正常,除非它们非常慢,因为该表包含超过5mil的记录。
有没有写一个高效的重叠检查?
修改: 我想到了另外一个解决方案,只有当count()在带有索引的NOT NULL列上完成时,oracle才会计算索引。如果beginRange和endRange为NOT NULL且两者都有索引,我们可以有三个总和:
count(endRange) where inputBegin > endRange
+
count(beginRange) where inputEnd < beginRange
=
count(beginRange/endRange)
所以使用UNION ALL我会得到三行,在代码中我需要检查前两个的总和是否等于第三个。当然我假设只计算索引,不会访问任何行。还有别的吗?
答案 0 :(得分: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;
这将为您提供与其下一个范围重叠的每个范围(其中“next”的含义在beginRange
排序的上下文中定义)。
严格来说,这甚至不需要复合索引(除非你想要covering) - 只需{beginRange}
上的一个简单索引即可确保良好的性能。
答案 1 :(得分:1)
这是一个答案 - 如果可以做出某些断言:
您有一个包含beginRange
和endRange
列的表格,其中没有两个现有行重叠(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
答案 2 :(得分: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?
你可以做的是找到另一个范围与它们重叠的所有范围。
你不想要的是发现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
现在,前两个条件形成一个范围搜索,并提供一个小得多的数据集来扫描最后一个条件。
你怎么知道最大的lnegth?你可以扫描整个表格,但这是一次优化的自我失败尝试。
相反,您可以索引计算字段;给出范围最大长度的计算。 SELECT MAX(calculatedField) FROM yourTable
然后避免扫描整个表格。或者你可以用触发器跟踪。这对于INSERTS来说很好,但是当你有一个删除时有点麻烦(如果你删除最长的范围,你是否再次扫描整个表以找到新的最长范围?可能不是,你可能想要保持旧的最大长度代替)。
答案 3 :(得分:0)
假设现有范围不重叠,则{beginRange}
应该是(主要或备用)密钥,并检测新范围是否与某些现有范围重叠可以像这样:
SELECT *
FROM YOUR_TABLE
WHERE beginRange = (
SELECT MAX(beginRange)
FROM YOUR_TABLE
WHERE beginRange < :inputEnd
)
AND :inputStart < endRange
{beginRange}
键下面的索引足以提高效率(我们只需要支持“MAX扫描”)。