Jak obliczyć procent za pomocą instrukcji SQL

177

Mam tabelę SQL Server zawierającą użytkowników i ich oceny. Dla uproszczenia powiedzmy, że istnieją 2 kolumny -name & grade. Tak więc typowym wierszem będzie Imię: „Jan Kowalski”, Ocena: „A”.

Szukam jednej instrukcji SQL, która znajdzie procenty wszystkich możliwych odpowiedzi. (A, B, C, itd ...) Ponadto, czy istnieje sposób na zrobienie tego bez definiowania wszystkich możliwych odpowiedzi (otwarte pole tekstowe - użytkownicy mogą wpisać „pass / fail”, „none”, itd.)

Ostateczny wynik, którego szukam, to A: 5%, B: 15%, C: 40% itd.

Alex
źródło

Odpowiedzi:

227

Przetestowałem następujące elementy i to działa. Odpowiedź gordyii była bliska, ale pomnożenie 100 w niewłaściwym miejscu i brakowało kilku nawiasów.

Select Grade, (Count(Grade)* 100 / (Select Count(*) From MyTable)) as Score
From MyTable
Group By Grade
Jason
źródło
21
daje to wynik w liczbach całkowitych. suma wyników nie jest równa 100.
Grzmot
10
Nie jest to najbardziej wydajne, ponieważ tabela zostanie przeskanowana dwukrotnie. Zapytanie również nie będzie wyglądać tak prosto, jeśli istnieje odwołanie do więcej niż jednej tabeli.
Alex Aza
14
@ Thunder możesz zmienić 100 na 100.0 dla wartości dziesiętnych.
Joseph
Czy ktoś może wyjaśnić, dlaczego matematyczna składnia zapytania SQL nie jest tym, czego można się spodziewać normalnie? Na przykład normalne podzieliłbym przez sumę, a następnie razy przez 100? Naprawdę ciekawy tego z logicznego punktu widzenia.
Digitalsa1nt
4
@ Digitalsa1nt (100 * 2) / 4 = 50, (2/4) * 100 = 50, o ile moduł wyliczający jest mnożoną częścią. Ze względu na pierwszeństwo instrukcji SQL będzie tak samo. jednak ze względu na typy danych, jeśli używasz 100, nadal możesz uzyskać wynik zaokrąglony do 0 miejsc po przecinku, które chcesz dla%, gdzie tak, jakbyś umieścił go po operacji dzielenia, musisz się upewnić, że rzutujesz na typ danych, który może obsłużyć miejsca dziesiętne, w przeciwnym razie otrzymasz 100 lub 0, a nigdy rzeczywistą wartość procentową
Matt
231
  1. Najbardziej wydajne (przy użyciu over ()).

    select Grade, count(*) * 100.0 / sum(count(*)) over()
    from MyTable
    group by Grade
  2. Uniwersalny (dowolna wersja SQL).

    select Grade, count(*) * 100.0 / (select count(*) from MyTable)
    from MyTable
    group by Grade;
  3. Z CTE najmniej wydajne.

    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
źródło
13
over () działał idealnie na moim SQL Server 2008, wykonałem obliczenia, aby potwierdzić. Aby to zaokrąglić do 2 miejsc po przecinku, użyłem CAST (count ( ) * 100,0 / sum (count ( )) over () AS DECIMAL (18, 2)). Dzięki za post!
RJB
3
W przypadku przelania się w 100 namnażania (na przykład arytmetyczny błąd przelewu konwersji wyraz danych typu int ), wymienić go z podziałem w mianowniku zamiast:cast((count(*) / (sum(count(*)) over() / 100)) AS DECIMAL(18, 2)) as Percentage
Nikicki G.
@RJB Dlaczego musisz pomnożyć przez 100,0, a nie tylko 100, kiedy rzutujesz wynik jako ułamek dziesiętny?
AS91,
2
@ AS91, ponieważ rzutowanie na ułamek dziesiętny następuje PO operacji dzielenia. Jeśli zostawisz int (100), podzielenie przez inny int spowoduje również int, który zaokrągli wynik. Dlatego sztuczka polega zawsze na wymuszeniu rzutu na dywidendę przed faktycznym dzieleniem (możesz pomnożyć przez dosłowny ułamek dziesiętny, taki jak 1.0, lub rzucić / przekonwertować)
luiggig
Opcja 1 z over()działa świetnie na Postgresql 10
James Daily,
40

Zamiast używać oddzielnego CTE do uzyskania sumy całkowitej, możesz użyć funkcji okna bez klauzuli „partition by”.

Jeśli używasz:

count(*)

aby uzyskać licznik dla grupy, możesz użyć:

sum(count(*)) over ()

aby uzyskać całkowitą liczbę.

Na przykład:

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

Z mojego doświadczenia wynika, że ​​jest szybszy, ale myślę, że w niektórych przypadkach może wewnętrznie używać tabeli tymczasowej (widziałem „Worktable”, gdy działałem z włączoną opcją „ustaw statystyki we”).

EDYCJA: Nie jestem pewien, czy moje przykładowe zapytanie jest tym, czego szukasz, tylko zilustrowałem, jak działają funkcje okienkowe.

