SQL temeli: DDL, kısıtlar, SELECT ve join
Tablo kurmak (CREATE TABLE ve kısıtlar), tek tablodan sorgu yazmak ve tabloları birleştirmek (inner, natural, outer, self join). Vizedeki "write the SQL" sorularının temeli.
SQL (Structured Query Language), ilişkisel veritabanıyla konuşmanın standart dili. Dördüncü ve beşinci haftada ilişkisel cebirle (relational algebra) ne istediğini matematiksel olarak yazdın. SQL aynı fikri okunabilir bir dille yazar. Sınavda en çok puanı “write an SQL query that…” soruları getirir. Bu sayfada iki şeyi öğreneceksin: tabloyu kurmak (DDL) ve tablolardan veri çekmek (SELECT ve join).
Altın kurallar:
- SELECT-FROM-WHERE okuma sırası FROM → WHERE → SELECT. Önce tabloyu kur, sonra satırları süz, en son sütunları seç.
- SQL tekrarları silmez. İlişkisel cebirde tekrarları atar, SQL’de bunun için
DISTINCTyazman gerekir. - Her join’in bir koşulu olmalı. Koşulsuz
FROM r, sKartezyen çarpımdır. - NULL hiçbir şeye eşit değil, kendine bile. NULL arıyorsan
IS NULLyaz. - “Eşi olmayanlar” sorusu = outer join + IS NULL (ya da 7. haftadaki NOT EXISTS).
Örnek veritabanı
Bütün ders boyunca aynı küçük üniversite veritabanını kullanıyoruz. Altı tablo:
| Tablo | Sütunlar (ilk sütun anahtar; parantez = bileşik anahtar) | Satırlar |
|---|---|---|
| department | dept_name, building, budget | CS Galata 500000, EE Galata 350000, MATH Rumeli 200000, BIO Kadikoy 150000 |
| instructor | inst_id, name, dept_name, salary | I1 Aksoy CS 95000, I2 Demir CS 80000, I3 Kaya EE 85000, I4 Sahin MATH 70000, I5 Celik MATH 72000, I6 Yildiz BIO 60000 |
| student | stu_id, name, dept_name, tot_cred | S1 Ada CS 60, S2 Bora CS 30, S3 Cem EE 45, S4 Defne MATH 90, S5 Ece CS 15, S6 Firat EE 0, S7 Gizem NULL 10 |
| course | course_id, title, dept_name, credits | CS101 Intro to Programming 4, CS201 Data Structures 4, CS305 Databases 3, EE210 Circuits 3, MA101 Calculus 4, MA202 Linear Algebra 3, BI100 Biology 3 |
| teaches | (inst_id, course_id) | I1-CS101, I1-CS305, I2-CS201, I3-EE210, I4-MA101, I5-MA202 |
| takes | (stu_id, course_id), grade | S1: CS101 A, CS201 B, CS305 A, MA101 B · S2: CS101 B, CS201 C · S3: EE210 A, MA101 C, CS101 NULL · S4: MA101 A, MA202 A, CS305 B · S5: CS101 F · S7: MA202 B |
Dikkat et: Firat (S6) hiç ders almıyor, Gizem’in bölümü NULL, Yildiz ders vermiyor, Biology’yi kimse vermiyor ve almıyor, Cem’in CS101 notu henüz NULL. Bu boşluklar outer join ve NULL sorularının malzemesi.
DDL: tabloyu kurmak
DDL (Data Definition Language), şemayı tanımlar. Satır değil, yapı yaratır.
CREATE TABLE course (
course_id TEXT PRIMARY KEY,
title TEXT NOT NULL,
dept_name TEXT REFERENCES department(dept_name),
credits INTEGER CHECK (credits BETWEEN 1 AND 6)
);
CREATE TABLE takes (
stu_id TEXT REFERENCES student(stu_id),
course_id TEXT REFERENCES course(course_id),
grade TEXT,
PRIMARY KEY (stu_id, course_id)
);
Sık kullanılan veri tipleri (data types): INT/INTEGER, NUMERIC(p,d) (p basamak, d’si ondalık), VARCHAR(n) (en çok n karakter), CHAR(n) (sabit n), DATE, REAL. SQLite tiplere gevşek bakar (TEXT, INTEGER, REAL), standart SQL ve diğer sistemler daha katıdır.
Bütünlük kısıtları (integrity constraints)
| Kısıt | Ne yapar | İhlal örneği (SQLite mesajı) |
|---|---|---|
PRIMARY KEY | tekil ve NULL olamaz | INSERT INTO student VALUES ('S1','Ali','CS',0) → UNIQUE constraint failed: student.stu_id |
NOT NULL | boş bırakılamaz | instructor name NULL → NOT NULL constraint failed |
UNIQUE | tekrar yok (aday anahtar) | iki kulüp aynı isim → UNIQUE constraint failed |
CHECK (P) | her satır P’yi sağlamalı | credits = 8 → CHECK constraint failed |
DEFAULT v | değer verilmezse v | tot_cred verilmeden ekleme → 0 |
FOREIGN KEY / REFERENCES | değer, hedef tabloda olmalı | takes içine S99 → FOREIGN KEY constraint failed |
Yabancı anahtar (foreign key) iki yönden korur. Var olmayan öğrenciye kayıt ekleyemezsin. Ders alan bir öğrenciyi de silemezsin: DELETE FROM student WHERE stu_id='S1' hata verir, çünkü takes hâlâ S1’e işaret ediyor. Silmeye izin vermek istiyorsan davranışı sen seçersin:
CREATE TABLE advisor (
stu_id TEXT PRIMARY KEY REFERENCES student(stu_id) ON DELETE CASCADE,
inst_id TEXT NOT NULL REFERENCES instructor(inst_id)
);
ON DELETE CASCADE ile öğrenci silinince advisor satırı da kendiliğinden silinir. Diğer seçenekler ON DELETE SET NULL ve varsayılan davranış olan reddetmek.
TUZAK · SQLite’ta FK kontrolü varsayılan olarak kapalı. SQLite yabancı anahtarları ancak
PRAGMA foreign_keys = ON;çalıştırılınca uygular. Doğrulama script’imiz bunu açıyor. Standart SQL’de ve PostgreSQL’de bu kontrol hep açıktır.
ALTER ve DROP
ALTER TABLE student ADD COLUMN email TEXT; -- var olan satırlarda email = NULL
DROP TABLE club; -- tablo ve şeması tamamen gider
DELETE FROM club; -- satırlar gider, boş tablo kalır
Temel sorgu: SELECT-FROM-WHERE
SELECT name, salary FROM instructor WHERE dept_name = 'CS' AND salary > 85000;
| name | salary |
|---|---|
| Aksoy | 95000 |
Bu sorgu ilişkisel cebirde demek. Eşleme şöyle:
| SQL | İlişkisel cebir |
|---|---|
FROM r, s | |
WHERE P | |
SELECT DISTINCT A | |
AS |
DISTINCT ve tekrarlar
SELECT dept_name FROM instructor 6 satır döndürür: CS, CS, EE, MATH, MATH, BIO. SQL bir çoklu küme (multiset, bag) döndürür. DISTINCT ekleyince:
| dept_name |
|---|
| CS |
| EE |
| MATH |
| BIO |
AS ve aritmetik
SELECT name, salary / 12 AS monthly FROM instructor WHERE dept_name = 'CS';
| name | monthly |
|---|---|
| Aksoy | 7916 |
| Demir | 6666 |
TUZAK · tam sayı bölmesi. SQLite’ta (ve PostgreSQL’de) iki INTEGER’ın bölümü tam sayıdır, ondalık atılır.
salary / 12.0yazınca 7916.666666666667 ve 6666.666666666667 gelir.
LIKE, BETWEEN, IN
LIKE kalıp eşleştirir: % herhangi uzunlukta dizi (boş da olabilir), _ tam bir karakter.
SELECT course_id, title FROM course WHERE title LIKE '%a%s';
| course_id | title |
|---|---|
| CS201 | Data Structures |
| CS305 | Databases |
| MA101 | Calculus |
Kalıp “içinde bir yerde a olsun ve s ile bitsin” diyor. Circuits s ile biter ama içinde a yok. course_id LIKE 'CS_01' ise CS101 ve CS201’i bulur.
TUZAK · LIKE ve büyük harf. Standart SQL’de
LIKEbüyük/küçük harfe duyarlıdır. SQLite ASCII harflerde duyarsızdır:title LIKE 'data%'SQLite’ta Data Structures ve Databases’ı bulur, PostgreSQL’de hiçbir şey bulmaz.
BETWEEN iki ucu dahil eder:
SELECT name, salary FROM instructor WHERE salary BETWEEN 70000 AND 85000;
| name | salary |
|---|---|
| Demir | 80000 |
| Kaya | 85000 |
| Sahin | 70000 |
| Celik | 72000 |
IN bir listeye üyeliği sorar: WHERE dept_name IN ('EE', 'MATH') → Cem, Defne, Firat. Bu, dept_name = 'EE' OR dept_name = 'MATH' ile aynıdır.
ORDER BY
SELECT name, dept_name, salary FROM instructor ORDER BY dept_name ASC, salary DESC;
| name | dept_name | salary |
|---|---|---|
| Yildiz | BIO | 60000 |
| Aksoy | CS | 95000 |
| Demir | CS | 80000 |
| Kaya | EE | 85000 |
| Celik | MATH | 72000 |
| Sahin | MATH | 70000 |
Önce bölüme göre artan, aynı bölümde maaşa göre azalan. ORDER BY olmadan satırların sırası garanti değildir. İlk n satır için SQLite ve MySQL LIMIT n, standart SQL FETCH FIRST n ROWS ONLY kullanır (SQLite ikincisini tanımaz).
Join: tabloları birleştirmek
Inner join ve üç yazım biçimi
EE öğrencilerinin aldığı dersler:
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 = 'EE';
| name | course_id | grade |
|---|---|---|
| Cem | CS101 | NULL |
| Cem | EE210 | A |
| Cem | MA101 | C |
Aynı sonucu veren dört yazım (hepsi script’te çalıştırıldı):
FROM student s JOIN takes t ON s.stu_id = t.stu_id -- ON: koşulu sen yazarsın
FROM student JOIN takes USING (stu_id) -- USING: bu sütunlarda eşitle
FROM student NATURAL JOIN takes -- aynı isimli TÜM sütunlar
FROM student, takes WHERE student.stu_id = takes.stu_id -- eski stil
Firat bu sonuçta yok. Inner join sadece eşi olan satırları tutar.
NATURAL JOIN tuzağı
student JOIN takes USING (stu_id) JOIN course USING (course_id) 14 satır verir, her kayıt bir kez. Ama:
SELECT name, course_id, dept_name FROM student NATURAL JOIN takes NATURAL JOIN course;
sadece 9 satır döndürür. Sebep: student ve course ikisinde de dept_name var. NATURAL JOIN onları da eşitler, sorgu sessizce “öğrencinin kendi bölümünden aldığı dersler” sorusuna dönüşür. Ada’nın MA101’i, Cem’in MA101 ve CS101’i, Defne’nin CS305’i ve Gizem’in MA202’si kaybolur.
Outer join
Inner join eşi olmayan satırları atar. Outer join onları tutar ve eksik tarafı NULL ile doldurur.
LEFT JOIN: soldaki tüm satırlar.RIGHT JOIN: sağdaki tüm satırlar.FULL OUTER JOIN: iki taraftaki tüm satırlar.
Hiç ders almamış öğrenciler:
SELECT s.stu_id, s.name, t.course_id
FROM student s LEFT JOIN takes t ON s.stu_id = t.stu_id
WHERE t.course_id IS NULL;
| stu_id | name | course_id |
|---|---|---|
| S6 | Firat | NULL |
Öğrenciler ve bölümleri, iki taraftaki yetimler dahil:
SELECT s.name, d.dept_name, d.building
FROM student s FULL OUTER JOIN department d ON s.dept_name = d.dept_name;
| name | dept_name | building |
|---|---|---|
| Ada | CS | Galata |
| Bora | CS | Galata |
| Cem | EE | Galata |
| Defne | MATH | Rumeli |
| Ece | CS | Galata |
| Firat | EE | Galata |
| Gizem | NULL | NULL |
| NULL | BIO | Kadikoy |
Gizem’in bölümü NULL olduğu için hiçbir bölümle eşleşmez. BIO’nun öğrencisi yok.
TUZAK · FULL OUTER JOIN her yerde yok. SQLite 3.39’dan (2022) önce
RIGHTveFULLjoin desteklemiyordu; bu sayfadaki sorgular 3.53’te çalıştırıldı. MySQL hâlâFULL OUTER JOINdesteklemez. OradaLEFT JOINsonucunu, sağdaki eşsiz satırlarlaUNIONyaparak aynı sonucu kurarsın.
Self join
Aynı tabloyu iki farklı takma adla kullanırsın. Aynı bölümde, diğerinden fazla kazanan hocalar:
SELECT a.name AS higher, b.name AS lower
FROM instructor a JOIN instructor b
ON a.dept_name = b.dept_name AND a.salary > b.salary;
| higher | lower |
|---|---|
| Aksoy | Demir |
| Celik | Sahin |
Takma ad (alias) zorunlu: instructor.salary > instructor.salary hangisinin hangisi olduğunu söyleyemez.
Sık yapılan hatalar
| Yanlış | Doğrusu |
|---|---|
FROM student, department ve join koşulu yok | Kartezyen çarpım: 7 × 4 = 28 satır. Koşulu ekle. |
WHERE dept_name = NULL | Hiçbir satır gelmez. WHERE dept_name IS NULL |
| SQL sonucu küme sanmak | Tekrarlar kalır; tekil isteniyorsa DISTINCT |
3 tabloyu NATURAL JOIN ile bağlamak | Gizli ortak sütunlar (dept_name) da eşitlenir; USING ya da ON kullan |
LEFT JOIN ... ON ... AND t.x IS NULL | Filtre olmaz, tüm sol satırlar gelir. IS NULL koşulu WHEREe |
BETWEEN 70000 AND 85000 uçları hariç sanmak | İki uç da dahil |
salary / 12 ondalık verir sanmak | INTEGER / INTEGER tam sayıdır; 12.0 ile böl |
Self join’de a.id <> b.id | Her çift iki kez gelir; a.id < b.id |
ORDER BY yazmadan sıra beklemek | Sıra tanımsızdır |
DROP TABLE ile satır silmek | Tablo tamamen gider; satırlar için DELETE FROM |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
Using the university database, write an SQL query that lists the names of CS students who have at least 30 total credits.
Cevabı göster
Tek tablo yeterli: student. İki koşul AND ile bağlanır. "At least" demek >= demek.
CS öğrencileri: Ada (60), Bora (30), Ece (15). 30 ve üstü: Ada, Bora. Bora tam sınırda, > yazsaydın kaybolurdu.
How many rows does SELECT DISTINCT building FROM department; return? How many does it return without DISTINCT?
Cevabı göster
department tablosunda 4 satır var, binalar: Galata, Galata, Rumeli, Kadikoy.
SQL varsayılan olarak tekrarları silmez (multiset). DISTINCT olmadan 4 satır gelir, Galata iki kez. DISTINCT ile 3.
Write an SQL query that lists the titles of all courses taken by the student named 'Ada'.
Cevabı göster
İsim studentta, ders başlığı courseta, ikisini bağlayan takes. Üç tablo, iki join koşulu.
Ada = S1. S1'in takes satırları: CS101, CS201, CS305, MA101. Başlıklar: Intro to Programming, Data Structures, Databases, Calculus.
Write an SQL query that lists the names of instructors whose salary is between 72000 and 85000 (inclusive), highest salary first.
Cevabı göster
BETWEEN a AND b iki ucu da dahil eder, yani salary >= 72000 AND salary <= 85000.
Uyanlar: Kaya 85000, Demir 80000, Celik 72000. ORDER BY salary DESC ile sonuç: Kaya, Demir, Celik.
Write an SQL query that lists (without duplicates) the names of instructors who teach at least one 4-credit course.
Cevabı göster
4 kredilik dersler: CS101, CS201, MA101. Bunları verenler: I1 Aksoy (CS101), I2 Demir (CS201), I4 Sahin (MA101).
Bir hoca birden fazla 4 kredilik ders verseydi adı iki kez gelirdi; "without duplicates" dediği için DISTINCT şart.
Sonuç: Aksoy, Demir, Sahin.
Write an SQL query that lists the titles of courses that no instructor teaches. Use an outer join.
Cevabı göster
LEFT JOIN, eşi olmayan course satırlarını da tutar ve teaches sütunlarını NULL ile doldurur. Sonra WHERE t.inst_id IS NULL sadece eşi olmayanları bırakır.
Sonuç: Biology (BI100).
Tuzak: IS NULL koşulunu ON içine yazarsan filtre olmaz; 7 dersin hepsi gelir. Koşul WHEREde olmalı.
A student wrote the query below to list each student's name with the building of their department, for departments with a budget over 300000. How many rows does it return, and what is the bug?
SELECT name, building
FROM student, department
WHERE budget > 300000;Cevabı göster
FROM student, department Kartezyen çarpımdır: 7 × 4 = 28 satır. WHERE sadece bütçeyi süzer: CS ve EE kalır, 7 × 2 = 14 satır. Her öğrenci iki bölümle eşleşir, anlamsız.
Düzeltme: WHERE student.dept_name = department.dept_name AND budget > 300000 (ya da JOIN ... ON). Doğru sorgu 5 satır döndürür: Ada, Bora, Cem, Ece, Firat (hepsi Galata).
Write an SQL query that returns every pair of distinct students who are in the same department, each pair listed only once, as (s1, s2) student IDs.
Cevabı göster
Aynı tabloyu iki takma adla (alias) kullanan self join.a.stu_id < b.stu_id iki işi birden yapar: kendisiyle eşleşmeyi (S1,S1) ve ters tekrarı (S2,S1) atar. <> yazsaydın her çift iki kez gelirdi (8 satır).
Sonuç: (S1,S2), (S1,S5), (S2,S5), (S3,S6). Gizem'in bölümü NULL; NULL = NULL doğru değil, kimseyle eşleşmez.
How many rows does the query below return? (Correct join on stu_id and course_id gives 14 rows.)
SELECT name, course_id
FROM student NATURAL JOIN takes NATURAL JOIN course;Cevabı göster
NATURAL JOIN aynı isimli bütün sütunları eşitler. student ile course ikisinde de dept_name var, yani gizlice student.dept_name = course.dept_name koşulu da eklenir.
Öğrencinin kendi bölümü dışındaki dersler düşer: Ada-MA101, Cem-MA101, Cem-CS101, Defne-CS305, Gizem-MA202 (Gizem'in bölümü NULL). 14 − 5 = 9.
Bu yüzden ikiden fazla tabloda JOIN ... USING (...) ya da ON daha güvenli.
How many rows does this full outer join return, and which rows contain NULLs?
SELECT s.name, d.dept_name
FROM student s FULL OUTER JOIN department d
ON s.dept_name = d.dept_name;Cevabı göster
Eşleşen satırlar: 6 öğrenci (Gizem hariç) kendi bölümüyle, 6 satır.
Soldan eşi olmayan: Gizem (bölümü NULL), sağ taraf NULL ile gelir.
Sağdan eşi olmayan: BIO bölümünde öğrenci yok, sol taraf NULL ile gelir.
6 + 1 + 1 = 8. Not: SQLite FULL OUTER JOINu 3.39 sürümünden beri destekler; MySQL hiç desteklemez (orada LEFT JOIN + UNION ile yazılır).
Which statement fails, and which constraint does it violate? (a) INSERT INTO student (stu_id, name, dept_name) VALUES ('S8', 'Hakan', 'CS'); (b) INSERT INTO course VALUES ('CS999', 'Capstone', 'CS', 8);
Cevabı göster
(a) Geçerli. tot_cred verilmedi, DEFAULT 0 devreye girer. S8 yeni bir anahtar, CS var olan bir bölüm.
(b) credits sütununda CHECK (credits BETWEEN 1 AND 6) var; 8 bu aralıkta değil. SQLite mesajı: CHECK constraint failed.
Write a CREATE TABLE statement for advisor(stu_id, inst_id): each student has at most one advisor, inst_id is required and must be an existing instructor, and when a student is deleted their advisor row must be deleted automatically.
Cevabı göster
"At most one advisor per student": stu_id tek başına anahtar, yani PRIMARY KEY.
"Required": NOT NULL. "Existing instructor": REFERENCES instructor(inst_id).
"Deleted automatically": ON DELETE CASCADE, öğrenci silinince ona bağlı satır da silinir.
Script'te S6 silinince advisor satırının da gittiği, var olmayan I99 ekleyince FK hatası verdiği görüldü.
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ı; sonuç tabloları script çıktısından birebir alındı, kısıt ihlalleri hata mesajıyla doğrulandı (verify/SEN2104/06.mjs, ortak veri lib.mjs).