Grafikus C# – Windows Forms tanfolyam › 3.12 lecke

Windows Forms · 3.12 lecke · összesítések

Összesítések SQL-ben: SUM, GROUP BY, COALESCE

Eddig az adatbázisból sorokat kértél. Most azt tanulod meg, hogyan számoltasd ki az adatbázissal az összeget, az átlagot, a legnagyobbat, és hogyan kapsz csoportonként (például részlegenként) külön eredményt. Ezt a tudást használják a kimutatások, a statisztikák és a grafikonok is.

  • kb. 100 perc
  • közepes szint
  • összesítés
Diák C# programozást gyakorol egy tanulósarokban

A lecke végére

  • a COUNT, SUM, AVG, MIN és MAX függvényekkel egyetlen számot számoltatsz ki a táblából;
  • érted, mi a NULL, és a COALESCE-szel kezeled;
  • a GROUP BY-jal csoportonként összesítesz, és sorba rendezed az eredményt;
  • az eredményt Windows Forms alkalmazásban megjeleníted: egy számot ExecuteScalar-ral, egy kis táblát DataGridView-ban;
  • felismered a leggyakoribb összesítős hibákat.

Mielőtt elkezded

Ehhez a leckéhez kell a 3.1 lecke (DB Browser, COUNT(*)), a 3.2 lecke (DB.cs, ExecuteScalar), a 3.5 lecke (WHERE, ORDER BY, LIMIT), a 3.7 lecke (paraméterek) és a 3.8 lecke (DataTable, SQLiteDataAdapter).

← 3.11 Mini projekt

Gyakorló tábla: dolgozók és fizetések

Az összesítést olyan táblán érdemes megtanulni, ahol számolni is lehet, ezért készítünk egy új, kis gyakorló adatbázist. A DB Browser programban (3.1 lecke) hozz létre egy új adatbázist gyakorlo.db néven, nyisd meg az Execute SQL fület, másold be az alábbi kódot, futtasd (Ctrl+Enter), végül mentsd a változásokat (Write Changes, Ctrl+S).

SQLSQLite
CREATE TABLE IF NOT EXISTS Dolgozok (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nev TEXT NOT NULL,
reszleg TEXT NOT NULL,
fizetes INTEGER NOT NULL,
premium INTEGER
);

INSERT INTO Dolgozok (nev, reszleg, fizetes, premium) VALUES
('Kiss Anna', 'Iroda', 420000, 50000),
('Nagy Béla', 'Iroda', 380000, NULL),
('Tóth Cili', 'Raktár', 310000, NULL),
('Szabó Dénes', 'Raktár', 330000, 20000),
('Horváth Eszter', 'Raktár', 295000, NULL),
('Varga Feri', 'Műhely', 450000, 80000),
('Kovács Gréta', 'Műhely', 410000, NULL),
('Farkas Hugó', 'Iroda', 365000, 30000);
Ide kerül a képernyőkép: a Dolgozok tábla a DB BrowserbenA Browse Data fülön nyolc sor látszik; a premium oszlopban néhány cellában NULL áll.Kép helye: DB Browser for SQLite, Browse Data fül.
premium INTEGERItt nincs NOT NULL, mert nem mindenki kap prémiumot. Ahol nincs prémium, a cella NULL.
NULLNem nulla és nem üres szöveg, hanem azt jelenti: nincs érték. Ennek lesz jelentősége az összesítésnél.

A táblában nyolc ember van három részlegen: az Irodában három, a Raktárban három, a Műhelyben két ember dolgozik.

Egy szám az egész táblából: összesítő függvények

Az összesítő függvény sok sort egyetlen értékké von össze. Eddig csak a COUNT(*)-ot láttad (3.1). A többi is hasonlóan működik:

FüggvényMit számol?
COUNT(*)hány sor van
SUM(oszlop)az oszlop értékeinek összege
AVG(oszlop)az átlag
MIN(oszlop)a legkisebb érték
MAX(oszlop)a legnagyobb érték

