超模糊间隙和岛屿分组问题
Ultra Fuzzy gaps and islands grouping problem
我有一堆测试数据。每个测试都进行了几十次,并且在 CTE 中计算了每个测试的平均值和误差范围。在下一步中,我想对每个子组进行 dense_rank 测试。下面是我要查找的数据子组和排名值的示例:
AvgScore StdErr DesiredRank
65550 2109 1
67188 2050 1
67407 2146 1
67414 1973 1
67486 1889 2
67581 2320 2
67858 1993 2
68509 2029 2
68645 2039 2
68868 2051 2
68902 1943 2
69305 1564 3
69430 2037 3
69509 1594 3
387223 12521 4
389709 12975 4
392200 11344 4
398916 11755 4
399018 11480 5
401144 11021 5
401640 10973 5
403442 10688 5
请注意,每个分数的误差范围使得许多分数表面上相当。是的,这会导致某些行在技术上属于多个组,但使其成为最近的组的一部分会给出最准确的结果。
我查看了 ,但这个版本似乎复杂得多,因为从一组切换到另一组不仅需要两行都在彼此的误差范围内,而且可能会在等效项之间发生切换行。
这是示例中出现的最复杂的情况:第 1 行的第 2-6 行在其范围内,尽管第 6 行也在第 1 行的范围内,但第 5 行的范围内没有第 1 行,因此新排名必须从第 5 行开始。
结果集中只有几百个组,因此性能应该不是问题。我只是在努力寻找逻辑,它不仅可以在有序范围内的两个方向上查看,而且可以识别一些中间行已经强制开始一个新组。显然,使用游标很简单,但是在排名之后我还有其他处理要做,所以如果可能的话,我正在寻找基于 SET 的解决方案。
我在 2017 年,但如果有一个基于集合的非递归答案需要 2019 年,我可以接受。
这确实是一个艰难的过程:首先,我认为如果存在特定的缺失重叠,我可以递归地增加 Rank,方法是检查一步中的最高 Rank,这样较低的 AvgScore 将具有较少的 Rank 增量。但是我认识到一个递归CTE的递归元素不能有
- 聚合 + GROUP BY
- 多次引用递归 CTE
- 嵌套的 CTE 定义
所以我放弃了这个方向。似乎数据应该以某种方式 "prepared" 以便可以将其馈送到简单的递归(除了递归之外想不出任何其他解决方案)。
因此,我的解决方案是找到属于第一个超出范围的 AvgScore 的最低 AvgScore,并将其标记为新 Rank 的第一个元素,然后 "jump" 到该元素并重复,所以最后所有行都在一个设置为应分配新排名的第一行("first" 表示按 AvgScore 排序)。之后将所有行放在一起并对其进行排名。
因此,如果您的集合称为@UltraFuzzy,您可以通过几个 CTE 发送它:
;WITH UltraFuzzyCTE AS (
SELECT AvgScore, StdErr, AvgScore - StdErr as RangeMIN, AvgScore + StdErr as RangeMAX
FROM @UltraFuzzy
)
-- SELECT * FROM UltraFuzzyCTE ORDER BY AvgScore
,FirstOutOfRangeCTE AS (
SELECT
Original.*
,MIN (Helper.AvgScore) as FirstOutOfRange
FROM UltraFuzzyCTE as Original
LEFT OUTER JOIN UltraFuzzyCTE as Helper
ON Original.RangeMAX < Helper.AvgScore OR Original.AvgScore < Helper.RangeMIN
GROUP BY Original.AvgScore, Original.StdErr, Original.RangeMIN, Original.RangeMAX
)
-- SELECT * FROM FirstOutOfRangeCTE ORDER BY AvgScore
,NewRankFirstMemberCTE AS (
SELECT * FROM FirstOutOfRangeCTE WHERE AvgScore = (SELECT MIN (AvgScore) FROM FirstOutOfRangeCTE)
UNION ALL
SELECT f.*
FROM NewRankFirstMemberCTE as n
INNER JOIN FirstOutOfRangeCTE as f ON n.FirstOutOfRange = f.AvgScore
)
-- SELECT * FROM NewRankFirstMemberCTE ORDER BY AvgScore
,RankCTE AS (
SELECT *, 1 as NewRankFirstMember FROM NewRankFirstMemberCTE
UNION ALL
SELECT *, 0 as NewRankFirstMember FROM FirstOutOfRangeCTE WHERE AvgScore NOT IN (SELECT AvgScore FROM NewRankFirstMemberCTE)
)
-- SELECT * FROM RankCTE ORDER BY AvgScore
SELECT *, SUM (NewRankFirstMember) OVER (ORDER BY AvgScore) as Rank
FROM RankCTE
ORDER BY AvgScore
当然可以简化,为了调试,我使用了 SELECT *
,但可以丢弃不必要的字段 - 并且使用的 CTE 更少。注释的东西是为了逐步分析。
当递归深度取决于数据中的行数而不是数据的实际深度时,我真的不喜欢它。这个解决方案对我来说很好,因为我要排名的行很少。尽管如此,对于未来的读者,如果有人有非递归解决方案,我很乐意将其标记为答案而不是我自己的。
为了演示此 IS 集,我添加了一个 GROUP BY 列。递归深度取决于要排序的项目数而不是组数。同时处理所有组。此代码在我的生产数据集上进行了测试,并与通过数据的顺序循环生成的答案进行了比较,因此我知道它适用于更大、更复杂的数据集。
WITH T AS (
SELECT *
FROM(VALUES ('Type1', 65550 ,2109 ,1),('Type2', 65550 ,2109 ,1),
('Type1', 67188 ,2050 ,1),('Type2', 67188 ,2050 ,1),
('Type1', 67407 ,2146 ,1),('Type2', 67407 ,2146 ,1),
('Type1', 67414 ,1973 ,1),('Type2', 67414 ,1973 ,1),
('Type1', 67486 ,1889 ,2),('Type2', 67486 ,1889 ,2),
('Type1', 67581 ,2320 ,2),('Type2', 67581 ,2320 ,2),
('Type1', 67858 ,1993 ,2),('Type2', 67858 ,1993 ,2),
('Type1', 68509 ,2029 ,2),('Type2', 68509 ,2029 ,2),
('Type1', 68645 ,2039 ,2),('Type2', 68645 ,2039 ,2),
('Type1', 68868 ,2051 ,2),('Type2', 68868 ,2051 ,2),
('Type1', 68902 ,1943 ,2),('Type2', 68902 ,1943 ,2),
('Type1', 69305 ,1564 ,3),('Type2', 69305 ,1564 ,3),
('Type1', 69430 ,2037 ,3),('Type2', 69430 ,2037 ,3),
('Type1', 69509 ,1594 ,3),('Type2', 69509 ,1594 ,3)) X(TestType,AvgScore,StdErr,DesiredRank)
), X AS (
SELECT *,ROW_NUMBER() OVER(PARTITION BY TestType ORDER BY AvgScore) GRow,1 Rnk,AvgScore RAvg, AvgScore+StdErr RMax
FROM T
), Y AS (
SELECT TestType,AvgScore,StdErr,DesiredRank,GRow,Rnk,RAvg,RMax,0 NewRank,0 pravg,0 prmin
FROM X
WHERE GRow = 1
UNION ALL
SELECT Z.TestType,Z.AvgScore,Z.StdErr,Z.DesiredRank,Z.GRow
,CASE WHEN W.NewRank = 1 THEN Y.Rnk+1 ELSE Y.Rnk END Rnk
,CASE WHEN W.NewRank = 1 THEN Z.RAvg ELSE Y.RAvg END RAvg
,CASE WHEN W.NewRank = 1 THEN Z.RMax ELSE Y.RMax END RMin
,W.NewRank,Y.RAvg pravg,y.RMax prmin
FROM Y
CROSS APPLY (SELECT * FROM X WHERE X.TestType=Y.TestType and X.GRow = Y.GRow+1) Z
CROSS APPLY (VALUES (CASE WHEN Z.AvgScore <= Y.RMax and Z.AvgScore - Z.StdErr <= Y.RAvg THEN 0 ELSE 1 END)) W(NewRank)
)
SELECT * FROM Y
ORDER BY TestType,AvgScore;
我有一堆测试数据。每个测试都进行了几十次,并且在 CTE 中计算了每个测试的平均值和误差范围。在下一步中,我想对每个子组进行 dense_rank 测试。下面是我要查找的数据子组和排名值的示例:
AvgScore StdErr DesiredRank
65550 2109 1
67188 2050 1
67407 2146 1
67414 1973 1
67486 1889 2
67581 2320 2
67858 1993 2
68509 2029 2
68645 2039 2
68868 2051 2
68902 1943 2
69305 1564 3
69430 2037 3
69509 1594 3
387223 12521 4
389709 12975 4
392200 11344 4
398916 11755 4
399018 11480 5
401144 11021 5
401640 10973 5
403442 10688 5
请注意,每个分数的误差范围使得许多分数表面上相当。是的,这会导致某些行在技术上属于多个组,但使其成为最近的组的一部分会给出最准确的结果。
我查看了
这是示例中出现的最复杂的情况:第 1 行的第 2-6 行在其范围内,尽管第 6 行也在第 1 行的范围内,但第 5 行的范围内没有第 1 行,因此新排名必须从第 5 行开始。
结果集中只有几百个组,因此性能应该不是问题。我只是在努力寻找逻辑,它不仅可以在有序范围内的两个方向上查看,而且可以识别一些中间行已经强制开始一个新组。显然,使用游标很简单,但是在排名之后我还有其他处理要做,所以如果可能的话,我正在寻找基于 SET 的解决方案。
我在 2017 年,但如果有一个基于集合的非递归答案需要 2019 年,我可以接受。
这确实是一个艰难的过程:首先,我认为如果存在特定的缺失重叠,我可以递归地增加 Rank,方法是检查一步中的最高 Rank,这样较低的 AvgScore 将具有较少的 Rank 增量。但是我认识到一个递归CTE的递归元素不能有
- 聚合 + GROUP BY
- 多次引用递归 CTE
- 嵌套的 CTE 定义
所以我放弃了这个方向。似乎数据应该以某种方式 "prepared" 以便可以将其馈送到简单的递归(除了递归之外想不出任何其他解决方案)。
因此,我的解决方案是找到属于第一个超出范围的 AvgScore 的最低 AvgScore,并将其标记为新 Rank 的第一个元素,然后 "jump" 到该元素并重复,所以最后所有行都在一个设置为应分配新排名的第一行("first" 表示按 AvgScore 排序)。之后将所有行放在一起并对其进行排名。
因此,如果您的集合称为@UltraFuzzy,您可以通过几个 CTE 发送它:
;WITH UltraFuzzyCTE AS (
SELECT AvgScore, StdErr, AvgScore - StdErr as RangeMIN, AvgScore + StdErr as RangeMAX
FROM @UltraFuzzy
)
-- SELECT * FROM UltraFuzzyCTE ORDER BY AvgScore
,FirstOutOfRangeCTE AS (
SELECT
Original.*
,MIN (Helper.AvgScore) as FirstOutOfRange
FROM UltraFuzzyCTE as Original
LEFT OUTER JOIN UltraFuzzyCTE as Helper
ON Original.RangeMAX < Helper.AvgScore OR Original.AvgScore < Helper.RangeMIN
GROUP BY Original.AvgScore, Original.StdErr, Original.RangeMIN, Original.RangeMAX
)
-- SELECT * FROM FirstOutOfRangeCTE ORDER BY AvgScore
,NewRankFirstMemberCTE AS (
SELECT * FROM FirstOutOfRangeCTE WHERE AvgScore = (SELECT MIN (AvgScore) FROM FirstOutOfRangeCTE)
UNION ALL
SELECT f.*
FROM NewRankFirstMemberCTE as n
INNER JOIN FirstOutOfRangeCTE as f ON n.FirstOutOfRange = f.AvgScore
)
-- SELECT * FROM NewRankFirstMemberCTE ORDER BY AvgScore
,RankCTE AS (
SELECT *, 1 as NewRankFirstMember FROM NewRankFirstMemberCTE
UNION ALL
SELECT *, 0 as NewRankFirstMember FROM FirstOutOfRangeCTE WHERE AvgScore NOT IN (SELECT AvgScore FROM NewRankFirstMemberCTE)
)
-- SELECT * FROM RankCTE ORDER BY AvgScore
SELECT *, SUM (NewRankFirstMember) OVER (ORDER BY AvgScore) as Rank
FROM RankCTE
ORDER BY AvgScore
当然可以简化,为了调试,我使用了 SELECT *
,但可以丢弃不必要的字段 - 并且使用的 CTE 更少。注释的东西是为了逐步分析。
当递归深度取决于数据中的行数而不是数据的实际深度时,我真的不喜欢它。这个解决方案对我来说很好,因为我要排名的行很少。尽管如此,对于未来的读者,如果有人有非递归解决方案,我很乐意将其标记为答案而不是我自己的。
为了演示此 IS 集,我添加了一个 GROUP BY 列。递归深度取决于要排序的项目数而不是组数。同时处理所有组。此代码在我的生产数据集上进行了测试,并与通过数据的顺序循环生成的答案进行了比较,因此我知道它适用于更大、更复杂的数据集。
WITH T AS (
SELECT *
FROM(VALUES ('Type1', 65550 ,2109 ,1),('Type2', 65550 ,2109 ,1),
('Type1', 67188 ,2050 ,1),('Type2', 67188 ,2050 ,1),
('Type1', 67407 ,2146 ,1),('Type2', 67407 ,2146 ,1),
('Type1', 67414 ,1973 ,1),('Type2', 67414 ,1973 ,1),
('Type1', 67486 ,1889 ,2),('Type2', 67486 ,1889 ,2),
('Type1', 67581 ,2320 ,2),('Type2', 67581 ,2320 ,2),
('Type1', 67858 ,1993 ,2),('Type2', 67858 ,1993 ,2),
('Type1', 68509 ,2029 ,2),('Type2', 68509 ,2029 ,2),
('Type1', 68645 ,2039 ,2),('Type2', 68645 ,2039 ,2),
('Type1', 68868 ,2051 ,2),('Type2', 68868 ,2051 ,2),
('Type1', 68902 ,1943 ,2),('Type2', 68902 ,1943 ,2),
('Type1', 69305 ,1564 ,3),('Type2', 69305 ,1564 ,3),
('Type1', 69430 ,2037 ,3),('Type2', 69430 ,2037 ,3),
('Type1', 69509 ,1594 ,3),('Type2', 69509 ,1594 ,3)) X(TestType,AvgScore,StdErr,DesiredRank)
), X AS (
SELECT *,ROW_NUMBER() OVER(PARTITION BY TestType ORDER BY AvgScore) GRow,1 Rnk,AvgScore RAvg, AvgScore+StdErr RMax
FROM T
), Y AS (
SELECT TestType,AvgScore,StdErr,DesiredRank,GRow,Rnk,RAvg,RMax,0 NewRank,0 pravg,0 prmin
FROM X
WHERE GRow = 1
UNION ALL
SELECT Z.TestType,Z.AvgScore,Z.StdErr,Z.DesiredRank,Z.GRow
,CASE WHEN W.NewRank = 1 THEN Y.Rnk+1 ELSE Y.Rnk END Rnk
,CASE WHEN W.NewRank = 1 THEN Z.RAvg ELSE Y.RAvg END RAvg
,CASE WHEN W.NewRank = 1 THEN Z.RMax ELSE Y.RMax END RMin
,W.NewRank,Y.RAvg pravg,y.RMax prmin
FROM Y
CROSS APPLY (SELECT * FROM X WHERE X.TestType=Y.TestType and X.GRow = Y.GRow+1) Z
CROSS APPLY (VALUES (CASE WHEN Z.AvgScore <= Y.RMax and Z.AvgScore - Z.StdErr <= Y.RAvg THEN 0 ELSE 1 END)) W(NewRank)
)
SELECT * FROM Y
ORDER BY TestType,AvgScore;