Cómo calcular el porcentaje con una declaración SQL

177

Tengo una tabla de SQL Server que contiene usuarios y sus calificaciones. Por simplicidad, digamos que hay 2 columnas - name& grade. Entonces, una fila típica sería Nombre: "John Doe", Grado: "A".

Estoy buscando una declaración SQL que encuentre los porcentajes de todas las respuestas posibles. (A, B, C, etc.) Además, ¿hay alguna manera de hacerlo sin definir todas las respuestas posibles (campo de texto abierto: los usuarios pueden ingresar 'pasar / fallar', 'ninguno', etc.)

El resultado final que estoy buscando es A: 5%, B: 15%, C: 40%, etc.

Alex
fuente

Respuestas:

227

He probado lo siguiente y esto funciona. La respuesta de gordyii fue cercana, pero tenía la multiplicación de 100 en el lugar equivocado y faltaban algunos paréntesis.

Select Grade, (Count(Grade)* 100 / (Select Count(*) From MyTable)) as Score
From MyTable
Group By Grade
Jason
fuente
21
esto da como resultado números enteros. la suma de resultados no es igual a 100.
Thunder
10
No es el más eficiente, ya que la tabla se escaneará dos veces. Además, la consulta no se verá tan simple si hay más de una tabla referenciada.
Alex Aza
14
@ Thunder puede cambiar de 100 a 100.0 para valores decimales.
Joseph
¿Alguien puede explicar por qué la sintaxis matemática de la consulta SQL no es lo que esperarías hacer normalmente? Por ejemplo, normal, habría dividido por el total y luego por 100? Genuinamente curioso sobre esto desde un punto de vista lógico.
Digitalsa1nt
44
@ Digitalsa1nt (100 * 2) / 4 = 50, (2/4) * 100 = 50 siempre que el enumerador sea la parte que se está multiplicando. Debido a la precedencia de las declaraciones SQL, será lo mismo. sin embargo, debido a los tipos de datos, si usa 100 aún puede obtener el resultado redondeado a 0 decimales que desee para el%, donde, como si lo colocara después de la operación de división, tendría que asegurarse de convertir a un tipo de datos que pueda manejar los lugares decimales de lo contrario terminarás con 100 o 0 y nunca un porcentaje real
Matt
231
  1. El más eficiente (usando over ()).

    select Grade, count(*) * 100.0 / sum(count(*)) over()
    from MyTable
    group by Grade
    
  2. Universal (cualquier versión de SQL).

    select Grade, count(*) * 100.0 / (select count(*) from MyTable)
    from MyTable
    group by Grade;
    
  3. Con CTE, el 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;
    
Alex Aza
fuente
13
over () funcionó perfectamente en mi SQL Server 2008, hice los cálculos para confirmar. Para redondearlo a 2 decimales, utilicé CAST (count ( ) * 100.0 / sum (count ( )) over () AS DECIMAL (18, 2)). ¡Gracias por la publicacion!
RJB
3
En caso de que se desborde en la multiplicación 100 (p. Ej., Error de desbordamiento aritmético al convertir la expresión al tipo de datos int ), reemplácelo con una división en el denominador:cast((count(*) / (sum(count(*)) over() / 100)) AS DECIMAL(18, 2)) as Percentage
Nikita G.
@RJB ¿Por qué tienes que multiplicar por 100.0 y no solo por 100 cuando estás convirtiendo la salida en decimal?
AS91
2
@ AS91, porque la conversión a decimal ocurre DESPUÉS de la operación de división. Si deja un int (100), dividir por otro int también dará como resultado un int, que redondeará el resultado. Es por eso que el truco siempre es forzar un lanzamiento en el dividendo antes de la división real (puede multiplicar por un decimal literal como 1.0 o lanzar / convertir)
luiggig
Opción 1 con over()trabajos excelentes en Postgresql 10
James Daily
40

En lugar de usar un CTE separado para obtener el total, puede usar una función de ventana sin la cláusula "partición por".

Si estás usando:

count(*)

Para obtener el recuento de un grupo, puede usar:

sum(count(*)) over ()

para obtener el recuento total.

Por ejemplo:

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

En mi experiencia, tiende a ser más rápido, pero creo que internamente podría usar una tabla temporal en algunos casos (he visto "Worktable" cuando se ejecuta con "set stats io on").

EDITAR: No estoy seguro de si mi consulta de ejemplo es lo que está buscando, solo estaba ilustrando cómo funcionan las funciones de ventanas.

John Gibb
fuente
+1. Esto es genial. También se puede usar si en lugar de 'table' hay una instrucción select.
mr_georg
1
Utiliza un carrete en el tempdbque se encuentra la mesa de trabajo. Las lecturas lógicas parecen más altas, pero se cuentan de manera diferente a lo normal
Martin Smith
1
En realidad, COUNT(*) OVER ()en su consulta devolvería una figura completamente no relacionada (específicamente, el número de filas del conjunto de resultados agrupados ). Deberías usar SUM(COUNT(*)) OVER ()en su lugar.
Andriy M
10

