Tek tablodan veri çekmek kolay. Asıl güç, birden fazla tabloyu ilişkilendirip anlamlı sorular sormakta. Bu gün, T-SQL’in en güçlü özelliği: JOIN’ler.
Örnek Şema
CREATE TABLE Departments (
Id INT IDENTITY(1,1) PRIMARY KEY,
Name NVARCHAR(50),
Budget DECIMAL(12, 2)
);
CREATE TABLE Employees (
Id INT IDENTITY(1,1) PRIMARY KEY,
Name NVARCHAR(100),
DepartmentId INT FOREIGN KEY REFERENCES Departments(Id),
ManagerId INT FOREIGN KEY REFERENCES Employees(Id), -- self-reference
Salary DECIMAL(10, 2)
);
CREATE TABLE Orders (
Id INT IDENTITY(1,1) PRIMARY KEY,
EmployeeId INT FOREIGN KEY REFERENCES Employees(Id),
Amount DECIMAL(10, 2),
OrderDate DATE
);
1. INNER JOIN — Kesişim
İki tabloda eşleşen kayıtları döner. NULL satırlar dışarda:
SELECT e.Name, d.Name AS DeptName, e.Salary
FROM Employees AS e
INNER JOIN Departments AS d
ON e.DepartmentId = d.Id;
Sadece hem Employees’de hem Departments’da eşleşen satırlar gelir.
Employees Departments
┌────┬──────┬──────┐ ┌────┬─────────┐
│ 1 │ Ali │ 10 │ │ 10 │ IT │
│ 2 │ Ayşe │ 20 │ │ 20 │ HR │
│ 3 │ M. │ NULL │ │ 30 │ Sales │ (no match)
└────┴──────┴──────┘ └────┴─────────┘
INNER JOIN sonucu:
┌──────┬────────┐
│ Ali │ IT │
│ Ayşe │ HR │
└──────┴────────┘
(Mehmet ve Sales dışarda)
2. LEFT JOIN — Sol Tabloyu Korur
Sol tablonun tüm satırları gelir, sağdan eşleşmeyen NULL olur:
SELECT e.Name, d.Name AS DeptName
FROM Employees AS e
LEFT JOIN Departments AS d
ON e.DepartmentId = d.Id;
Sonuç:
┌──────┬────────┐
│ Ali │ IT │
│ Ayşe │ HR │
│ M. │ NULL │ ← departmansız çalışan da gelir
└──────┴────────┘
Klasik kullanım: “Sipariş VERMEMİŞ müşterileri bul”:
SELECT c.Name
FROM Customers c
LEFT JOIN Orders o ON c.Id = o.CustomerId
WHERE o.Id IS NULL;
3. RIGHT JOIN — Sağ Tabloyu Korur
LEFT’in tersi. Aynı sonucu LEFT JOIN ile de yazabilirsin (tabloları değiştirerek), bu yüzden pratikte çok az kullanılır.
SELECT e.Name, d.Name
FROM Employees AS e
RIGHT JOIN Departments AS d ON e.DepartmentId = d.Id;
4. FULL OUTER JOIN — İkisini de Koru
İki tablodaki tüm satırlar, eşleşmeyenler NULL:
SELECT e.Name, d.Name AS DeptName
FROM Employees AS e
FULL OUTER JOIN Departments AS d
ON e.DepartmentId = d.Id;
5. Çoklu JOIN
Birden fazla tablo zincirleyebilirsin:
SELECT
e.Name AS Employee,
d.Name AS Department,
m.Name AS Manager
FROM Employees AS e
INNER JOIN Departments AS d ON e.DepartmentId = d.Id
LEFT JOIN Employees AS m ON e.ManagerId = m.Id;
6. Self JOIN
Bir tabloyu kendisiyle birleştirme. Yöneticileri bulmak için:
SELECT
e.Name AS Employee,
m.Name AS Manager
FROM Employees AS e
LEFT JOIN Employees AS m ON e.ManagerId = m.Id;
Alias zorunlu — aynı tablo iki kez FROM’da olduğu için.
7. Subquery (Alt Sorgu)
Bir sorgunun içine başka sorgu:
Scalar Subquery (Tek Değer)
SELECT Name, Salary
FROM Employees
WHERE Salary > (SELECT AVG(Salary) FROM Employees);
-- Ortalama maaşın üstündekiler
Liste Subquery
SELECT Name
FROM Employees
WHERE DepartmentId IN (
SELECT Id FROM Departments WHERE Budget > 100000
);
Korelasyonlu Subquery
Dış sorguyla ilişkili alt sorgu — her dış satır için ayrı çalışır:
SELECT Name, Salary
FROM Employees AS e
WHERE Salary > (
SELECT AVG(Salary)
FROM Employees
WHERE DepartmentId = e.DepartmentId -- dış sorguya referans
);
-- Departman ortalamasının üstündekiler
8. EXISTS — En Verimli Filtre
EXISTS, alt sorgunun herhangi bir satır döndürüp döndürmediğine bakar.
IN’den çoğu zaman daha hızlıdır:
SELECT Name FROM Customers c
WHERE EXISTS (
SELECT 1 FROM Orders o
WHERE o.CustomerId = c.Id
);
-- Hiç sipariş veren müşteriler
NOT EXISTS
SELECT Name FROM Customers c
WHERE NOT EXISTS (
SELECT 1 FROM Orders o
WHERE o.CustomerId = c.Id
);
-- Hiç sipariş vermemiş müşteriler
9. CTE — Common Table Expression
CTE, sorgu içinde isimlendirilmiş geçici tablo. Karmaşık sorguları okunabilir hale getirir:
WITH DeptStats AS (
SELECT
DepartmentId,
AVG(Salary) AS AvgSal,
COUNT(*) AS HeadCount
FROM Employees
GROUP BY DepartmentId
)
SELECT d.Name, s.AvgSal, s.HeadCount
FROM DeptStats AS s
INNER JOIN Departments AS d ON s.DepartmentId = d.Id
WHERE s.HeadCount > 5;
Birden Fazla CTE
WITH
HighEarners AS (
SELECT * FROM Employees WHERE Salary > 10000
),
BigDepts AS (
SELECT Id FROM Departments WHERE Budget > 100000
)
SELECT e.Name
FROM HighEarners AS e
INNER JOIN BigDepts AS d ON e.DepartmentId = d.Id;
Recursive CTE
Hiyerarşi sorguları için (organizasyon ağacı, kategori ağacı):
WITH OrgChart AS (
-- Anchor: CEO (manager yok)
SELECT Id, Name, ManagerId, 0 AS Level
FROM Employees
WHERE ManagerId IS NULL
UNION ALL
-- Recursive: her seviye sonraki seviyeyi getirir
SELECT e.Id, e.Name, e.ManagerId, oc.Level + 1
FROM Employees AS e
INNER JOIN OrgChart AS oc ON e.ManagerId = oc.Id
)
SELECT * FROM OrgChart
ORDER BY Level, Name;
Pratik Sorular
Soru 1: Her departmandaki çalışan sayısı (departman ismiyle).
SELECT d.Name, COUNT(e.Id) AS EmployeeCount
FROM Departments AS d
LEFT JOIN Employees AS e ON e.DepartmentId = d.Id
GROUP BY d.Name;
Soru 2: En yüksek maaşlı 3 çalışan ve departmanları.
SELECT TOP 3
e.Name,
d.Name AS Department,
e.Salary
FROM Employees AS e
INNER JOIN Departments AS d ON e.DepartmentId = d.Id
ORDER BY e.Salary DESC;
Soru 3: Hiç sipariş VERMEMİŞ çalışanları bul.
SELECT e.Name
FROM Employees AS e
LEFT JOIN Orders AS o ON o.EmployeeId = e.Id
WHERE o.Id IS NULL;
Soru 4: Her çalışanın kendi departman ortalamasından yüksek mi düşük mü maaş aldığı.
WITH DeptAvg AS (
SELECT DepartmentId, AVG(Salary) AS AvgSal
FROM Employees
GROUP BY DepartmentId
)
SELECT
e.Name,
e.Salary,
da.AvgSal,
CASE
WHEN e.Salary > da.AvgSal THEN 'Yüksek'
WHEN e.Salary < da.AvgSal THEN 'Düşük'
ELSE 'Ortalama'
END AS Status
FROM Employees AS e
INNER JOIN DeptAvg AS da ON e.DepartmentId = da.DepartmentId;
Soru 5: Yöneticilerin altındaki toplam kişi sayısı (1 seviye).
SELECT
m.Name AS Manager,
COUNT(e.Id) AS Reports
FROM Employees AS m
LEFT JOIN Employees AS e ON e.ManagerId = m.Id
GROUP BY m.Name
HAVING COUNT(e.Id) > 0;
Günün Özeti
Bugün ilişkisel sorgu sanatını öğrendik:
INNER JOIN— kesişimLEFT / RIGHT / FULL OUTER JOIN— bir veya iki tarafı koruma- Self join — aynı tabloyla birleşim
- Subquery ve korelasyonlu alt sorgu
EXISTS / NOT EXISTSile verimli filtreleme- CTE (
WITH ...) ile okunaklı karmaşık sorgular - Recursive CTE ile hiyerarşi sorguları
Yarın Gün 4’te veri manipülasyonuna geçiyoruz — INSERT, UPDATE,
DELETE, transaction’lar ve constraint’ler.