Veri analisti mülakatında sorulan 10 SQL sorusu
senaryo · çözüm · sık yapılan hatalar · 20 dakika okuma
Veri analisti mülakatlarının büyük çoğunluğunda SQL testi var. Bazen beyaz tahta, bazen online editor, bazen sadece "şunu nasıl yazarsın?" sorusu olarak geliyor. Ama sorulan konular tahmin edilebilir.
Bu yazıdaki 10 soru, gerçek mülakatlarda en sık karşılaşılan yapıları kapsıyor. Her soru için senaryo, çözüm ve "burada takılıyorlar" notunu ekledim.
Sorularla pratik yapmak için SQL Playground'ı kullanabilirsin — tarayıcıda gerçek SQL motoru.
Her departmandaki çalışan sayısını bul
Senaryo: İnsan kaynakları veritabanında employees tablosu var. Her departmanda kaç çalışan olduğunu öğrenmek istiyorlar.
SELECT
department,
COUNT(*) AS calisан_sayisi
FROM employees
GROUP BY department
ORDER BY calisаn_sayisi DESC;Sık yapılan hata: SELECT'te hem department hem desalary yazıp GROUP BY'a sadece department koymak.GROUP BY'da olmayan her sütun ya aggregate fonksiyona girmeli ya da dışarıda kalmalı.
Ortalamanın üzerinde maaş alan çalışanları listele
Senaryo: Maaşı şirket ortalamasının üzerinde olan çalışanların adını ve maaşını getir. Bu soru subquery kullanımını test ediyor.
SELECT
name,
salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees)
ORDER BY salary DESC;Sık yapılan hata: WHERE salary > AVG(salary) yazmak — bu çalışmaz.WHEREiçinde aggregate fonksiyon kullanılamaz, subquery gerekir. Alternatif olarak CTE'yi de kabul ediyorlar:
WITH ortalama AS (
SELECT AVG(salary) AS avg_salary FROM employees
)
SELECT name, salary
FROM employees, ortalama
WHERE salary > avg_salary;Her müşterinin son siparişini getir
Senaryo: E-ticaret veritabanında orders tablosu var. Her müşterinin en son verdiği siparişin tarihini ve tutarını bul. Bu soru ROW_NUMBER() window fonksiyonu ya da subquery ile çözülüyor.
-- Window function ile (tercih edilen yol):
SELECT customer_id, order_date, amount
FROM (
SELECT
customer_id,
order_date,
amount,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY order_date DESC
) AS rn
FROM orders
) ranked
WHERE rn = 1;Sık yapılan hata: MAX(order_date) ile tarihi doğru getirip tutarı yanlış eşleştirmek. GROUP BY customer_id yapınca amountbelirsizleşir — hangi siparişin tutarı? Window function bu sorunu çözer.
Birden fazla tablodaki veriyi birleştir — JOIN
Senaryo: Çalışanların adı employees tablosunda, departman adı ayrı bir departments tablosunda. Her çalışanın adını ve departman adını yan yana getir.
SELECT
e.name,
d.department_name,
e.salary
FROM employees e
JOIN departments d ON e.department_id = d.id
ORDER BY d.department_name, e.name;Mülakatta sorabilirler:"INNER JOIN ile LEFT JOIN farkı nedir?" Net cevap: INNER JOIN her iki tabloda eşleşen kayıtları getirir.LEFT JOIN sol tablonun tüm kayıtlarını getirir, sağda eşleşme yoksa NULL. Departmanı olmayan çalışanları da görmek istiyorsan LEFT JOIN.
Müşteri başına toplam harcamayı hesapla, 1000 TL altını filtrele
Senaryo:Sipariş tablosundan müşteri bazında toplam harcamayı bul, ama sadece toplamı 1000 TL'nin üzerindeki müşterileri göster. Bu soru HAVING kullanımını test ediyor.
SELECT
customer_id,
SUM(amount) AS toplam_harcama
FROM orders
GROUP BY customer_id
HAVING SUM(amount) > 1000
ORDER BY toplam_harcama DESC;Sık yapılan hata: WHERE SUM(amount) > 1000 yazmak.WHERE satırları filtreler, HAVING grupları filtreler. Aggregate işlemi sonrası filtreleme her zaman HAVING.
Ay bazında satış trendini çıkar
Senaryo: Hangi ayda ne kadar satış yapıldı? Tarih sütunundan ay bilgisini çıkar, aylık toplamları hesapla. Zaman serisi ve tarih fonksiyonlarını test ediyor.
-- PostgreSQL / Standart SQL:
SELECT
DATE_TRUNC('month', order_date) AS ay,
COUNT(*) AS siparis_sayisi,
SUM(amount) AS toplam_satis
FROM orders
GROUP BY DATE_TRUNC('month', order_date)
ORDER BY ay;
-- SQLite (SQL Playground'da çalışır):
SELECT
STRFTIME('%Y-%m', order_date) AS ay,
COUNT(*) AS siparis_sayisi,
SUM(amount) AS toplam_satis
FROM orders
GROUP BY STRFTIME('%Y-%m', order_date)
ORDER BY ay;İpucu: Tarih fonksiyonları veritabanına göre değişiyor. Mülakatta hangi veritabanını kullandıklarını sor — DATE_TRUNC,STRFTIME, FORMAT hepsi farklı syntax.
Çalışanları maaşa göre sıralayıp sıra numarası ver
Senaryo: Her çalışana maaş sıralamasına göre bir numara ver. Window fonksiyonlarının temel sorusu — neredeyse her mülakatta çıkıyor.
SELECT
name,
department,
salary,
RANK() OVER (ORDER BY salary DESC) AS sirа,
DENSE_RANK() OVER (ORDER BY salary DESC) AS yogun_sira,
ROW_NUMBER() OVER (ORDER BY salary DESC) AS satir_no
FROM employees;Mülakatta sorabilirler: "RANK, DENSE_RANK,ROW_NUMBERfarkı nedir?"
ROW_NUMBER: Eşit değerlere bile farklı numara verir (1, 2, 3, 4)RANK: Eşit değerler aynı sıra, sonraki atlıyor (1, 1, 3, 4)DENSE_RANK: Eşit değerler aynı sıra, sonraki atlamıyor (1, 1, 2, 3)
Hiç sipariş vermemiş müşterileri bul
Senaryo: customers ve orders tablosu var. Hiç sipariş vermemiş müşterilerin listesini getir. Üç farklı yol var — hangisini seçtiğin önemli.
-- Yol 1: LEFT JOIN + NULL kontrolü (yaygın ve okunabilir):
SELECT c.customer_id, c.name
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE o.customer_id IS NULL;
-- Yol 2: NOT EXISTS (büyük tablolarda genellikle daha hızlı):
SELECT customer_id, name
FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.customer_id
);
-- Yol 3: NOT IN (dikkatli kullan — NULL varsa sorun çıkar):
SELECT customer_id, name
FROM customers
WHERE customer_id NOT IN (
SELECT DISTINCT customer_id FROM orders
WHERE customer_id IS NOT NULL -- NULL varsa sonuç boş döner!
);İpucu: Mülakatta üç yolu da bildiğini göster, tercihini ve nedenini açıkla.NOT IN'deki NULL tuzağını bilmek seni ayırt eder.
Running total — kümülatif toplam hesapla
Senaryo: Günlük satışların üzerine kümülatif toplamı ekle. Her gün o güne kadar yapılan toplam satışı göster.
SELECT
order_date,
daily_sales,
SUM(daily_sales) OVER (
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS kumulatif_satis
FROM (
SELECT
order_date,
SUM(amount) AS daily_sales
FROM orders
GROUP BY order_date
) gunluk
ORDER BY order_date;Neden önemli: Running total, retention analizi, LTV hesabı gibi gerçek iş sorularında çok kullanılıyor. Window frame (ROWS BETWEEN...) syntax'ını bilmek ileri seviye SQL bilen olduğunu gösterir.
Self join: yöneticiyi çalışanla eşleştir
Senaryo: Tek bir employees tablosunda hem çalışanlar hem yöneticiler var. Her çalışanın yanına yöneticisinin adını getir. Tablo kendisiyle join ediliyor.
SELECT
e.name AS calisan,
m.name AS yonetici,
e.department,
e.salary
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.employee_id
ORDER BY e.department, e.name;Sık yapılan hata: INNER JOIN kullanmak — o zaman yöneticisi olmayan (tepedeki yönetici) kayıt siliniyor. LEFT JOIN ile yöneticisi olmayan çalışan da listede kalır, yönetici sütunu NULL döner.
Mülakat öncesi pratik planı
Mülakat 1 hafta sonraysa:
- 1-2. gün: Bu 10 soruyu elle yaz, çalıştır, hatalarını gör
- 3-4. gün: Kaggle'da SQL yarışmalarına gir (8 Week SQL Challenge öneririm)
- 5. gün: Şirketin ürününü araştır, veritabanında ne tür tablolar olduğunu tahmin et
- 6. gün: Window function sorularına odaklan — en çok bu konu ayırıyor
- 7. gün: Dinlen, kafan temiz olsun
Mülakatı geçen SQL kodunu değil, düşünce sürecini satar. Yüksek sesle konuş: "Önce şunu anladım, şunu deneyeceğim, burada şu sorunu görüyorum." Sessiz kalmak seni saydamlaştırır.
Soru takıldığın, anlamadığın veya farklı çözüm yolu bulduysan X'ten yazabilirsin.
Sorularla pratik yapmak için: SQL Playground — e-ticaret, İK ve spor verisiyle gerçek SQL motoru.