Debe calcular el total de calificaciones. Si se trata de SQL 2005, puede usar 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-
fuente
1
Por supuesto, esto solo da los porcentajes para los códigos de calificación presentes en la tabla, no para aquellos que podrían estar presentes y no lo están. Pero sin una lista definitiva de los códigos de calificación relevantes (válidos), no puede hacerlo mejor. De ahí el +1 de mi parte.
Jonathan Leffler
1
La joya escondida para mí fue que comentaste CONVERT.
Chris Catignani
9

Necesitas agruparte en el campo de calificación. Esta consulta debería darle lo que está buscando en prácticamente cualquier base de datos.

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

Debe especificar el sistema que está utilizando.

Jeremy
fuente
2
Ya que tiene un agregado ('sum (CountofGrade)') en la selección externa, ¿no necesita un grupo por cláusula también? Y en SQL estándar, creo que podría usar '/ (SELECT COUNT (*) FROM Grades)' para obtener el total general.
Jonathan Leffler
A IBM Informix Dynamic Server no le gusta el SUM desnudo en la lista de selección (aunque da un mensaje algo menos que útil cuando se queja). Como se señaló en mi respuesta y comentario anterior, usar una expresión de sub-selección completa en la lista de selección funciona en IDS.
Jonathan Leffler
Esto también es mejor porque uno puede aplicar complejos dónde realizar consultas internas.
mvmn
9

Simplemente uso esto cuando necesito calcular un porcentaje ...

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

Tenga en cuenta que 100.0 devuelve decimales, mientras que 100 por sí solo redondeará el resultado al número entero más cercano, ¡incluso con la función ROUND ()!

Fandango68
fuente
7

Lo siguiente debería funcionar

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

EDITAR: movió * 100y agregó el 1.0para asegurarse de que no haga división entera

Select 
   Grade, Count(ID) * 100.0 / ((Select Count(ID) From MyTable) * 1.0)
From MyTable
Group By Grade
GordyII
fuente
1
esto funciona, pero todas las respuestas vuelven a ser 0: ¿necesito hacer algún tipo de formato o conversión de números para ver la respuesta correcta?
Alex el
1
Seleccionar calificación, redondear (Conteo (calificación) * 100.0 / ((Seleccionar conteo (calificación) De calificaciones) * 1.0), 2) De calificaciones Agrupar por calificación para agregar una función de ronda en el servidor sql-returend, por ejemplo: 21.56000000000
Thunder
5

Esta es, creo, una solución general, aunque la probé utilizando IBM Informix Dynamic Server 11.50.FC3. La siguiente 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;

da el siguiente resultado en los datos de prueba que se muestran debajo de la regla horizontal. La ROUNDfunción puede ser específica de DBMS, pero el resto (probablemente) no lo es. (Tenga en cuenta que cambié de 100 a 100.0 para garantizar que el cálculo se realice usando aritmética no entera - DECIMAL, NUMERIC -; vea los comentarios y gracias a 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
fuente
da un porcentaje entero en sql-server
Thunder
@ Trueno: interesante; ¿Qué sucede si cambia, digamos, de 100 a 100.00?
Jonathan Leffler
Claro, el resultado está en decimal con 100.0
Thunder
4
SELECT Grade, GradeCount / SUM(GradeCount)
FROM (SELECT Grade, COUNT(*) As GradeCount
      FROM myTable
      GROUP BY Grade) Grades
Aakashi
fuente
3

En cualquier versión de servidor sql, puede usar una variable para el total de todos los grados como este:

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

select
Grade,  COUNT(*) / @countOfAll * 100
from Grades
group by Grade
Steve Willcock
fuente
3

Puede usar una subselección en su consulta from (no probado y no está seguro de cuál es más rápido):

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

O

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

O

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

También puede usar un procedimiento almacenado (disculpas por la sintaxis de 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.
fuente
0

Tuve un problema similar a esto. debería poder obtener el resultado correcto multiplicando por 1.0 en lugar de 100. Vea la imagen adjunta de ejemplo

Seleccione Grado, (Recuento (Grado) * 1.0 / (Seleccione Recuento (*) De MyTable)) como Puntuación del Grupo MyTable Por Grado Ver imagen de referencia adjunta

gabriel egbenya
fuente
No comparta información como imágenes a menos que sea absolutamente necesario. Ver: meta.stackoverflow.com/questions/303812/… .
AMC
0

Este está funcionando bien en MS SQL. Transforma varchar al resultado de flotante con dos decimales limitados.

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
fuente