下表每个id
和value
包含一条记录,它有数百条记录:
id | value
-----+------------
1 | 118.89
2 | 113.90
3 | 110.62
4 | 105.37
5 | 119.16
6 | 118.33
7 | 116.93
8 | 117.74
9 | 118.01
10 | 125.00
11 | 130.62
12 | 137.50
13 | 136.65
14 | 133.80
15 | 132.53
16 | 133.03
17 | 131.91
18 | 134.06
19 | 131.03
20 | 132.38
我正在寻找对此表具有良好性能的SQL查询,当id
更改为value
(浮点数)时,该行为我提供了连续n
的最小数量的行数字),移到任意一侧(+或-)。
例如,如果n=13.5
,则应显示具有id
4,5的行,如果n=19.2
,则应显示具有id
9-12的行。
答案 0 :(得分:1)
感谢您分享这个非常有趣的挑战。 下次,如果您可以提供以下信息,将非常有帮助: 完整的DDL,示例数据的实际INSERT语句,所需结果集的详细信息以及更详细的解释。 一些人发布答案,并在误解了问题后将其删除。
我写下了DDL并插入:
CREATE TABLE FOO
(ID INT PRIMARY KEY, Value DECIMAL(5,2));
INSERT INTO FOO (ID, Value)
VALUES ( 1 , 118.89 ),
( 2 , 113.90 ),
( 3 , 110.62 ),
( 4 , 105.37 ),
( 5 , 119.16 ),
( 6 , 118.33 ),
( 7 , 116.93 ),
( 8 , 117.74 ),
( 9 , 118.01 ),
( 10 , 125.00 ),
( 11 , 130.62 ),
( 12 , 137.50 ),
( 13 , 136.65 ),
( 14 , 133.80 ),
( 15 , 132.53 ),
( 16 , 133.03 ),
( 17 , 131.91 ),
( 18 , 134.06 ),
( 19 , 131.03 ),
( 20 , 132.38 );
SELECT *
FROM FOO;
我希望我能正确理解您的问题,所以这是我的解决方法。
在使用实际的SQL解决方案之前,我试图了解数学上的复杂性。 假设表中有10行。 不同顺序组的数目是自然数的发散序列或三角数。 它从1选项开始,表示1-10之间的10个连续行。 那么对于9个连续行中的任意一组,我们有2个选项:1-9和2-10。 然后,对于任何8行的组,依次为3。 任何长度的连续基团总数均可轻松计算。 如果它是一个完整的梯形数字,则讨论区将为n(n + 1)/ 2。 在这里,由于最小的组由2行组成,而不是1行,因此它的大小为(n-1)(n-1 + 1)/ 2 = n(n-1)/ 2。
我将为此使用SQL Server语法,因为我不喜欢使用PL / pgSQL,也没有太多经验。 欢迎对PL / pgSQL具有更多经验的人进行转换,应该不要太难。 我从来不明白为什么这么多RDBMS不允许在同一脚本范围内将命令性构造与SQL结合在一起。
我的第一个想法是尝试使用一种简单的,基于集合的方法来使用递归查询来计算所有可能的组, OVER子句的组大小不同。 对于500行,我们需要计算500 * 499/2组的总增量=〜125K。 如果我们可以做类似的事情,那将是很好的:
DECLARE @MaxGroupSize INT = (SELECT COUNT(*) FROM Foo);
DECLARE @Threshold DECIMAL(5,2) = 13.5;
WITH GroupDeltas
AS
(
SELECT 1 AS GroupSize,
ID,
CAST(( LEAD(Value)
OVER(ORDER BY ID ASC) - Value)
AS DECIMAL(38,2)) AS GroupDelta
FROM Foo
UNION ALL
SELECT (GroupSize + 1),
ID,
SUM(GroupDelta)
OVER ( ORDER BY ID ASC
ROWS BETWEEN CURRENT ROW AND 0 /*NO GO WITH (GroupSize - 2)*/ FOLLOWING)
FROM GroupDeltas
WHERE (GroupSize + 1) <= @MaxGroupSize
)
SELECT *
FROM GroupDeltas
WHERE ABS(GroupDelta) >= @Threshold
AND
GroupSize = (
SELECT MIN(GroupSize)
FROM GroupDeltas
WHERE GroupSize > 1 -- Eliminate Anchor
AND
ABS(GroupDelta) >= @Threshold
);
但是不幸的是,帧偏移必须使用一个常量表达式。 不允许变量或列表达式。 请注意,上面的查询适用于第一个示例,组大小为2, 但这仅仅是因为我使用了原义0偏移量,而不是不允许的(GroupSize-2)...
如果我们可以向递归成员添加停止条件,那就太好了
AND NOT EXISTS (
SELECT NULL
FROM GroupDeltas
WHERE ABS(GroupDelta) >= 13.5
)
但是我们只能引用一次递归成员中的CTE ...
无论如何,这种方法从一开始就行不通,所以我没有对其进行任何进一步的测试。
我只是在这里添加了它,作为我进行的一项有趣的智力锻炼。
这为我们提供了一种迭代方法。 由于您还要求提供“效果良好”的查询, 我认为我们可以不用计算所有可能的组就能逃脱。
我的想法是创建一个以最小的组大小开始的循环, 当我们打比赛时停下来。 我不想使用RBAR游标,所以我选择了更有效的窗口功能, 使用动态执行来规避偏移常数限制。 以下是我的尝试。 请注意,如果有超过1个满足阈值的组,则将同时显示两个。
DROP TABLE IF EXISTS #GroupDeltas;
GO
DECLARE @Threshold DECIMAL(5,2) = 19.2,
@MaxGroupSize INT = (SELECT COUNT(*) FROM FOO),
@GroupSize INT = 2, -- Initial Group Size
@SQL VARCHAR(1000);
CREATE TABLE #GroupDeltas
(
StartID INT,
GroupSize INT,
GroupDelta DECIMAL(9,2),
PRIMARY KEY (StartID, GroupSize)
);
WHILE @GroupSize <= @MaxGroupSize
BEGIN
SET @SQL = '
;WITH DeltasFromNext
AS
(
SELECT ID,
LEAD(Value) OVER(ORDER BY ID ASC) - Value AS Delta
FROM FOO
)
SELECT ID,
' + CAST(@GroupSize AS VARCHAR(5)) +',
SUM(Delta)
OVER ( ORDER BY ID
ROWS BETWEEN
CURRENT ROW AND
' + CAST(@GroupSize - 2 AS VARCHAR(5))
+ ' FOLLOWING)
FROM DeltasFromNext;
'
INSERT INTO #GroupDeltas
EXECUTE (@SQL);
IF EXISTS (
SELECT NULL
FROM #GroupDeltas
WHERE ABS(GroupDelta) >= @Threshold
)
BREAK;
SET @GroupSize += 1
END
SELECT *
FROM #GroupDeltas
WHERE ABS(GroupDelta) >= @Threshold
ORDER BY GroupSize, StartID;
PS: 反馈和改进建议非常欢迎。我发现这是一个非常有趣的练习,可能有更好的方法来实现它。 如果有时间,我可能会再次访问。
答案 1 :(得分:-1)
假设id
之间没有缝隙,则可以使用以下方法来实现:
select id, (next_id - id + 1) as cnt
from (select t.*,
(select min(t2.id)
from t t2
where t2.id > t.id and
t2.value > t.value + 13.5
) as next_id
from t
) t
order by cnt asc
fetch first 1 row only;
对于我来说,如何使用窗口功能并不明显。