SQL temelleri: veri analistinin en önemli becerisi
2026 · 20 dakika okuma · PostgreSQL / MySQL / BigQuery uyumlu
Python öğrenmeden önce, makine öğrenmesine girmeden önce, dashboard kurmadan önce: SQL. Her veri analistinin ilk öğrenmesi gereken, son bırakacağı beceri. 15 yıldır her gün kullanıyorum. Hâlâ vazgeçilmez.
Bu rehberde sıfırdan başlıyoruz. Ama sadece sözdizimi değil —neden böyle çalıştığınıda anlatıyorum. Çünkü SQL'i anlayan analist, sadece bilen analistten çok daha hızlı büyür.
SQL nedir, neden önemli?
SQL (Structured Query Language) ilişkisel veritabanlarıyla konuşmanın dili. Şirketlerin %90'ından fazlası verilerini bir veritabanında tutuyor. O veriye ulaşmak için SQL bilmek zorundasın.
Python her şeyi yapabilir, ama milyonlarca satırı bellekte işlemek yerine veritabanında sorgulamak hem hızlı hem de doğru yaklaşım. İyi bir SQL sorgusu, saatler sürecek Python kodunun işini saniyede bitirir.
SQL bilmek iş ilanlarında en çok aranan beceri. Veri analisti, data scientist, backend geliştirici, product manager — hepsinde SQL isteniyor.
1. Temel SELECT: veri çekmek
Her şey SELECT ile başlar. Tablodan veri çekmek için kullanılır.
-- Tüm sütunları çek SELECT * FROM musteriler; -- Sadece belirli sütunları çek SELECT ad, soyad, email FROM musteriler; -- Sütuna alias (takma ad) ver SELECT ad AS isim, soyad AS soyisim, email AS eposta FROM musteriler; -- Hesaplama yap SELECT urun_adi, fiyat, fiyat * 1.18 AS kdvli_fiyat FROM urunler;
2. WHERE: filtreleme
WHERE ile hangi satırları istediğini belirtirsin. SQL'in en sık kullandığın parçası.
-- Basit eşitlik
SELECT * FROM musteriler WHERE sehir = 'İzmir';
-- Sayısal karşılaştırma
SELECT * FROM siparisler WHERE toplam > 500;
-- Birden fazla koşul
SELECT * FROM musteriler
WHERE sehir = 'İzmir' AND yas > 25;
-- OR kullanımı
SELECT * FROM musteriler
WHERE sehir = 'İzmir' OR sehir = 'İstanbul';
-- IN ile çoklu değer
SELECT * FROM musteriler
WHERE sehir IN ('İzmir', 'İstanbul', 'Ankara');
-- BETWEEN ile aralık
SELECT * FROM siparisler
WHERE siparis_tarihi BETWEEN '2024-01-01' AND '2024-12-31';
-- LIKE ile metin arama
SELECT * FROM musteriler WHERE email LIKE '%@gmail.com';
SELECT * FROM urunler WHERE urun_adi LIKE 'iPhone%';
-- NULL kontrolü
SELECT * FROM musteriler WHERE telefon IS NULL;
SELECT * FROM musteriler WHERE telefon IS NOT NULL;3. ORDER BY ve LIMIT: sıralama ve kısıtlama
-- Büyükten küçüğe sırala SELECT * FROM siparisler ORDER BY toplam DESC; -- Küçükten büyüğe SELECT * FROM urunler ORDER BY fiyat ASC; -- Birden fazla sütuna göre sırala SELECT * FROM musteriler ORDER BY sehir ASC, soyad ASC; -- İlk 10 satırı getir SELECT * FROM siparisler ORDER BY siparis_tarihi DESC LIMIT 10; -- En pahalı 5 ürün SELECT urun_adi, fiyat FROM urunler ORDER BY fiyat DESC LIMIT 5;
4. GROUP BY ve aggregate fonksiyonlar
Veri analistinin günlük ekmeği. Gruplama ve özet hesaplama olmadan anlamlı analiz yapılamaz.
-- Temel aggregate fonksiyonlar SELECT COUNT(*) AS toplam_musteri, COUNT(telefon) AS telefonlu_musteri, -- NULL'ları saymaz AVG(yas) AS ortalama_yas, MIN(yas) AS en_genc, MAX(yas) AS en_yasli, SUM(toplam) AS toplam_ciro FROM musteriler; -- Şehre göre grupla SELECT sehir, COUNT(*) AS musteri_sayisi, AVG(yas) AS ort_yas FROM musteriler GROUP BY sehir ORDER BY musteri_sayisi DESC; -- Ürün kategorisine göre özet SELECT kategori, COUNT(*) AS urun_sayisi, AVG(fiyat) AS ort_fiyat, SUM(stok_adedi) AS toplam_stok FROM urunler GROUP BY kategori ORDER BY ort_fiyat DESC;
5. HAVING: gruplama sonrası filtreleme
WHERE satırları filtreler, HAVING ise GROUP BY sonrası grupları filtreler. Karıştırmak çok yaygın bir hata.
-- En az 10 müşterisi olan şehirler SELECT sehir, COUNT(*) AS musteri_sayisi FROM musteriler GROUP BY sehir HAVING COUNT(*) >= 10 ORDER BY musteri_sayisi DESC; -- Ortalama sipariş tutarı 200 TL'den yüksek müşteriler SELECT musteri_id, COUNT(*) AS siparis_sayisi, AVG(toplam) AS ort_siparis FROM siparisler GROUP BY musteri_id HAVING AVG(toplam) > 200 ORDER BY ort_siparis DESC; -- WHERE ve HAVING birlikte SELECT sehir, COUNT(*) AS musteri_sayisi FROM musteriler WHERE yas > 18 -- önce satırları filtrele GROUP BY sehir HAVING COUNT(*) >= 5 -- sonra grupları filtrele ORDER BY musteri_sayisi DESC;
SQL çalışma sırası: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT. Bu sırayı bilmek hataları önler. SELECT'te tanımladığın alias'ı WHERE'de kullanamazsın çünkü WHERE, SELECT'ten önce çalışır.
6. JOIN: tabloları birleştirme
İlişkisel veritabanlarının özü. Gerçek dünya verisi her zaman birden fazla tabloya dağılmıştır.
-- INNER JOIN: her iki tabloda da eşleşen kayıtlar SELECT m.ad, m.soyad, s.siparis_tarihi, s.toplam FROM musteriler m INNER JOIN siparisler s ON m.id = s.musteri_id; -- LEFT JOIN: sol tablonun tüm kayıtları + eşleşenler -- Hiç sipariş vermemiş müşterileri de gösterir SELECT m.ad, m.soyad, COUNT(s.id) AS siparis_sayisi FROM musteriler m LEFT JOIN siparisler s ON m.id = s.musteri_id GROUP BY m.id, m.ad, m.soyad ORDER BY siparis_sayisi DESC; -- Üç tabloyu birleştirme SELECT m.ad AS musteri, u.urun_adi, sd.adet, sd.birim_fiyat FROM musteriler m INNER JOIN siparisler s ON m.id = s.musteri_id INNER JOIN siparis_detay sd ON s.id = sd.siparis_id INNER JOIN urunler u ON sd.urun_id = u.id WHERE s.siparis_tarihi >= '2024-01-01';
7. Subquery ve CTE: karmaşık sorgular
Gerçek analizlerde tek sorguda iş bitmez. Subquery ve CTE (Common Table Expression) ile sorguları parçalara böleriz.
-- Subquery: ortalama üzerindeki siparişler
SELECT *
FROM siparisler
WHERE toplam > (SELECT AVG(toplam) FROM siparisler);
-- CTE ile okunabilir sorgu (WITH ifadesi)
WITH aylik_ciro AS (
SELECT
DATE_TRUNC('month', siparis_tarihi) AS ay,
SUM(toplam) AS ciro
FROM siparisler
WHERE siparis_tarihi >= '2024-01-01'
GROUP BY DATE_TRUNC('month', siparis_tarihi)
),
buyume AS (
SELECT
ay,
ciro,
LAG(ciro) OVER (ORDER BY ay) AS onceki_ay_ciro
FROM aylik_ciro
)
SELECT
ay,
ciro,
onceki_ay_ciro,
ROUND((ciro - onceki_ay_ciro) / onceki_ay_ciro * 100, 1) AS buyume_orani
FROM buyume
ORDER BY ay;8. Window fonksiyonlar: analistin süper gücü
Window fonksiyonlar GROUP BY gibi değil — satırları gruplamaz, her satıra gruba ait bir hesaplama ekler. Sıralama, hareketli ortalama, büyüme oranı gibi analizler için vazgeçilmez.
-- ROW_NUMBER: sıra numarası
SELECT
urun_adi,
fiyat,
ROW_NUMBER() OVER (ORDER BY fiyat DESC) AS sira
FROM urunler;
-- RANK: aynı değere aynı sıra (atlama var)
-- DENSE_RANK: aynı değere aynı sıra (atlama yok)
-- Her kategoride en pahalı ürün
SELECT *
FROM (
SELECT
urun_adi,
kategori,
fiyat,
ROW_NUMBER() OVER (PARTITION BY kategori ORDER BY fiyat DESC) AS sira
FROM urunler
) ranked
WHERE sira = 1;
-- Hareketli ortalama (7 günlük)
SELECT
tarih,
gunluk_ciro,
AVG(gunluk_ciro) OVER (
ORDER BY tarih
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS hareketli_7gun_ort
FROM gunluk_satis;
-- Kümülatif toplam
SELECT
tarih,
gunluk_ciro,
SUM(gunluk_ciro) OVER (ORDER BY tarih) AS kumulatif_ciro
FROM gunluk_satis;
-- Bir önceki aya göre değişim
SELECT
ay,
ciro,
LAG(ciro, 1) OVER (ORDER BY ay) AS onceki_ay,
ciro - LAG(ciro, 1) OVER (ORDER BY ay) AS fark
FROM aylik_ciro;Pratik ipuçları
- Önce küçük veri ile test et. LIMIT 100 ekle, sorgu doğru mu kontrol et, sonra kaldır. Yanlış sorgu milyonlarca satırda çalışırsa hem yavaş hem tehlikeli.
- SELECT * kullanma. Sadece ihtiyacın olan sütunları çek. Hem hızlı hem okunabilir olur. Veri ambarlarında maliyet de düşer.
- Yorumları ihmal etme. SQL'de yorum satırı:
-- bu bir yorum. Karmaşık sorgularda ne yaptığını yaz. 3 ay sonra tekrar açtığında anlarsın. - JOIN öncesi COUNT ile kontrol et. JOIN beklediğinden fazla satır üretebilir (çarpım etkisi). Her iki tablonun satır sayısını önceden bil.
- NULL'a dikkat et.NULL = NULL yazmaz, her zaman IS NULL kullan. AVG, SUM gibi fonksiyonlar NULL'ları otomatik atlar — bu bazen hata yaratır.
- Index'leri anla.WHERE ve JOIN'de kullandığın sütunlarda index varsa sorgu çok daha hızlı çalışır. Yavaş sorgu gördüğünde önce EXPLAIN ANALYZE çalıştır.
Hangi veritabanını öğreneyim?
SQL standart bir dil ama her veritabanının küçük farkları var. Başlangıç için önerim:
- PostgreSQL: Açık kaynak, güçlü, modern. Öğrenmek için ideal, production'da da yaygın.
- MySQL/MariaDB: Web uygulamalarında en yaygın. Öğrenmesi kolay.
- SQLite: Dosya tabanlı, kurulum yok. Python ile gelen SQLite modülüyle hemen başlayabilirsin.
- BigQuery / Snowflake: Büyük veri için bulut çözümleri. Standart SQL konuşuyorlar.
- DuckDB: Analitik sorgular için yeni nesil, Python ile entegrasyonu mükemmel.
Sıradaki yazıda SQL ile veri temizleme: gerçek hayat verisindeki tutarsızlıkları, tekrarları ve NULL'ları SQL ile nasıl temizlersin?