Как рассчитать процент с помощью оператора SQL


177

У меня есть таблица SQL Server, которая содержит пользователей и их оценки. Для простоты, давайте просто скажем, что есть 2 столбца - name& grade. Таким образом, типичный ряд будет Имя: «Джон Доу», класс: «А».

Я ищу один оператор SQL, который найдет проценты всех возможных ответов. (A, B, C и т. Д.) Кроме того, есть ли способ сделать это без определения всех возможных ответов (открытое текстовое поле - пользователи могут ввести «пройти / не пройти», «нет» и т. Д.)

Конечный результат, который я ищу, - A: 5%, B: 15%, C: 40% и т. Д.

Ответы:


227

Я проверил следующее, и это работает. Ответ от gordyii был близок, но умножил 100 в неправильном месте и пропустил несколько скобок.

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

21
это дает результат в целых числах. Сумма результатов не равна 100.
— Гром

10
Не самый эффективный, так как таблица будет отсканирована дважды. Кроме того, запрос не будет выглядеть так просто, если на него ссылаются несколько таблиц.
— Алекс Аза

14
@ Thunder вы можете изменить 100 на 100,0 для десятичных значений.
— Джозеф

Может кто-нибудь объяснить, почему математический синтаксис SQL-запроса не соответствует ожиданиям? Например нормальный я бы поделил на итоговое количество раз на 100? Искренне любопытно об этом с логической точки зрения.
— Digitalsa1nt

4
@ Digitalsa1nt (100 * 2) / 4 = 50, (2/4) * 100 = 50, пока счетчик является умножаемой частью. Из-за приоритета операторов SQL это будет то же самое. однако из-за типов данных, если использовать 100, вы все равно можете получить результат, округленный до 0 десятичных знаков, который вы хотите получить в%, где, как если бы вы поместили его после операции деления, вам нужно было бы убедиться, что вы приводите к типу данных, который может обрабатывать десятичные разряды, в противном случае вы получите 100 или 0, а не фактический процент
— Matt

232
  1. Наиболее эффективный (используя over ()).

    select Grade, count(*) * 100.0 / sum(count(*)) over()
    from MyTable
    group by Grade
  2. Универсальный (любая версия SQL).

    select Grade, count(*) * 100.0 / (select count(*) from MyTable)
    from MyTable
    group by Grade;
  3. С CTE наименее эффективный.

    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 () отлично работал на моем SQL Server 2008, я сделал математику для подтверждения. Для округления до 2 десятичных знаков я использовал CAST (count ( ) * 100.0 / sum (count ( )) over () AS DECIMAL (18, 2)). Спасибо за пост!
— RJB

3
В случае переполнения при умножении 100 (например, ошибка арифметического переполнения при преобразовании выражения в тип данных int ) замените его вместо деления в знаменателе:cast((count(*) / (sum(count(*)) over() / 100)) AS DECIMAL(18, 2)) as Percentage
— Никита Г.

@RJB Почему вы должны умножать на 100,0, а не просто на 100, когда вы выводите вывод в виде десятичного числа?
— AS91

2
@ AS91, потому что приведение к десятичному типу происходит ПОСЛЕ операции деления. Если вы оставите int (100), деление на другое int также приведет к int, что округлит результат. Вот почему хитрость заключается в том, чтобы всегда вызывать приведение к дивиденду до фактического деления (вы можете умножить его на десятичное число буквально, например, 1,0 или приведение / преобразование)
— luiggig

Вариант 1 с over()прекрасно работает на Postgresql 10
— James Daily

40

Вместо того, чтобы использовать отдельный CTE для получения суммы, вы можете использовать оконную функцию без предложения "partition by".

Если вы используете:

count(*)

чтобы получить счет для группы, вы можете использовать:

sum(count(*)) over ()

чтобы получить общее количество.

Например:

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

В моем случае это происходит быстрее, но я думаю, что в некоторых случаях он может использовать временную таблицу (я видел «Рабочий стол» при работе с «Установить статистику io вкл.»).

РЕДАКТИРОВАТЬ: я не уверен, что мой пример запроса, что вы ищете, я просто иллюстрировал, как работают функции окон.


+1. Это круто. Его также можно использовать, если вместо «таблицы» есть оператор выбора.
— mr_georg

