Giriş
Modern iş dünyasında veri, en değerli varlık haline gelmiştir. Ancak ham veri, işlenip anlamlı bir bilgiye dönüştürülmediği sürece tek başına bir değer ifade etmez. Şirketlerin stratejik kararlar alabilmesi, finansal durumlarını analiz edebilmesi ve kullanıcı davranışlarını anlamlandırabilmesi için gelişmiş veri analizine ihtiyacı vardır. Tam bu noktada, Sql & Veritabanı İle Karmaşık Veri Raporlama Nasıl Yapılır? sorusu, veri analistleri, arka uç (backend) geliştiricileri ve veritabanı yöneticileri için kritik bir önem kazanmaktadır.
Basit SELECT ve WHERE sorguları günlük operasyonel işleri çözmeye yetse de, yönetim kurullarına veya iş birimlerine sunulacak haftalık, aylık veya yıllık performans raporları çok daha gelişmiş teknikler gerektirir. Bu rehberde, ilişkisel veritabanı yönetim sistemlerinde (RDBMS) karmaşık veri raporlama süreçlerini adım adım ele alacağız. Performans kaybı yaşamadan, okunabilir ve sürdürülebilir SQL sorguları yazmanın tüm inceliklerini öğreneceksiniz.
Bu makale boyunca; Ortak Tablo İfadelerinden (CTE), Pencere Fonksiyonlarına (Window Functions), veri pivotlama tekniklerinden, gelişmiş gruplama (ROLLUP, CUBE) yöntemlerine ve performans optimizasyonuna kadar geniş bir yelpazeyi pratik, çalışır kod örnekleriyle inceleyeceğiz. Hazırsanız, veritabanınızın gücünü sonuna kadar kullanacağımız teknik yolculuğumuza başlayalım.
Gereksinimler ve Ön Hazırlık
Bu eğitimdeki teknikleri ve SQL sorgularını kendi bilgisayarınızda veya test ortamınızda deneyebilmeniz için aşağıdaki bileşenlerin hazır olması gerekmektedir:
- RDBMS (Veritabanı Yönetim Sistemi): Tercihen modern SQL standartlarını tam olarak destekleyen PostgreSQL (Sürüm 13 ve üzeri), MS SQL Server (2019 ve üzeri) veya MySQL (Sürüm 8.0 ve üzeri).
- Veritabanı İstemcisi: Sorguları çalıştırmak için pgAdmin, DBeaver, DataGrip veya SQL Server Management Studio (SSMS) gibi bir arayüz aracı.
- Temel SQL Bilgisi: Temel
JOIN,GROUP BYve agregasyon (toplama) fonksiyonlarına (SUM,AVG,COUNT) aşinalık.
Raporlama sorgularımızı gerçekçi bir senaryo üzerinden test etmek amacıyla, bir e-ticaret platformunun veritabanı yapısını simüle edeceğiz. İlk adım olarak bu yapıyı kurup örnek verilerimizi yükleyelim.
Adım 1: Raporlama Veri Modelinin Hazırlanması ve Mock Veri Seti
Karmaşık raporlama sorgularını test edebilmek için ilişkisel bir veri modeline ihtiyacımız vardır. Bu adımda, müşterileri, siparişleri, sipariş kalemlerini ve ürün kategorilerini içeren dört tablolu bir şema oluşturacağız. Ardından, analizlerimizde kullanmak üzere bu tablolara anlamlı test verileri ekleyeceğiz.
Aşağıdaki SQL kodu, raporlama yapacağımız tabloları oluşturur ve aralarındaki yabancı anahtar (Foreign Key) ilişkilerini tanımlar. Bu şema, ANSI SQL standartlarına uygun olarak hazırlanmıştır.
-- 1. Tablo: Müşteriler (users)
CREATE TABLE users (
user_id SERIAL PRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
city VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 2. Tablo: Ürün Kategorileri (categories)
CREATE TABLE categories (
category_id SERIAL PRIMARY KEY,
category_name VARCHAR(100) NOT NULL
);
-- 3. Tablo: Ürünler (products)
CREATE TABLE products (
product_id SERIAL PRIMARY KEY,
product_name VARCHAR(150) NOT NULL,
category_id INT REFERENCES categories(category_id),
price DECIMAL(10, 2) NOT NULL,
stock_quantity INT NOT NULL
);
-- 4. Tablo: Siparişler (orders)
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(user_id),
order_date TIMESTAMP NOT NULL,
status VARCHAR(20) DEFAULT 'Completed'
);
-- 5. Tablo: Sipariş Detayları (order_items)
CREATE TABLE order_items (
item_id SERIAL PRIMARY KEY,
order_id INT REFERENCES orders(order_id) ON DELETE CASCADE,
product_id INT REFERENCES products(product_id),
quantity INT NOT NULL,
unit_price DECIMAL(10, 2) NOT NULL
);
Yukarıdaki şema oluşturulduktan sonra, analizlerimizin anlamlı sonuçlar üretebilmesi için tablolara gerçekçi test verileri eklememiz gerekir. Aşağıdaki SQL bloğu ile tablolarımızı dolduruyoruz:
-- Kategorilerin Eklenmesi
INSERT INTO categories (category_name) VALUES
('Elektronik'), ('Kitap'), ('Giyim'), ('Ev & Yaşam');
-- Ürünlerin Eklenmesi
INSERT INTO products (product_name, category_id, price, stock_quantity) VALUES
('Akıllı Telefon', 1, 15000.00, 50),
('Kablosuz Kulaklık', 1, 2500.00, 120),
('SQL Öğreniyorum Kitabı', 2, 150.00, 300),
('Algoritmalar Kitabı', 2, 220.00, 150),
('Pamuklu Tişört', 3, 450.00, 500),
('Kışlık Mont', 3, 2800.00, 80),
('Kahve Makinesi', 4, 3500.00, 40);
-- Kullanıcıların Eklenmesi
INSERT INTO users (first_name, last_name, email, city) VALUES
('Ahmet', 'Yılmaz', 'ahmet.yilmaz@example.com', 'İstanbul'),
('Mehmet', 'Kaya', 'mehmet.kaya@example.com', 'Ankara'),
('Ayşe', 'Demir', 'ayse.demir@example.com', 'İzmir'),
('Fatma', 'Çelik', 'fatma.celik@example.com', 'Bursa'),
('Can', 'Öztürk', 'can.ozturk@example.com', 'İstanbul');
-- Siparişlerin Eklenmesi (Farklı Tarihlerde)
INSERT INTO orders (user_id, order_date, status) VALUES
(1, '2026-01-10 10:30:00', 'Completed'),
(2, '2026-01-15 14:20:00', 'Completed'),
(3, '2026-02-01 09:15:00', 'Completed'),
(1, '2026-02-12 16:45:00', 'Completed'),
(4, '2026-02-20 11:00:00', 'Completed'),
(5, '2026-03-05 13:10:00', 'Completed'),
(2, '2026-03-18 15:30:00', 'Completed'),
(3, '2026-03-22 18:00:00', 'Completed');
-- Sipariş Detaylarının Eklenmesi
INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES
(1, 1, 1, 15000.00), -- Ahmet: Akıllı Telefon
(1, 3, 2, 150.00), -- Ahmet: SQL Kitabı (2 adet)
(2, 2, 1, 2500.00), -- Mehmet: Kulaklık
(2, 5, 3, 450.00), -- Mehmet: Tişört (3 adet)
(3, 6, 1, 2800.00), -- Ayşe: Mont
(4, 2, 2, 2500.00), -- Ahmet: Kulaklık (2 adet)
(5, 7, 1, 3500.00), -- Fatma: Kahve Makinesi
(5, 4, 1, 220.00), -- Fatma: Algoritmalar Kitabı
(6, 1, 1, 15000.00), -- Can: Akıllı Telefon
(7, 3, 1, 150.00), -- Mehmet: SQL Kitabı
(8, 5, 2, 450.00); -- Ayşe: Tişört (2 adet)
Veritabanı şemamız ve test verilerimiz hazır olduğuna göre, artık karmaşık raporlama tekniklerini adım adım uygulamaya başlayabiliriz.
Adım 2: CTE (Common Table Expressions) ile Okunabilir Raporlar Tasarlama
Karmaşık raporlama sorgularında karşılaşılan en büyük sorunlardan biri, iç içe geçmiş alt sorguların (subqueries) kodun okunabilirliğini ve bakımını zorlaştırmasıdır. CTE (Common Table Expressions - Ortak Tablo İfadeleri), geçici sonuç kümeleri oluşturarak sorguları mantıksal parçalara ayırmamızı sağlar. Bu yöntem, SQL kodunu adeta bir programlama dilindeki değişken tanımlamaları gibi yukarıdan aşağıya okunabilir kılar.
Aşağıdaki örnek kodda, her bir müşterinin toplam harcamasını hesaplayacağız ve ardından bu veriyi kullanarak müşterileri harcama miktarlarına göre "VIP", "Düzenli" ve "Yeni/Düşük" olarak segmentlere ayıran bir rapor hazırlayacağız.
WITH CustomerSpend AS (
-- Her müşterinin toplam harcamasını hesaplayan ilk CTE
SELECT
u.user_id,
CONCAT(u.first_name, ' ', u.last_name) AS customer_name,
u.city,
COUNT(DISTINCT o.order_id) AS total_orders,
SUM(oi.quantity * oi.unit_price) AS total_spent
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
LEFT JOIN order_items oi ON o.order_id = oi.order_id
GROUP BY u.user_id, u.first_name, u.last_name, u.city
),
AverageSpend AS (
-- Genel ortalama harcamayı hesaplayan ikinci CTE
SELECT AVG(total_spent) AS general_average FROM CustomerSpend WHERE total_orders > 0
)
-- Ana sorgu: Segmentasyon ve nihai raporlama
SELECT
cs.customer_name,
cs.city,
cs.total_orders,
COALESCE(cs.total_spent, 0) AS total_spent,
ROUND(COALESCE(cs.total_spent, 0) / (SELECT general_average FROM AverageSpend), 2) AS spend_ratio_to_avg,
CASE
WHEN cs.total_spent >= 10000 THEN 'VIP Müşteri'
WHEN cs.total_spent >= 2000 AND cs.total_spent < 10000 THEN 'Düzenli Müşteri'
ELSE 'Standart Müşteri'
END AS customer_segment
FROM CustomerSpend cs
ORDER BY total_spent DESC;
Sorgunun Çalışma Mantığı: İlk olarak CustomerSpend adında bir CTE tanımladık. Bu geçici tablo, kullanıcı bilgilerini ve toplam harcamalarını hesaplar. İkinci CTE olan AverageSpend ise aktif müşterilerin genel harcama ortalamasını bulur. Ana sorguda bu iki yapıyı birleştirerek, her müşterinin ortalamaya göre oranını (spend_ratio_to_avg) hesapladık ve CASE WHEN yapısı ile dinamik segmentasyon gerçekleştirdik.
Adım 3: Window Functions (Pencere Fonksiyonları) ile Trend Analizi Nasıl Yapılır?
Pencere Fonksiyonları (Window Functions), satırları tek bir grup haline getirmeden (yani GROUP BY yapıp satır sayısını azaltmadan), mevcut satırların etrafındaki bir "pencereye" göre hesaplama yapmamızı sağlar. Bu fonksiyonlar özellikle finansal raporlarda, kümülatif toplam (running total), hareketli ortalama (moving average) ve bir önceki dönemle karşılaştırma (trend analizi) yaparken hayat kurtarır.
Aşağıdaki örnek kodda, siparişlerin tarihlerine göre günlük ciroları hesaplayacağız ve ardından pencere fonksiyonlarını kullanarak kümülatif (yığılmalı) ciro artışını ve bir önceki sipariş gününe göre büyüme oranını analiz edeceğiz.
WITH DailySales AS (
-- Günlük toplam satışları hesaplayan CTE
SELECT
DATE(o.order_date) AS sales_date,
SUM(oi.quantity * oi.unit_price) AS daily_revenue
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
GROUP BY DATE(o.order_date)
)
SELECT
sales_date,
daily_revenue,
-- Kümülatif Toplam (Running Total) hesaplama
SUM(daily_revenue) OVER (
ORDER BY sales_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_revenue,
-- Bir önceki günün cirosunu getirme (LAG fonksiyonu)
LAG(daily_revenue, 1) OVER (ORDER BY sales_date) AS previous_day_revenue,
-- Günlük büyüme yüzdesi hesaplama
ROUND(
((daily_revenue - LAG(daily_revenue, 1) OVER (ORDER BY sales_date)) /
LAG(daily_revenue, 1) OVER (ORDER BY sales_date)) * 100, 2
) AS daily_growth_percentage
FROM DailySales
ORDER BY sales_date;
Sorgunun Çalışma Mantığı: SUM(daily_revenue) OVER (...) ifadesi, ilk günden itibaren mevcut güne kadar olan tüm ciroları toplayarak kümülatif toplamı üretir. LAG(daily_revenue, 1) fonksiyonu ise bir önceki satırdaki (gündeki) ciro değerine erişmemizi sağlar. Bu sayede, alt sorgularla veya karmaşık join işlemleriyle uğraşmadan, tek bir taramada (scan) trend analizini gerçekleştirmiş oluruz.
Kritik Performans Uyarısı: Pencere fonksiyonlarında kullanılanORDER BYifadesi, veritabanı motorunun veriyi bellekte sıralamasını gerektirir. Büyük veri kümelerinde bu işlemin performansını artırmak için sıralama yapılan sütunlarda (örneğinorder_date) mutlaka uygun indekslerin (B-Tree Index) tanımlanmış olması gerekir.
Adım 4: SQL Pivot ve CASE WHEN ile Çapraz Tablo (Cross-Tab) Raporlama
İş birimleri ve yöneticiler, verileri dikey
Büyük Veri Kümelerinde Raporlama Performansını Optimize Etme Teknikleri
Karmaşık raporlama sorguları, milyonlarca satırdan oluşan tablolarda çalıştırıldığında ciddi performans darboğazlarına yol açabilir. Canlı (OLTP) veritabanı üzerinde doğrudan çalıştırılan ağır raporlar, kilitlenmelere (deadlock) ve kullanıcı deneyiminin yavaşlamasına neden olur. Bu durumun önüne geçmek için uygulanabilecek en etkili optimizasyon stratejileri şunlardır:
1. Materialized View (Maddileştirilmiş Görünümler) Kullanımı
Sırad


Yorumlar (0)
Yorum Yaz