【问题标题】:Ultra Fuzzy gaps and islands grouping problem超模糊间隙和岛屿分组问题
【发布时间】:2023-03-28 09:35:01
【问题描述】:

我有一堆测试数据。每个测试都进行了几十次,并且在 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

请注意,每个分数的误差幅度使得许多分数在表面上是相等的。是的,这会导致某些行在技术上属于多个组,但使其成为最近组的一部分会得到最准确的结果。

我查看了Grouping data into fuzzy gaps and islands,但这个版本似乎要复杂得多,因为从一组切换到另一组不仅需要两行在彼此的误差范围内,而且可能会在等效行之间发生切换。

以下是示例中出现的最复杂的情​​况:第 1 行在其范围内有第 2-6 行,尽管第 6 行也在第 1 行的范围内,但第 5 行在其范围内没有第 1 行,因此新排名必须从第 5 行开始。

结果集中只有几百个组,因此性能应该不是问题。我只是在努力寻找不仅可以在有序范围内同时查看两个方向的逻辑,而且还可以认识到某些中间行已经强制启动了一个新组。显然,使用游标很简单,但在排名之后我还有额外的处理要做,所以如果可能的话,我正在寻找基于 SET 的解决方案。

我在 2017 年,但如果有一个基于集合的非递归答案需要 2019 年,我可以接受。

【问题讨论】:

  • 您有 SQL Server 2012 或更高版本吗?
  • 这不是您所需要的,但我希望它可以作为一个起点:demo 这是一种天真的方法 - 获取组的第一个元素并添加 stderr,当实际值 > newRange 时更改组。
  • 谢谢。递归 CTE 不是我的首选,但它们在特定情况下肯定很有用。

标签: sql-server gaps-and-islands


【解决方案1】:

当递归深度取决于数据中的行数而不是数据的实际深度时,我真的不喜欢它。这个解决方案对我来说没问题,因为我要排名的行太少了。尽管如此,对于未来的读者来说,如果有人有非递归解决方案,我很乐意将其标记为答案,而不是我自己的。

为了演示这个基于 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;

【讨论】:

    【解决方案2】:

    这确实是一个艰难的过程:首先,我认为如果存在某些缺失的重叠,我可以递归地增加 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。注释的内容是为了逐步分析。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-02
      • 1970-01-01
      • 1970-01-01
      • 2019-07-29
      • 1970-01-01
      • 2020-05-24
      • 2021-07-30
      • 2017-04-23
      相关资源
      最近更新 更多