我有很多测试数据。每个测试进行了数十次,并且每个测试的平均误差范围在CTE中计算。在下一步中,我想对测试的每个子组进行密集排序。这是我正在寻找的数据子组和等级值的示例:
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
请注意,每个分数的误差幅度使许多分数表面上等效。是的,这会导致某些行在技术上属于多个组,但将其归为最接近的组将提供最准确的结果。
我查看了Grouping data into fuzzy gaps and islands,但此版本似乎要复杂得多,因为从一组切换到另一组不仅要求两行都在彼此的误差范围内,而且可能会在等效行之间进行切换。
这是示例中出现的最复杂的情况:第1行在其范围内有第2-6行,尽管第6行也在第1行的范围内,但第5行没有在其范围内的第1行,因此有了新的排名必须从第5行开始。
结果集中只有几百个组,因此性能不成问题。我只是在寻找一种逻辑,该逻辑不仅可以在有序范围内双向查看,而且还认识到某些中间行已迫使新组的成立。显然,使用游标很简单,但是在排名之后我还要进行其他处理,因此,我在寻找可能的基于SET的解决方案。
我在2017年,但是如果有一个基于集合的非递归答案需要2019年,那么我就可以了。
答案 0 :(得分:0)
这确实是一个艰难的过程:首先,我认为如果有一定的遗漏重叠,我可以递归地提高Rank,方法是一步检查最高的Rank,以便较低的AvgScore的Rank增量更少。但是我认识到递归CTE的递归元素不能具有
-聚合+ GROUP BY
-对递归CTE的多个引用
-定义的嵌套CTE
所以我放弃了这个方向。似乎应该以某种方式“准备”数据,以便可以将其提供给简单的递归(除了递归,别无其他解决方案)。
因此,我的解决方案是找到属于范围之外的第一个AvgScore的最低AvgScore,并将其标记为新Rank的第一个元素,然后“跳转”到该元素并重复,因此最后将所有行集中是应分配新等级的第一行(“第一”是指按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更少。评论的内容用于分步分析。
答案 1 :(得分:0)
当递归的深度取决于数据中的行数而不是数据的实际深度时,我真的不喜欢它。这个解决方案对我来说行得通,因为我的行数很少。一样,对于将来的读者,如果有人有非递归解决方案,我很乐意将其标记为答案,而不是我自己的答案。
为了演示基于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;