Jag har haft stora problem med en otrolig kurs i databas som är det enda jag har kvar för att få ut min examen. I uppgiften har jag satt upp en databas i Access för en skola och på denna fråga jag fått rest på ska jag:
"Visa antal kurstillfällen per kurs under 2008! Samtliga kurser skall finnas med."
Jag har löst det med en UNION på detta sätt:
SELECT Kursnamn, COUNT(*) AS Antal
FROM Kurstillfälle
WHERE Datum BETWEEN CDate('2008-01-01') AND CDate('2008-12-31')
GROUP BY Kursnamn
UNION SELECT DISTINCT namn, 0
FROM Kurs, Kurstillfälle
WHERE Kurstillfälle.Datum BETWEEN CDate('2008-01-01') AND CDate('2008-12-31')
AND Kurs.namn NOT IN (SELECT kursnamn FROM Kurstillfälle);
SQL-satsen verkar ta fram rätt information, men läraren skriver "Vad gör den undre delen av union? Kolla era villkor." Så vad har jag missat? Jag använder Microsoft Access 2007 och bifogar en bild på databasens relationer.
Din bild är helt oläslig och de flesta tabellerna är ju helt irrelevanta för denna fråga. Det vore bättre om du enbart beskrev vilka kolumner som ingår i tabellerna kurs och kurstillfälle.
Det fel som finns i din fråga är att datumvillkoret ligger fel. Du vill ha namnet på de kurser som inte gavs 2008. Det du hämtar är de kurser som aldrig har haft något kurstillfälle.
Hela frågan kan för övrigt skrivas utan union genom att använda en outer join.
Vad konstigt, den bifogade bilden ser bra ut på min dator. Jag bifogar en ny där jag har ringat in de berörda tabellerna och markerat kolumnerna som är intressanta för frågan. Det är alltså tabellen "Kurs" med kolumnen "namn" och tabellen "Kurstillfälle" med kolumnen "datum" som frågan gäller för.
Vad konstigt för när jag kör frågan får jag ändå "Kurs A: 1, Kurs B: 0, Kurs C: 3" och så vidare, så det blir ju svaret rätt på frågan hur många kurstillfällen varje kurs haft. Jag tittade i tabellen för "Kurstillfälle" och där stämmer antal tillfällen för 2008 överens med de vid körning av SQL-frågan.
I min SQL-sats har jag väl tänkt att först visas alla kurser som haft kurstillfällen med eras antal, sedan med UNION sätts det ihop med de kurser som inte haft några kurstillfällen och visas med siffran 0.
Frågan är lite konstigt ställd. Man ska visa alla kurstillfällen under 2008. Ok, det kan jag köpa. Sedan ska samtliga kurser finnas med. Varför då? Om man följer frågan slaviskt så får man alltså (kanske) en hög kurser med 0 kurstillfällen, alltså kurser som inte har några kurstillfällen under 2008.
Men om det nu är det som efterfrågas så behöver du en LEFT JOIN!
SELECT k.namn, COUNT(kt.kursID)
FROM kurs k
LEFT JOIN kurstillfälle kt ON k.namn = kt.kursnamn
AND kt.Datum BETWEEN CDate('2008-01-01') AND CDate('2008-12-31')
WHERE 1
GROUP BY k.namn
Vad som sker är ungefär följande. (innan GROUP BY och COUNT)
Förklaring: Kurs 1 har i detta exempel tre tillfällen, med id 0, 1, 2. Kurs 2 har ett tillfälle. Kurs 3 har två. Kurs 4 har inget, vilket gör att LEFT JOIN producerar en rad med null-värden för alla kt's värden. (En INNER JOIN hade istället utelämnat kurs 4) Kurs 5 har slutligen ett tillfälle. Det är runda ett.
Runda två är GROUP BY och beräkning av aggregatsfunktionen COUNT.
Det är inga direkta konstigheter utöver Kurs 4. COUNT ignorerar nämligen null-rader, just för att man ska kunna utföra denna typ av beräkningar! Alltså får du att kurs 4 inte har några tillfällen (inom det specificerade perioden.)
SELECT DISTINCT namn, 0
FROM Kurs, Kurstillfälle
WHERE Kurstillfälle.Datum BETWEEN Date'2008-01-01' AND Date'2008-12-31'
AND Kurs.namn NOT IN (SELECT kursnamn FROM Kurstillfälle);
Om du tittar på sista villkoret så får du bara med de kurser som aldrig har getts. Du får inte med kurser givna något annat år som inte givits under 2008
med följande testmaterial
create table kurs (namn char(20) primary key);
create table kurstillfälle(kursnamn char(20), datum date);
insert into kurs values('A');
insert into kurs values('B');
insert into kurs values('C');
insert into kurstillfälle values('A',CDate('2008-12-24'));
insert into kurstillfälle values('A',CDate('2008-12-25'));
insert into kurstillfälle values('A',CDate('2007-12-25'));
insert into kurstillfälle values('B',CDate('2007-12-24'));
så ger din fråga
SELECT Kursnamn, COUNT(*) AS Antal
FROM Kurstillfälle
WHERE Datum BETWEEN Date'2008-01-01' AND Date'2008-12-31'
GROUP BY Kursnamn
UNION
SELECT DISTINCT namn, 0
FROM Kurs, Kurstillfälle
WHERE Kurstillfälle.Datum BETWEEN Date'2008-01-01' AND Date'2008-12-31'
AND Kurs.namn NOT IN (SELECT kursnamn FROM Kurstillfälle);
resultatet
kursnamn Antal
======== =====
A 2
C 0
Du får alltså inte med kursen B.
Jämför med denna fråga
SELECT Kursnamn, COUNT(*) AS Antal
FROM Kurstillfälle
WHERE Datum BETWEEN CDate('2008-01-01') AND CDate('2008-12-31')
GROUP BY Kursnamn
UNION all
SELECT namn, 0
FROM Kurs
WHERE Kurs.namn NOT IN
(SELECT kursnamn
FROM Kurstillfälle
WHERE Datum BETWEEN CDate('2008-01-01')
AND CDate('2008-12-31'));
Kommentaren om bilden var mer tänkt inför kommande poster :)
265 ms totalt · 4 externa anrop · v20260731065814-full.a51de22e