SEN2104 · 7. hafta · Structured Query Language (SQL)

İleri SQL: GROUP BY, NULL, alt sorgular, DML ve view

Aggregate fonksiyonlar, GROUP BY/HAVING, NULL'un üç değerli mantığı, IN/EXISTS/korelasyonlu alt sorgular, küme işlemleri, INSERT/UPDATE/DELETE ve view. Vizenin en zor SQL soruları buradan.

aggregate functionCOUNT(*)GROUP BYHAVINGNULLthree-valued logicunknownIS NULLsubquerynested queryIN / NOT INEXISTS / NOT EXISTScorrelated subqueryscalar subqueryALL / SOMEdivisionUNIONINTERSECTEXCEPTUNION ALLINSERTUPDATEDELETECASEview

Altıncı haftada tek tek satırları seçip birleştirdin. Bu hafta satırları özetliyoruz (kaç tane, ortalama ne), sorgunun içine sorgu koyuyoruz ve veriyi değiştiriyoruz. Vize SQL bölümündeki zor sorular neredeyse hep şu üç şeyden birine dayanır: GROUP BY/HAVING ayrımı, NULL’un garip davranışı ve “her X’i yapanlar” (bölme) sorusu.

Altın kurallar:

  1. WHERE satırı, HAVING grubu süzer. Aggregate fonksiyon WHERE içine giremez.
  2. NULL ile karşılaştırma TRUE ya da FALSE değil, UNKNOWN verir. WHERE sadece TRUE olan satırları tutar.
  3. NOT IN ve NULL birlikte zehirlidir. Alt sorguda bir NULL varsa NOT IN hiçbir satır döndürmez.
  4. “Her/tüm (every/all)” geçen soru bölme sorusudur. Çift NOT EXISTS ile yaz.
  5. COUNT(*) satır sayar, COUNT(sütun) NULL olmayan değer sayar.

Örnek veritabanı 6. haftadakiyle aynı (department, instructor, student, course, teaches, takes).

Aggregate fonksiyonlar

Beş temel aggregate: COUNT, SUM, AVG, MIN, MAX. Bir sütundaki çok değeri tek değere indirirler.

SELECT COUNT(*), COUNT(grade), COUNT(DISTINCT stu_id) FROM takes;
COUNT(*)COUNT(grade)COUNT(DISTINCT stu_id)
14136

14 kayıt var. Biri NULL notlu (Cem, CS101), o yüzden COUNT(grade) 13. Ders alan farklı öğrenci 6 (Firat yok).

SELECT SUM(salary), AVG(salary), MIN(salary), MAX(salary) FROM instructor;
SUM(salary)AVG(salary)MIN(salary)MAX(salary)
462000770006000095000

SQLite’ta AVG her zaman REAL döndürür (typeof = real), SUM tam sayı sütunda INTEGER kalır. Bu yüzden AVG(credits) 3.4285714285714284 verir. Ama 7/2 hâlâ 3: sadece AVG kendiliğinden ondalığa geçer.

Boş bir kümede COUNT 0, diğerleri NULL döner: WHERE salary > 100000 ile COUNT(*), SUM(salary), AVG(salary) sonucu 0, NULL, NULL.

GROUP BY ve HAVING

GROUP BY satırları ortak değere göre gruplara ayırır, aggregate her gruba ayrı uygulanır.

SELECT dept_name, COUNT(*) AS n, AVG(salary) AS avg_sal
FROM instructor GROUP BY dept_name;
dept_namenavg_sal
BIO160000
CS287500
EE185000
MATH271000

Gruplara koşul koymak için HAVING:

SELECT dept_name, AVG(salary) AS avg_sal
FROM instructor GROUP BY dept_name HAVING AVG(salary) > 80000;
dept_nameavg_sal
CS87500
EE85000

WHERE ile HAVING birlikte

“20’den fazla kredisi olan öğrencilerden en az 2 tanesi bulunan bölümler”:

