web-dev-qa-db-fra.com

Calcul des classements en centiles en MS SQL

Quelle est la meilleure façon de calculer les classements en centiles (par exemple, le 90e centile ou le score médian) dans MSSQL 2005?

J'aimerais pouvoir sélectionner le 25e, la médiane et le 75e centiles pour une seule colonne de scores (de préférence dans un seul enregistrement afin que je puisse combiner avec moyenne, max et min). Ainsi, par exemple, les résultats de la table pourraient être:

Group  MinScore  MaxScore  AvgScore  pct25  median  pct75
-----  --------  --------  --------  -----  ------  -----
T1     52        96        74        68     76      84
T2     48        98        74        68     75      85
25
Soldarnal

Je penserais que ce serait la solution la plus simple:

SELECT TOP N PERCENT FROM TheTable ORDER BY TheScore DESC

Où N = (100 - centile souhaité). Donc, si vous voulez toutes les lignes du 90e centile, vous devez sélectionner les 10% supérieurs.

Je ne suis pas sûr de ce que vous entendez par "de préférence dans un seul enregistrement". Voulez-vous dire calculer dans quel centile se situerait un score donné pour un seul enregistrement? par exemple. voulez-vous pouvoir faire des déclarations telles que "votre score est de 83, ce qui vous place dans le 91ème centile". ?

EDIT: OK, j’ai réfléchi un peu plus à votre question et proposé cette interprétation. Demandez-vous comment calculer le seuil de coupure pour un centile particulier? par exemple. quelque chose comme ceci: pour être dans le 90ème centile, vous devez avoir un score supérieur à 78.

Si c'est le cas, cette requête fonctionne. Je n'aime pas les sous-requêtes, alors, selon le but recherché, j'essaierais probablement de trouver une solution plus élégante. Cependant, il ne renvoie qu'un seul enregistrement avec un seul score.

-- Find the minimum score for all scores in the 90th percentile
SELECT Min(subq.TheScore) FROM
(SELECT TOP 10 PERCENT TheScore FROM TheTable
ORDER BY TheScore DESC) AS subq
14
Matt

Consultez la commande NTILE - elle vous donnera des centiles assez facilement!

SELECT  SalesOrderID, 
    OrderQty,
    RowNum = Row_Number() OVER(Order By OrderQty),
    Rnk = RANK() OVER(ORDER BY OrderQty),
    DenseRnk = DENSE_RANK() OVER(ORDER BY OrderQty),
    NTile4  = NTILE(4) OVER(ORDER BY OrderQty)
FROM Sales.SalesOrderDetail 
WHERE SalesOrderID IN (43689, 63181)
9
Elizabeth

Que dis-tu de ça:

SELECT
  Group,
  75_percentile =  MAX(case when NTILE(4) OVER(ORDER BY score ASC) = 3 then score  else 0 end),
  90_percentile =  MAX(case when NTILE(10) OVER(ORDER BY score  ASC) = 9 then score  else 0 end)     
FROM TheScore
GROUP BY Group
2
Paul

Le 50e centile est identique à la médiane. Lorsque vous calculez un autre centile, disons le 80e, triez les données pour 80% de données par ordre croissant et les 20% restantes par ordre décroissant, puis prenez la moyenne des deux valeurs centrales.

NB: La requête médiane existe depuis longtemps, mais je ne me souviens plus d'où elle provient exactement, je ne l'ai modifiée que pour calculer d'autres centiles.

DECLARE @Temp TABLE(Id INT IDENTITY(1,1), DATA DECIMAL(10,5))

INSERT INTO @Temp VALUES(0)
INSERT INTO @Temp VALUES(2)
INSERT INTO @Temp VALUES(8)
INSERT INTO @Temp VALUES(4)
INSERT INTO @Temp VALUES(3)
INSERT INTO @Temp VALUES(6)
INSERT INTO @Temp VALUES(6)
INSERT INTO @Temp VALUES(6) 
INSERT INTO @Temp VALUES(7)
INSERT INTO @Temp VALUES(0)
INSERT INTO @Temp VALUES(1)
INSERT INTO @Temp VALUES(NULL)


