Son gün! Profesyonel T-SQL geliştirmenin vazgeçilmez araçlarına bakıyoruz: stored procedure’ler, window functions ve performans.
1. Stored Procedures
Stored procedure, veritabanında saklanan, parametre alabilen, yeniden kullanılabilir SQL koddur.
Temel Yapı
CREATE PROCEDURE sp_GetEmployeesByDepartment
@Department NVARCHAR(50)
AS
BEGIN
SET NOCOUNT ON;
SELECT Name, Salary, HireDate
FROM Employees
WHERE Department = @Department
ORDER BY Salary DESC;
END;
Çağırma
EXEC sp_GetEmployeesByDepartment @Department = 'IT';
-- ya da kısa:
EXEC sp_GetEmployeesByDepartment 'IT';
Birden Fazla Parametre
CREATE PROCEDURE sp_GetEmployees
@Department NVARCHAR(50) = NULL, -- opsiyonel (default NULL)
@MinSalary DECIMAL(10, 2) = 0,
@MaxSalary DECIMAL(10, 2) = 999999
AS
BEGIN
SET NOCOUNT ON;
SELECT *
FROM Employees
WHERE (@Department IS NULL OR Department = @Department)
AND Salary BETWEEN @MinSalary AND @MaxSalary;
END;
EXEC sp_GetEmployees @MinSalary = 5000, @MaxSalary = 10000;
EXEC sp_GetEmployees @Department = 'IT';
OUTPUT Parametre
CREATE PROCEDURE sp_AddEmployee
@Name NVARCHAR(100),
@Department NVARCHAR(50),
@NewId INT OUTPUT
AS
BEGIN
INSERT INTO Employees (Name, Department)
VALUES (@Name, @Department);
SET @NewId = SCOPE_IDENTITY();
END;
DECLARE @Id INT;
EXEC sp_AddEmployee @Name = 'Ejder', @Department = 'IT', @NewId = @Id OUTPUT;
PRINT @Id;
Stored Procedure Avantajları
- Performans — Query plan cache’lenir, parametreyle yeniden kullanılır
- Güvenlik — Direct table erişimi yerine yetkilendirme
- Bakım — Kod merkezi yerde, application kodunda dağılmıyor
- Network — Sadece SP çağrısı gönderilir, sorgu metni değil
2. User-Defined Functions
Scalar Function — Tek Değer Döner
CREATE FUNCTION fn_GetEmployeeFullName (@FirstName NVARCHAR(50), @LastName NVARCHAR(50))
RETURNS NVARCHAR(101)
AS
BEGIN
RETURN CONCAT(@FirstName, ' ', @LastName);
END;
SELECT dbo.fn_GetEmployeeFullName('Ali', 'Yılmaz'); -- 'Ali Yılmaz'
Table-Valued Function — Tablo Döner
Inline TVF (genelde tercih edilen):
CREATE FUNCTION fn_GetDepartmentEmployees (@Dept NVARCHAR(50))
RETURNS TABLE
AS
RETURN (
SELECT Id, Name, Salary
FROM Employees
WHERE Department = @Dept
);
SELECT * FROM dbo.fn_GetDepartmentEmployees('IT');
-- Hatta JOIN'le bile kullanabilirsin
Function vs Stored Procedure: Function
SELECTiçinde kullanılabilir, SP kullanılamaz. SP veri değiştirebilir, function genelde sadece okur.
3. Trigger’lar
Trigger’lar, bir tablo üzerinde INSERT / UPDATE / DELETE olduğunda otomatik tetiklenen SP’lerdir.
AFTER Trigger (post-event)
CREATE TRIGGER trg_Employees_AuditUpdate
ON Employees
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO EmployeeAudit (EmployeeId, OldSalary, NewSalary, ChangedAt)
SELECT
i.Id,
d.Salary, -- DELETED: eski hali
i.Salary, -- INSERTED: yeni hali
GETDATE()
FROM inserted AS i
INNER JOIN deleted AS d ON i.Id = d.Id
WHERE i.Salary <> d.Salary;
END;
inserted ve deleted sanal tabloları:
- INSERT sonrası → sadece
inserteddoludur - DELETE sonrası → sadece
deleteddoludur - UPDATE sonrası → her ikisi de doludur (eski/yeni hal)
INSTEAD OF Trigger
Asıl işlemi yerine geçer:
CREATE TRIGGER trg_Employees_PreventDelete
ON Employees
INSTEAD OF DELETE
AS
BEGIN
-- Silmek yerine soft-delete
UPDATE Employees
SET Active = 0, DeletedAt = GETDATE()
WHERE Id IN (SELECT Id FROM deleted);
END;
Trigger’lar güçlü ama kötüye kullanılırsa debugging cehenneme döner. Görünmez yan etkiler oluşturur. Şüphede kalırsan SP kullan.
4. Window Functions — Modern T-SQL’in En İyi Özelliği
Aggregate gibi davranır ama satırları gruplamaz — her satır için “penceredeki” hesabı verir.
ROW_NUMBER()
Sıralı numara verir:
SELECT
Name,
Department,
Salary,
ROW_NUMBER() OVER (ORDER BY Salary DESC) AS Rank
FROM Employees;
Partition ile (Grup İçinde Sıralama)
SELECT
Name,
Department,
Salary,
ROW_NUMBER() OVER (
PARTITION BY Department
ORDER BY Salary DESC
) AS DeptRank
FROM Employees;
Sonuç: Her departmandaki çalışanlara kendi departmanı içinde sıra numarası verir. Bu klasik “her gruptaki en yüksek N kayıt” problemini çözer:
WITH Ranked AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY Department ORDER BY Salary DESC) AS rn
FROM Employees
)
SELECT * FROM Ranked WHERE rn <= 3;
-- Her departmandaki en yüksek 3 maaşlı
RANK() ve DENSE_RANK()
Eşit değerlere aynı sıra:
SELECT
Name,
Salary,
RANK() OVER (ORDER BY Salary DESC) AS R, -- eşitlerde gap
DENSE_RANK() OVER (ORDER BY Salary DESC) AS DR -- gap yok
FROM Employees;
Örnek: Maaşlar [10000, 10000, 8000, 7000] ise:
ROW_NUMBER:1, 2, 3, 4RANK:1, 1, 3, 4(2 atlanır)DENSE_RANK:1, 1, 2, 3
LAG / LEAD — Önceki / Sonraki Satır
SELECT
OrderDate,
Amount,
LAG(Amount, 1) OVER (ORDER BY OrderDate) AS PrevAmount,
LEAD(Amount, 1) OVER (ORDER BY OrderDate) AS NextAmount,
Amount - LAG(Amount, 1) OVER (ORDER BY OrderDate) AS DiffFromPrev
FROM Orders;
Trend analizi, dönem karşılaştırması için ideal.
Aggregate Window Functions
SELECT
Name,
Department,
Salary,
AVG(Salary) OVER (PARTITION BY Department) AS DeptAvg,
Salary - AVG(Salary) OVER (PARTITION BY Department) AS DiffFromAvg,
SUM(Salary) OVER (ORDER BY Id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS RunningTotal
FROM Employees;
Running total (kümülatif toplam) — finansal raporlarda klasik.
5. Query Plan ve Performans
Execution Plan Görmek
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
SELECT * FROM Orders WHERE CustomerId = 42;
-- Ya da SSMS'de: Ctrl+M ile "Include Actual Execution Plan"
Common Anti-patterns
❌ SELECT * — Sadece ihtiyacın olan sütunları çek:
-- Kötü
SELECT * FROM Orders;
-- İyi
SELECT Id, OrderDate, Amount FROM Orders;
❌ WHERE’de fonksiyon — Index’i kullanılamaz hale getirir:
-- Kötü (index kullanamaz)
WHERE YEAR(OrderDate) = 2024
-- İyi (index kullanır)
WHERE OrderDate >= '2024-01-01' AND OrderDate < '2025-01-01'
❌ %text% — Index baştan kullanılamaz:
-- Yavaş
WHERE Name LIKE '%ahmet%'
-- Daha hızlı
WHERE Name LIKE 'ahmet%'
❌ N+1 problem — Loop içinde sorgu. Tek JOIN ile çöz.
Stored Procedure Plan Cache
-- Plan cache'i temizle (sadece test ortamında!)
DBCC FREEPROCCACHE;
-- Bir SP'nin parametrelerle nasıl plan oluşturduğunu görmek için:
EXEC sp_GetEmployees @Department = 'IT';
-- Plan cache'lenir, sonraki çağrılar aynı planı kullanır
sp_executesql ile Dynamic SQL
DECLARE @sql NVARCHAR(MAX) = N'SELECT * FROM Employees WHERE Department = @dept';
EXEC sp_executesql @sql, N'@dept NVARCHAR(50)', @dept = 'IT';
EXEC(@sql) yerine sp_executesql kullan — parametre cache’lenir,
SQL injection güvenliği var.
Mini Proje: Aylık Satış Raporu
-- 1. Aylık satış toplamı + bir önceki aydan değişim
SELECT
FORMAT(OrderDate, 'yyyy-MM') AS Month,
SUM(Amount) AS Total,
LAG(SUM(Amount)) OVER (ORDER BY FORMAT(OrderDate, 'yyyy-MM')) AS PrevMonth,
SUM(Amount) - LAG(SUM(Amount)) OVER (ORDER BY FORMAT(OrderDate, 'yyyy-MM')) AS Change
FROM Orders
WHERE OrderDate >= DATEADD(YEAR, -1, GETDATE())
GROUP BY FORMAT(OrderDate, 'yyyy-MM')
ORDER BY Month;
-- 2. Top 5 müşteri (geçen ayın)
WITH MonthlyTotals AS (
SELECT
CustomerId,
SUM(Amount) AS Total,
ROW_NUMBER() OVER (ORDER BY SUM(Amount) DESC) AS rn
FROM Orders
WHERE OrderDate >= DATEADD(MONTH, -1, GETDATE())
GROUP BY CustomerId
)
SELECT c.Name, m.Total
FROM MonthlyTotals AS m
INNER JOIN Customers AS c ON c.Id = m.CustomerId
WHERE m.rn <= 5;
-- 3. Departman bazlı satış payı (her departmanın yüzdesi)
SELECT
Department,
SUM(o.Amount) AS Total,
SUM(o.Amount) * 100.0 / SUM(SUM(o.Amount)) OVER () AS PercentOfTotal
FROM Employees AS e
INNER JOIN Orders AS o ON o.EmployeeId = e.Id
WHERE o.OrderDate >= DATEADD(YEAR, -1, GETDATE())
GROUP BY Department;
🎉 Tebrikler!
5 günlük T-SQL BootCamp’ini başarıyla tamamladın!
Artık:
- Veritabanı tasarımı ve şema oluşturmayı biliyorsun
- Karmaşık sorgular yazıp gruplama / aggregate yapabilirsin
- JOIN’ler ile ilişkisel sorguları rahatlıkla çözüyorsun
- Veriyi güvenle yazıyorsun — transaction’lar, constraint’ler
- Stored procedure, function ve trigger yazabiliyorsun
- Window functions ile modern analitik sorgular yazıyorsun
- Performans sorunlarını tanıyıp önlemeyi biliyorsun
Sonraki Adımlar
- Veritabanı tasarımı (normalization, denormalization stratejileri)
- Advanced indexing — covering index, filtered index, columnstore
- Query tuning — hint’ler, query store, dynamic management views
- Partitioning — büyük tabloları bölümlemek
- Replication / Always On — yüksek erişilebilirlik
- Other DBMS’ler — PostgreSQL, MySQL’in SQL’i öğrenmen kolaylaşır
T-SQL öğrenme yolculuğun burada bitmiyor. SQL Server’ın derinliklerinde keşfedecek çok şey var. Mutlu sorgulamalar! ⛃