John Gibb
źródło
+1. To jest świetne. Można go również użyć, jeśli zamiast „tabeli” znajduje się instrukcja wyboru.
mr_georg
1
Wykorzystuje szpulę, w tempdbktórej znajduje się stół roboczy. Odczyty logiczne wydają się wyższe, ale są liczone inaczej niż normalnie
Martin Smith
1
W rzeczywistości COUNT(*) OVER ()zapytanie w zapytaniu zwróci całkowicie niepowiązaną liczbę (w szczególności liczbę wierszy w zgrupowanym zestawie wyników). Zamiast tego powinieneś użyć SUM(COUNT(*)) OVER ().
Andriy M
10

Musisz obliczyć sumę ocen. Jeśli jest to SQL 2005, możesz użyć 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-
źródło
1
Oczywiście podaje to tylko wartości procentowe dla kodów ocen obecnych w tabeli, a nie dla tych, które mogą być obecne, a których nie. Ale bez ostatecznej listy odpowiednich (ważnych) kodów ocen nie można zrobić lepiej. Stąd +1 ode mnie.
Jonathan Leffler
1
Ukrytym klejnotem dla mnie był Twój komentarz CONVERT.
Chris Catignani
9

Musisz zgrupować w polu oceny. To zapytanie powinno dać ci to, czego szukasz w prawie każdej bazie danych.

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

Powinieneś określić system, którego używasz.

Jeremy
źródło
2
Ponieważ masz agregację („suma (CountofGrade)”) w zewnętrznym zaznaczeniu, czy nie potrzebujesz w niej również klauzuli group by? I myślę, że w standardowym SQL można użyć „/ (SELECT COUNT (*) FROM Grades)”, aby uzyskać całkowitą sumę.
Jonathan Leffler
IBM Informix Dynamic Server nie lubi nagiej wartości SUM na liście wyboru (chociaż wyświetla nieco mniej pomocny komunikat, gdy narzeka). Jak zauważyłem w mojej odpowiedzi i wcześniejszym komentarzu, użycie pełnego wyrażenia podwyboru na liście wyboru działa w IDS.
Jonathan Leffler
Jest to również lepsze, ponieważ można zastosować złożone gdzie do wewnętrznego zapytania.
mvmn
9

Po prostu używam tego, gdy muszę wyliczyć procent.

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

Zwróć uwagę, że 100.0 zwraca liczby dziesiętne, podczas gdy 100 samo w sobie zaokrągli wynik do najbliższej liczby całkowitej, nawet z funkcją ROUND ()!

Fandango68
źródło
7

Poniższe powinny działać

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

EDYCJA: Przeniesiono * 100i dodano, 1.0aby upewnić się, że nie wykonuje dzielenia liczb całkowitych

Select 
   Grade, Count(ID) * 100.0 / ((Select Count(ID) From MyTable) * 1.0)
From MyTable
Group By Grade
GordyII
źródło
1
to działa, ale wszystkie odpowiedzi wracają jako 0 - czy muszę wykonać jakieś formatowanie lub konwersję liczb, aby zobaczyć poprawną odpowiedź?
Alex
1
Wybierz Grade, round (Count (grade) * 100.0 / ((Select Count (grade) Fromieses) * 1.0), 2) From grades Group By Grade, aby dodać funkcję rundy w sql-server returend, np .: 21.56000000000
Thunder
5

Uważam, że jest to rozwiązanie ogólne, chociaż testowałem je na serwerze IBM Informix Dynamic Server 11.50.FC3. Następujące zapytanie:

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;

daje następujący wynik na danych testowych pokazanych poniżej linii poziomej. ROUNDFunkcja może być DBMS specyficzne, ale reszta (chyba) nie jest. (Zauważ, że zmieniłem 100 na 100.0, aby upewnić się, że obliczenia są wykonywane przy użyciu niecałkowitych - DECIMAL, NUMERIC - arytmetyczne; zobacz komentarze i podziękowania dla 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
źródło
daje procent całkowity w sql-server
Thunder
@ Thunder: interesujące; co się stanie, jeśli zmienisz, powiedzmy, 100 na 100,00?
Jonathan Leffler
Jasne, wynik jest dziesiętny i wynosi 100,0
Grzmot
4
SELECT Grade, GradeCount / SUM(GradeCount)
FROM (SELECT Grade, COUNT(*) As GradeCount
      FROM myTable
      GROUP BY Grade) Grades
Aakashi
źródło
3

W dowolnej wersji serwera sql możesz użyć zmiennej dla sumy wszystkich ocen, takiej jak ta:

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

select
Grade,  COUNT(*) / @countOfAll * 100
from Grades
group by Grade
Steve Willcock
źródło
3

Możesz użyć podselekcji w zapytaniu z zapytania (nieprzetestowane i nie wiesz, która jest szybsza):

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

Lub

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

Lub

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

Możesz także użyć procedury składowanej (przepraszam za składnię 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.
źródło
0

Miałem podobny problem do tego. powinieneś być w stanie uzyskać poprawny wynik mnożąc przez 1,0 zamiast 100.Patrz przykład Obraz w załączeniu

Wybierz ocenę, (Count (Grade) * 1.0 / (Select Count (*) From MyTable)) jako wynik z MyTable Group By Grade Zobacz załączony obraz referencyjny

gabriel egbenya
źródło
Nie udostępniaj informacji w postaci obrazów, chyba że jest to absolutnie konieczne. Zobacz: meta.stackoverflow.com/questions/303812/… .
AMC
0

Ten działa dobrze w MS SQL. Przekształca varchar w wynik zmiennoprzecinkowy z ograniczeniem do dwóch miejsc po przecinku.

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
źródło