Constraint'ler ve Şema Değişikliği: PRIMARY KEY, FOREIGN KEY, UNIQUE, CHECK, DEFAULT ve ALTER TABLE
Constraint'ler, veriye hangi yoldan yazılırsa yazılsın uygulanan kurallardır; SQL Server'da UNIQUE column tek NULL kabul eder, SQL:2023'ün NULLS NOT DISTINCT seçeneği yok, çözüm filtered unique index. İsimli constraint'leri, IDENTITY ile SEQUENCE farkını, ON DELETE CASCADE ile NO ACTION'ı, CHECK'in NULL'ı geçirmesini, ALTER TABLE ile güvenli şema değişikliğini ve WITH NOCHECK'in constraint'i untrusted yapıp planı nasıl bozduğunu gösteriyoruz.
Sürüm notu: Bu yazıdaki SQL (Structured Query Language) sorguları SQL Server 2025 (17.0, Cumulative Update 10) üzerinde, PostgreSQL karşılaştırmaları PostgreSQL 18.6 üzerinde çalıştırılmıştır.
Önce Tahmin Et: İki NULL Aynı Değer mi?
Bookstore hediye kartı satmaya başlıyor. Kural şu: her kartın kodu benzersiz, bir müşteriye en fazla bir kart bağlanabilir ama kart henüz kimseye bağlanmamış da olabilir. Table'ı buna göre kuruyoruz:
CREATE TABLE dbo.gift_cards (
gift_card_id INT IDENTITY(1,1) NOT NULL,
code VARCHAR(20) NOT NULL,
customer_id INT NULL,
CONSTRAINT PK_gift_cards PRIMARY KEY (gift_card_id),
CONSTRAINT UQ_gift_cards_code UNIQUE (code),
CONSTRAINT UQ_gift_cards_customer UNIQUE (customer_id)
);
Şimdi üç kart ekliyoruz: biri Ada'ya bağlı, ikisi henüz kimseye bağlı değil. Kaç row eklenir?
INSERT INTO dbo.gift_cards (code, customer_id) VALUES ('GIFT-0001', 1);
INSERT INTO dbo.gift_cards (code, customer_id) VALUES ('GIFT-0002', NULL);
INSERT INTO dbo.gift_cards (code, customer_id) VALUES ('GIFT-0003', NULL);
SELECT gift_card_id, code, customer_id FROM dbo.gift_cards;
(1 rows affected)
(1 rows affected)
Msg 2627, Level 14, State 1, Server b8be943222fa, Line 3
Violation of UNIQUE KEY constraint 'UQ_gift_cards_customer'. Cannot insert duplicate key in object 'dbo.gift_cards'. The duplicate key value is (<NULL>).
The statement has been terminated.
gift_card_id code customer_id
------------ --------- -----------
1 GIFT-0001 1
2 GIFT-0002 NULL
(2 rows affected)
SQL Server ikinci NULL'ı "tekrar eden değer" saydı. Oysa NULL "bilinmiyor" demek ve iki bilinmeyenin eşit olduğunu söylemek için bir sebep yok. Aynı üç INSERT PostgreSQL 18.6'da üç row ekliyor. Hangisi doğru? Cevap "ikisi de, çünkü standart yıllarca bunu ürüne bıraktı". Bu yazıda constraint'leri, yani veritabanının kendi kendine uyguladığı kuralları ele alacağız: NOT NULL, primary key, foreign key, UNIQUE, CHECK, DEFAULT ve var olan bir table'a bunları ALTER TABLE ile güvenle eklemenin yolları.
Constraint Nedir ve Neden İsim Vermeli?
Constraint, bir table'daki verinin her an sağlaması gereken bir kural. Uygulama katmanındaki doğrulamadan farkı şu: veritabanına hangi yoldan yazılırsa yazılsın uygulanır. API'den (Application Programming Interface), bir admin script'inden, bir ETL (Extract, Transform, Load) işinden ya da SSMS (SQL Server Management Studio) penceresinden gelen INSERT aynı kurala takılır. En basiti NOT NULL: column boş bırakılamaz.
Constraint'e isim vermezsen SQL Server bir isim uydurur:
CREATE TABLE dbo.coupons (
coupon_id INT PRIMARY KEY,
code VARCHAR(20) UNIQUE,
percent_off TINYINT CHECK (percent_off BETWEEN 1 AND 90)
);
SELECT name, type_desc FROM sys.objects WHERE parent_object_id = OBJECT_ID('dbo.coupons') ORDER BY type_desc;
name type_desc
------------------------------ ----------------------
CK__coupons__percent__02084FDA CHECK_CONSTRAINT
PK__coupons__58CF638990E1C041 PRIMARY_KEY_CONSTRAINT
UQ__coupons__357D4CF9120A0819 UNIQUE_CONSTRAINT
(3 rows affected)
Sondaki hex kısım her veritabanında farklı üretilir. Development'ta PK__coupons__58CF638990E1C041 olan constraint production'da başka bir isim taşır ve "bu constraint'i drop et" diyen bir migration script'i bir ortamda çalışıp diğerinde düşer. Hata mesajları da isimle gelir; CK_reviews_rating okunur, CK__reviews__rating__5EBF139D okunmaz. Bookstore'da her constraint PK_, FK_, UQ_, CK_, DF_ önekiyle ve table adıyla isimli.
PRIMARY KEY: Tek Column, Composite ve Değeri Kim Üretir?
Primary key bir row'u benzersiz tanımlar ve iki kural getirir: değerler tekrar etmez, hiçbiri NULL olmaz. SQL Server primary key için varsayılan olarak clustered bir index oluşturur; table'ın fiziksel düzeni bu karara bağlı, ayrıntısı "SQL Server Derinlemesine" serisinde. Key birden fazla column'dan da oluşabilir. Bookstore'daki book_authors buna örnek: PRIMARY KEY (book_id, author_id) aynı yazarın aynı kitaba iki kez bağlanmasını engeller, ama bir kitabın birden çok yazarı olabilir.
Tek column'lu key'lerde değeri kimin ürettiği bir tasarım kararı. Üç yaygın seçenek var. IDENTITY table'a bağlı bir sayaçtır; değer INSERT sırasında üretilir ve transaction geri alınsa da geri verilmez:
CREATE TABLE dbo.id_demo (id INT IDENTITY(1,1) PRIMARY KEY, note VARCHAR(10));
INSERT INTO dbo.id_demo (note) VALUES ('first');
BEGIN TRANSACTION;
INSERT INTO dbo.id_demo (note) VALUES ('rolled');
ROLLBACK TRANSACTION;
INSERT INTO dbo.id_demo (note) VALUES ('third');
SELECT id, note FROM dbo.id_demo;
id note
-- -----
1 first
3 third
2 numara kayboldu. Identity değerleri boşluksuz bir sıra garantisi vermez; fatura numarası gibi boşluksuz olması gereken bir numara için identity kullanmak hatadır. SEQUENCE ise table'dan bağımsız bir sayaçtır, standartta SQL:2003'ten beri var ve değeri INSERT'ten önce alabilirsin:
CREATE SEQUENCE dbo.invoice_number_seq AS INT START WITH 1000 INCREMENT BY 1;
SELECT NEXT VALUE FOR dbo.invoice_number_seq AS first_value,
NEXT VALUE FOR dbo.invoice_number_seq AS same_row_value;
SELECT NEXT VALUE FOR dbo.invoice_number_seq AS next_call;
first_value same_row_value
----------- --------------
1000 1000
(1 rows affected)
next_call
---------
1001
(1 rows affected)
Aynı row içinde iki kez çağrılan NEXT VALUE FOR aynı değeri döndürür; bu SQL Server'ın belgelenmiş davranışı. Üçüncü seçenek UNIQUEIDENTIFIER: değeri uygulama da üretebilir, birden çok sistem çakışmadan id üretebilir, ama 16 byte'tır ve rastgele GUID'ler (Globally Unique Identifier) clustered index'te sayfa bölünmelerine yol açar. Bu üç seçeneğin derin karşılaştırması "SQL Server Derinlemesine" serisinin 2. yazısında; dışarıya açılan id tasarımı ise Solution Pattern serisinin "Id'yi Dışarıya Açmak: int Identity, GUID v7 ve Public Id Ayrımı" yazısında.
FOREIGN KEY ve ON DELETE
Foreign key, bir column'daki değerin başka bir table'ın key'inde var olmasını şart koşar. Kitap baskılarını tutan table'ı bu kez constraint'leriyle kuralım:
CREATE TABLE dbo.book_editions (
edition_id INT IDENTITY(1,1) NOT NULL,
book_id INT NOT NULL,
edition_label NVARCHAR(100) NOT NULL,
isbn CHAR(13) NOT NULL,
page_count SMALLINT NULL,
list_price DECIMAL(10,2) NOT NULL,
is_hardcover BIT NOT NULL CONSTRAINT DF_book_editions_is_hardcover DEFAULT (0),
created_at DATETIME2(0) NOT NULL CONSTRAINT DF_book_editions_created_at DEFAULT (SYSUTCDATETIME()),
CONSTRAINT PK_book_editions PRIMARY KEY (edition_id),
CONSTRAINT FK_book_editions_books FOREIGN KEY (book_id) REFERENCES dbo.books (book_id),
CONSTRAINT UQ_book_editions_isbn UNIQUE (isbn),
CONSTRAINT CK_book_editions_list_price CHECK (list_price >= 0),
CONSTRAINT CK_book_editions_page_count CHECK (page_count > 0)
);
INSERT INTO dbo.book_editions (book_id, edition_label, isbn, page_count, list_price)
VALUES (75, N'İnce Memed 1, pocket edition', '9798900000751', 436, 289.90);
Var olmayan bir kitaba baskı eklemeye çalışınca:
INSERT INTO dbo.book_editions (book_id, edition_label, isbn, list_price)
VALUES (999, N'Ghost Edition', '9798900009999', 100.00);
Msg 547, Level 16, State 1, Server b8be943222fa, Line 1
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_book_editions_books". The conflict occurred in database "sqlt_04", table "dbo.books", column 'book_id'.
The statement has been terminated.
Kural iki yönlü çalışır. Artık bir baskısı olan Dune'u silmeye çalışırsan, bu kez DELETE engellenir: The DELETE statement conflicted with the REFERENCE constraint "FK_book_editions_books". Parent silindiğinde child row'lara ne olacağını ON DELETE belirler. Varsayılan NO ACTION: child row varken parent'ın silinmesini reddeder. CASCADE child'ları da siler, SET NULL ve SET DEFAULT foreign key column'unu boşaltır ya da varsayılana çeker. Bookstore'da order_items siparişe CASCADE ile, payments ise NO ACTION ile bağlı. Bu karışımın sonucunu iki siparişte görelim. 6 numara iptal edilmiş, ödemesi yok:
BEGIN TRANSACTION;
SELECT COUNT(*) AS items_before FROM dbo.order_items WHERE order_id = 6;
DELETE FROM dbo.orders WHERE order_id = 6;
SELECT COUNT(*) AS items_after FROM dbo.order_items WHERE order_id = 6;
ROLLBACK TRANSACTION;
items_before
------------
1
(1 rows affected)
(1 rows affected)
items_after
-----------
0
(1 rows affected)
Sipariş silindi, kalemi de onunla gitti. 263 numara ise ödenmiş bir sipariş ve aynı DELETE orada durdu:
Msg 547, Level 16, State 1, Server b8be943222fa, Line 1
The DELETE statement conflicted with the REFERENCE constraint "FK_payments_orders". The conflict occurred in database "sqlt_04", table "dbo.payments", column 'order_id'.
The statement has been terminated.
Bu bilinçli bir tasarım. Sipariş kalemleri siparişin parçası, onsuz anlamı yok; ödeme ise muhasebe kaydı ve bir sipariş "yanlışlıkla" silinirken sessizce kaybolmamalı. CASCADE'i ancak child row'un parent'sız hiçbir anlamı yoksa kullan.
CHECK ve DEFAULT
CHECK bir row'un sağlaması gereken bir koşul. Bookstore'da stok eksiye düşemez ve puan 1 ile 5 arasında olmalı. 100 adet Dune satılmış gibi stoku düşürmeye çalışalım:
UPDATE dbo.stock_items SET quantity_on_hand = quantity_on_hand - 100 WHERE book_id = 26;
Msg 547, Level 16, State 1, Server b8be943222fa, Line 1
The UPDATE statement conflicted with the CHECK constraint "CK_stock_items_quantity_on_hand". The conflict occurred in database "sqlt_04", table "dbo.stock_items", column 'quantity_on_hand'.
The statement has been terminated.
Uygulamada "önce stoğu oku, yetiyorsa düş" diye yazılan kod, iki eşzamanlı istekte stoğu eksiye düşürebilir; bu constraint son savunma hattı. Puan için de aynısı: rating = 6 ile gelen bir yorum CK_reviews_rating'e takılıyor. Şimdi ince bir nokta. Negatif fiyatlı bir baskı reddediliyor, peki sayfa sayısı NULL olan bir sesli kitap baskısı?
INSERT INTO dbo.book_editions (book_id, edition_label, isbn, page_count, list_price)
VALUES (26, N'Dune, audio', '9798900000270', NULL, 520.00);
SELECT edition_id, edition_label, page_count, is_hardcover FROM dbo.book_editions;
edition_id edition_label page_count is_hardcover
---------- ---------------------------- ---------- ------------
1 İnce Memed 1, pocket edition 436 0
5 Dune, audio NULL 0
(2 rows affected)
Kabul edildi. NULL > 0 ne doğru ne yanlış, "bilinmiyor"; ve CHECK yalnız sonuç yanlış (FALSE) olduğunda reddeder. WHERE ise yalnız doğru (TRUE) olanı geçirir. Aynı üç değerli mantık, iki yerde iki farklı sonuç. NULL'ın da reddedilmesini istiyorsan column'u NOT NULL yap. Bir şey daha: edition_id 1'den 5'e atladı, çünkü başarısız her INSERT bir identity değeri tüketti. is_hardcover'ı yazmadık, DEFAULT 0 koydu. DEFAULT yalnız column hiç yazılmadığında devreye girer; NULL'ı açıkça yazarsan NULL yazılır.
UNIQUE ve NULL: SQL Server, Standart ve PostgreSQL
Açılıştaki soruya dönelim. SQL:2023'e kadar standart, UNIQUE bir column'da birden fazla NULL olup olamayacağını ürüne bırakıyordu. SQL:2023 bunu seçilebilir yaptı: UNIQUE NULLS DISTINCT (NULL'lar birbirinden farklı sayılır, çok sayıda NULL serbest) ve UNIQUE NULLS NOT DISTINCT (NULL'lar aynı sayılır, tek NULL). PostgreSQL 15'ten beri ikisini de destekliyor ve varsayılanı NULLS DISTINCT. Aynı denemenin PostgreSQL 18.6 çıktısı:
gift_card_id | code | customer_id
--------------+-----------+-------------
1 | GIFT-0001 | 1
2 | GIFT-0002 |
3 | GIFT-0003 |
(3 rows)
Column'u UNIQUE NULLS NOT DISTINCT ile tanımlayınca PostgreSQL de SQL Server gibi davranıyor: Key (customer_id)=(null) already exists. SQL Server 2025 ise bu syntax'ı tanımıyor (Incorrect syntax near 'NULLS'.); UNIQUE constraint'i her zaman tek NULL'a izin veriyor. "Değer varsa benzersiz olsun, yoksa serbest" kuralının SQL Server'daki çözümü filtered unique index:
ALTER TABLE dbo.gift_cards DROP CONSTRAINT UQ_gift_cards_customer;
CREATE UNIQUE INDEX UX_gift_cards_customer
ON dbo.gift_cards (customer_id)
WHERE customer_id IS NOT NULL;
INSERT INTO dbo.gift_cards (code, customer_id) VALUES ('GIFT-0003', NULL);
INSERT INTO dbo.gift_cards (code, customer_id) VALUES ('GIFT-0004', NULL);
INSERT INTO dbo.gift_cards (code, customer_id) VALUES ('GIFT-0005', 1);
SELECT gift_card_id, code, customer_id FROM dbo.gift_cards;
Msg 2601, Level 14, State 1, Server b8be943222fa, Line 7
Cannot insert duplicate key row in object 'dbo.gift_cards' with unique index 'UX_gift_cards_customer'. The duplicate key value is (1).
The statement has been terminated.
gift_card_id code customer_id
------------ --------- -----------
1 GIFT-0001 1
2 GIFT-0002 NULL
4 GIFT-0003 NULL
5 GIFT-0004 NULL
(4 rows affected)
İstediğimiz tam bu: NULL'lar serbest, Ada'ya ikinci kart reddedildi. Index yalnız WHERE koşulunu sağlayan row'ları içerdiği için benzersizlik de yalnız onlarda aranıyor. Filtered index'in bir şartı var: oturumda QUOTED_IDENTIFIER ve ANSI_NULLS açık olmalı, aksi halde o table'a yazan her INSERT hata verir. Önceki yazıda sqlcmd'ye -I bayrağını bu yüzden ekledik. Aynı yöntem soft delete'te "silinmemiş kayıtlar arasında e-posta benzersiz olsun" kuralı için de kullanılıyor; Solution Pattern serisinin "Soft Delete'i Doğru Yapmak: IsDeleted, DeletedAt, Filtered Unique Index ve Archive Tablosu" yazısında işlendi.
ALTER TABLE ile Şema Değişikliği
Dolu bir table'a zorunlu bir column eklemek isteyelim. Var olan 40 müşteri için değer nereden gelecek?
ALTER TABLE dbo.customers ADD birth_date DATE NOT NULL;
Msg 4901, Level 16, State 1, Server b8be943222fa, Line 1
ALTER TABLE only allows columns to be added that can contain nulls, or have a DEFAULT definition specified, or the column being added is an identity or timestamp column, or alternatively if none of the previous conditions are satisfied the table must be empty to allow addition of this column. Column 'birth_date' cannot be added to non-empty table 'customers' because it does not satisfy these conditions.
Hata mesajı seçenekleri sayıyor: ya column NULL alabilir ya da bir DEFAULT'u olur. Sadakat puanı için sıfır mantıklı bir varsayılan:
ALTER TABLE dbo.customers ADD loyalty_points INT NOT NULL
CONSTRAINT DF_customers_loyalty_points DEFAULT (0);
GO
SELECT TOP (3) customer_id, first_name, loyalty_points FROM dbo.customers ORDER BY customer_id;
customer_id first_name loyalty_points
----------- ---------- --------------
1 Ada 0
2 Alan 0
3 Grace 0
(3 rows affected)
Buradaki GO gereksiz değil: aynı batch'te ALTER TABLE ile yeni column'u kullanan SELECT'i yazınca batch derlenirken column henüz olmadığı için Invalid column name 'loyalty_points' hatası alırsın. Column'u daraltmak da veriyle çatışabilir. Telefon numaralarını VARCHAR(10)'a sığdırmaya çalışınca ALTER TABLE dbo.customers ALTER COLUMN phone VARCHAR(10) NULL Msg 2628 ile, "String or binary data would be truncated" diyerek duruyor. SQL Server veriyi sessizce kesmiyor.
Production'da büyük bir table'da şema değişikliği yapmanın güvenli sırası genelde "genişlet, taşı, daralt" (expand/contract) şeklindedir: önce yeni column'u nullable ekle; uygulamanın yeni sürümü iki column'a birden yazsın; eski row'ları küçük partiler halinde doldur (backfill); sonra constraint'i ekle ve column'u NOT NULL yap; en son eski column'u kaldır. Her adım geri alınabilir ve hiçbiri table'ı uzun süre kilitlemez. Entity Framework Core (EF Core) migration'larıyla bu adımları nasıl ayrı migration'lara böleceğin EF Core serisinin "EF Core Migrations: migrations add, database update, Script ve Bundle" yazısında.
WITH NOCHECK ve Untrusted Constraint'ler
Yeni bir iş kuralı geldi: katalogda 250 TL'nin altında kitap olmayacak. Constraint'i ekleyelim:
ALTER TABLE dbo.books ADD CONSTRAINT CK_books_min_price CHECK (list_price >= 250);
Msg 547, Level 16, State 1, Server b8be943222fa, Line 1
The ALTER TABLE statement conflicted with the CHECK constraint "CK_books_min_price". The conflict occurred in database "sqlt_04", table "dbo.books", column 'list_price'.
Katalogda 250 TL'nin altında 9 kitap var ve SQL Server var olan veriyi de kontrol ettiği için constraint eklenemiyor. İnternette en sık önerilen "çözüm" WITH NOCHECK:
ALTER TABLE dbo.books WITH NOCHECK ADD CONSTRAINT CK_books_min_price CHECK (list_price >= 250);
SELECT name, is_disabled, is_not_trusted FROM sys.check_constraints WHERE parent_object_id = OBJECT_ID('dbo.books');
name is_disabled is_not_trusted
------------------- ----------- --------------
CK_books_min_price 0 1
CK_books_list_price 0 0
CK_books_page_count 0 0
(3 rows affected)
Constraint eklendi ve bundan sonraki yazmalarda uygulanacak, ama is_not_trusted = 1. SQL Server bu kurala uymayan row'ların table'da durduğunu biliyor ve constraint'e "güvenilmez" etiketi koyuyor. Bunun bedeli yalnız eski veride değil, sorgu planında da var. Güvenilir bir constraint optimizer'a bilgi verir. CK_books_list_price (fiyat sıfırdan küçük olamaz) güvenilirken "fiyatı sıfırdan küçük kitapları say" sorgusunun planı:
SET SHOWPLAN_TEXT ON;
GO
SELECT COUNT(*) AS n FROM dbo.books WHERE list_price < 0 OPTION (RECOMPILE);
GO
SET SHOWPLAN_TEXT OFF;
StmtText
--------
|--Constant Scan(VALUES:(((0))))
Table'a hiç dokunulmuyor; optimizer constraint sayesinde cevabın sıfır olduğunu biliyor. Aynı constraint'i NOCHECK ile kapatıp yalnız CHECK CONSTRAINT ile açınca, yani mevcut veriyi doğrulamadan geri açınca, is_not_trusted 1 oluyor ve aynı sorgu books'u taramaya başlıyor: Table 'books'. Scan count 1, logical reads 3. Foreign key'lerde de aynısı geçerli; güvenilmeyen bir foreign key, optimizer'ın gereksiz join'leri plandan atmasını engeller. Toplu veri yüklemeden önce constraint'leri kapatıp sonra açan script'ler bu yüzden tehlikeli. Tekrar güvenilir yapmak için mevcut veriyi doğrulatman gerekiyor; burada CHECK kelimesinin iki kez yazılması bir yazım hatası değil:
ALTER TABLE dbo.order_items WITH CHECK CHECK CONSTRAINT FK_order_items_books;
SELECT name, is_disabled, is_not_trusted FROM sys.foreign_keys WHERE name = 'FK_order_items_books';
name is_disabled is_not_trusted
-------------------- ----------- --------------
FK_order_items_books 0 0
(1 rows affected)
Asgari fiyat kuralının doğru çözümü ise NOCHECK değil: önce 9 kitabın fiyatını iş birimiyle birlikte düzelt, sonra constraint'i normal şekilde ekle.
Üretimde Dikkat
Constraint eklemek, table'ı tararken kilit alır; milyonlarca row'luk bir table'da bu dakikalar sürebilir. SQL Server 2025'te Enterprise edition'da ADD CONSTRAINT resumable olarak çalıştırılabiliyor; Standard'da bakım penceresi planla. Hangi constraint'lerin güvenilmez olduğunu düzenli kontrol et: sys.check_constraints ve sys.foreign_keys'te is_not_trusted = 1 olan her satır, ya temizlenmesi gereken eski bir veri ya da unutulmuş bir yükleme script'i demek. Uygulama tarafında constraint hatalarını tanı: Msg 547 (foreign key ya da check), 2627 (unique constraint) ve 2601 (unique index) kullanıcıya 500 değil, anlamlı bir 409 ya da 422 olarak dönmeli. Bu eşleme ASP.NET Core Web API serisinin "ASP.NET Core'da Global Hata Yönetimi: Exception Middleware ve ProblemDetails" yazısındaki yaklaşımla yapılabilir.
Sık Düşülen Tuzaklar
- Constraint'lere isim vermemek ve migration script'lerinin ortamdan ortama farklı isimlerle karşılaşması.
CHECK'inNULL'ı geçirdiğini unutmak. KuralNULLiçin de geçerliyse column'uNOT NULLyap.- SQL Server'da
UNIQUEbir column'un birden fazlaNULLkabul edeceğini sanmak; çözüm filtered unique index. - Her foreign key'i
ON DELETE CASCADEyapmak. Bir müşteriyi silerken siparişlerini, ödemelerini ve yorumlarını sessizce silmek istemezsin. - Identity değerlerinin boşluksuz olacağını varsaymak; rollback ve başarısız
INSERT'ler değer tüketir. WITH NOCHECKile eklenen ya daNOCHECKile kapatılıp öylece açılan constraint'leri "çalışıyor" sanmak; güvenilmez constraint'ler eski veriyi korumaz ve optimizer'a yardım etmez.- Dolu bir table'a
DEFAULT'suzNOT NULLcolumn eklemeye çalışmak ya da büyük bir table'da tek adımda column tipi değiştirmek.
Küçük Bir Deneme
★☆☆ book_editions'a CONSTRAINT CK_book_editions_label CHECK (LEN(edition_label) >= 3) ekle ve kuralı ihlal eden bir INSERT dene. Hata mesajındaki constraint adını oku.
★★☆ customers.phone için "telefon varsa benzersiz olsun" kuralını filtered unique index ile kur. Sonra iki müşteriye aynı telefonu yazmayı dene. Neden bu kural için UNIQUE constraint işe yaramıyor?
★★★ reviews table'ının customers'a bağlı foreign key'ini ON DELETE CASCADE ile yeniden tanımla, bir transaction içinde hiç siparişi olmayan bir müşteriyi ve yorumu olan bir müşteriyi silmeyi dene, sonra ROLLBACK yap. Hangi silme neden başarısız oldu? Hangi foreign key yolu engelledi?
Şema kuruldu, kurallar yerinde. Bir sonraki yazıda verinin okuma tarafına geçiyoruz: SELECT, WHERE ve ORDER BY, SQL'in mantıksal sorgu işleme sırası ve "en yeni 20 kitap, 3. sayfa" sorgusunu TOP ve OFFSET/FETCH ile doğru yazmak.