Okumayı öğrendik, şimdi yazma sırası. Veri eklemek, güncellemek, silmek — ve bunları güvenli yapmak. Transaction’lar olmadan production’da SQL yazmayın.

1. INSERT — Veri Ekleme

Tek Satır

INSERT INTO Employees (Name, Department, Salary)
VALUES ('Ali Yılmaz', 'IT', 8500.00);

Sütun isimlerini her zaman yaz. Tablo yapısı değişirse sorgun bozulmaz.

Birden Fazla Satır (Tek Sorguda)

INSERT INTO Employees (Name, Department, Salary)
VALUES
    ('Ayşe Demir', 'HR', 6200),
    ('Mehmet Kaya', 'Marketing', 7000),
    ('Fatma Şahin', 'IT', 9500);

SELECT’ten INSERT

Başka tablodan veri kopyala:

INSERT INTO EmployeesArchive (Name, Department, Salary, ArchivedAt)
SELECT Name, Department, Salary, GETDATE()
FROM Employees
WHERE Active = 0;

IDENTITY Değeri Geri Almak

OUTPUT ile:

INSERT INTO Employees (Name, Department)
OUTPUT INSERTED.Id, INSERTED.Name
VALUES ('Cem Öztürk', 'Sales');

Veya:

INSERT INTO Employees (Name) VALUES ('Cem');
SELECT SCOPE_IDENTITY();  -- son INSERT edilen IDENTITY değeri

2. UPDATE — Kayıt Güncelleme

UPDATE Employees
SET Salary = Salary * 1.10  -- %10 zam
WHERE Department = 'IT';

Birden Fazla Sütun

UPDATE Employees
SET
    Salary = 9000,
    Department = 'Senior IT',
    UpdatedAt = GETDATE()
WHERE Id = 5;

JOIN ile UPDATE

UPDATE e
SET e.Salary = e.Salary + 500
FROM Employees AS e
INNER JOIN Departments AS d ON e.DepartmentId = d.Id
WHERE d.Budget > 100000;
-- Bütçesi yüksek departmanlardakilere zam

UYARI: WHERE olmadan UPDATE = tüm satırları günceller. Hayatınızın en pahalı dersini almak istemiyorsanız her zaman önce SELECT ile aynı WHERE ile test edin.

3. DELETE — Kayıt Silme

DELETE FROM Employees
WHERE Active = 0
  AND LastLoginDate < DATEADD(YEAR, -2, GETDATE());

TRUNCATE — Hızlı Tam Temizleme

TRUNCATE TABLE Employees;

DELETE vs TRUNCATE

ÖzellikDELETETRUNCATE
WHEREVarYok (hep tüm tablo)
HızYavaşÇok hızlı
LogHer satır loglanırMinimal
IDENTITYReset etmezSıfırlar
TriggerÇalışırÇalışmaz
Foreign Key referansı varsaÇalışır (cascade’e göre)Hata verir

Boş tabloya çekmek için → TRUNCATE. Şartlı silmek için → DELETE.

4. MERGE — Upsert (Insert + Update)

“Varsa güncelle, yoksa ekle” tek komutta:

MERGE INTO Inventory AS target
USING (VALUES (101, 'Widget', 50)) AS source (Id, Name, Quantity)
ON target.Id = source.Id

WHEN MATCHED THEN
    UPDATE SET
        Quantity = target.Quantity + source.Quantity

WHEN NOT MATCHED THEN
    INSERT (Id, Name, Quantity)
    VALUES (source.Id, source.Name, source.Quantity);

MERGE güçlü ama karmaşık. Basit upsert için bazen IF EXISTS ... UPDATE ELSE INSERT daha okunaklı olabilir.

5. Transaction Yönetimi

Veritabanı ACID prensibinin kalbi. Birden fazla işlemi tek bir atomic operasyon olarak yapmak için:

BEGIN TRANSACTION;

UPDATE Accounts SET Balance = Balance - 1000 WHERE Id = 1;
UPDATE Accounts SET Balance = Balance + 1000 WHERE Id = 2;

COMMIT TRANSACTION;
-- ya da: ROLLBACK TRANSACTION; (geri al)

Eğer ortada bir hata olursa hiçbir değişiklik kalıcı olmaz.

TRY / CATCH ile Güvenli Transaction

Production pattern:

BEGIN TRY
    BEGIN TRANSACTION;

    UPDATE Accounts SET Balance = Balance - 1000 WHERE Id = 1;

    IF (SELECT Balance FROM Accounts WHERE Id = 1) < 0
        THROW 50001, 'Yetersiz bakiye', 1;

    UPDATE Accounts SET Balance = Balance + 1000 WHERE Id = 2;

    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
        ROLLBACK TRANSACTION;

    -- Hatayı yeniden fırlat veya logla
    DECLARE @ErrorMsg NVARCHAR(4000) = ERROR_MESSAGE();
    THROW;
END CATCH

