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 ...

Alex
la source

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
Jason
la source
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;
Alex Aza
la source
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.

John Gibb
la source
+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
Jhonny D. Cano -Leftware-
la source
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.

Jérémie
la source
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 ()!

Fandango68
la source
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
GordyII
la source
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');
Jonathan Leffler
la source
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
Aakashi
la source
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
Steve Willcock
la source
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
lc.
la source
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

Gabriel Egbenya
la source
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;
Kokokoko
la source