--50th percentile or median
SELECT ((
        SELECT TOP 1 DATA
        FROM   (
                SELECT  TOP 50 PERCENT DATA
                FROM    @Temp
                WHERE   DATA IS NOT NULL
                ORDER BY DATA
                ) AS A
        ORDER BY DATA DESC) + 
        (
        SELECT TOP 1 DATA
        FROM   (
                SELECT  TOP 50 PERCENT DATA
                FROM    @Temp
                WHERE   DATA IS NOT NULL
                ORDER BY DATA DESC
                ) AS A
        ORDER BY DATA ASC)) / 2.0


--90th percentile 
SELECT ((
        SELECT TOP 1 DATA
        FROM   (
                SELECT  TOP 90 PERCENT DATA
                FROM    @Temp
                WHERE   DATA IS NOT NULL
                ORDER BY DATA
                ) AS A
        ORDER BY DATA DESC) + 
        (
        SELECT TOP 1 DATA
        FROM   (
                SELECT  TOP 10 PERCENT DATA
                FROM    @Temp
                WHERE   DATA IS NOT NULL
                ORDER BY DATA DESC
                ) AS A
        ORDER BY DATA ASC)) / 2.0


--75th percentile
SELECT ((
        SELECT TOP 1 DATA
        FROM   (
                SELECT  TOP 75 PERCENT DATA
                FROM    @Temp
                WHERE   DATA IS NOT NULL
                ORDER BY DATA
                ) AS A
        ORDER BY DATA DESC) + 
        (
        SELECT TOP 1 DATA
        FROM   (
                SELECT  TOP 25 PERCENT DATA
                FROM    @Temp
                WHERE   DATA IS NOT NULL
                ORDER BY DATA DESC
                ) AS A
        ORDER BY DATA ASC)) / 2.0
1
Kay Aliu

J'ai travaillé un peu plus là-dessus, et voici ce que j'ai inventé jusqu'à présent:

CREATE PROCEDURE [dbo].[TestGetPercentile]

@percentile as float,
@resultval as float output

AS

BEGIN

WITH scores(score, prev_rank, curr_rank, next_rank) AS (
    SELECT dblScore,
        (ROW_NUMBER() OVER ( ORDER BY dblScore ) - 1.0) / ((SELECT COUNT(*) FROM TestScores) + 1)  [prev_rank],
        (ROW_NUMBER() OVER ( ORDER BY dblScore ) + 0.0) / ((SELECT COUNT(*) FROM TestScores) + 1)  [curr_rank],
        (ROW_NUMBER() OVER ( ORDER BY dblScore ) + 1.0) / ((SELECT COUNT(*) FROM TestScores) + 1)  [next_rank]
    FROM TestScores
)

SELECT @resultval = (
    SELECT TOP 1 
    CASE WHEN t1.score = t2.score
        THEN t1.score
    ELSE
        t1.score + (t2.score - t1.score) * ((@percentile - t1.curr_rank) / (t2.curr_rank - t1.curr_rank))
    END
    FROM scores t1, scores t2
    WHERE (t1.curr_rank = @percentile OR (t1.curr_rank < @percentile AND t1.next_rank > @percentile))
        AND (t2.curr_rank = @percentile OR (t2.curr_rank > @percentile AND t2.prev_rank < @percentile))
)

END

Ensuite, dans une autre procédure stockée, je fais ceci:

DECLARE @pct25 float;
DECLARE @pct50 float;
DECLARE @pct75 float;

exec SurveyGetPercentile .25, @pct25 output
exec SurveyGetPercentile .50, @pct50 output
exec SurveyGetPercentile .75, @pct75 output

Select
    min(dblScore) as minScore,
    max(dblScore) as maxScore,
    avg(dblScore) as avgScore,
    @pct25 as percentile25,
    @pct50 as percentile50,
    @pct75 as percentile75
From TestScores

Il ne fait toujours pas tout à fait ce que je cherche. Cela donnera les statistiques pour tous les tests; alors que j'aimerais pouvoir sélectionner un tableau TestScores contenant plusieurs tests différents et obtenir les mêmes statistiques pour chaque test différent (comme dans mon exemple de tableau de ma question).

1
Soldarnal

j'utiliserais probablement un serveur SQL 2005 

row_number () over (order by score)/(sélectionnez le compte (*) parmi les scores)

ou quelque chose du genre. 

0
karl prosser

je ferais quelque chose comme:

select @n = count(*) from tbl1
select @median = @n / 2
select @p75 = @n * 3 / 4
select @p90 = @n * 9 / 10

select top 1 score from (select top @median score from tbl1 order by score asc) order by score desc

est-ce correct?

0
syap