SELECT dept_name, COUNT(*) AS n
FROM student
WHERE tot_cred > 20
GROUP BY dept_name
HAVING COUNT(*) >= 2;
dept_namen
CS2

Adım adım: WHERE önce Ece (15), Firat (0) ve Gizem’i (10) atar. Kalanlar gruplanır: CS 2 (Ada, Bora), EE 1 (Cem), MATH 1 (Defne). HAVING sadece CS’yi bırakır.

FROM student          7 satır
WHERE tot_cred > 20   4 satır: Ada, Bora, Cem, Defne
GROUP BY dept_name    CS{Ada,Bora}  EE{Cem}  MATH{Defne}
HAVING COUNT(*) >= 2  CS
SELECT                CS, 2

TUZAK · gruplanmamış sütun. Standart SQL’de SELECT listesinde aggregate dışındaki her sütun GROUP BYda olmalı. SELECT dept_name, name, MAX(salary) FROM instructor GROUP BY dept_name PostgreSQL’de hata verir. SQLite hata vermez ve name için gruptan bir satır seçer (MAX ile kullanılınca en yüksek maaşlının adı: Aksoy, Kaya, Celik, Yildiz). Sınavda bu sorguyu hatalı say.

Sıfırları saymak

Her dersin öğrenci sayısı, hiç öğrencisi olmayanlar dahil:

SELECT c.course_id, COUNT(t.stu_id) AS enrolled
FROM course c LEFT JOIN takes t ON c.course_id = t.course_id
GROUP BY c.course_id;
course_idenrolled
BI1000
CS1014
CS2012
CS3052
EE2101
MA1013
MA2022

COUNT(*) yazsaydın BI100 için 1 çıkardı, çünkü LEFT JOIN onu (BI100, NULL, NULL) satırıyla getirir.

NULL ve üç değerli mantık

NULL “bilinmiyor” ya da “yok” demek. Aritmetikte bulaşıcıdır: 5 + NULL = NULL. Karşılaştırmada ise sonuç UNKNOWN olur: NULL = NULL bile UNKNOWN.

ANDTRUEUNKNOWNFALSE
TRUETRUEUNKNOWNFALSE
UNKNOWNUNKNOWNUNKNOWNFALSE
FALSEFALSEFALSEFALSE

OR için: TRUE OR UNKNOWN = TRUE, FALSE OR UNKNOWN = UNKNOWN. NOT UNKNOWN = UNKNOWN. SQLite’ta SELECT NULL = NULL, NULL AND 0, NULL OR 1, NULL AND 1, NOT NULL sonucu NULL, 0, 1, NULL, NULL (0 = FALSE, 1 = TRUE).

Sonuçlar:

  • WHERE dept_name = NULL hiçbir satır döndürmez. Doğrusu WHERE dept_name IS NULL → Gizem.
  • WHERE dept_name <> 'CS' 3 satır döndürür (Cem, Defne, Firat). Gizem gelmez: NULL <> 'CS' UNKNOWN.
  • WHERE grade = 'A' OR grade <> 'A' 14 değil 13 satır verir. “Her şey” gibi görünen koşul NULL’u kaçırır.
  • GROUP BY ise NULL’ları bir grup sayar: GROUP BY dept_name öğrencilerde NULL 1, CS 3, EE 2, MATH 1 verir.

Alt sorgular (subqueries)

IN

I1’in (Aksoy) verdiği bir dersi alan öğrenciler:

SELECT name FROM student
WHERE stu_id IN (SELECT t.stu_id FROM takes t JOIN teaches te ON t.course_id = te.course_id
                 WHERE te.inst_id = 'I1');

Sonuç: Ada, Bora, Cem, Defne, Ece. Aksoy CS101 ve CS305 veriyor; CS101’i S1, S2, S3, S5, CS305’i S1, S4 almış. IN tekrar üretmez, Ada bir kez gelir.

NOT IN tuzağı ve NOT EXISTS

Öğrencisi olmayan bölümleri arayalım:

SELECT dept_name FROM department
WHERE dept_name NOT IN (SELECT dept_name FROM student);

