MSSQL Öğreniyorum Serisi 01: Kütüphane Veritabanını Oluşturmak
Bu seriyle Microsoft SQL Server (MSSQL) üzerinde adım adım ilerleyeceğiz. Her yazıda aynı veritabanını kullanacağız: küçük bir okul kütüphanesi. İlk yazıda veritabanını sıfırdan kuruyoruz; tabloları oluşturuyor, kısıtları tanımlıyor ve örnek verileri ekliyoruz.
Serideki tüm SQL dosyaları GitHub'da. Kodları kopyalamak yerine indirip doğrudan SSMS'te açabilirsiniz.
Senaryo
Okul kütüphanesi kitaplarını, yazarlarını, kategorilerini ve üyelerini kayıt altına almak istiyor:
Bir kitabın birden fazla yazarı olabilir, bir yazarın da birden fazla kitabı olabilir.
Her kitap tek bir kategoriye aittir.
Üyeler kitap ödünç alır ve belirli bir süre içinde iade eder.
Bu cümlelerden altı tablo çıkıyor: Kategoriler, Yazarlar, Kitaplar, KitapYazar, Uyeler ve Oduncler.
ER Diyagramı

İlişki | Tür | Tabloda nasıl kurulur |
Bir kategoride birden çok kitap bulunur | 1:N | Kitaplar.KategoriID → Kategoriler |
Bir kitabın birden çok yazarı, bir yazarın birden çok kitabı olabilir | N:M | KitapYazar ara tablosu, bileşik PK (KitapID, YazarID) |
Bir üye birden çok kez ödünç alır | 1:N | Oduncler.UyeID → Uyeler |
Bir kitap birden çok kez ödünç verilir | 1:N | Oduncler.KitapID → Kitaplar |
Kural: 1:N ilişkide yabancı anahtar her zaman "N" (çok) tarafındaki tabloya eklenir. N:M ilişki doğrudan kurulamaz; iki tarafın birincil anahtarlarını taşıyan bir ara tablo gerekir.
Adım 1: Veritabanını Oluşturmak
CREATE DATABASE KutuphaneDB;
GO
USE KutuphaneDB;
GOAdım 2: Tabloları Oluşturmak
Tablo oluşturma sırası önemlidir. Önce başka bir tabloya bağlı olmayan tabloları (Kategoriler, Yazarlar, Uyeler), sonra yabancı anahtar içeren tabloları (Kitaplar, KitapYazar, Oduncler) oluştururuz. Ters sırada çalıştırırsak FOREIGN KEY, henüz var olmayan bir tabloyu gösteremez ve hata alırız.
Bağımsız tablolar
CREATE TABLE Kategoriler (
KategoriID INT IDENTITY(1,1) PRIMARY KEY,
KategoriAdi NVARCHAR(50) NOT NULL UNIQUE
);
CREATE TABLE Yazarlar (
YazarID INT IDENTITY(1,1) PRIMARY KEY,
Ad NVARCHAR(50) NOT NULL,
Soyad NVARCHAR(50) NOT NULL,
Ulke NVARCHAR(50) NULL
);
CREATE TABLE Uyeler (
UyeID INT IDENTITY(1,1) PRIMARY KEY,
Ad NVARCHAR(50) NOT NULL,
Soyad NVARCHAR(50) NOT NULL,
Eposta NVARCHAR(100) NULL UNIQUE,
Telefon VARCHAR(15) NULL,
KayitTarihi DATE NOT NULL DEFAULT GETDATE()
);
GOYabancı anahtar içeren tablolar
CREATE TABLE Kitaplar (
KitapID INT IDENTITY(1,1) PRIMARY KEY,
ISBN CHAR(13) NOT NULL UNIQUE,
KitapAdi NVARCHAR(150) NOT NULL,
YayinYili SMALLINT NULL,
SayfaSayisi INT NULL CHECK (SayfaSayisi > 0),
StokAdedi INT NOT NULL DEFAULT 1 CHECK (StokAdedi >= 0),
KategoriID INT NOT NULL,
CONSTRAINT FK_Kitaplar_Kategoriler
FOREIGN KEY (KategoriID) REFERENCES Kategoriler(KategoriID)
);
-- Kitaplar - Yazarlar arasındaki N:M ilişkiyi çözen ara tablo
CREATE TABLE KitapYazar (
KitapID INT NOT NULL,
YazarID INT NOT NULL,
CONSTRAINT PK_KitapYazar PRIMARY KEY (KitapID, YazarID), -- bileşik birincil anahtar
CONSTRAINT FK_KitapYazar_Kitaplar FOREIGN KEY (KitapID) REFERENCES Kitaplar(KitapID),
CONSTRAINT FK_KitapYazar_Yazarlar FOREIGN KEY (YazarID) REFERENCES Yazarlar(YazarID)
);
CREATE TABLE Oduncler (
OduncID INT IDENTITY(1,1) PRIMARY KEY,
KitapID INT NOT NULL,
UyeID INT NOT NULL,
OduncTarihi DATE NOT NULL DEFAULT GETDATE(),
SonTeslimTarihi DATE NOT NULL,
IadeTarihi DATE NULL, -- NULL ise kitap henüz iade edilmemiştir
CONSTRAINT FK_Oduncler_Kitaplar FOREIGN KEY (KitapID) REFERENCES Kitaplar(KitapID),
CONSTRAINT FK_Oduncler_Uyeler FOREIGN KEY (UyeID) REFERENCES Uyeler(UyeID),
CONSTRAINT CK_Oduncler_Tarih CHECK (SonTeslimTarihi >= OduncTarihi)
);
GOKullandığımız kısıtlar
Kısıt | Ne yapar | Örnek |
PRIMARY KEY | Her satırı benzersiz tanımlar | KitapID |
FOREIGN KEY | Başka tablodaki bir kayda bağlar | Kitaplar.KategoriID |
UNIQUE | Aynı değerin iki kez girilmesini engeller | ISBN, Eposta |
NOT NULL | Alanın boş bırakılmasını engeller | KitapAdi |
DEFAULT | Değer verilmezse varsayılanı yazar | KayitTarihi = bugün |
CHECK | Kurala uymayan değeri engeller | StokAdedi >= 0 |
Türkçe karakterlerin (ç, ğ, ı, ö, ş, ü) doğru saklanması için metin alanlarında NVARCHAR, metin değerlerinde de N'...' önekini kullanıyoruz. Tablo ve sütun adlarında ise Türkçe karakter kullanmıyoruz.
Adım 3: Veri Eklemek (INSERT)
Serinin sonraki yazılarındaki bütün örnekler bu veriye göre hazırlandı. Kayıtların her seferinde aynı numaraları alması için ID'leri SET IDENTITY_INSERT ... ON ile elle veriyoruz. Normalde IDENTITY alanlarına değer girilmez; numarayı SQL Server kendisi verir.
Kategoriler ve yazarlar
SET IDENTITY_INSERT Kategoriler ON;
INSERT INTO Kategoriler (KategoriID, KategoriAdi) VALUES
(1, N'Roman'), (2, N'Bilim'), (3, N'Tarih'), (4, N'Şiir'), (5, N'Çocuk'),
(6, N'Felsefe'), (7, N'Psikoloji'), (8, N'Biyografi'), (9, N'Bilim Kurgu'), (10, N'Polisiye');
SET IDENTITY_INSERT Kategoriler OFF;
GO
SET IDENTITY_INSERT Yazarlar ON;
INSERT INTO Yazarlar (YazarID, Ad, Soyad, Ulke) VALUES
(1, N'Sabahattin', N'Ali', N'Türkiye'),
(2, N'Yaşar', N'Kemal', N'Türkiye'),
(3, N'Orhan', N'Pamuk', N'Türkiye'),
(4, N'Stephen', N'Hawking', N'İngiltere'),
(5, N'Leonard', N'Mlodinow', N'ABD'),
(6, N'İlber', N'Ortaylı', N'Türkiye'),
(7, N'Nazım', N'Hikmet', N'Türkiye'),
(8, N'Antoine', N'de Saint-Exupéry', N'Fransa'),
(9, N'Fyodor', N'Dostoyevski', N'Rusya'),
(10, N'George', N'Orwell', N'İngiltere'),
(11, N'Agatha', N'Christie', N'İngiltere'),
(12, N'Isaac', N'Asimov', N'ABD'),
(13, N'Ahmet Hamdi', N'Tanpınar', N'Türkiye'),
(14, N'Platon', N'', N'Yunanistan'),
(15, N'Carl', N'Sagan', N'ABD');
SET IDENTITY_INSERT Yazarlar OFF;
GOKitaplar ve kitap-yazar eşleştirmesi
SET IDENTITY_INSERT Kitaplar ON;
INSERT INTO Kitaplar (KitapID, ISBN, KitapAdi, YayinYili, SayfaSayisi, StokAdedi, KategoriID) VALUES
(1, '9780000000011', N'Kürk Mantolu Madonna', 1943, 160, 3, 1),
(2, '9780000000028', N'İnce Memed', 1955, 436, 2, 1),
(3, '9780000000035', N'Kırmızı Saçlı Kadın', 2016, 204, 1, 1),
(4, '9780000000042', N'Zamanın Kısa Tarihi', 1988, 256, 2, 2),
(5, '9780000000059', N'Zamanın Daha Kısa Tarihi', 2005, 176, 1, 2),
(6, '9780000000066', N'Osmanlı''yı Yeniden Keşfetmek', 2006, 296, 2, 3),
(7, '9780000000073', N'Memleketimden İnsan Manzaraları', 1966, 480, 1, 4),
(8, '9780000000080', N'Küçük Prens', 1943, 112, 4, 5),
(9, '9780000000097', N'Benim Adım Kırmızı', 1998, 472, 2, 1),
(10, '9780000000103', N'Suç ve Ceza', 1866, 687, 3, 1),
(11, '9780000000110', N'1984', 1949, 352, 3, 9),
(12, '9780000000127', N'Hayvan Çiftliği', 1945, 152, 2, 1),
(13, '9780000000134', N'Doğu Ekspresinde Cinayet', 1934, 256, 2, 10),
(14, '9780000000141', N'Ben, Robot', 1950, 272, 1, 9),
(15, '9780000000158', N'Saatleri Ayarlama Enstitüsü', 1961, 382, 2, 1),
(16, '9780000000165', N'Devlet', NULL, 400, 1, 6),
(17, '9780000000172', N'Kozmos', 1980, 396, 2, 2),
(18, '9780000000189', N'Huzur', 1949, 432, 1, 1);
SET IDENTITY_INSERT Kitaplar OFF;
GO
-- Kitap 5'in iki yazarı var (N:M ilişki örneği)
INSERT INTO KitapYazar (KitapID, YazarID) VALUES
(1, 1), (2, 2), (3, 3), (4, 4), (5, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 3),
(10, 9), (11, 10), (12, 10), (13, 11), (14, 12), (15, 13), (16, 14), (17, 15), (18, 13);
GOMetin içinde tek tırnak geçiyorsa iki kez yazılır: N'Osmanlı''yı Yeniden Keşfetmek'. Yayın yılı bilinmeyen "Devlet" kitabı için bilerek NULL girdik; NULL değerlerin sorgularda nasıl davrandığını sonraki yazılarda göreceğiz.
Üyeler ve ödünçler
SET IDENTITY_INSERT Uyeler ON;
INSERT INTO Uyeler (UyeID, Ad, Soyad, Eposta, Telefon, KayitTarihi) VALUES
(1, N'Ayşe', N'Yılmaz', N'ayse.yilmaz@okul.edu.tr', '05321112233', '2026-09-08'),
(2, N'Mehmet', N'Demir', N'mehmet.demir@okul.edu.tr', '05332223344', '2026-09-08'),
(3, N'Zeynep', N'Kaya', N'zeynep.kaya@okul.edu.tr', NULL, '2026-09-10'),
(4, N'Can', N'Öztürk', N'can.ozturk@okul.edu.tr', '05443334455', '2026-09-15'),
(5, N'Elif', N'Şahin', NULL, '05554445566', '2026-09-20'),
(6, N'Burak', N'Çelik', N'burak.celik@okul.edu.tr', '05065556677', '2026-10-01'),
(7, N'Selin', N'Arslan', N'selin.arslan@okul.edu.tr', '05376667788', '2026-09-11'),
(8, N'Emre', N'Koç', N'emre.koc@okul.edu.tr', '05427778899', '2026-09-12'),
(9, N'Deniz', N'Aydın', N'deniz.aydin@okul.edu.tr', NULL, '2026-09-18'),
(10, N'Merve', N'Yıldız', N'merve.yildiz@okul.edu.tr', '05318889900', '2026-09-22'),
(11, N'Oğuz', N'Kurt', N'oguz.kurt@okul.edu.tr', '05459990011', '2026-09-25'),
(12, N'Ece', N'Polat', N'ece.polat@okul.edu.tr', '05360001122', '2026-10-05');
SET IDENTITY_INSERT Uyeler OFF;
GO
SET IDENTITY_INSERT Oduncler ON;
INSERT INTO Oduncler (OduncID, KitapID, UyeID, OduncTarihi, SonTeslimTarihi, IadeTarihi) VALUES
(1, 1, 1, '2026-09-10', '2026-09-24', '2026-09-22'),
(2, 8, 1, '2026-09-25', '2026-10-09', NULL),
(3, 2, 2, '2026-09-12', '2026-09-26', '2026-09-30'),
(4, 4, 2, '2026-09-28', '2026-10-12', NULL),
(5, 1, 3, '2026-09-15', '2026-09-29', NULL),
(6, 6, 3, '2026-09-16', '2026-09-30', '2026-09-29'),
(7, 9, 4, '2026-09-18', '2026-10-02', NULL),
(8, 5, 5, '2026-09-21', '2026-10-05', '2026-10-03'),
(9, 1, 5, '2026-10-01', '2026-10-15', NULL),
(10, 10, 7, '2026-09-22', '2026-10-06', '2026-10-05'),
(11, 11, 8, '2026-09-24', '2026-10-08', NULL),
(12, 13, 9, '2026-09-29', '2026-10-13', NULL),
(13, 11, 10, '2026-10-02', '2026-10-16', NULL),
(14, 15, 11, '2026-10-05', '2026-10-19', NULL),
(15, 12, 7, '2026-10-07', '2026-10-21', NULL),
(16, 17, 4, '2026-10-08', '2026-10-22', NULL);
SET IDENTITY_INSERT Oduncler OFF;
GOE-posta alanı UNIQUE olduğu için SQL Server bu sütunda yalnızca bir NULL değere izin verir; bu yüzden e-postası boş olan tek üye Elif. Burak ve Ece hiç ödünç almadı. Bu kayıtlar ileride LEFT JOIN örneklerinde işimize yarayacak.
Her şeyin yerinde olduğunu kontrol edelim:
SELECT N'Kategoriler' AS Tablo, COUNT(*) AS KayitSayisi FROM Kategoriler
UNION ALL SELECT N'Yazarlar', COUNT(*) FROM Yazarlar
UNION ALL SELECT N'Kitaplar', COUNT(*) FROM Kitaplar
UNION ALL SELECT N'KitapYazar', COUNT(*) FROM KitapYazar
UNION ALL SELECT N'Uyeler', COUNT(*) FROM Uyeler
UNION ALL SELECT N'Oduncler', COUNT(*) FROM Oduncler;
-- Beklenen: 10, 15, 18, 19, 12, 16Adım 4: Veri Güncellemek (UPDATE)
-- Zeynep, Kürk Mantolu Madonna'yı bugün iade etti
UPDATE Oduncler
SET IadeTarihi = CAST(GETDATE() AS DATE)
WHERE UyeID = 3 AND KitapID = 1 AND IadeTarihi IS NULL;
-- Küçük Prens'in stoğu 2 adet artırıldı
UPDATE Kitaplar
SET StokAdedi = StokAdedi + 2
WHERE KitapAdi = N'Küçük Prens';Adım 5: Veri Silmek (DELETE)
-- Deneme amaçlı bir üye ekleyip silelim
INSERT INTO Uyeler (Ad, Soyad, Eposta) VALUES (N'Deneme', N'Üye', N'deneme@okul.edu.tr');
DELETE FROM Uyeler
WHERE Eposta = N'deneme@okul.edu.tr';Dikkat: UPDATE ve DELETE komutlarında WHERE unutulursa tablodaki TÜM satırlar etkilenir. Önce aynı WHERE ile bir SELECT çalıştırıp hangi satırların etkileneceğini görmek iyi bir alışkanlıktır.
Bu iki adımda verileri değiştirdik. Sonraki yazılardaki beklenen sonuçlarla aynı sonucu almak için verileri ilk haline döndürmeniz yeterli: GitHub'daki 01_Veri_Ekleme.sql dosyasını yeniden çalıştırın.
Adım 6: Kısıtları Test Etmek
Aşağıdaki komutlar bilerek hata verir. Tek tek çalıştırıp hata mesajlarını okuyun ve hangi kısıtın devreye girdiğini bulun.
-- 1) UNIQUE: aynı ISBN ile ikinci kitap (Msg 2627)
INSERT INTO Kitaplar (ISBN, KitapAdi, KategoriID) VALUES ('9780000000011', N'Kopya Kitap', 1);
-- 2) FOREIGN KEY: olmayan kategori (Msg 547)
INSERT INTO Kitaplar (ISBN, KitapAdi, KategoriID) VALUES ('9780000000999', N'Yeni Kitap', 99);
-- 3) CHECK: negatif stok (Msg 547)
UPDATE Kitaplar SET StokAdedi = -1 WHERE KitapID = 1;
-- 4) FOREIGN KEY: ödünç kaydı olan üyeyi silmek (Msg 547)
DELETE FROM Uyeler WHERE UyeID = 1;
-- 5) NOT NULL: adı olmayan yazar (Msg 515)
INSERT INTO Yazarlar (Soyad) VALUES (N'İsimsiz');Sırada Ne Var?
Veritabanımız hazır. Bir sonraki yazıda WHERE ile filtrelemeyi işleyeceğiz: karşılaştırma operatörleri, tarihli BETWEEN örnekleri ve LIKE ile metin arama.
Ders Videoları
Bu konunun ders videolarını YouTube oynatma listemde bulabilirsiniz.



Yorumlar