Jósolj!

Mit fog adni ez a lekérdezés a nyolc dolgozóra? Gondold végig a számokat, mielőtt lefuttatod: hány dolgozó van, mekkora az összes fizetés, mennyi az átlag, mennyi a legkisebb és a legnagyobb fizetés?

SQLSQLite
SELECT COUNT(*)      AS db,
SUM(fizetes) AS osszeg,
AVG(fizetes) AS atlag,
MIN(fizetes) AS legkisebb,
MAX(fizetes) AS legnagyobb
FROM Dolgozok;
Jóslat ellenőrzése

Az eredmény egyetlen sor: db = 8, osszeg = 2 960 000, atlag = 370 000, legkisebb = 295 000, legnagyobb = 450 000. Az összesítő függvény az egész táblát egy sorba vonja össze, ezért itt nem látsz külön dolgozókat.

AS névAz AS nevet ad az eredmény oszlopának (alias). Nélküle az oszlop neve ilyesmi lenne: SUM(fizetes). A névvel a DB Browser, a DataGridView fejléce és a ORDER BY is egyszerűbb.
WHERE az összesítés előttA WHERE előbb kiválasztja a sorokat, és az összesítés csak azokra számol. Például SELECT SUM(fizetes) FROM Dolgozok WHERE reszleg = 'Iroda'; csak az iroda összegét adja: 1 165 000.

Hiányzó értékek: NULL és COALESCE

A prémium oszlopban nem mindenkinek van érték. Nézzük meg, mit csinálnak az összesítő függvények a NULL értékekkel:

SQLSQLite
SELECT COUNT(*)       AS mindenki,
COUNT(premium) AS premiumosok,
SUM(premium) AS premiumOsszeg
FROM Dolgozok;

Jósolj!

A nyolc dolgozóból hányan kapnak prémiumot a táblázat szerint? Mit ad vajon a COUNT(premium), és mit a COUNT(*)?

Jóslat ellenőrzése

Az eredmény: mindenki = 8, premiumosok = 4, premiumOsszeg = 180 000. A COUNT(*) minden sort számol, a COUNT(oszlop) viszont csak azokat, ahol az oszlop nem NULL. A SUM, az AVG, a MIN és a MAX is átugorja a NULL értékeket.

Amikor az összeg is NULL

Ha egy összesítésnél egyetlen sor sem felel meg a feltételnek, a SUM eredménye nem nulla, hanem NULL. Próbáld ki:

SQLSQLite
SELECT SUM(premium) FROM Dolgozok WHERE reszleg = 'Nincs ilyen';

A DB Browser egy NULL cellát mutat. Egy C# programban ez gondot okoz, mert a NULL értéket nem lehet számmá alakítani (lásd a Gyakori hibák szakaszt). A megoldás a COALESCE:

SQLSQLite
SELECT COALESCE(SUM(premium), 0) FROM Dolgozok WHERE reszleg = 'Nincs ilyen';
COALESCE(a, b)Az első olyan értéket adja vissza, amelyik nem NULL. Ha az a NULL, akkor a b-t.
COALESCE(SUM(...), 0)Ha az összeg NULL (nincs mit összeadni), nullát kapsz helyette. Így a program mindig számot kap.

A COALESCE a sorok szintjén is hasznos. Ha a fizetéshez hozzá akarod adni a prémiumot, a NULL prémiumot nullának kell venni, különben az egész összeg NULL lenne:

SQLSQLite
SELECT nev, fizetes + COALESCE(premium, 0) AS teljes
FROM Dolgozok
ORDER BY teljes DESC;

Csoportonként: GROUP BY

Az eddigi lekérdezések az egész táblát egyetlen sorba vonták össze. A GROUP BY azt mondja: csoportosítsd a sorokat egy oszlop értéke szerint, és minden csoportra külön számolj. Így kapsz részlegenként összegeket.

SQLSQLite
SELECT reszleg,
COUNT(*) AS fo,
SUM(fizetes) AS osszeg
FROM Dolgozok
GROUP BY reszleg;