Sonuç boş. Alt sorgu Gizem’in NULL’unu da döndürür. 'BIO' NOT IN (CS, EE, MATH, NULL) aslında 'BIO' <> 'CS' AND ... AND 'BIO' <> NULL demek ve son terim UNKNOWN. Doğrusu:

SELECT d.dept_name FROM department d
WHERE NOT EXISTS (SELECT * FROM student s WHERE s.dept_name = d.dept_name);
dept_name
BIO

TUZAK · NOT IN + NULL. Alt sorgu tek bir NULL bile döndürürse NOT IN hiçbir zaman TRUE olmaz. Ya NOT EXISTS kullan ya da alt sorguya WHERE ... IS NOT NULL ekle. IN için böyle bir sorun yok.

EXISTS ve korelasyonlu alt sorgu

EXISTS (alt sorgu) alt sorgu en az bir satır döndürürse TRUE. Alt sorgu dıştaki satıra referans veriyorsa korelasyonlu (correlated) denir; dıştaki her satır için yeniden değerlendirilir.

SELECT c.course_id FROM course c
WHERE EXISTS (SELECT * FROM takes t WHERE t.course_id = c.course_id AND t.grade = 'A');

Sonuç: CS101, CS305, EE210, MA101, MA202 (en az bir A notu verilmiş dersler).

Kendi bölüm ortalamasından fazla kazananlar:

SELECT i.name, i.salary FROM instructor i
WHERE i.salary > (SELECT AVG(i2.salary) FROM instructor i2 WHERE i2.dept_name = i.dept_name);
namesalary
Aksoy95000
Celik72000

Skaler alt sorgu

Tek satır, tek sütun döndüren alt sorgu bir değer gibi kullanılabilir, SELECT listesinde bile:

SELECT d.dept_name,
       (SELECT COUNT(*) FROM instructor i WHERE i.dept_name = d.dept_name) AS n_inst
FROM department d;

Sonuç: BIO 1, CS 2, EE 1, MATH 2. FROM içinde de alt sorgu olur: SELECT ... FROM (SELECT dept_name, AVG(salary) AS avg_sal FROM instructor GROUP BY dept_name) WHERE avg_sal > 80000 HAVING örneğiyle aynı sonucu verir.

ALL ve SOME

Standart SQL’de x > ALL (küme) “kümedeki her elemandan büyük”, x > SOME (küme) (ya da ANY) “en az birinden büyük” demek.

Standart SQLEşdeğeri (SQLite’ta çalışan)MATH maaşlarına göre sonuç
salary > ALL (MATH maaşları)salary > (SELECT MAX(...))Aksoy, Demir, Kaya
salary > SOME (MATH maaşları)salary > (SELECT MIN(...))Aksoy, Demir, Kaya, Celik
= SOMEIN
<> ALLNOT IN

TUZAK · SQLite’ta ALL/SOME yok. > ALL (...) SQLite’ta syntax error verir. Sınavda standart yazımı bil, SQLite’ta MAX/MIN ile yaz. Celik 72000, MATH’teki 70000’den büyük ama 72000’den (kendisinden) büyük değil: SOME’da var, ALL’da yok.

Bölme: “hepsini alan” öğrenciler

İlişkisel cebirdeki ÷\div işleminin SQL’de doğrudan karşılığı yok. “Bütün CS derslerini alan öğrenciler” sorusunu “alınmamış CS dersi olmayan öğrenciler” diye çevir:

SELECT s.name FROM student s
WHERE NOT EXISTS (
  SELECT c.course_id FROM course c WHERE c.dept_name = 'CS'
  EXCEPT
  SELECT t.course_id FROM takes t WHERE t.stu_id = s.stu_id);
name
Ada

Her öğrenci için “CS dersleri eksi onun aldıkları” hesaplanır. Küme boşsa öğrenci hepsini almış. Aynı sorgu çift NOT EXISTS ile de yazılır (sorular bölümünde MATH örneği var).

Küme işlemleri

