Comment calculer le pourcentage avec une instruction SQL


177

J'ai une table SQL Server qui contient les utilisateurs et leurs notes. Par souci de simplicité, disons simplement qu'il y a 2 colonnes - name& grade. Ainsi, une ligne typique serait Nom: "John Doe", Grade: "A".

Je recherche une instruction SQL qui trouvera les pourcentages de toutes les réponses possibles. (A, B, C, etc ...) En outre, y a-t-il un moyen de le faire sans définir toutes les réponses possibles (champ de texte ouvert - les utilisateurs peuvent entrer 'succès / échec', 'aucun', etc ...)

Le résultat final que je recherche est A: 5%, B: 15%, C: 40%, etc ...

Réponses:


227

J'ai testé ce qui suit et cela fonctionne. La réponse de gordyii était proche mais avait la multiplication de 100 au mauvais endroit et avait des parenthèses manquantes.

Select Grade, (Count(Grade)* 100 / (Select Count(*) From MyTable)) as Score
From MyTable
Group By Grade

21
cela donne le résultat en nombres entiers. La somme des résultats n'est pas égale à 100.
Thunder

10
Pas le plus efficace car le tableau sera scanné deux fois. De plus, la requête n'aura pas l'air aussi simple s'il y a plus d'une table référencée.
Alex Aza

14
@Thunder vous pouvez changer 100 en 100,0 pour les valeurs décimales.
Joseph

Quelqu'un peut-il expliquer pourquoi la syntaxe mathématique de la requête SQL n'est pas ce que vous vous attendez à faire normalement? Par exemple normal j'aurais divisé par le total puis fois par 100? Vraiment curieux à ce sujet d'un point de vue logique.
Digitalsa1nt

4
@ Digitalsa1nt (100 * 2) / 4 = 50, (2/4) * 100 = 50 tant que l'énumérateur est la partie multipliée. En raison de la priorité des instructions SQL, il en sera de même. cependant, en raison des types de données si vous utilisez 100, vous pouvez toujours obtenir le résultat arrondi à 0 décimale que vous souhaitez pour le% où, comme si vous le placiez après l'opération de division, vous devrez vous assurer que vous transtypez en un type de données qui peut gérer les décimales sinon vous vous retrouverez avec 100 ou 0 et jamais un pourcentage réel
Matt

232
  1. Le plus efficace (en utilisant over ()).

    select Grade, count(*) * 100.0 / sum(count(*)) over()
    from MyTable
    group by Grade
  2. Universel (toute version SQL).

    select Grade, count(*) * 100.0 / (select count(*) from MyTable)
    from MyTable
    group by Grade;
  3. Avec CTE, le moins efficace.

    with t(Grade, GradeCount) 
    as 
    ( 
        select Grade, count(*) 
        from MyTable
        group by Grade
    )
    select Grade, GradeCount * 100.0/(select sum(GradeCount) from t)
    from t;

13
over () a parfaitement fonctionné sur mon SQL Server 2008, j'ai fait le calcul pour confirmer. Afin de l'arrondir à 2 décimales, j'ai utilisé CAST (count ( ) * 100.0 / sum (count ( )) over () AS DECIMAL (18, 2)). Merci pour le post!
RJB

