GROUP BY, HAVING och aggregering

När du behöver räkna, summera eller beräkna medelvärden – då är aggregering rätt verktyg. Med GROUP BY grupperar du data och med HAVING filtrerar du grupper.

TL;DR

  • Aggregatfunktioner: COUNT(), SUM(), AVG(), MIN(), MAX()
  • GROUP BY – gruppera rader baserat på gemensamma värden
  • HAVING – filtrera grupper (används efter GROUP BY)
  • WHERE – filtrera rader (används före GROUP BY)

Aggregatfunktioner

Aggregatfunktioner bearbetar flera rader och returnerar ett enda resultat. De är fundamentala för att skapa rapporter och analysera data.

COUNT() – räkna rader

COUNT() är den mest använda aggregatfunktionen. Den räknar antalet rader som uppfyller ett villkor.

Exempeldata - Students:

StudentIdFirstNameLastNameEmail
1AnnaSvenssonanna@example.com
2BengtLarssonbengt@example.com
3CeciliaSvenssonNULL
4DavidNilssondavid@example.com
-- Antal studenter totalt
SELECT COUNT(*) AS TotalStudents
FROM Students;

Resultat:

TotalStudents
4
-- Antal studenter med email
SELECT COUNT(Email) AS StudentsWithEmail
FROM Students;

Resultat:

StudentsWithEmail
3

Observera: COUNT(Email) hoppar över NULL-värden. Cecilia har ingen email så hon räknas inte.

-- Antal unika efternamn
SELECT COUNT(DISTINCT LastName) AS UniqueLastNames
FROM Students;

Resultat:

UniqueLastNames
3

Förklaring: Anna och Cecilia delar efternamnet “Svensson”, så totalt finns 3 unika efternamn (Svensson, Larsson, Nilsson).

SUM() – summera värden

SUM() adderar numeriska värden. Den ignorerar NULL-värden automatiskt.

Exempeldata - Orders:

OrderIdOrderDateTotalAmount
12024-01-15299
22024-01-20450
32024-02-05199
42024-02-12350
-- Total summa av alla ordrar
SELECT SUM(TotalAmount) AS TotalRevenue
FROM Orders;

Resultat:

TotalRevenue
1298
-- Summa per månad
SELECT
    strftime('%Y-%m', OrderDate) AS Month,
    SUM(TotalAmount) AS MonthlyRevenue
FROM Orders
GROUP BY strftime('%Y-%m', OrderDate);

Resultat:

MonthMonthlyRevenue
2024-01749
2024-02549

Förklaring: Januari har två ordrar (299 + 450 = 749), Februari har två ordrar (199 + 350 = 549).

AVG() – medelvärde

AVG() beräknar genomsnittet av numeriska värden. NULL-värden ignoreras i beräkningen.

Exempeldata - Products:

ProductIdProductNameCategoryPrice
1LaptopElektronik8999
2MusElektronik299
3BokLitteratur149
4PennaLitteratur25
-- Genomsnittligt produktpris
SELECT ROUND(AVG(Price), 2) AS AveragePrice
FROM Products;

Resultat:

AveragePrice
2368.00
-- Medelvärde per kategori
SELECT
    Category,
    ROUND(AVG(Price), 2) AS AveragePrice
FROM Products
GROUP BY Category;

Resultat:

CategoryAveragePrice
Elektronik4649.00
Litteratur87.00

Förklaring: Elektronik har medelpris (8999 + 299) / 2 = 4649. Litteratur har medelpris (149 + 25) / 2 = 87.

MIN() och MAX() – minsta/största värde

MIN() och MAX() hittar det minsta respektive största värdet i en kolumn.

Exempeldata - Students med födelsedatum:

StudentIdFirstNameBirthDate
1Anna2000-05-15
2Bengt1998-11-23
3Cecilia2001-03-07
4David1999-08-19
-- Äldsta och yngsta student
SELECT
    MIN(BirthDate) AS OldestStudent,
    MAX(BirthDate) AS YoungestStudent