İşlemAnlamıTekrar
UNION∪\cupsiler
INTERSECT∩\capsiler
EXCEPT (Oracle: MINUS)−-siler
UNION ALLçoklu küme birleşimitutar
SELECT course_id FROM takes WHERE stu_id = 'S1'
INTERSECT
SELECT course_id FROM takes WHERE stu_id = 'S2';

Sonuç: CS101, CS201. EXCEPT ile çevirirsen (S1 eksi S2) CS305, MA101 gelir. SELECT course_id FROM course EXCEPT SELECT course_id FROM takes hiç alınmamış dersi verir: BI100.

SELECT dept_name FROM instructor UNION SELECT dept_name FROM student 5 satır döndürür (NULL, BIO, CS, EE, MATH); UNION ALL ile 6 + 7 = 13. İki tarafın sütun sayısı ve tipleri uyumlu olmalı. Standart SQL’de INTERSECT ALL ve EXCEPT ALL da var, SQLite’ta yok.

Veri değiştirme: INSERT, UPDATE, DELETE

Her örnek taze bir veritabanı kopyasında çalıştırıldı.

INSERT INTO course (course_id, title, dept_name, credits)
VALUES ('CS410', 'Machine Learning', 'CS', 3);

-- 30+ kredili CS öğrencilerini yeni derse kaydet
INSERT INTO takes (stu_id, course_id, grade)
SELECT stu_id, 'CS410', NULL FROM student WHERE dept_name = 'CS' AND tot_cred >= 30;

İkinci ifade S1 ve S2 için iki satır ekler.

UPDATE instructor SET salary = salary * 1.05 WHERE salary < 75000;

Sahin 73500, Celik 75600, Yildiz 63000 olur, diğerleri değişmez. İki farklı kural varsa tek ifadede CASE kullan; iki ayrı UPDATE yazarsan birincinin yükselttiği satır ikincinin koşuluna girebilir:

UPDATE instructor
SET salary = CASE WHEN salary < 80000 THEN salary + 5000 ELSE salary + 2000 END;

Sonuç: Aksoy 97000, Demir 82000, Kaya 87000, Sahin 75000, Celik 77000, Yildiz 65000.

DELETE FROM takes WHERE grade = 'F';                     -- 14 -> 13 satır
DELETE FROM takes
WHERE stu_id IN (SELECT stu_id FROM student WHERE tot_cred < 20);   -- 13 -> 12

DELETE FROM instructor WHERE salary < (SELECT AVG(salary) FROM instructor) ilginç: silme sırasında ortalama değişir mi? Hayır. SQL alt sorguyu silmeden önce bir kez hesaplar (77000) ve sonra siler. FK kontrolü kapalı bir kopyada Sahin, Celik, Yildiz silinir. FK açıkken ise ifade reddedilir, çünkü Sahin ve Celik teaches tablosunda ders veriyor.

View

View, adı olan kayıtlı bir sorgudur. Veri kopyalamaz; her kullanıldığında tanımı yeniden çalışır.

CREATE VIEW cs_roster AS
SELECT s.name, t.course_id, t.grade
FROM student s JOIN takes t ON s.stu_id = t.stu_id
WHERE s.dept_name = 'CS';

SELECT course_id, COUNT(*) AS n FROM cs_roster GROUP BY course_id;
course_idn
CS1013
CS2012
CS3051
MA1011

takes tablosuna yeni bir CS öğrencisi kaydı eklenince view kendiliğinden 7’den 8 satıra çıkar. View’lar güvenlik (kullanıcıya sadece bazı sütunları göstermek) ve karmaşık sorguyu basitleştirmek için kullanılır. View üzerinden INSERT her sistemde mümkün değil; SQLite cannot modify cs_roster because it is a view der.

Sık yapılan hatalar

