SQL Server NTILE - 不同四分位中的相同值

问题描述 投票:0回答:5

我有一个场景,我使用下面的 SQL Server NTILE 函数将多个结果拆分为四分位数。目标是每个类中的行数相同

case NTILE(4) over (order by t2.TotalStd) 
   when 1 then 'A' when 2 then 'B' when 3 then 'C' else 'D' end as Class

成绩表如下所示,分为(9,9,8,8)A,B,C,D 4个班级组。

有两个结果给我带来了问题,两行的总标准值相同,均为 30,但分配给不同的四分位数。

8   30  A
2   30  B

我想知道是否有一种方法可以确保具有相同值的行分配给相同的四分位数?我可以按另一列进行分组或分区以获得此行为吗?

Pos TotalStd    class
1   16  A
2   23  A
3   21  A
4   29  A
5   25  A
6   26  A
7   28  A
8   30  A
9   29  A
1   31  B
2   30  B
3   32  B
4   32  B
5   34  B
6   32  B
7   34  B
8   32  B
9   33  B
1   36  C
2   35  C
3   35  C
4   35  C
5   40  C
6   38  C
7   41  C
8   43  C
1   43  D
2   48  D
3   45  D
4   47  D
5   44  D
6   48  D
7   46  D
8   57  D
sql-server sql-server-2008
5个回答
10
投票

您将需要使用排名函数重新创建 Ntile 函数。 排名函数对具有相同值的行给出相同的排名。该值稍后会“跳”到下一个排名,就像您使用 row_number 一样。 我们可以使用此行为来模仿 Ntile 函数,强制它为具有相同值的行提供相同的 Ntile 值。然而 - 这将导致 Ntile 分区具有不同的大小。 请参阅下面的示例,了解使用 4 个 bin 的新 Ntile:

declare @data table ( x int )

insert @data values 
(1),(2),
(2),(3),
(3),(4),
(4),(5)

select  
    x,
    1+(rank() over (order by x)-1) * 4 / count(1) over (partition by (select 1)) as new_ntile
from @data

结果:

x   new_ntile
---------------
1   1
2   1
2   1
3   2
3   2
4   3
4   3
5   4

3
投票

不确定您期望在这里发生什么,真的。正如您所要求的,SQL Server已将数据分为尽可能大小相等的4组。你想要发生什么?看看这个例子:

declare @data table ( x int )

insert @data values 
(1),(2),
(2),(3),
(3),(4),
(4),(5)

select  
    x,
    NTILE(4) over (order by x) as ntile
from @data

结果:

x           ntile
----------- ----------
1           1
2           1
2           2
3           2
3           3
4           3
4           4
5           4

现在每个 ntile 组都与其旁边的组共享一个值!但它还应该做什么?


2
投票

试试这个:

; with a as (
                select TotalStd,Class=case ntile(4)over( order by TotalStd )
                                when 1 then 'A'
                                when 2 then 'B'
                                when 3 then 'C'
                                when 4 then 'D'
                                end
                from t2
                group by TotalStd
)
select d.*, a.Class from t2 d
inner join a on a.TotalStd=d.TotalStd
order by Class,Pos;

1
投票

这里我们有一个 34 行的表。

DECLARE @x TABLE (TotalStd INT) 
INSERT @x (TotalStd) VALUES (16), (21), (23), (25), (26), (28), (29), (29), (30), (30), (31), (32), (32), (32), (32), (33), (34), 
    (34), (35), (35), (35), (36), (38), (40), (41), (43), (43), (44), (45), (46), (47), (48), (48), (57)
SELECT '@x', TotalStd FROM @x ORDER BY TotalStd

我们想分成四分位数。如果我们使用

NTILE
,桶的大小将大致相同(每个 8 到 9 行),但关系会任意断开:

SELECT '@x with NTILE', TotalStd, NTILE(4) OVER (ORDER BY TotalStd) quantile FROM @x

查看 30 如何出现两次:一次在分位数 1 中,一次在分位数 2 中。类似地,43 都出现在分位数 3 和 4 中。

我应该在分位数 1 中找到 10 个项目,在分位数 2 中找到 8 个项目,在分位数 3 中找到 7 个项目,在分位数 4 中找到 9 个项目(即不是完美的 9-8-9-8 分割,但如果我们是不得随意断绝关系)。我可以使用

NTILE
来确定临时表中的截止点:

DECLARE @cutoffs TABLE (quantile INT, min_value INT, max_value INT)

INSERT @cutoffs (quantile, min_value)
SELECT y.quantile, MIN(y.TotalStd)
FROM (SELECT TotalStd, NTILE(4) OVER (ORDER BY TotalStd) AS quantile FROM @x) y
GROUP BY y.quantile

-- The max values are the minimum values of the next quintiles
UPDATE c1 SET c1.max_value = ISNULL(C2.min_value, (SELECT MAX(TotalStd) + 1 FROM @x))
FROM @cutoffs c1 LEFT OUTER JOIN @cutoffs c2 ON c2.quantile - 1 = c1.quantile

SELECT '@cutoffs', * FROM @cutoffs

我们将使用

@cutoffs
表中的边界值来创建最终表:

SELECT x.TotalStd, c.quantile FROM @x x 
    INNER JOIN @cutoffs c ON x.TotalStd >= c.min_value AND x.TotalStd < c.max_value

0
投票

我明白你的意思——在某些情况下,ntile 分配的一致性比相等的存储桶大小更重要。在 RFM 分析过程中使用 ntiles 可能是一个例子。

我解决这个问题的方法是先取 ntile,然后取

min(ntile) over (partition by [whatever the value is you initially took the ntile on]) as final_ntile
.

这样的事情将使您的 ntile 分配每个值保持一致,但会牺牲存储桶大小。

© www.soinside.com 2019 - 2024. All rights reserved.