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

177

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

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

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

Alex
источник

Ответы:

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 ()!

Fandango68
источник
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
GordyII
источник
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
Aakashi
источник
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;
Kokokoko
источник