Jósolj!

Hány sora lesz az eredménynek? Mennyi lesz az Iroda, a Raktár és a Műhely fizetéseinek összege? (Segítség: az iroda összegét az előbb már láttad.)

Jóslat ellenőrzése

Három sor lesz, részlegenként egy: Iroda: 3 fő, 1 165 000; Műhely: 2 fő, 860 000; Raktár: 3 fő, 935 000. A három összeg együtt 2 960 000, ugyanannyi, mint a teljes táblára számolt összeg.

reszlegfoosszeg
Iroda31165000
Műhely2860000
Raktár3935000

Mi kerülhet a SELECT-be?

Csoportosított lekérdezésben a SELECT után két fajta elem állhat: maga a csoportosító oszlop (itt a reszleg) és összesítő függvények. Minden más oszlop (például a nev) értelmetlen, mert egy csoportban több név van.

A lekérdezés sorai: WHERE, GROUP BY, ORDER BY

A három záradék mindig ebben a sorrendben áll: először a WHERE szűr, utána a GROUP BY csoportosít, végül az ORDER BY rendez. Például a legalább 350 000 Ft-ot keresők összege részlegenként, a legnagyobb összeggel kezdve:

SQLSQLite
SELECT reszleg, SUM(fizetes) AS osszeg
FROM Dolgozok
WHERE fizetes >= 350000
GROUP BY reszleg
ORDER BY osszeg DESC;

Az eredmény két sor: Iroda 1 165 000, Műhely 860 000. A Raktárban senki sem keres ennyit, ezért az a csoport meg sem jelenik.

Rendezés összeg szerint, döntetlennel

Az ORDER BY az összesítés eredményére is hivatkozhat az aliasával. Ha két csoport összege egyenlő, érdemes egy második rendezési szempontot is adni, hogy a sorrend mindig ugyanaz legyen:

SQLSQLite
SELECT reszleg, COUNT(*) AS fo
FROM Dolgozok
GROUP BY reszleg
ORDER BY fo DESC, reszleg;

Az Iroda és a Raktár is három fős, ezért a második szempont (a név betűrendje) dönt: Iroda 3, Raktár 3, Műhely 2.

Kipróbálás

Futtasd le mindegyik lekérdezést a DB Browserben, és ellenőrizd, hogy a saját számolásod egyezik-e az eredménnyel. Utána próbáld ki, mi történik, ha a GROUP BY reszleg helyett GROUP BY nev-et írsz: hány sort kapsz, és miért?

Összesítés Windows Forms alkalmazásban

Most vigyük át ugyanezt egy programba. Két módon kapsz eredményt: egy egyetlen számot (összeg) az ExecuteScalar ad vissza, egy kis táblát (részlegenként) pedig az SQLiteDataAdapter tölt be, mint a 3.8 leckében.

Előkészítés

  1. Hozz létre egy új Windows Forms (.NET Framework) projektet Osszesites néven, és telepítsd a System.Data.SQLite csomagot a NuGettel (3.2 lecke).
  2. Add hozzá a DB.cs osztályt a 3.2 leckéből, de az adatbázis fájlneve legyen "gyakorlo.db".
  3. Másold a DB Browserben mentett gyakorlo.db fájlt a projekt bin\Debug mappájába.
  4. A Formra tegyél egy Button-t (btnOsszeg), egy Label-t (lblEredmeny), egy másik Button-t (btnReszlegek), és egy DataGridView-t (dgvEredmeny, ReadOnly = true).

Egy szám: ExecuteScalar