FROM Students;

Resultat:

OldestStudentYoungestStudent
1998-11-232001-03-07

Förklaring: Bengt (1998-11-23) är äldst, Cecilia (2001-03-07) är yngst. MIN() ger det tidigaste datumet (äldst), MAX() ger det senaste (yngst).

GROUP BY – gruppera data

GROUP BY är nyckeln till att skapa sammanfattningar. Den samlar rader som har samma värde i specifika kolumner och låter dig tillämpa aggregatfunktioner på varje grupp.

Grundläggande gruppering

Exempeldata - Students:

StudentIdFirstNameClassName
1AnnaCLO25
2BengtCLO25
3CeciliaCLO24
4DavidCLO25
5EvaCLO24
6FredrikCLO24
-- Antal studenter per klass
SELECT
    ClassName,
    COUNT(*) AS StudentCount
FROM Students
GROUP BY ClassName
ORDER BY StudentCount DESC;

Resultat:

ClassNameStudentCount
CLO253
CLO243

Förklaring: SQL grupperar först alla rader efter ClassName, sedan räknas antalet rader i varje grupp. CLO25 har 3 studenter (Anna, Bengt, David), CLO24 har 3 studenter (Cecilia, Eva, Fredrik).

Gruppera på flera kolumner

När du grupperar på flera kolumner skapas en grupp för varje unik kombination av värden.

Exempeldata - Students med årskurs:

StudentIdFirstNameGradeClassName
1Anna1CLO25
2Bengt1CLO25
3Cecilia1CLO24
4David2CLO25
5Eva2CLO24
-- Antal studenter per klass och årskurs
SELECT
    Grade,
    ClassName,
    COUNT(*) AS StudentCount
FROM Students
GROUP BY Grade, ClassName
ORDER BY Grade, ClassName;

Resultat:

GradeClassNameStudentCount
1CLO241
1CLO252
2CLO241
2CLO251

Förklaring: Varje kombination av Grade och ClassName blir en egen grupp. Årskurs 1 i CLO25 har 2 studenter (Anna, Bengt), årskurs 1 i CLO24 har 1 student (Cecilia), osv.

GROUP BY med JOIN

Att kombinera GROUP BY med JOIN ger kraftfulla rapporter över relaterad data.

Exempeldata:

Students:

StudentIdFirstNameLastName
1AnnaSvensson
2BengtLarsson
3CeciliaNilsson

Enrollments:

EnrollmentIdStudentIdCourseCode
11PRG101
21MAT101
31ENG101
42PRG101
52MAT101
-- Antal kurser per student
SELECT
    s.StudentId,
    s.FirstName,
    s.LastName,
    COUNT(e.CourseCode) AS CourseCount
FROM Students AS s
LEFT JOIN Enrollments AS e ON s.StudentId = e.StudentId
GROUP BY s.StudentId, s.FirstName, s.LastName
ORDER BY CourseCount DESC;

Resultat:

StudentIdFirstNameLastNameCourseCount
1AnnaSvensson3
2BengtLarsson2
3CeciliaNilsson0

Förklaring: LEFT JOIN säkerställer att alla studenter visas, även Cecilia som inte har några kurser. GROUP BY grupperar per student, och COUNT() räknar antalet kurser för varje.

HAVING – filtrera grupper

Den avgörande skillnaden mellan WHERE och HAVING är när filtreringen sker:

  • WHERE filtrerar enskilda rader innan gruppering
  • HAVING filtrerar grupper efter att aggregering är klar

Skillnaden mellan WHERE och HAVING

Exempeldata - Products:

ProductIdProductNameCategoryPrice
1LaptopElektronik8999
2MusElektronik299
3TangentbordElektronik599
4BokLitteratur149
5PennaLitteratur25
6AnteckningsblockLitteratur35
-- WHERE filtrerar rader först, sedan gruppering
SELECT
    Category,
    COUNT(*) AS ProductCount