YanlışDoğrusu
WHERE AVG(salary) > 75000Aggregate koşulu HAVINGe
SELECT dept_name, name, MAX(salary) ... GROUP BY dept_namename gruplanmamış; standart SQL’de hata
Sıfırlı gruplar için inner join + COUNT(*)LEFT JOIN + COUNT(sağ.sütun)
WHERE x = NULLWHERE x IS NULL
NOT IN (alt sorgu) alt sorgu NULL içerirkenNOT EXISTS ya da IS NOT NULL filtresi
COUNT(grade) ile satır saymakNULL notlar sayılmaz; satır için COUNT(*)
“Hepsini alan” için ININ “en az birini” verir; bölme için çift NOT EXISTS
> ALL yerine > SOMEALL = en büyükten büyük, SOME = en küçükten büyük
UNION tekrar tutar sanmakTutan UNION ALL
İki kuralı iki ayrı UPDATE ile yapmakEtkileşebilir; tek UPDATE + CASE
View’ı tablo kopyası sanmakSaklanan sorgu; veri her seferinde yeniden hesaplanır

Kendini test et

Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.

Soru 1kolay

What does the query below return?

SELECT COUNT(*), COUNT(grade)
FROM takes
WHERE course_id = 'CS101';
Cevabı göster
4, 3

CS101'i alanlar: S1 (A), S2 (B), S3 (NULL), S5 (F). Dört satır, COUNT(*) = 4.COUNT(grade) NULL olmayan değerleri sayar. Cem'in notu NULL olduğu için sayılmaz: 3.

Soru 2orta

Write an SQL query that lists each department name with the average salary of its instructors, but only for departments whose average salary is above 75000.

Cevabı göster
SELECT dept_name, AVG(salary) FROM instructor GROUP BY dept_name HAVING AVG(salary) > 75000;

Grup bazında koşul olduğu için WHERE değil HAVING.
Grup ortalamaları: BIO 60000, CS (95000 + 80000)/2 = 87500, EE 85000, MATH (70000 + 72000)/2 = 71000.
75000'in üstü: CS 87500, EE 85000.

Soru 3orta

Write an SQL query that lists every course_id with the number of students enrolled in it. Courses with no students must appear with 0.

Cevabı göster
SELECT c.course_id, COUNT(t.stu_id) FROM course c LEFT JOIN takes t ON c.course_id = t.course_id GROUP BY c.course_id;

İki tuzak var. Inner join kullanırsan BI100 hiç gelmez, bu yüzden LEFT JOIN.COUNT(*) yazarsan BI100 için 1 çıkar, çünkü LEFT JOIN onu NULL'larla dolu bir satırla getirir. COUNT(t.stu_id) NULL'u saymaz, 0 verir.
Sonuç: BI100 0, CS101 4, CS201 2, CS305 2, EE210 1, MA101 3, MA202 2.

Soru 4zor

The query below is meant to list departments that have no students. What does it return on our database, why, and how do you fix it?

SELECT dept_name FROM department
WHERE dept_name NOT IN (SELECT dept_name FROM student);
Cevabı göster
Empty result, because the subquery contains a NULL (Gizem); fix with NOT EXISTS or add WHERE dept_name IS NOT NULL in the subquery (then BIO)

Alt sorgu şunu döndürür: CS, CS, EE, MATH, CS, EE, NULL.
BIO için 'BIO' NOT IN (..., NULL) demek 'BIO' <> 'CS' AND ... AND 'BIO' <> NULL. Son terim unknown. TRUE AND unknown = unknown, satır seçilmez. Hiçbir bölüm için TRUE çıkmaz: sonuç boş.
Düzeltme 1: NOT EXISTS (SELECT * FROM student s WHERE s.dept_name = d.dept_name).
Düzeltme 2: alt sorguya WHERE dept_name IS NOT NULL ekle. İkisi de BIO döndürür.

Soru 5orta

Write an SQL query using NOT EXISTS that lists the names of students who have not taken any course.

Cevabı göster
SELECT name FROM student s WHERE NOT EXISTS (SELECT * FROM takes t WHERE t.stu_id = s.stu_id);

