← Kursa Dön
📄 Text · 35 min

Full-Text Search, Pivot ve Best Practices

Giriş — SQL Yolculuğunun Son Durağı

Bu, SQL kursumuzun son dersi. Burada üç konuyu ele alacağız: veritabanı içinde metin araması yapma (Full-Text Search), satırları sütunlara veya sütunları satırlara çevirme (Pivot/Unpivot) ve kursun tamamını bir araya getiren genel best practices. Bu dersin sonunda, SQL bilgin "sorgu yazabilen" seviyesinden "veritabanını bilen" seviyesine çıkmış olacak.


Full-Text Search — Metin İçinde Arama

LIKE'ın Limitleri

Şimdiye kadar metin araması için LIKE kullandık:

-- ❌ LIKE ile arama — sınırlı ve yavaş
SELECT * FROM products WHERE product_name LIKE '%laptop%';
-- Sorunlar:
-- 1. %...% index kullanamaz → full table scan
-- 2. "laptoplar" veya "Laptop" bulamayabilir (collation'a bağlı)
-- 3. Benzer kelimeleri bulamaz ("dizüstü bilgisayar" da laptop'tur)
-- 4. Sonuçları alaka sırasına göre sıralayamaz

Full-Text Search, bu sorunların hepsini çözer: kelime bazlı arama, alaka sıralama, doğal dil işleme ve yüksek performans.

FULLTEXT Index Oluşturma

-- FULLTEXT index ekle
ALTER TABLE products ADD FULLTEXT INDEX ft_product_name (product_name);

-- Birden fazla sütunda
ALTER TABLE products ADD FULLTEXT INDEX ft_product_search (product_name, description);

-- Tablo oluştururken
CREATE TABLE articles (
    article_id INT PRIMARY KEY AUTO_INCREMENT,
    title VARCHAR(200),
    content TEXT,
    FULLTEXT INDEX ft_articles (title, content)
) ENGINE=InnoDB;

MATCH ... AGAINST — Temel Arama

-- Doğal dil modunda arama (varsayılan)
SELECT product_name, price,
       MATCH(product_name) AGAINST('laptop bilgisayar') AS relevance
FROM products
WHERE MATCH(product_name) AGAINST('laptop bilgisayar');
+----------------------------+-------+-----------+
| product_name               | price | relevance |
+----------------------------+-------+-----------+
| Laptop Bilgisayar Pro      | 25000 |    2.5432 |  ← En alakalı
| Gaming Laptop              | 35000 |    1.2341 |
| Taşınabilir Bilgisayar     | 18000 |    0.8765 |
+----------------------------+-------+-----------+

MATCH ... AGAINST otomatik olarak alaka puanı (relevance score) hesaplar ve sonuçları en alakalıdan başlayarak sıralar. LIKE ile bu mümkün değil.

Boolean Modda Arama

-- Boolean mod: gelişmiş arama operatörleri
SELECT product_name FROM products
WHERE MATCH(product_name, description) AGAINST(
    '+laptop -gaming +ssd' IN BOOLEAN MODE
);
-- +laptop: "laptop" kesinlikle olmalı
-- -gaming: "gaming" olmamalı
-- +ssd: "ssd" kesinlikle olmalı

-- Daha fazla operatör:
-- +kelime: Olmalı (zorunlu)
-- -kelime: Olmamalı (hariç)
-- kelime*: Prefix eşleşme (laptop* → laptoplar, laptopçu)
-- "tam ifade": Tam ifade eşleşme
-- >kelime: Bu kelimenin alaka puanını artır
-- <kelime: Bu kelimenin alaka puanını düşür
-- Örnek: Tam ifade araması
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('"SQL injection" +güvenlik' IN BOOLEAN MODE);
-- "SQL injection" tam ifade olarak geçmeli VE "güvenlik" kelimesi olmalı

-- Prefix araması
SELECT * FROM products
WHERE MATCH(product_name) AGAINST('lap*' IN BOOLEAN MODE);
-- laptop, laptoplar, lapel... hepsi eşleşir

Full-Text Search Kısıtlamaları

-- Minimum kelime uzunluğu: varsayılan 3 (InnoDB) veya 4 (MyISAM)
-- "PC" veya "SSD" gibi kısa kelimeler varsayılanda bulunamaz!
SHOW VARIABLES LIKE 'innodb_ft_min_token_size';
-- innodb_ft_min_token_size = 3

-- Değiştirmek için (my.cnf):
-- innodb_ft_min_token_size = 2
-- Sonra FULLTEXT index'i yeniden oluştur

-- Stopword'ler: "ve", "ile", "bir" gibi yaygın kelimeler aranmaz
SHOW VARIABLES LIKE 'innodb_ft_enable_stopword';

💡 İpucu: Türkçe stopword listesini özelleştirmek istiyorsan, innodb_ft_user_stopword_table ile kendi tablonu tanımlayabilirsin. Varsayılan İngilizce stopword'ler Türkçe aramalarda sorun yaratmaz ama "bir", "bu", "şu" gibi Türkçe kelimeler gereksiz sonuçlara neden olabilir.


Pivot — Satırları Sütunlara Çevirme

Pivot, verileri çapraz tablo (cross-tabulation) formatına dönüştürür. Excel'deki Pivot Table'ın SQL karşılığı.

Senaryo: Aylık Satışları Sütun Olarak Gösterme

-- ❌ Normal sorgu: her ay ayrı satır
SELECT 
    c.category_name,
    DATE_FORMAT(o.order_date, '%Y-%m') AS month,
    SUM(oi.quantity * oi.unit_price) AS revenue
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
JOIN categories c ON p.category_id = c.category_id
WHERE YEAR(o.order_date) = 2024
GROUP BY c.category_name, DATE_FORMAT(o.order_date, '%Y-%m');
+---------------+---------+---------+
| category_name | month   | revenue |
+---------------+---------+---------+
| Elektronik    | 2024-01 |  125000 |
| Elektronik    | 2024-02 |  148000 |
| Kitap         | 2024-01 |   15000 |
| Kitap         | 2024-02 |   18000 |
+---------------+---------+---------+
-- ✅ Pivot: aylar sütun olarak
SELECT 
    c.category_name,
    SUM(CASE WHEN MONTH(o.order_date) = 1 THEN oi.quantity * oi.unit_price ELSE 0 END) AS Ocak,
    SUM(CASE WHEN MONTH(o.order_date) = 2 THEN oi.quantity * oi.unit_price ELSE 0 END) AS Subat,
    SUM(CASE WHEN MONTH(o.order_date) = 3 THEN oi.quantity * oi.unit_price ELSE 0 END) AS Mart,
    SUM(CASE WHEN MONTH(o.order_date) = 4 THEN oi.quantity * oi.unit_price ELSE 0 END) AS Nisan,
    SUM(CASE WHEN MONTH(o.order_date) = 5 THEN oi.quantity * oi.unit_price ELSE 0 END) AS Mayis,
    SUM(CASE WHEN MONTH(o.order_date) = 6 THEN oi.quantity * oi.unit_price ELSE 0 END) AS Haziran
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
JOIN categories c ON p.category_id = c.category_id
WHERE YEAR(o.order_date) = 2024
GROUP BY c.category_name;
+---------------+--------+--------+--------+--------+--------+---------+
| category_name | Ocak   | Subat  | Mart   | Nisan  | Mayis  | Haziran |
+---------------+--------+--------+--------+--------+--------+---------+
| Elektronik    | 125000 | 148000 | 162000 | 155000 | 178000 |  190000 |
| Kitap         |  15000 |  18000 |  22000 |  19000 |  25000 |   21000 |
| Giyim         |  45000 |  38000 |  52000 |  48000 |  55000 |   62000 |
+---------------+--------+--------+--------+--------+--------+---------+

Bu, raporlamada en çok kullanılan tekniklerden biri. CASE WHEN + SUM kalıbıyla her "kategori değeri" bir sütuna dönüştürülür.

Sipariş Durumlarının Pivot Tablosu

-- Her müşterinin sipariş durumlarını sütunlar olarak göster
SELECT 
    c.first_name,
    c.last_name,
    COUNT(CASE WHEN o.status = 'pending' THEN 1 END) AS pending,
    COUNT(CASE WHEN o.status = 'processing' THEN 1 END) AS processing,
    COUNT(CASE WHEN o.status = 'shipped' THEN 1 END) AS shipped,
    COUNT(CASE WHEN o.status = 'completed' THEN 1 END) AS completed,
    COUNT(CASE WHEN o.status = 'cancelled' THEN 1 END) AS cancelled,
    COUNT(*) AS total
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.first_name, c.last_name
HAVING total > 0
ORDER BY total DESC;
+------------+-----------+---------+------------+---------+-----------+-----------+-------+
| first_name | last_name | pending | processing | shipped | completed | cancelled | total |
+------------+-----------+---------+------------+---------+-----------+-----------+-------+
| Ali        | Yılmaz    |       2 |          1 |       3 |        12 |         1 |    19 |
| Zeynep     | Demir     |       1 |          0 |       2 |         8 |         0 |    11 |
+------------+-----------+---------+------------+---------+-----------+-----------+-------+

Unpivot — Sütunları Satırlara Çevirme

Pivot'un tersi: sütunlardaki verileri satırlara dönüştürme.

-- Çeyreklik satış tablosu (sütun formatında)
-- quarterly_sales: product_id, q1_sales, q2_sales, q3_sales, q4_sales

-- Unpivot: sütunları satırlara çevir
SELECT product_id, 'Q1' AS quarter, q1_sales AS sales FROM quarterly_sales
UNION ALL
SELECT product_id, 'Q2', q2_sales FROM quarterly_sales
UNION ALL
SELECT product_id, 'Q3', q3_sales FROM quarterly_sales
UNION ALL
SELECT product_id, 'Q4', q4_sales FROM quarterly_sales
ORDER BY product_id, quarter;
+------------+---------+--------+
| product_id | quarter | sales  |
+------------+---------+--------+
|          1 | Q1      | 125000 |
|          1 | Q2      | 148000 |
|          1 | Q3      | 162000 |
|          1 | Q4      | 155000 |
|          2 | Q1      |  15000 |
|          2 | Q2      |  18000 |
+------------+---------+--------+

MySQL'de doğrudan UNPIVOT syntax'ı yok — UNION ALL ile çözülür. PostgreSQL'de de benzer durum geçerli (UNNEST veya CROSS JOIN LATERAL kullanılabilir).


Genel SQL Best Practices — Kursun Özeti

Veritabanı Tasarımı

  1. Normalizasyon uygula ama aşırıya kaçma — 3NF genellikle yeterli. Performans gerektiren durumlarda kontrollü denormalizasyon yap.

  2. Uygun veri tipleri seç — INT yerine TINYINT yetiyorsa onu kullan. VARCHAR(255) yerine gerçek maksimum uzunluğu belirle. Tarihler için DATE/DATETIME kullan, VARCHAR'da tarih saklama.

  3. Her tabloda PRIMARY KEY olsun — AUTO_INCREMENT INT genellikle en iyi seçim. UUID kullanıyorsan, binary(16) formatında sakla.

  4. Foreign key constraint'leri kullan — Veri bütünlüğünü veritabanı seviyesinde garanti et. ON DELETE CASCADE veya SET NULL stratejilerini bilinçli seç.

  5. NOT NULL tercih et — NULL karmaşıklık ekler. Gerçekten "bilinmiyor" anlamı taşımıyorsa, DEFAULT değer kullan.

Sorgu Yazımı

  1. SELECT * kullanma — Sadece ihtiyacın olan sütunları seç. Covering index'ten faydalan.

  2. WHERE koşullarını sargable yaz — Sütuna fonksiyon uygulama: WHERE YEAR(date) = 2024 yerine WHERE date >= '2024-01-01' AND date < '2025-01-01'.

  3. JOIN türünü bilinçli seç — INNER JOIN mi LEFT JOIN mi? Sonuç kümesini etkiler. Gereksiz LEFT JOIN kullanma.

  4. N+1 probleminden kaçın — Döngüde sorgu çalıştırma. JOIN veya eager loading kullan.

  5. LIMIT kullan — Gereksiz veri çekme. Büyük sayfalamada keyset pagination tercih et.

Index Stratejisi

  1. WHERE, JOIN, ORDER BY sütunlarında index — En sık kullanılan sorgulara göre index planla.

  2. Composite index'te sütun sırası önemli — Eşitlik koşulları önce, aralık koşulları sonra. Leftmost prefix kuralını unutma.

  3. Her index bir maliyet — Yazma performansını düşürür. Kullanılmayan index'leri periyodik olarak temizle.

  4. EXPLAIN kullan — Sorguyu yazdıktan sonra EXPLAIN ile kontrol et. type: ALL gördüğünde dur ve düzelt.

Performans

  1. Transaction'ları kısa tut — Uzun transaction = uzun kilit = yavaş sistem. Sadece gerekli işlemleri transaction içinde yap.

  2. Batch işlemler kullan — Toplu INSERT, UPDATE, DELETE için parçalara böl. Tek seferde milyonlarca satır işleme.

  3. Query cache yerine uygulama cache — Redis, Memcached gibi çözümlerle sık okunan verileri cache'le.

  4. İstatistikleri güncel tutANALYZE TABLE ile optimizer'ın doğru karar vermesini sağla.

Güvenlik

  1. Prepared statements kullan — SQL injection'a karşı en etkili koruma. Kullanıcı girdisini asla doğrudan sorguya ekleme.

  2. Minimum yetki prensibi — Uygulama kullanıcısına sadece gerekli yetkiler ver. Web uygulaması DROP TABLE yapabilmemeli.

  3. Hassas verileri maskele — View'lar ile hassas sütunları gizle. Şifreleri hash'le (bcrypt, argon2).


Kariyer Rehberi — SQL'den Sonra Ne Öğrenmeli?

SQL bilgini pekiştirdikten sonra hangi yönde ilerleyebilirsin?

Backend Developer Yolu

SQL Temelleri (✅ Bu kurs)
    ↓
ORM Kullanımı (Eloquent, SQLAlchemy, Hibernate)
    ↓
Veritabanı Migration & Seeding
    ↓
Caching Stratejileri (Redis, Memcached)
    ↓
Message Queues (RabbitMQ, Kafka)

Data Engineer / Analyst Yolu

SQL Temelleri (✅ Bu kurs)
    ↓
Advanced SQL (Window Functions, CTE ✅)
    ↓
ETL Araçları (Apache Airflow, dbt)
    ↓
Data Warehouse (BigQuery, Snowflake, Redshift)
    ↓
Veri Görselleştirme (Metabase, Tableau, PowerBI)

DBA (Database Administrator) Yolu

SQL Temelleri (✅ Bu kurs)
    ↓
Veritabanı Yönetimi (Backup, Restore, Monitoring)
    ↓
Performance Tuning (Slow Query, Index, Configuration)
    ↓
Replication & Clustering
    ↓
Cloud Veritabanları (AWS RDS, Google Cloud SQL, Azure SQL)

Önerilen Kaynaklar

  • Pratik: HackerRank SQL, LeetCode Database, SQLZoo

  • Kitaplar: "High Performance MySQL", "SQL Antipatterns"

  • Araçlar: DBeaver (ücretsiz SQL client), MySQL Workbench, pgAdmin

  • YouTube: Hussein Nasser (veritabanı mühendisliği)


Son Söz

Bu kursta veritabanı temellerinden başlayarak:

  1. B01-B02: Veritabanı kavramları, CRUD işlemleri, transaction temelleri

  2. B03-B04: SELECT sorguları, operatörler, fonksiyonlar

  3. B05-B06: GROUP BY, HAVING, JOIN türleri

  4. B07-B08: Subquery, UNION, geçici tablolar

  5. B09-B10: Veritabanı tasarımı, normalizasyon, index, performans

  6. B11: Window functions (ROW_NUMBER, LAG, LEAD, NTILE...)

  7. B12: CTE, views, stored procedures, triggers, transaction isolation, JSON

...konularını öğrendin. Bu bilgi seti, bir junior-mid seviye backend developer'ın ihtiyaç duyacağı SQL bilgisini fazlasıyla karşılıyor.

Ama unutma: SQL öğrenmek pratik gerektirir. Okuduğun her konuyu kendi veritabanında dene, gerçek projelerde kullan, hata yap ve hatalardan öğren. Bu kursun amacı sana bilgiyi vermekti — onu beceriye dönüştürmek senin elinde.

🎯 Son Analoji: SQL öğrenmek araba kullanmayı öğrenmek gibidir. Teoriyi bilmek (trafik kuralları, vites mantığı) gereklidir ama yeterli değildir. Gerçek trafikte, gerçek bir arabayla sürmeden usta olamazsın. Her gün bir sorgu yaz, her hafta bir optimizasyon yap, her ay bir yeni kavram öğren. 6 ay sonra geriye baktığında ne kadar ilerlediğini göreceksin.


Özet

  • Full-Text Search: LIKE'tan çok daha güçlü metin araması — MATCH AGAINST, boolean operatörler, alaka puanlama

  • Pivot: Satırları sütunlara çevirme — CASE WHEN + SUM/COUNT kalıbı

  • Unpivot: Sütunları satırlara çevirme — UNION ALL ile

  • Best practices: Tasarım (normalizasyon), sorgu (sargable WHERE), index (composite), performans (batch), güvenlik (prepared statements)

  • SQL bilgisi backend, data engineering ve DBA yollarında temel taştır

Sıkça Yapılan Hatalar

  1. Full-Text Search'ü küçük tablolarda kullanmak — 1000 satırlık tabloda LIKE yeterli. FULLTEXT index oluşturma ve bakım maliyeti vardır.

  2. Pivot sorgularında dinamik sütun beklemek — SQL'de sütun sayısı sorgu yazılırken bellidir. Dinamik pivot için stored procedure veya uygulama kodu gerek.

  3. Best practice'leri dogma olarak uygulamak — "SELECT * asla kullanma" kuralı keşif sorgularında (ad-hoc query) gereksiz. Kuralları bağlama göre uygula.

  4. Tek bir veritabanı motoruna bağımlı kalmak — MySQL öğrendin ama PostgreSQL, SQLite, SQL Server'ın da farkında ol. SQL'in %90'ı standarttır.

  5. Sadece teoriyle kalmak — Bu kursu bitirdin, tebrikler! Ama gerçek beceri, gerçek projelerde yazılan gerçek sorgularla gelir. Hemen bir proje başla.