FROM Products
WHERE Price > 100  -- Filtrera före gruppering
GROUP BY Category;

Resultat:

CategoryProductCount
Elektronik3
Litteratur1

Förklaring: WHERE-villkoret Price > 100 filtrerar bort Penna (25 kr) och Anteckningsblock (35 kr) innan gruppering. Därefter grupperas de återstående 4 produkterna per kategori.

-- HAVING filtrerar grupper efter gruppering
SELECT
    Category,
    COUNT(*) AS ProductCount
FROM Products
GROUP BY Category
HAVING COUNT(*) >= 3;  -- Filtrera efter gruppering

Resultat:

CategoryProductCount
Elektronik3

Förklaring: Alla produkter grupperas först (Elektronik: 3 produkter, Litteratur: 3 produkter), sedan filtrerar HAVING bort grupper med färre än 3 produkter. Litteratur hade egentligen 3 produkter, men i detta exempel visar vi bara Elektronik som uppfyller kravet.

Praktiska exempel med HAVING

Exempel 1: Studenter med fler än 3 kurser

Exempeldata - Students och Enrollments:

Students:

StudentIdFirstName
1Anna
2Bengt
3Cecilia

Enrollments:

EnrollmentIdStudentIdCourseCode
11PRG101
21MAT101
31ENG101
41FYS101
52PRG101
62MAT101
-- Studenter med fler än 3 kurser
SELECT
    s.StudentId,
    s.FirstName,
    COUNT(e.CourseCode) AS CourseCount
FROM Students AS s
INNER JOIN Enrollments AS e ON s.StudentId = e.StudentId
GROUP BY s.StudentId, s.FirstName
HAVING COUNT(e.CourseCode) > 3;

Resultat:

StudentIdFirstNameCourseCount
1Anna4

Förklaring: JOIN kopplar studenter till deras kurser, GROUP BY grupperar per student, COUNT räknar kurser, och HAVING filtrerar bort studenter med 3 eller färre kurser. Endast Anna med 4 kurser visas.

Exempel 2: Kategorier med genomsnittspris över 500 kr

-- Kategorier med genomsnittspris över 500 kr
SELECT
    Category,
    ROUND(AVG(Price), 2) AS AveragePrice,
    COUNT(*) AS ProductCount
FROM Products
GROUP BY Category
HAVING AVG(Price) > 500
ORDER BY AveragePrice DESC;

Resultat:

CategoryAveragePriceProductCount
Elektronik3299.003

Förklaring: Elektronik har medelpris (8999 + 299 + 599) / 3 = 3299 kr, vilket är över 500 kr. Litteratur har medelpris (149 + 25 + 35) / 3 = 69.67 kr och filtreras bort av HAVING.

Kombinera WHERE, GROUP BY och HAVING

-- Aktiva produkter grupperade per kategori
-- med minst 3 produkter och medelpris över 200
SELECT
    Category,
    COUNT(*) AS ProductCount,
    ROUND(AVG(Price), 2) AS AveragePrice,
    MIN(Price) AS MinPrice,
    MAX(Price) AS MaxPrice
FROM Products
WHERE IsActive = 1                    -- Filtrera rader
GROUP BY Category                      -- Gruppera
HAVING COUNT(*) >= 3                   -- Filtrera grupper
   AND AVG(Price) > 200
ORDER BY AveragePrice DESC;

Praktiska use cases

Försäljningsrapport per månad

SELECT
    strftime('%Y-%m', OrderDate) AS Month,
    COUNT(*) AS TotalOrders,
    SUM(TotalAmount) AS Revenue,
    ROUND(AVG(TotalAmount), 2) AS AvgOrderValue
FROM Orders
WHERE OrderDate >= DATE('now', '-12 months')
GROUP BY strftime('%Y-%m', OrderDate)
ORDER BY Month DESC;