Korelasyonlu alt sorgu: dıştaki her s satırı için içteki sorgu o öğrencinin kayıtlarını arar. Hiç satır yoksa NOT EXISTS TRUE olur.
Kaydı olmayan tek öğrenci: Firat (S6).

Soru 6zor

Write an SQL query that lists the names of students who have taken every course offered by the MATH department.

Cevabı göster
SELECT s.name FROM student s WHERE NOT EXISTS (SELECT * FROM course c WHERE c.dept_name = 'MATH' AND NOT EXISTS (SELECT * FROM takes t WHERE t.stu_id = s.stu_id AND t.course_id = c.course_id));

"Every" sorusu bölme (division) sorusudur. SQL'de çift NOT EXISTS ile yazılır: "almadığı bir MATH dersi olmayan öğrenciler".
MATH dersleri: MA101, MA202. MA101'i alanlar S1, S3, S4; MA202'yi alanlar S4, S7. İkisini de alan sadece S4: Defne.
Aynı soru NOT EXISTS (MATH dersleri EXCEPT öğrencinin dersleri) biçiminde de yazılabilir. İlişkisel cebirde πstu_id,course_id(takes)÷πcourse_id(σdept_name=′MATH′(course))\pi_{stu\_id,course\_id}(takes) \div \pi_{course\_id}(\sigma_{dept\_name='MATH'}(course)) de S4 verdi.

Soru 7orta

In standard SQL, the query below lists instructors earning more than every MATH instructor. SQLite rejects it. Rewrite it so it runs in SQLite and give the result.

SELECT name FROM instructor
WHERE salary > ALL (SELECT salary FROM instructor
                    WHERE dept_name = 'MATH');
Cevabı göster
SELECT name FROM instructor WHERE salary > (SELECT MAX(salary) FROM instructor WHERE dept_name = 'MATH'); -- Aksoy, Demir, Kaya

> ALL (küme), kümedeki en büyük değerden büyük olmak demek. Bu yüzden > (SELECT MAX(...)) ile aynı. (> SOME ise > MIN olur.)
MATH maaşları 70000 ve 72000, en büyüğü 72000. Üstünde olanlar: Aksoy 95000, Demir 80000, Kaya 85000.
SQLite ALL/SOME/ANY karşılaştırmalarını desteklemez (syntax error). PostgreSQL ve Oracle destekler.
Not: MATH'te hiç hoca olmasaydı > ALL her satır için TRUE verirdi, > MAX (NULL ile karşılaştırma) hiçbir satır vermezdi.

Soru 8kolay

teaches has 6 rows and takes has 14 rows. How many rows does each query return? (a) SELECT course_id FROM teaches UNION ALL SELECT course_id FROM takes; (b) the same with UNION.

Cevabı göster
(a) 20, (b) 6

UNION ALL tekrarları korur: 6 + 14 = 20.UNION tekrarları siler. İki tablodaki farklı course_id değerleri: CS101, CS201, CS305, EE210, MA101, MA202, yani 6. BI100 ikisinde de yok.

Soru 9orta

Write an SQL query that lists the name and salary of instructors who earn more than the average salary of their own department.

Cevabı göster
SELECT i.name, i.salary FROM instructor i WHERE i.salary > (SELECT AVG(i2.salary) FROM instructor i2 WHERE i2.dept_name = i.dept_name);

"Kendi bölümünün" dediği için alt sorgu dıştaki satıra bağlı: korelasyonlu (correlated) alt sorgu. İçteki i2.dept_name = i.dept_name bu bağı kurar.
Bölüm ortalamaları: CS 87500, EE 85000, MATH 71000, BIO 60000.
Ortalamadan büyük olanlar: Aksoy (95000 > 87500), Celik (72000 > 71000). Kaya ve Yildiz bölümlerinin tek hocası, ortalamaya eşit, büyük değil.

Soru 10orta

After running the statement below, what is Ada's (S1) tot_cred? Which other students change?

UPDATE student
SET tot_cred = tot_cred + 5
WHERE stu_id IN (SELECT stu_id FROM takes WHERE grade = 'A');
Cevabı göster
65; Cem (S3) becomes 50 and Defne (S4) becomes 95