Form1.csC#
private void btnOsszeg_Click(object sender, EventArgs e)
{
try
{
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();

using (SQLiteCommand parancs = new SQLiteCommand("SELECT COALESCE(SUM(fizetes), 0) FROM Dolgozok", kapcsolat))
{
int osszeg = Convert.ToInt32(parancs.ExecuteScalar());
lblEredmeny.Text = "Összes fizetés: " + osszeg + " Ft";
}
}
}
catch (Exception ex)
{
MessageBox.Show("Hiba: " + ex.Message);
}
}
ExecuteScalar()A lekérdezés első sorának első oszlopát adja vissza object típusban. Összesítésnél pontosan egy ilyen érték van.
Convert.ToInt32(...)Az object-et egész számmá alakítja. Ez azért kell, mert az SQLite ezt az értéket nagyobb számtípusban adja vissza.
COALESCE(..., 0)Ha a tábla üres, a SUM NULL lenne, és a Convert.ToInt32 hibával leállna. A COALESCE miatt ilyenkor 0 a válasz.

Egy kis tábla: részlegenként

Form1.csC#
private void btnReszlegek_Click(object sender, EventArgs e)
{
string sql = "SELECT reszleg AS Reszleg, COUNT(*) AS Fo, SUM(fizetes) AS Osszeg " +
"FROM Dolgozok GROUP BY reszleg ORDER BY Osszeg DESC, Reszleg";

try
{
DataTable tabla = new DataTable();

using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
using (SQLiteDataAdapter adapter = new SQLiteDataAdapter(sql, kapcsolat))
{
adapter.Fill(tabla);
}

dgvEredmeny.DataSource = tabla;
}
catch (Exception ex)
{
MessageBox.Show("Hiba: " + ex.Message);
}
}

A táblázat fejlécei az SQL-ben megadott aliasok (Reszleg, Fo, Osszeg). Az eredmény három sor: Iroda 3 fő, 1 165 000; Raktár 3 fő, 935 000; Műhely 2 fő, 860 000.

Ide kerül a képernyőkép: a kész alkalmazásA Form felső részén a két gomb és az „Összes fizetés: 2960000 Ft” felirat látszik, alatta a DataGridView három sora részlegenként.Kép helye: a futó alkalmazás ablaka.

Paraméterrel szűrt összeg

A szűrő értékét (például egy NumericUpDown-ból) paraméterként add át, ahogy a 3.7 leckében tanultad:

Form1.csC#
private void btnSzurt_Click(object sender, EventArgs e)
{
try
{
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();

using (SQLiteCommand parancs = new SQLiteCommand("SELECT COALESCE(SUM(fizetes), 0) FROM Dolgozok WHERE fizetes >= @kuszob", kapcsolat))
{
parancs.Parameters.AddWithValue("@kuszob", (int)nudKuszob.Value);

int osszeg = Convert.ToInt32(parancs.ExecuteScalar());
lblEredmeny.Text = "A küszöb feletti fizetések összege: " + osszeg + " Ft";
}
}
}
catch (Exception ex)
{
MessageBox.Show("Hiba: " + ex.Message);
}
}

Ha a nudKuszob értéke 350000 (állítsd be a Maximum-ot elég nagyra, például 1000000-ra), az eredmény 2 025 000 Ft: az Iroda 1 165 000 és a Műhely 860 000 összege.

A leggyakoribb hibák

1. NULL összeget próbálsz számmá alakítani

A SUM üres halmazon NULL-t ad, a Convert.ToInt32 pedig nem tud NULL-ból számot csinálni.InvalidCastException: Object cannot be cast from DBNull to other types.

Javítás: COALESCE(SUM(...), 0) az SQL-ben.

2. Nem csoportosított oszlop a SELECT-ben

A SELECT nev, SUM(fizetes) FROM Dolgozok GROUP BY reszleg az SQLite-ban nem ad hibaüzenetet, hanem a csoport egyik tetszőleges nevét írja ki. Az eredmény félrevezető.Hiba nélkül lefut, de a név nem a csoport „nyertesének” a neve

Javítás: a SELECT-ben csak a csoportosító oszlop és összesítő függvények szerepeljenek.

3. WHERE a GROUP BY után

A záradékok sorrendje kötött: WHERE, GROUP BY, ORDER BY. Ha a sorrendet felcseréled, szintaktikai hibát kapsz.SQLiteException: near "WHERE": syntax error

