Sz
Sıfır Gecikme
veri bilimi · türkçe
Tümüİnteraktif📖Rehber🛠Araç📊Vaka💼Kariyer🐍Playground📚Öğren
Hakkımda
Ana sayfarehber

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ı

Hangi veritabanını öğreneyim?

SQL standart bir dil ama her veritabanının küçük farkları var. Başlangıç için önerim:

Sıradaki yazıda SQL ile veri temizleme: gerçek hayat verisindeki tutarsızlıkları, tekrarları ve NULL'ları SQL ile nasıl temizlersin?

💬 Yorumlar