Alt sorgu A alanları bulur: S1 (iki tane A), S3, S4. IN üyelik sorar, S1'in iki A'sı olması onu iki kez güncellemez.
Ada 60 + 5 = 65, Cem 45 + 5 = 50, Defne 90 + 5 = 95. Diğerleri değişmez.

Soru 11kolay

Find the bug and fix it.

SELECT dept_name
FROM instructor
WHERE AVG(salary) > 75000
GROUP BY dept_name;
Cevabı göster
Aggregates are not allowed in WHERE; use: SELECT dept_name FROM instructor GROUP BY dept_name HAVING AVG(salary) > 75000;

WHERE gruplamadan önce, tek tek satırlara uygulanır. O anda henüz grup yok, AVG hesaplanamaz. SQLite mesajı: misuse of aggregate function AVG(). (Ayrıca WHERE, GROUP BYdan sonra yazılamaz.)
Grup koşulu HAVING ile yazılır. Düzeltilmiş sorgu CS ve EE döndürür.

Soru 12orta

Give the result table of SELECT grade, COUNT(*) FROM takes GROUP BY grade ORDER BY grade;

Cevabı göster
NULL 1; A 5; B 5; C 2; F 1

GROUP BY, NULL değerlerini tek bir grup olarak toplar (WHERE'deki = mantığından farklı). Burada COUNT(*) kullanıldığı için NULL grubu 1 sayılır (Cem'in CS101'i). COUNT(grade) olsaydı o satır 0 olurdu.
A: S1-CS101, S1-CS305, S3-EE210, S4-MA101, S4-MA202 = 5. B: S1-CS201, S1-MA101, S2-CS101, S4-CS305, S7-MA202 = 5. C: S2-CS201, S3-MA101 = 2. F: S5-CS101 = 1.
SQLite artan sıralamada NULL'u en başa koyar. Toplam 1 + 5 + 5 + 2 + 1 = 14 = takes satır sayısı.

Doğrulama: Sayfadaki her SQL ifadesi ve her soru cevabı node:sqlite (SQLite 3.53.4) ile örnek üniversite veritabanında çalıştırıldı; DML örnekleri her biri taze bir veritabanı kopyasında. ALL/SOME SQLite'ta olmadığı için hata verdiği ve MAX/MIN eşdeğerlerinin aynı sonucu verdiği hem SQL hem JS ile doğrulandı; bölme sorgusu JS ilişkisel cebir bölmesiyle karşılaştırıldı (verify/SEN2104/07.mjs).

Deftere not kartı

COUNT(*) satır sayar; COUNT(col), SUM, AVG, MIN, MAX NULL'ları atlar Boş kümede COUNT = 0, SUM/AVG/MIN/MAX = NULL Sıra: FROM -> WHERE (satır) -> GROUP BY -> HAVING (grup) -> SELECT -> ORDER BY SELECT'te aggregate dışı her sütun GROUP BY'da olmalı (SQLite bunu zorlamaz) "0 olanlar da görünsün" = LEFT JOIN + COUNT(sağ_tablo.col) NULL ile her karşılaştırma UNKNOWN; WHERE sadece TRUE'yu tutar TRUE AND U = U, FALSE AND U = FALSE, TRUE OR U = TRUE, NOT U = U x NOT IN (..., NULL) asla TRUE olmaz -> NOT EXISTS kullan "her/all" sorusu = çift NOT EXISTS (ya da NOT EXISTS (A EXCEPT B)) > ALL = > MAX, > SOME = > MIN (SQLite'ta ALL/SOME yok) UNION/INTERSECT/EXCEPT tekrarı siler; UNION ALL tutar UPDATE ... SET x = CASE WHEN ... THEN ... ELSE ... END DELETE'te alt sorgu silmeden önce bir kez hesaplanır VIEW = kayıtlı sorgu, veri tutmaz, her okunuşta yeniden çalışır