2009-04-21 85 views
112

Tengo una tabla de SQL Server que contiene los usuarios & sus calificaciones. En aras de la simplicidad, digamos que hay 2 columnas: name & grade. Entonces una fila típica sería Nombre: "John Doe", Grado: "A".Cómo calcular el porcentaje con una declaración de SQL

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 'aprobado/suspendido', 'ninguno', etc ...)

la salida final que estoy buscando es un: 5%, B: 15% C: 40%, etc ...

+0

Conseguir el% es fácil, el redondeo es la parte difícil :) –

+1

esto es una base de datos SQL Server – Alex

Respuesta

149

tengo probado lo siguiente y esto funciona.La respuesta de gordyii estaba cerca pero tenía la multiplicación de 100 en el lugar equivocado y tenía algunos paréntesis faltantes.

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

obtiene mi voto por simplicidad – Alex

+15

esto da como resultado enteros .sum de resultados no es igual a 100. – Thunder

+7

No es el más eficiente, ya que la tabla se escaneará dos veces. Además, la consulta no se verá tan simple si se hace referencia a más de una tabla. –

8

tienes que calcular el total de grados Si es que 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 
+1

Por supuesto, esto sólo da los porcentajes correspondientes a códigos de clasificación presentes en la mesa, no para aquellos que pudieran estar presentes y aren' t. Pero sin una lista definitiva de los códigos de grado relevantes (válidos), no se puede hacer mejor. De ahí el +1 de mí. –

8

Debe agrupar en el campo de calificación. Esta consulta debería darle lo que busca 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.

+2

Dado que tiene un agregado ('sum (CountofGrade)') en la selección externa, ¿no necesita una cláusula group by también? Y en SQL estándar, creo que podría usar '/ (SELECT COUNT (*) FROM Grades)' para obtener el total general. –

+0

maldita sea. tienes razón. gracias por la captura – Jeremy

+0

IBM Informix Dynamic Server no le gusta la SUMA desnuda en la lista de selección (aunque da un mensaje algo menos que útil cuando se queja). Como mencioné en mi respuesta y comentario anterior, usar una expresión de sub selección completa en la lista de selección funciona en IDS. –

2

En cualquier versión de SQL Server se puede utilizar una variable para el total de todos los grados de esta manera:

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

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

Se puede utilizar una subselección en su de consulta (no probado y no está seguro de que 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 se puede utilizar un procedimiento almacenado (disculpas por la sintaxis 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 
4

lo siguiente debe funcionar

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

EDIT: Se movió la * 100 y añadió la 1.0 para asegurar que no hace la división entera

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

esto funciona, pero todas las respuestas vuelven a ser 0 - ¿Debo hacer algún tipo de formateo o conversión de números para ver la respuesta correcta? – Alex

+0

Editado la respuesta para resolver su problema con suerte. – GordyII

+0

Seleccione Grado, redondo (Recuento (grado) * 100.0/((Seleccione Recuento (grado) De grados) * 1.0), 2) De grados Agrupar por Grado para agregar una función de redondeo en el servidor sql, por ejemplo: 21.56000000000 – Thunder

4

Esto es, creo, una solución general, aunque la probé usando 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 la siguiente salida en los datos de prueba que se muestran debajo de la regla horizontal. La función ROUND puede ser específica de DBMS, pero el resto (probablemente) no. (Tenga en cuenta que he cambiado de 100 a 100,0 para asegurar que el cálculo se produce utilizando no entero - DECIMAL, numéricos - aritmética; ver 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'); 
+0

da número entero porcentaje en el servidor sql – Thunder

+0

@Thunder: interesante; ¿Qué sucede si cambias, digamos, de 100 a 100.00? –

+0

Seguro el resultado es en decimal con 100.0 – Thunder

23

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

Si está utilizando:

count(*) 

para obtener el recuento de un grupo, puede utilizar:

sum(count(*)) over() 

para obtener el recuento total.

Por ejemplo:

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

Tiende a ser más rápido en mi experiencia, pero creo que se podría utilizar internamente una tabla temporal en algunos casos (que he visto "mesa de trabajo" cuando se ejecuta con el "ajuste estadísticas io on ").

EDIT: no estoy seguro de si mi consulta de ejemplo es lo que buscas, sólo estaba ilustrando cómo funcionan al sistema de ventanas.

+0

+1. Esto es genial. También se puede usar si en lugar de 'tabla' hay una instrucción de selección. –

+1

Utiliza un spool en 'tempdb' que es la tabla de trabajo. Las lecturas lógicas parecen más altas [pero se cuentan de forma diferente a lo normal] (http://stackoverflow.com/a/5194902/73226) –

+1

En realidad, el 'COUNT (*) OVER()' en su consulta devolvería una relación completamente distinta cifra (específicamente, el número de filas del conjunto de resultados * agrupado *). Debería usar 'SUM (COUNT (*)) OVER()' en su lugar. –

142
  1. El más eficiente (usando más()).

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

    select Rate, count(*) * 100.0/(select count(*) from MyTable) 
    from MyTable 
    group by Rate; 
    
  3. Con CTE, el menos eficiente.

    with t(Rate, RateCount) 
    as 
    ( 
        select Rate, count(*) 
        from MyTable 
        group by Rate 
    ) 
    select Rate, RateCount * 100.0/(select sum(RateCount) from t) 
    from t; 
    
+8

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

+0

¡Gracias por compartir el truco de Over()! ¡Me alegró el día! –

+2

En caso de desbordamiento en la multiplicación 100 (ej. _Arithmetic overflow error convirtiendo expresión a tipo de datos int_), reemplácelo con división en denominador en su lugar: 'cast ((count (*)/(sum (count (*)) over()/100)) AS DECIMAL (18, 2)) como Porcentaje ' –

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

simplemente uso de este, cuando cada vez que necesito para llegar a un porcentaje ..

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

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

Cuestiones relacionadas