Como calcular porcentagem com uma instrução SQL


177

Eu tenho uma tabela do SQL Server que contém usuários e suas notas. Por uma questão de simplicidade, vamos apenas dizer que existem 2 colunas - name& grade. Portanto, uma linha típica seria Nome: "John Doe", Nota: "A".

Estou procurando uma instrução SQL que encontre as porcentagens de todas as respostas possíveis. (A, B, C, etc ...) Além disso, existe uma maneira de fazer isso sem definir todas as respostas possíveis (campo de texto aberto - os usuários podem inserir 'passa / falha', 'nenhum', etc ...)

O resultado final que procuro é A: 5%, B: 15%, C: 40%, etc ...

Respostas:


227

Eu testei o seguinte e isso funciona. A resposta de gordyii foi próxima, mas teve a multiplicação de 100 no lugar errado e faltaram parênteses.

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

21
isso dá resultado em números inteiros. a soma dos resultados não é igual a 100. #
Thunder Thunder

10
Não é o mais eficiente, pois a tabela será digitalizada duas vezes. Além disso, a consulta não parecerá tão simples se houver mais de uma tabela referenciada.
Alex Aza #

14
@Thunder, você pode alterar 100 para 100.0 para valores decimais.
Joseph

Alguém pode explicar por que a sintaxe matemática da consulta SQL não é o que você esperaria fazer normalmente? Por exemplo normal, eu teria dividido pelo total, em seguida, vezes por 100? Genuinamente curioso sobre isso do ponto de vista lógico.
precisa

4
@ Digitalsa1nt (100 * 2) / 4 = 50, (2/4) * 100 = 50 enquanto o enumerador estiver, é a parte que está sendo multiplicada. Devido à precedência das instruções SQL, será a mesma. no entanto, devido aos tipos de dados, se usando 100, você ainda pode obter o resultado arredondado para 0 casas decimais desejadas para o% onde, como se você o colocasse após a operação de divisão, teria que certificar-se de transmitir para um tipo de dados que possa manipular as casas decimais caso contrário você vai acabar com 100 ou 0 e nunca uma porcentagem real
Matt

231
  1. O mais eficiente (usando over ()).

    select Grade, count(*) * 100.0 / sum(count(*)) over()
    from MyTable
    group by Grade
  2. Universal (qualquer versão do SQL).

    select Grade, count(*) * 100.0 / (select count(*) from MyTable)
    from MyTable
    group by Grade;
  3. Com o CTE, o menos eficiente.

    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 () funcionou perfeitamente no meu SQL Server 2008, fiz as contas para confirmar. Para arredondar para duas casas decimais, usei CAST (count ( ) * 100.0 / sum (count ( )) sobre () AS DECIMAL (18, 2)). Obrigado pelo post!
RJB

3
Caso você transborde na multiplicação 100 (por exemplo, erro aritmético de transbordo ao converter expressão em tipo de dados int ), substitua-o por divisão no denominador:cast((count(*) / (sum(count(*)) over() / 100)) AS DECIMAL(18, 2)) as Percentage
Nikita G.

@RJB Por que você precisa multiplicar por 100,0 e não apenas 100 quando está lançando a saída como decimal?
AS91

2
@ AS91, porque a conversão para decimal acontece APÓS a operação de divisão. Se você deixar um int (100), dividir por outro int também resultará em um int, que arredondará o resultado. É por isso que o truque é sempre para forçar um elenco sobre o dividendo antes da divisão real (você pode multiplicar por um decimal literal como 1.0 ou fundido / Convert)
luiggig

Opção 1 com over()trabalhos excelentes no Postgresql 10
James Daily

40

Em vez de usar um CTE separado para obter o total, você pode usar uma função de janela sem a cláusula "partição por".

Se você estiver usando:

count(*)

para obter a contagem de um grupo, você pode usar:

sum(count(*)) over ()

para obter a contagem total.

Por exemplo:

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

Tende a ser mais rápido em minha experiência, mas acho que ela pode usar internamente uma tabela temporária em alguns casos (vi o "Worktable" ao executar com "definir estatísticas io on").

Edição: Não tenho certeza se o meu exemplo de consulta é o que você está procurando, eu estava apenas ilustrando como as funções de janelas funcionam.


+1. Isso é ótimo. Também pode ser usado se no lugar de 'table' houver uma instrução select.
Mr_georg 01/09/09