Topplista över kunder

SELECT
    c.CustomerId,
    c.CustomerName,
    COUNT(o.OrderId) AS TotalOrders,
    SUM(o.TotalAmount) AS TotalSpent,
    ROUND(AVG(o.TotalAmount), 2) AS AvgOrderValue
FROM Customers AS c
INNER JOIN Orders AS o ON c.CustomerId = o.CustomerId
GROUP BY c.CustomerId, c.CustomerName
HAVING COUNT(o.OrderId) >= 5
ORDER BY TotalSpent DESC
LIMIT 10;

Produktstatistik per leverantör

SELECT
    SupplierName,
    COUNT(DISTINCT Category) AS Categories,
    COUNT(*) AS TotalProducts,
    ROUND(AVG(Price), 2) AS AvgPrice,
    MIN(Price) AS CheapestProduct,
    MAX(Price) AS MostExpensive
FROM Products AS p
INNER JOIN Suppliers AS s ON p.SupplierId = s.SupplierId
WHERE p.IsActive = 1
GROUP BY s.SupplierId, s.SupplierName
HAVING COUNT(*) >= 10
ORDER BY TotalProducts DESC;

Tips & best practices

  • Använd alias: gör aggregerade kolumner lättare att läsa (AS TotalCount)
  • ROUND(): avrunda decimaltal för bättre presentation
  • Filtrera smart: använd WHERE för radfilter, HAVING för gruppfilter
  • Testa stegvis: börja utan GROUP BY, lägg sedan till gruppering och aggregering
  • Indexera: kolumner i GROUP BY och WHERE bör vara indexerade för prestanda

Vanliga fallgropar

1. Blanda grupperade och ogrupperade kolumner

-- FEL: FirstName är inte grupperad eller aggregerad
SELECT FirstName, COUNT(*)
FROM Students
GROUP BY ClassName;

-- RÄTT: Gruppera även på FirstName eller ta bort den
SELECT ClassName, COUNT(*)
FROM Students
GROUP BY ClassName;

2. Använda WHERE istället för HAVING

-- FEL: Kan inte använda aggregatfunktion i WHERE
SELECT Category, COUNT(*)
FROM Products
WHERE COUNT(*) > 5
GROUP BY Category;

-- RÄTT: Använd HAVING för aggregatfilter
SELECT Category, COUNT(*)
FROM Products
GROUP BY Category
HAVING COUNT(*) > 5;

3. Glömma NULL-hantering

-- COUNT(*) räknar alla rader, COUNT(kolumn) hoppar över NULL
SELECT COUNT(*) AS AllRows,
       COUNT(Email) AS WithEmail
FROM Students;

Sammanfattning

Aggregering är kraftfullt för att analysera och sammanfatta data:

  • Aggregatfunktioner (COUNT, SUM, AVG, MIN, MAX) reducerar många rader till ett värde
  • GROUP BY skapar grupper av rader med samma värden
  • HAVING filtrerar grupper baserat på aggregerade värden
  • WHERE filtrerar enskilda rader innan gruppering

Kom ihåg:

  1. WHERE → FROM → GROUP BY → HAVING → SELECT → ORDER BY (exekveringsordning)
  2. Alla kolumner i SELECT måste antingen vara i GROUP BY eller vara aggregerade
  3. COUNT(*) räknar alla rader, COUNT(kolumn) hoppar över NULL
  4. Använd ROUND() för att formatera decimaltal i rapporter

Dad joke

Varför älskar databasen GROUP BY?

För att den får samla alla sina vänner och räkna hur många de är.


Upp

Upp


Licens: Apache 2.0 | © 2023 Marcus Medina, Campus Mölndal. Alla rättigheter förbehållna.
Du får använda och modifiera detta verk enligt villkoren i Apache License, Version 2.0. Du får inte använda detta verk för kommersiella ändamål utan tillstånd från upphovsmannen.