Sınav ve Mülakat Serisi 1/8
Veri analisti, veri bilimci ya da yazılım geliştirici ilanlarının çoğunda SQL isteniyor ve teknik mülakatlarda neredeyse her zaman birkaç SQL sorusu karşına çıkıyor. Soruların biçimi farklı olsa da arkasında hep aynı birkaç kalıp var. Bu yazıda en temel dört konuyu, yani filtreleme, gruplama, tablo birleştirme ve sıralamayı, soru tiplerine ayırarak çalışacağız.
Her soru tipinde önce kısa bir hatırlatma, sonra çözümlü bir örnek, ardından da en sık düşülen tuzak var. Yazının sonunda cevaplarıyla birlikte 10 alıştırma sorusu bulacaksın.
Not: Buradaki soruların hiçbiri gerçek bir sınavdan ya da mülakattan alınmadı. Hepsi konuyu pekiştirmek için hazırladığım örnekler; veriler de hayali.
SQL’in ne olduğunu ilk kez öğreniyorsan önce Veritabanı Nedir? SQL ve NoSQL Farkı yazısına göz at.
Örnek veritabanı
Tüm soruları iki küçük tablo üzerinden çözeceğiz. Sekiz çalışan ve dört departman var:
calisanlar
| id | ad | departman_id | maas | sehir |
|---|---|---|---|---|
| 1 | Ayşe | 1 | 52000 | Adana |
| 2 | Mert | 1 | 45000 | İstanbul |
| 3 | Zeynep | 2 | 61000 | İzmir |
| 4 | Can | 2 | 48000 | Adana |
| 5 | Elif | 3 | 39000 | Ankara |
| 6 | Burak | NULL | 41000 | İstanbul |
| 7 | Deniz | 1 | 58000 | Ankara |
| 8 | Selin | 2 | NULL | İstanbul |
departmanlar
| id | ad |
|---|---|
| 1 | Yazılım |
| 2 | Veri |
| 3 | İnsan Kaynakları |
| 4 | Pazarlama |
Dikkat: Burak’ın departmanı yok, Selin’in maaşı boş, Pazarlama’da hiç çalışan yok. Bu üç detayı bilerek koydum; soru hazırlayanlar tuzakları tam da bu tür boşluklara kurar.
Tabloları kendin kurup sorguları denemek için bu kodu Colab’da çalıştırabilirsin. Python’un içinde gelen sqlite3 modülünü kullanıyor, kurulum gerekmiyor:
import sqlite3db = sqlite3.connect(":memory:")db.executescript("""CREATE TABLE departmanlar (id INTEGER PRIMARY KEY, ad TEXT);CREATE TABLE calisanlar (id INTEGER PRIMARY KEY, ad TEXT, departman_id INTEGER, maas INTEGER, sehir TEXT);INSERT INTO departmanlar VALUES (1,'Yazılım'),(2,'Veri'),(3,'İnsan Kaynakları'),(4,'Pazarlama');INSERT INTO calisanlar VALUES (1,'Ayşe',1,52000,'Adana'), (2,'Mert',1,45000,'İstanbul'), (3,'Zeynep',2,61000,'İzmir'), (4,'Can',2,48000,'Adana'), (5,'Elif',3,39000,'Ankara'), (6,'Burak',NULL,41000,'İstanbul'), (7,'Deniz',1,58000,'Ankara'), (8,'Selin',2,NULL,'İstanbul');""")def sorgu(sql): for satir in db.execute(sql): print(satir)sorgu("SELECT ad, maas FROM calisanlar WHERE maas > 50000")
Önce bir kavram: SQL hangi sırayla çalışır?
Sorguyu SELECT ile yazmaya başlıyoruz, ama veritabanı onu başka bir sırayla çalıştırıyor:
FROMveJOIN: Hangi tablolardan?WHERE: Hangi satırlar kalsın?GROUP BY: Satırlar nasıl gruplansın?HAVING: Hangi gruplar kalsın?SELECT: Hangi sütunlar gösterilsin?ORDER BY: Nasıl sıralansın?LIMIT: Kaç satır gösterilsin?
Bu sırayı bilmek, aşağıdaki tuzakların çoğunu açıklıyor. Örneğin WHERE gruplamadan önce çalıştığı için içinde COUNT ya da AVG kullanamazsın.
Soru tipi 1: Filtreleme (WHERE)
Soru: Maaşı 50.000’den yüksek olan çalışanları maaşa göre büyükten küçüğe listele.
SELECT ad, maasFROM calisanlarWHERE maas > 50000ORDER BY maas DESC;
Zeynep 61000Deniz 58000Ayşe 52000
WHERE ile birlikte en sık kullanılanlar:
IN ('Adana', 'Ankara'): Listedeki değerlerden biri.BETWEEN 40000 AND 55000: İki değer arasında, uçlar dahil.LIKE 'A%': A ile başlayan.%herhangi sayıda karakter,_tek bir karakter demek.AND,OR,NOT: Koşulları birleştirmek. Karışık koşullarda parantez kullan.
Tuzak: NULL eşittirle aranmaz
Maaşı girilmemiş çalışanı bulmak isteyelim:
SELECT ad FROM calisanlar WHERE maas = NULL; -- hiç sonuç dönmez!SELECT ad FROM calisanlar WHERE maas IS NULL; -- Selin
NULL “bilinmiyor” demek. “Bilinmeyen bir şey bilinmeyen bir şeye eşit mi?” sorusunun cevabı da “bilinmiyor”, yani doğru değil. Bu yüzden NULL her zaman IS NULL ya da IS NOT NULL ile aranır. Mülakatlarda çok sevilen bir soru.
Soru tipi 2: Gruplama (GROUP BY ve HAVING)
Soru: Her departmandaki çalışan sayısını ve ortalama maaşı bul.
SELECT departman_id, COUNT(*) AS kisi, ROUND(AVG(maas)) AS ort_maasFROM calisanlarGROUP BY departman_id;
NULL 1 410001 3 516672 3 545003 1 39000
GROUP BY aynı departman_id‘ye sahip satırları bir araya topluyor, COUNT ve AVG gibi toplama fonksiyonları da her grup için tek bir değer hesaplıyor. Departmanı olmayan Burak, NULL adında kendi grubunu oluşturdu.
Tuzak 1: COUNT(*) ile COUNT(sütun) aynı şey değil
SELECT COUNT(*), COUNT(maas) FROM calisanlar; -- 8, 7
COUNT(*) satırları sayar. COUNT(maas) ise sadece maaşı NULL olmayan satırları sayar.
Tuzak 2: AVG boş değerleri atlar
Veri departmanında üç kişi var ama ortalama 54.500 çıktı. Hesaplarsan: (61.000 + 48.000) / 2 = 54.500. Selin’in boş maaşı hesaba hiç katılmadı. Boş maaş sıfır sayılsaydı ortalama 36.333 olurdu. “Ortalama maaş kaç?” sorusunda bu farkı bilmek seni diğer adaylardan ayırır.
WHERE mi, HAVING mi?
Soru: En az 3 çalışanı olan departmanları listele.
SELECT departman_id, COUNT(*) AS kisiFROM calisanlarGROUP BY departman_idHAVING COUNT(*) >= 3;
1 32 3
Kural basit: Tek tek satırları elemek için WHERE, gruplamadan sonra grupları elemek için HAVING. WHERE COUNT(*) >= 3 yazarsan hata alırsın, çünkü WHERE çalıştığında henüz grup yok.
Soru tipi 3: Tabloları birleştirmek (JOIN)
Soru: Her çalışanın adını ve departmanının adını göster.
SELECT c.ad, d.ad AS departmanFROM calisanlar cINNER JOIN departmanlar d ON c.departman_id = d.id;
Bu sorgu 7 satır döndürüyor. Burak yok! INNER JOIN sadece iki tabloda da eşleşmesi olan satırları getiriyor. Burak’ın departmanı olmadığı için elendi.
Herkesi görmek istiyorsan LEFT JOIN kullan:
SELECT c.ad, d.ad AS departmanFROM calisanlar cLEFT JOIN departmanlar d ON c.departman_id = d.id;
LEFT JOIN soldaki tablonun (calisanlar) bütün satırlarını getirir. Eşleşme yoksa sağdaki sütunlar NULL olur. Artık 8 satır var ve Burak’ın departmanı NULL görünüyor.
Sorgulardaki c ve d tabloların kısa adları (alias). İki tabloda da ad sütunu olduğu için hangisini istediğimizi c.ad ve d.ad diye belirtmek zorundayız.
Klasik soru: Eşleşmeyenleri bulmak
Soru: Hiç çalışanı olmayan departmanları bul.
SELECT d.adFROM departmanlar dLEFT JOIN calisanlar c ON c.departman_id = d.idWHERE c.id IS NULL;
Pazarlama
Mülakatların en sevilen kalıplarından biri: LEFT JOIN ile her şeyi getir, sonra eşleşmesi olmayanları IS NULL ile süzgüle. “Hiç sipariş vermemiş müşteriler”, “hiç derse kaydolmamış öğrenciler” gibi soruların hepsi bu kalıpla çözülüyor.
Tuzak: LEFT JOIN’den sonra COUNT(*)
Her departmandaki çalışan sayısını, çalışanı olmayanlar dahil bulalım:
-- Yanlış: Pazarlama 1 görünürSELECT d.ad, COUNT(*) AS kisiFROM departmanlar dLEFT JOIN calisanlar c ON c.departman_id = d.idGROUP BY d.ad;-- Doğru: Pazarlama 0 görünürSELECT d.ad, COUNT(c.id) AS kisiFROM departmanlar dLEFT JOIN calisanlar c ON c.departman_id = d.idGROUP BY d.ad;
LEFT JOIN Pazarlama için içi NULL dolu bir satır üretiyor. COUNT(*) o satırı da sayıyor; COUNT(c.id) ise NULL olduğu için saymıyor. Tuzak 1’deki kural burada da işe yarıyor.
Soru tipi 4: En yüksek N ve “ikinci en yüksek”
Soru: En yüksek maaş alan 3 çalışanı bul.
SELECT ad, maasFROM calisanlarORDER BY maas DESCLIMIT 3;
Soru: İkinci en yüksek maaşı alan çalışan kim?
Bu sorunun iki yaygın çözümü var:
-- 1) Sırala, birini atla, sonrakini alSELECT ad, maas FROM calisanlarORDER BY maas DESCLIMIT 1 OFFSET 1;-- 2) En yüksekten küçük olanların en yükseğiSELECT ad, maas FROM calisanlarWHERE maas < (SELECT MAX(maas) FROM calisanlar)ORDER BY maas DESCLIMIT 1;
İkisi de Deniz’i (58.000) buluyor. Farkı şu: En yüksek maaşı iki kişi paylaşsaydı, birinci yöntem ikinci kişiyi yine en yüksek maaşla getirirdi; ikinci yöntem ise gerçekten bir alt maaş basamağını bulurdu. Mülakatta “maaşlar eşit olursa ne olur?” diye sorulması çok olası.
İkinci yöntemdeki parantez içindeki sorguya alt sorgu deniyor. Alt sorgulara ve bu tür soruları çok daha rahat çözen pencere fonksiyonlarına serinin 5. yazısında bakacağız.
Hepsi bir arada: çözümlü örnek
Soru: İzmir dışındaki çalışanları dikkate al. Ortalama maaşı 45.000’den yüksek olan departmanların adını ve ortalama maaşını, büyükten küçüğe sıralayarak listele.
Soruyu parçalara ayıralım:
- “Departmanın adı” → iki tabloya da ihtiyacımız var:
JOIN. - “İzmir dışındaki çalışanlar” → satır filtresi:
WHERE. - “Departmanların ortalama maaşı” →
GROUP BYveAVG. - “Ortalaması 45.000’den yüksek olan” → grup filtresi:
HAVING. - “Büyükten küçüğe” →
ORDER BY ... DESC.
SELECT d.ad, ROUND(AVG(c.maas)) AS ortFROM calisanlar cJOIN departmanlar d ON c.departman_id = d.idWHERE c.sehir <> 'İzmir'GROUP BY d.adHAVING AVG(c.maas) > 45000ORDER BY ort DESC;
Yazılım 51667Veri 48000
Zeynep İzmir’de olduğu için WHERE onu eledi; Veri’de geriye Can ve maaşı boş Selin kaldı, ortalama 48.000 oldu. İnsan Kaynakları’nın ortalaması 39.000 olduğu için HAVING onu eledi. Burak’ın departmanı olmadığı için JOIN onu en başta dışarıda bıraktı.
Uzun bir soruyla karşılaştığında hep böyle yap: Soruyu cümle cümle oku, her cümleyi bir SQL parçasına çevir.
[AFFILIATE: SQL alıştırma platformu ya da SQL kursu]
10 alıştırma sorusu
Aynı iki tabloyu kullan. Cevaplara bakmadan önce kendin dene; Colab’daki sorgu() fonksiyonuyla sonucunu kontrol edebilirsin.
- İstanbul’da çalışanların adlarını listele.
- Maaşı 45.000 ile 55.000 arasında (uçlar dahil) olanları ada göre sırala.
- Adında “e” harfi geçen çalışanları bul.
- Departmanı olmayan kaç çalışan var?
- Her şehirde kaç çalışan olduğunu, kalabalıktan aza doğru sırala.
- En yüksek ve en düşük maaş arasındaki fark kaç?
- En az 2 çalışanı olan şehirlerin ortalama maaşını bul.
- Veri departmanındaki çalışanların adlarını listele (departman adıyla filtrele, id ile değil).
- Her departmandaki en yüksek maaşı departman adıyla birlikte göster.
- 2’den az çalışanı olan departmanları, hiç çalışanı olmayanlar dahil listele.
Cevaplar
-- 1) Mert, Burak, SelinSELECT ad FROM calisanlar WHERE sehir = 'İstanbul';-- 2) Ayşe 52000, Can 48000, Mert 45000SELECT ad, maas FROM calisanlarWHERE maas BETWEEN 45000 AND 55000ORDER BY ad;-- 3) Ayşe, Deniz, Elif, Mert, Selin, Zeynep-- Not: Elif büyük E ile başlıyor. SQLite'ta LIKE, İngilizce harflerde-- büyük-küçük harf ayırmaz; bazı veritabanlarında ayırır.SELECT ad FROM calisanlar WHERE ad LIKE '%e%';-- 4) 1SELECT COUNT(*) FROM calisanlar WHERE departman_id IS NULL;-- 5) İstanbul 3, Adana 2, Ankara 2, İzmir 1SELECT sehir, COUNT(*) AS kisi FROM calisanlarGROUP BY sehirORDER BY kisi DESC;-- 6) 22000 (61000 - 39000; Selin'in boş maaşı hesaba girmez)SELECT MAX(maas) - MIN(maas) AS fark FROM calisanlar;-- 7) Adana 50000, Ankara 48500, İstanbul 43000-- (İstanbul'da 3 kişi var ama Selin'in maaşı boş; ortalama Mert ve Burak'tan)SELECT sehir, ROUND(AVG(maas)) AS ort FROM calisanlarGROUP BY sehirHAVING COUNT(*) >= 2ORDER BY ort DESC;-- 8) Can, Selin, ZeynepSELECT c.ad FROM calisanlar cJOIN departmanlar d ON c.departman_id = d.idWHERE d.ad = 'Veri';-- 9) Veri 61000, Yazılım 58000, İnsan Kaynakları 39000SELECT d.ad, MAX(c.maas) AS en_yuksek FROM departmanlar dJOIN calisanlar c ON c.departman_id = d.idGROUP BY d.adORDER BY en_yuksek DESC;-- 10) İnsan Kaynakları 1, Pazarlama 0SELECT d.ad, COUNT(c.id) AS kisi FROM departmanlar dLEFT JOIN calisanlar c ON c.departman_id = d.idGROUP BY d.adHAVING COUNT(c.id) < 2;
Bu yazıdan aklında kalması gerekenler
NULLeşittirle değil,IS NULLile aranır.COUNT(*)satırları,COUNT(sütun)dolu değerleri sayar.AVGboşları atlar.- Satır filtresi
WHERE, grup filtresiHAVING. INNER JOINeşleşmeyenleri eler,LEFT JOINsoldaki herkesi tutar.- “Hiç … olmayanlar” sorusu:
LEFT JOIN+IS NULL. - “İkinci en yüksek” sorusunda eşit değerleri düşün.
Serinin bir sonraki yazısında başka bir konunun soru tiplerine geçiyoruz. SQL’in devamı, yani alt sorgular ve pencere fonksiyonları, 5. yazıda.