3
Dans le cas où vous débordez sur la multiplication 100 (par exemple, erreur de dépassement arithmétique lors de la conversion de l'expression en type de données int ), remplacez-la par une division en dénominateur à la place:cast((count(*) / (sum(count(*)) over() / 100)) AS DECIMAL(18, 2)) as Percentage
Nikita G.

@RJB Pourquoi devez-vous multiplier par 100,0 et pas seulement par 100 lorsque vous convertissez la sortie en décimale?
AS91

2
@ AS91, car le transtypage en décimal se produit APRÈS l'opération de division. Si vous laissez un int (100), la division par un autre int se traduira également par un int, qui arrondira le résultat. C'est pourquoi l'astuce consiste toujours à forcer une distribution du dividende avant la division réelle (vous pouvez soit multiplier par une décimale littérale comme 1.0, soit lancer / convertir)
luiggig

Option 1 avec over()fonctionne très bien sur Postgresql 10
James Daily

40

Au lieu d'utiliser un CTE séparé pour obtenir le total, vous pouvez utiliser une fonction de fenêtre sans la clause «partition par».

Si vous utilisez:

count(*)

pour obtenir le décompte d'un groupe, vous pouvez utiliser:

sum(count(*)) over ()

pour obtenir le nombre total.

Par exemple:

select Grade, 100. * count(*) / sum(count(*)) over ()
from table
group by Grade;

Cela a tendance à être plus rapide dans mon expérience, mais je pense qu'il pourrait utiliser en interne une table temporaire dans certains cas (j'ai vu "Worktable" lors de l'exécution avec "set statistics io on").

EDIT: Je ne suis pas sûr si mon exemple de requête est ce que vous recherchez, je voulais juste illustrer le fonctionnement des fonctions de fenêtrage.


+1. C'est bien. Il peut également être utilisé si à la place de «table» il y a une instruction select.
mr_georg

1
Il utilise une bobine dans tempdblaquelle se trouve la table de travail. Les lectures logiques semblent plus élevées mais elles sont comptées différemment de la normale
Martin Smith

1
En fait, COUNT(*) OVER ()dans votre requête renvoie un chiffre totalement indépendant (en particulier, le nombre de lignes du jeu de résultats groupé ). Vous devriez utiliser à la SUM(COUNT(*)) OVER ()place.
Andriy M

10

Vous devez calculer le total des notes Si c'est SQL 2005, vous pouvez utiliser CTE

    WITH Tot(Total) (
    SELECT COUNT(*) FROM table
    )
    SELECT Grade, COUNT(*) / Total * 100
--, CONVERT(VARCHAR, COUNT(*) / Total * 100) + '%'  -- With percentage sign
--, CONVERT(VARCHAR, ROUND(COUNT(*) / Total * 100, -2)) + '%'  -- With Round
    FROM table
    GROUP BY Grade

1
Bien sûr, cela ne donne que les pourcentages pour les codes de qualité présents dans le tableau, pas pour ceux qui pourraient être présents et ne le sont pas. Mais sans une liste définitive des codes de qualité (valides) pertinents, vous ne pouvez pas faire mieux. D'où le +1 de moi.
Jonathan Leffler

1
Le joyau caché pour moi était que vous avez commenté CONVERT.
Chris Catignani

9

Vous devez regrouper sur le champ de note. Cette requête devrait vous donner ce que vous recherchez dans à peu près n'importe quelle base de données.

    Select Grade, CountofGrade / sum(CountofGrade) *100 
    from
    (
    Select Grade, Count(*) as CountofGrade
    From Grades
    Group By Grade) as sub
    Group by Grade

Vous devez spécifier le système que vous utilisez.


2
Puisque vous avez un agrégat ('sum (CountofGrade)') dans la sélection externe, n'avez-vous pas également besoin d'une clause group by? Et en SQL standard, je pense que vous pouvez utiliser '/ (SELECT COUNT (*) FROM Grades)' pour obtenir le total général.
Jonathan Leffler

IBM Informix Dynamic Server n'aime pas le SUM nu dans la liste de sélection (bien qu'il donne un message un peu moins qu'utile lorsqu'il se plaint). Comme indiqué dans ma réponse et mon commentaire précédent, l'utilisation d'une expression de sous-sélection complète dans la liste de sélection fonctionne dans IDS.
Jonathan Leffler

C'est également mieux car on peut appliquer une requête complexe où à une requête interne.
mvmn

9

Je l'utilise simplement chaque fois que j'ai besoin de calculer un pourcentage.

ROUND(CAST((Numerator * 100.0 / Denominator) AS FLOAT), 2) AS Percentage

Notez que 100.0 renvoie des décimales, alors que 100 à lui seul arrondira le résultat au nombre entier le plus proche, même avec la fonction ROUND ()!


7

Ce qui suit devrait fonctionner

ID - Key
Grade - A,B,C,D...

EDIT: déplacé * 100et ajouté le 1.0pour s'assurer qu'il ne fait pas de division entière

Select 
   Grade, Count(ID) * 100.0 / ((Select Count(ID) From MyTable) * 1.0)
From MyTable
Group By Grade

1
cela fonctionne, mais les réponses reviennent toutes comme 0 - dois-je faire une sorte de formatage ou de conversion des nombres pour voir la bonne réponse?
Alex

1
Sélectionnez Grade, round (Count (grade) * 100.0 / ((Select Count (grade) From grades) * 1.0), 2) From grades Group By Grade pour ajouter une fonction ronde dans sql-server returend, par exemple: 21.56000000000
Thunder

5

C'est, je crois, une solution générale, même si je l'ai testée à l'aide d'IBM Informix Dynamic Server 11.50.FC3. La requête suivante:

SELECT grade,
       ROUND(100.0 * grade_sum / (SELECT COUNT(*) FROM grades), 2) AS pct_of_grades
    FROM (SELECT grade, COUNT(*) AS grade_sum
            FROM grades
            GROUP BY grade
         )
    ORDER BY grade;

donne la sortie suivante sur les données de test indiquées sous la règle horizontale. La ROUNDfonction peut être spécifique au SGBD, mais le reste (probablement) ne l'est pas. (Notez que j'ai changé 100 en 100,0 pour m'assurer que le calcul s'effectue en utilisant une arithmétique non entière - DECIMAL, NUMERIC -; voir les commentaires, et merci à Thunder.)