4. Hiányzó adatbázisfájl a bin\Debug mappában

Ha a gyakorlo.db nem került a program mellé, a DB.UjKapcsolat() a 3.2 leckében látott módon hibát jelez.FileNotFoundException: Nem található az adatbázis

Alkoss: önálló feladatok

Előbb próbáld meg magad, aztán nyisd le a megoldást. Mindegyik a Dolgozok táblát használja.

1. Legmagasabb fizetés részlegenként

Írj lekérdezést, amely részlegenként megmutatja a legmagasabb fizetést, részleg szerint betűrendben. Elvárt eredmény: Iroda 420 000, Műhely 450 000, Raktár 330 000.

Megoldás
SQLSQLite
SELECT reszleg, MAX(fizetes) AS legtobb
FROM Dolgozok
GROUP BY reszleg
ORDER BY reszleg;

2. Ki keres a legtöbbet?

Csak egyetlen sort kérj: a legtöbbet kereső dolgozó nevét és a fizetését. Elvárt: Varga Feri, 450 000. (Segítség: a 3.5 lecke ORDER BY és LIMIT kulcsszavai.)

Megoldás
SQLSQLite
SELECT nev, fizetes
FROM Dolgozok
ORDER BY fizetes DESC, nev
LIMIT 1;

A rendezés a legnagyobb fizetéssel kezd, a LIMIT 1 az első sort tartja meg. A második rendezési szempont (nev) azért kell, hogy egyenlő fizetés esetén is mindig ugyanaz a név legyen az első.

3. Teljes költség prémiummal

Számold ki részlegenként, mennyibe kerül a cégnek a fizetés és a prémium együtt, a legdrágább részleggel kezdve. Elvárt: Iroda 1 245 000, Raktár 955 000, Műhely 940 000.

Megoldás
SQLSQLite
SELECT reszleg, SUM(fizetes + COALESCE(premium, 0)) AS teljes
FROM Dolgozok
GROUP BY reszleg
ORDER BY teljes DESC, reszleg;

A COALESCE nélkül a NULL prémiumú sorok teljes értéke is NULL lenne, és az összeg kevesebb sort számolna.

4. A programban: hányan kapnak prémiumot részlegenként?

Tegyél a Formra egy „Prémiumosok” gombot, amely a dgvEredmeny-ben részlegenként megmutatja, hány dolgozó kap prémiumot. Elvárt: Iroda 2, Műhely 1, Raktár 1.

Megoldás
Form1.csC#
private void btnPremiumosok_Click(object sender, EventArgs e)
{
string sql = "SELECT reszleg AS Reszleg, COUNT(premium) AS Premiumosok " +
"FROM Dolgozok GROUP BY reszleg ORDER BY Reszleg";

try
{
DataTable tabla = new DataTable();

using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
using (SQLiteDataAdapter adapter = new SQLiteDataAdapter(sql, kapcsolat))
{
adapter.Fill(tabla);
}

dgvEredmeny.DataSource = tabla;
}
catch (Exception ex)
{
MessageBox.Show("Hiba: " + ex.Message);
}
}

A COUNT(premium) csak azokat a sorokat számolja, ahol a prémium nem NULL, ezért kapjuk a prémiumosok számát.

Mit tudsz most?

  • A COUNT, SUM, AVG, MIN és MAX függvényekkel egy számot számoltatsz ki az adatbázissal.
  • Tudod, hogy az összesítések átugorják a NULL értékeket, és a COALESCE-szel adsz nekik alapértéket.
  • A GROUP BY-jal csoportonként összesítesz, és a WHERE, GROUP BY, ORDER BY sorrendet tartod.
  • Az eredményt ExecuteScalar-ral vagy SQLiteDataAdapter-rel hozod be a programba.

Tovább a tanmenetre

Az összesítésekre épül a 9.11 lecke is, amelyben a grafikon már az adatbázis számaiból rajzol, és a 16. fejezet összetett gyakorlófeladatai is.

Vissza a tanmenethez