@@TRANCOUNT aktif transaction sayısını verir. ERROR_MESSAGE(), ERROR_NUMBER(), ERROR_LINE() hata detayları için.

Savepoint

İç içe işlemlerde kısmi rollback:

BEGIN TRANSACTION;
    INSERT INTO Orders ... ;
    SAVE TRANSACTION OrderSaved;

    INSERT INTO OrderItems ... ;

    IF (kontrol başarısız)
        ROLLBACK TRANSACTION OrderSaved;  -- sadece OrderItems geri alınır
    ELSE
        COMMIT TRANSACTION;

6. Constraint’ler

Constraint’ler, veriye kısıtlama koyar — veritabanı seviyesinde doğrulama.

PRIMARY KEY

CREATE TABLE Users (
    Id INT IDENTITY(1,1) PRIMARY KEY,
    ...
);

Veya named constraint:

CREATE TABLE Users (
    Id INT IDENTITY(1,1),
    CONSTRAINT PK_Users PRIMARY KEY (Id)
);

FOREIGN KEY

Bir tablonun başka tabloyu referans vermesi:

CREATE TABLE Orders (
    Id INT IDENTITY(1,1) PRIMARY KEY,
    UserId INT NOT NULL,
    Amount DECIMAL(10, 2),

    CONSTRAINT FK_Orders_Users
        FOREIGN KEY (UserId) REFERENCES Users(Id)
        ON DELETE CASCADE       -- User silinince Order'lar da silinir
        ON UPDATE NO ACTION
);

Cascade seçenekleri:

UNIQUE

CREATE TABLE Users (
    Email NVARCHAR(150) UNIQUE,
    ...
);

NULL kabul eder ama tek bir NULL — birden fazla NULL hata verir (SQL Server’da varsayılan).

CHECK

Özel doğrulama kuralı:

CREATE TABLE Products (
    Price DECIMAL(10, 2) CHECK (Price >= 0),
    Stock INT CHECK (Stock >= 0),
    Status CHAR(1) CHECK (Status IN ('A', 'P', 'D'))
);

DEFAULT

CREATE TABLE Users (
    CreatedAt DATETIME DEFAULT GETDATE(),
    Status NVARCHAR(20) DEFAULT 'active'
);

Sonradan Constraint Eklemek

ALTER TABLE Users
ADD CONSTRAINT UQ_Email UNIQUE (Email);

ALTER TABLE Users
DROP CONSTRAINT UQ_Email;

7. Index Temelleri

Index, arama hızını artıran veri yapısı. Kitap arkasındaki dizin gibi.

Index Oluşturma

CREATE INDEX IX_Users_Email ON Users(Email);

Clustered vs Non-Clustered

Composite Index

Birden fazla sütun üzerinde:

CREATE INDEX IX_Orders_UserDate
ON Orders(UserId, OrderDate DESC);

Sıralama önemli: WHERE UserId = X çalışır (index kullanır), WHERE OrderDate = Y tek başına bu index’i kullanmaz.

Ne Zaman Index Eklemeli?

Pratik Sorular

Soru 1: Yeni bir kullanıcı ekle ve oluşturulan Id’sini geri al.

INSERT INTO Users (Name, Email)
OUTPUT INSERTED.Id, INSERTED.Email
VALUES ('Ejder', 'ejder@x.com');

Soru 2: IT departmanındaki tüm çalışanlara %15 zam yap. Önce kaç satırı etkileyeceğini test et.

-- Test
SELECT COUNT(*) FROM Employees WHERE Department = 'IT';

-- Update
UPDATE Employees
SET Salary = Salary * 1.15
WHERE Department = 'IT';

Soru 3: İki hesap arasında para transferi (transaction ile).

BEGIN TRY
    BEGIN TRANSACTION;

    UPDATE Accounts SET Balance = Balance - @Amount WHERE Id = @FromId;

    IF (SELECT Balance FROM Accounts WHERE Id = @FromId) < 0
        THROW 50001, 'Yetersiz bakiye', 1;

    UPDATE Accounts SET Balance = Balance + @Amount WHERE Id = @ToId;

    INSERT INTO Transfers (FromId, ToId, Amount, TransferDate)
    VALUES (@FromId, @ToId, @Amount, GETDATE());

    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
    THROW;
END CATCH

Soru 4: Products tablosunda fiyat 0’dan büyük olmalı kuralını ekle.

ALTER TABLE Products
ADD CONSTRAINT CHK_Price_Positive CHECK (Price > 0);

Soru 5: Sık aranan OrderDate ve CustomerId sütunlarına index ekle.

CREATE INDEX IX_Orders_CustomerDate
ON Orders(CustomerId, OrderDate DESC);

Günün Özeti

Bugün veriyi nasıl güvenli yazacağımızı öğrendik:

Yarın Gün 5’te son durağımız: stored procedure’ler, function’lar, window functions ve performans optimizasyonu.