grade  pct_of_grades
CHAR(1) DECIMAL(32,2)
A       32.26
B       16.13
C       12.90
D       12.90
E       9.68
F       16.13

CREATE TABLE grades
(
    id VARCHAR(10) NOT NULL,
    grade CHAR(1) NOT NULL CHECK (grade MATCHES '[ABCDEF]')
);

INSERT INTO grades VALUES('1001', 'A');
INSERT INTO grades VALUES('1002', 'B');
INSERT INTO grades VALUES('1003', 'F');
INSERT INTO grades VALUES('1004', 'C');
INSERT INTO grades VALUES('1005', 'D');
INSERT INTO grades VALUES('1006', 'A');
INSERT INTO grades VALUES('1007', 'F');
INSERT INTO grades VALUES('1008', 'C');
INSERT INTO grades VALUES('1009', 'A');
INSERT INTO grades VALUES('1010', 'E');
INSERT INTO grades VALUES('1001', 'A');
INSERT INTO grades VALUES('1012', 'F');
INSERT INTO grades VALUES('1013', 'D');
INSERT INTO grades VALUES('1014', 'B');
INSERT INTO grades VALUES('1015', 'E');
INSERT INTO grades VALUES('1016', 'A');
INSERT INTO grades VALUES('1017', 'F');
INSERT INTO grades VALUES('1018', 'B');
INSERT INTO grades VALUES('1019', 'C');
INSERT INTO grades VALUES('1020', 'A');
INSERT INTO grades VALUES('1021', 'A');
INSERT INTO grades VALUES('1022', 'E');
INSERT INTO grades VALUES('1023', 'D');
INSERT INTO grades VALUES('1024', 'B');
INSERT INTO grades VALUES('1025', 'A');
INSERT INTO grades VALUES('1026', 'A');
INSERT INTO grades VALUES('1027', 'D');
INSERT INTO grades VALUES('1028', 'B');
INSERT INTO grades VALUES('1029', 'A');
INSERT INTO grades VALUES('1030', 'C');
INSERT INTO grades VALUES('1031', 'F');

donne un pourcentage entier dans sql-server
Thunder

@Thunder: intéressant; que se passe-t-il si vous changez, disons, le 100 à 100,00?
Jonathan Leffler

Bien sûr, le résultat est en décimal avec 100,0
Thunder

4
SELECT Grade, GradeCount / SUM(GradeCount)
FROM (SELECT Grade, COUNT(*) As GradeCount
      FROM myTable
      GROUP BY Grade) Grades

3

Dans n'importe quelle version de serveur SQL, vous pouvez utiliser une variable pour le total de toutes les notes comme ceci:

declare @countOfAll decimal(18, 4)
select @countOfAll = COUNT(*) from Grades

select
Grade,  COUNT(*) / @countOfAll * 100
from Grades
group by Grade

3

Vous pouvez utiliser une sous-sélection dans votre requête from (non testée et vous ne savez pas laquelle est la plus rapide):

SELECT Grade, COUNT(*) / TotalRows
FROM (SELECT Grade, COUNT(*) As TotalRows
      FROM myTable) Grades
GROUP BY Grade, TotalRows

Ou

SELECT Grade, SUM(PartialCount)
FROM (SELECT Grade, 1/COUNT(*) AS PartialCount
      FROM myTable) Grades
GROUP BY Grade

Ou

SELECT Grade, GradeCount / SUM(GradeCount)
FROM (SELECT Grade, COUNT(*) As GradeCount
      FROM myTable
      GROUP BY Grade) Grades

Vous pouvez également utiliser une procédure stockée (excuses pour la syntaxe Firebird):

SELECT COUNT(*)
FROM myTable
INTO :TotalCount;

FOR SELECT Grade, COUNT(*)
FROM myTable
GROUP BY Grade
INTO :Grade, :GradeCount
DO
BEGIN
    Percent = :GradeCount / :TotalCount;
    SUSPEND;
END

0

J'ai eu un problème similaire à celui-ci. vous devriez être en mesure d'obtenir le résultat correct en multipliant par 1,0 au lieu de 100 Voir l'exemple Image ci-jointe

Sélectionnez Grade, (Count (Grade) * 1.0 / (Select Count (*) From MyTable)) comme Score From MyTable Group By Grade Voir l'image de référence ci-jointe


Veuillez ne pas partager d'informations sous forme d'images à moins que cela ne soit absolument nécessaire. Voir: meta.stackoverflow.com/questions/303812/… .
AMC

0

Celui-ci fonctionne bien dans MS SQL. Il transforme varchar en résultat d'un flottant limité à deux décimales.

Select field1, cast(Try_convert(float,(Count(field2)* 100) / 
Try_convert(float, (Select Count(*) From table1))) as decimal(10,2)) as new_field_name 
From table1 
Group By field1, field2;
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.