1
Он использует катушку, в tempdbкоторой находится рабочий стол. Логические чтения кажутся выше, но они учитываются не так, как обычно
— Мартин Смит

1
На самом деле, COUNT(*) OVER ()в вашем запросе будет возвращена совершенно не связанная цифра (в частности, количество строк в сгруппированном результирующем наборе). Вы должны использовать SUM(COUNT(*)) OVER ()вместо этого.
— Андрей М

10

Вы должны рассчитать общее количество оценок. Если это SQL 2005, вы можете использовать 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
Конечно, это дает только проценты для кодов оценок, представленных в таблице, а не для тех, которые могут присутствовать и не присутствуют. Но без окончательного списка соответствующих (действительных) кодов оценок вы не сможете добиться большего успеха. Отсюда +1 от меня.
— Джонатан Леффлер

1
Скрытой жемчужиной для меня было то, что вы закомментировали КОНВЕРТ.
— Крис Катиньяни

9

Вам нужно сгруппировать по полю оценки. Этот запрос должен дать вам то, что вы ищете практически в любой базе данных.

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

Вы должны указать систему, которую вы используете.


2
Поскольку у вас есть агрегат ('sum (CountofGrade)') во внешнем выборе, вам также не нужен в нем оператор group by? И в стандартном SQL, я думаю, вы можете использовать '/ (SELECT COUNT (*) FROM Grades)', чтобы получить общий итог.
— Джонатан Леффлер

IBM Informix Dynamic Server не нравится «пустая» сумма в списке выбора (хотя при подаче жалобы он выдает не совсем полезное сообщение). Как отмечалось в моем ответе и предыдущем комментарии, использование полного выражения sub-select в списке выбора работает в IDS.
— Джонатан Леффлер

Это также лучше, потому что можно применить сложный, где к внутреннему запросу.
— mvmn

9

Я просто использую это, когда мне нужно отработать процент.

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

Обратите внимание, что 100.0 возвращает десятичные дроби, а само по себе 100 округляет результат до ближайшего целого числа, даже с функцией ROUND ()!


7

Следующее должно работать

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

РЕДАКТИРОВАТЬ: переместил * 100и добавил, 1.0чтобы убедиться, что он не делает целочисленное деление

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

1
это работает, но все ответы возвращаются как 0 - мне нужно сделать какое-то форматирование числа или преобразование, чтобы увидеть правильный ответ?
— Алекс

1
Выберите Grade, round (Количество (оценка) * 100.0 / ((Выберите Count (оценка) По оценкам) * 1.0), 2) Из оценок Group By Grade для добавления функции округления при повторном запуске sql-сервера, например: 21.56000000000
— Thunder

5

Я считаю, что это общее решение, хотя я тестировал его с использованием IBM Informix Dynamic Server 11.50.FC3. Следующий запрос:

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;

дает следующий вывод на тестовые данные, показанные ниже горизонтального правила. ROUNDФункция может быть СУБД конкретного, но остальное (возможно) не является. (Обратите внимание, что я изменил 100 на 100,0, чтобы гарантировать, что вычисление происходит с использованием нецелого числа - DECIMAL, NUMERIC - арифметика; см. Комментарии и спасибо 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');

дает целочисленный процент в sql-сервере
— Гром

@ Thunder: интересно; что произойдет, если вы измените, скажем, 100 на 100,00?
— Джонатан Леффлер

Конечно, результат десятичный с 100,0
— Гром

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

3

В любой версии сервера SQL вы можете использовать переменную для общего количества всех оценок, как это:

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

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

3

Вы можете использовать вложенный выбор в вашем запросе from (не проверен и не уверен, что быстрее):

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

Или

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

Или

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

Вы также можете использовать хранимую процедуру (извинения за синтаксис 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

У меня была похожая проблема с этим. Вы должны иметь возможность получить правильный результат умножением на 1,0 вместо 100. См. пример Изображение прикреплено

Выберите оценку (Количество (класс) * 1,0 / (Выберите количество (*) из MyTable)) в качестве показателя из группы MyTable по классу См контрольное изображение прилагается


Пожалуйста, не передайте информацию в виде изображений, если это абсолютно необходимо. См .: meta.stackoverflow.com/questions/303812/… .
— AMC

0

Этот хорошо работает в MS SQL. Он преобразует varchar в результат числа с плавающей запятой, ограниченного двумя десятичными знаками.

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;
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.