1
Ele usa um carretel no tempdbqual está a mesa de trabalho. A lógica lê parecer maior , mas eles são contados de forma diferente do que o normal
Martin Smith

1
Na verdade, COUNT(*) OVER ()na sua consulta retornaria uma figura completamente não relacionada (especificamente, o número de linhas do conjunto de resultados agrupado ). Você deve usar em seu SUM(COUNT(*)) OVER ()lugar.
Andriy M

10

Você precisa calcular o total de notas Se for o SQL 2005, você pode usar o 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
Obviamente, isso fornece apenas as porcentagens para códigos de notas presentes na tabela, não para aqueles que poderiam estar presentes e não estarem. Mas sem uma lista definitiva dos códigos de classificação relevantes (válidos), você não pode fazer melhor. Daí o +1 de mim.
31416 Jonathan Leffler

1
A jóia escondida para mim foi que você comentou o CONVERT.
Chris Catignani 13/03

9

Você precisa agrupar no campo de notas. Essa consulta deve fornecer o que você procura em praticamente qualquer banco de dados.

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

Você deve especificar o sistema que está usando.


2
Como você tem um agregado ('sum (CountofGrade)') na seleção externa, você não precisa de uma cláusula group by também? E no SQL padrão, acho que você pode usar '/ (SELECT COUNT (*) FROM Grades)' para obter o total geral.
31416 Jonathan Leffler

O IBM Informix Dynamic Server não gosta do SUM nu na lista de seleção (apesar de fornecer uma mensagem um pouco menos que útil quando se queixa). Conforme observado na minha resposta e no comentário anterior, o uso de uma expressão completa de sub-seleção na lista de seleção funciona no IDS.
31411 Jonathan Leffler

Isso também é melhor porque é possível aplicar complexos onde a consulta interna.
Mvmn

9

Eu simplesmente uso isso sempre que preciso calcular uma porcentagem.

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

Observe que 100.0 retorna decimais, enquanto 100 por si só arredondará o resultado para o número inteiro mais próximo, mesmo com a função ROUND ()!


7

O seguinte deve funcionar

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

EDIT: Moveu o * 100e adicionou o 1.0para garantir que ele não faça divisão inteira

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

1
isso funciona, mas todas as respostas retornam como 0 - eu preciso fazer algum tipo de formatação ou conversão de número para ver a resposta correta?
Alex

1
Selecionar Nota, rodada (Contagem (nota) * 100,0 / ((Selecionar Contagem (nota) Das notas) * 1,0), 2) Das notas Agrupar por nota para adicionar uma função redonda no returend do servidor sql, por exemplo: 21.56000000000
Thunder

5

Esta é, acredito, uma solução geral, embora a tenha testado usando o IBM Informix Dynamic Server 11.50.FC3. A seguinte consulta:

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;

fornece a seguinte saída nos dados de teste mostrados abaixo da regra horizontal. A ROUNDfunção pode ser específica ao DBMS, mas o resto (provavelmente) não é. (Observe que alterei 100 para 100,0 para garantir que o cálculo ocorra usando aritmético não inteiro - DECIMAL, NUMÉRICO -; veja os comentários e obrigado ao 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');

dá porcentagem inteira no servidor sql
Thunder

@ Thunder: interessante; o que acontece se você alterar, digamos, os 100 para 100,00?
Jonathan Leffler

Certifique-se de que o resultado seja decimal com 100.0
Thunder

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

3

Em qualquer versão do servidor sql, você pode usar uma variável para o total de todas as notas como esta:

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

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

3

Você pode usar uma subseleção na consulta from (não testada e não tem certeza do que é mais rápido):

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

Você também pode usar um procedimento armazenado (desculpas pela sintaxe do 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

Eu tive um problema semelhante a isso. você poderá obter o resultado correto multiplicando por 1,0 em vez de 100. Veja o exemplo Imagem anexada

Selecionar nota, (Contagem (nota) * 1,0 / (Selecionar contagem (*) de MyTable)) como Pontuação do MyTable Agrupar por nota Veja a imagem de referência em anexo


Por favor, não compartilhe informações como imagens, a menos que seja absolutamente necessário. Consulte: meta.stackoverflow.com/questions/303812/… .
AMC

0

Este está funcionando bem no MS SQL. Transforma varchar no resultado de flutuação limitada de duas casas decimais.

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;
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.