【问题标题】:Split set into uneven percentage buckets将集合拆分为不均匀的百分比桶
【发布时间】:2019-03-20 18:05:09
【问题描述】:

每天我都会返回一组 x 行(5 到 2000 之间)。

我需要根据规则更新此集合中的一列。我认为这个(不完全有效)示例说明了这一点

/* 
    35% a
    25% b
    30% c
    10% null
*/

WITH tally
(vals, updateThis, bucket)
AS
(
    SELECT
         DATEADD(DAY, - ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), GETDATE())
        , NULL
        , NTILE(100) OVER (ORDER BY (SELECT NULL))
    FROM
    (
        VALUES (0), (0), (0), (0), (0), (0), (0), (0), (0)) AS a(n)
        CROSS JOIN (VALUES (0), (0), (0), (0), (0), (0), (0), (0), (0)) AS b(n)
        CROSS JOIN (VALUES (0), (0), (0), (0), (0), (0), (0), (0), (0)) AS c(n)
    )
--UPDATE
    --SET updateThis
, updated
AS
(
    SELECT
     t.vals
    , CASE
        WHEN t.bucket <= 35 THEN 'a'
        WHEN t.bucket > 35 AND t.bucket <=60 THEN 'b'
        WHEN t.bucket > 60 AND t.bucket <=90 THEN 'c'
        WHEN t.bucket > 60 AND t.bucket <=90 THEN 'NULL'
    END AS updated
    , t.bucket
    FROM tally t
)
SELECT 
    U.updated
    , COUNT(1) AS actual
FROM 
updated u
GROUP BY U.updated

此解决方案并不精确,即使 a + b + c 确实构成 100%,它也可能不会更新所有行。此外,它不适用于小于 100 行的集合。

我目前的工作解决方案是:

  • 计算总行数
  • 计算实际需要的行数(CEILING((@totalRows * ratio) / 100)
  • 在 WHILE LOOP 中更新最终集,选择当前值和所需的行。

有没有更好的基于集合的解决方案可以帮助我摆脱循环?

【问题讨论】:

    标签: tsql set ssms sql-server-2016


    【解决方案1】:

    不知道,如果我理解正确...

    首先这里似乎有一个相当明显的错误:

        WHEN t.bucket > 60 AND t.bucket <=90 THEN 'NULL'
    

    不应该是这样吗:

        WHEN t.bucket >90 THEN 'NULL'
    

    NTILE 函数会将你的集合分散到相当均匀的桶中。检查我的输出并找出它在极端情况下的表现。我建议使用每行计算的百分比,如下所示:

    WITH tally
    (vals, bucket)
    AS
    (
        SELECT
             DATEADD(DAY, - ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), GETDATE())
            ,NTILE(100) OVER (ORDER BY (SELECT NULL))
        FROM
        (
            VALUES (0), (0), (0), (0), (0), (0), (0), (0), (0)) AS a(n)
            CROSS JOIN (VALUES (0), (0), (0), (0), (0), (0), (0), (0), (0)) AS b(n)
            CROSS JOIN (VALUES (0), (0), (0), (0), (0), (0), (0), (0), (0)) AS c(n)
        )
    SELECT *
    INTO #tmpBuckets
    FROM Tally;
    

    --我使用这个#tmpBuckets-table 来更接近你的我有一张桌子场景

    WITH Numbered AS
    (
        SELECT *
              ,ROW_NUMBER() OVER(ORDER BY vals DESC) / ((SELECT COUNT(*) FROM #tmpBuckets)/100.0)  AS RunningPercentage
        FROM #tmpBuckets
    )
    ,ComputeBuckets AS
    (
        SELECT
         t.*
        , CASE
            WHEN t.RunningPercentage <= 35 THEN 'a'
            WHEN t.RunningPercentage > 35 AND t.RunningPercentage <=60 THEN 'b'
            WHEN t.RunningPercentage > 60 AND t.RunningPercentage <=90 THEN 'c'
            WHEN t.RunningPercentage >90  THEN 'NULL'
        END AS ShnugoMethod
        , CASE
            WHEN t.bucket <= 35 THEN 'a'
            WHEN t.bucket > 35 AND t.RunningPercentage <=60 THEN 'b'
            WHEN t.bucket > 60 AND t.RunningPercentage <=90 THEN 'c'
            WHEN t.bucket > 90  THEN 'NULL'
        END AS ZikatoMethod
        FROM Numbered t
    )
    SELECT cb.*
    FROM ComputeBuckets cb
    ORDER BY cb.vals DESC
    
    GO
    DROP TABLE #tmpBuckets;
    

    我想你知道,如何使用这样的 cte 来更新源表。否则就回来问另一个问题:-)

    【讨论】:

    • 这……太美了!我不知道为什么我没有想到这个,现在我看到它似乎很简单。
    • @Zikato 很高兴为您提供帮助 ;-)
    猜你喜欢
    • 2013-05-27
    • 1970-01-01
    • 2017-11-29
    • 2017-01-09
    • 2019-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-20
    相关资源
    最近更新 更多