Tablo Oluşturma: CREATE TABLE ve Constraints
Giriş — Veritabanının İskeleti
Önceki derste veritabanımızı oluşturduk — artık boş bir bina var. Bu derste o binanın odalarını oluşturacağız: tablolar. Tablolar, veritabanının temel yapı taşlarıdır. İyi tasarlanmış tablolar, uygulamanın sağlıklı çalışmasının temelidir.
Bu derste CREATE TABLE komutunun tüm detaylarını, constraints (kısıtlamalar) kavramını ve e-ticaret veritabanımızın tablolarını birlikte oluşturacağız.
🎯 Analoji: Bir Excel dosyası açıp sütun başlıkları yazarken her sütuna "bu sütunda ne tür veri olacak" diye düşünürsün.
CREATE TABLEda aynı şeyi yapar ama çok daha güçlü kurallar koyabilirsin: "Bu sütun boş kalamaz", "Bu değer benzersiz olmalı", "Bu sütun başka tabloya referans veriyor" gibi.
CREATE TABLE — Temel Sözdizimi
CREATE TABLE tablo_adi (
sutun1_adi VERI_TIPI [KISITLAMALAR],
sutun2_adi VERI_TIPI [KISITLAMALAR],
...
[TABLO_SEVIYESI_KISITLAMALAR]
);İlk Tablomuz — En Basit Hali
USE e_commerce;
CREATE TABLE categories (
category_id INT,
category_name VARCHAR(100)
);Bu tablo çalışır ama çok eksik. Hiçbir kısıtlama (constraint) yok — herhangi bir değer girebilirsin, tekrar eden değer girebilirsin, hatta boş bile bırakabilirsin. Gerçek dünyada buna izin vermeyiz.
Kısıtlamalarla Birlikte
DROP TABLE IF EXISTS categories;
CREATE TABLE categories (
category_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
category_name VARCHAR(100) NOT NULL,
parent_category_id INT UNSIGNED NULL,
description TEXT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);Şimdi bu tabloda ciddi kurallar var. Her bir constraint'i detaylıca inceleyelim.
Constraints (Kısıtlamalar) — Verinin Koruyucu Kalkanı
Constraint'ler, tabloya girilen verilerin belirli kurallara uymasını zorunlu kılar. Veritabanı seviyesinde veri bütünlüğünü korumanın en güçlü yoludur.
🎯 Analoji: Online bir form dolduruyor olduğunu düşün. "E-posta" alanına "merhaba" yazınca form kırmızı uyarı veriyor: "Geçerli bir e-posta girin." İşte constraint'ler veritabanı seviyesinde aynı işi yapar — ama form doğrulamasından çok daha güvenilirdir çünkü uygulamayı atlasan bile veritabanı veriyi reddeder.
1. PRIMARY KEY — Birincil Anahtar
Her tabloda, her satırı benzersiz şekilde tanımlayan bir sütun (veya sütun kombinasyonu) olmalıdır:
-- Sütun seviyesinde PRIMARY KEY
CREATE TABLE products (
product_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(200) NOT NULL
);
-- Tablo seviyesinde PRIMARY KEY (aynı şey, farklı yazım)
CREATE TABLE products (
product_id INT UNSIGNED AUTO_INCREMENT,
product_name VARCHAR(200) NOT NULL,
PRIMARY KEY (product_id)
);PRIMARY KEY kuralları:
Değer benzersiz olmalı — aynı değer iki kez olamaz
NULL olamaz — her satırda bir değer olmalı
Tabloda tek bir primary key olabilir
Otomatik olarak bir index oluşturulur (performans için)
Bileşik Primary Key (Composite Primary Key):
Bazen tek bir sütun yetmez — birden fazla sütunun kombinasyonu benzersiz olabilir:
CREATE TABLE order_items (
order_id INT UNSIGNED,
product_id INT UNSIGNED,
quantity INT UNSIGNED NOT NULL DEFAULT 1,
unit_price DECIMAL(10,2) NOT NULL,
PRIMARY KEY (order_id, product_id) -- İkisi birlikte benzersiz
);
-- Bu durumda:
-- (101, 5) → ✅ İlk kayıt
-- (101, 8) → ✅ Farklı ürün, aynı sipariş
-- (102, 5) → ✅ Aynı ürün, farklı sipariş
-- (101, 5) → ❌ HATA! Bu kombinasyon zaten var💡 İpucu: Composite primary key yerine genellikle surrogate key (yapay anahtar) tercih edilir — yani
item_id INT AUTO_INCREMENTgibi ayrı bir ID sütunu ekleyip onu primary key yapmak. Daha pratik, JOIN'lerde daha kolay ve ORM'lerle daha uyumlu.
AUTO_INCREMENT — Otomatik Artan Değer
CREATE TABLE customers (
customer_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL
);
-- ID belirtmeden ekleme — otomatik artar
INSERT INTO customers (first_name) VALUES ('Ahmet'); -- customer_id = 1
INSERT INTO customers (first_name) VALUES ('Zeynep'); -- customer_id = 2
INSERT INTO customers (first_name) VALUES ('Mehmet'); -- customer_id = 3
-- ID 2'yi silsek bile, sonraki kayıt 4 olur (3'e geri dönmez!)
DELETE FROM customers WHERE customer_id = 2;
INSERT INTO customers (first_name) VALUES ('Ayşe'); -- customer_id = 4 (2 değil!)AUTO_INCREMENT davranışı:
Her yeni kayıtta otomatik 1 artar
Silinen değerler tekrar kullanılmaz
Son değeri görmek:
SELECT LAST_INSERT_ID();Başlangıç değerini ayarlamak:
AUTO_INCREMENT = 1000
-- 1000'den başlat
CREATE TABLE invoices (
invoice_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
amount DECIMAL(10,2)
) AUTO_INCREMENT = 1000;
INSERT INTO invoices (amount) VALUES (599.99);
-- invoice_id = 1000📝 PostgreSQL Notu: PostgreSQL'de
AUTO_INCREMENTyerineSERIALveyaGENERATED ALWAYS AS IDENTITYkullanılır: ``sql -- PostgreSQL CREATE TABLE customers ( customer_id SERIAL PRIMARY KEY, -- veya customer_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY );``
2. NOT NULL — Boş Bırakılamaz
CREATE TABLE customers (
customer_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL, -- Boş bırakılamaz
last_name VARCHAR(50) NOT NULL, -- Boş bırakılamaz
email VARCHAR(100) NOT NULL, -- Boş bırakılamaz
phone VARCHAR(20), -- NULL olabilir (NOT NULL yok)
city VARCHAR(50) -- NULL olabilir
);
-- ✅ Başarılı
INSERT INTO customers (first_name, last_name, email)
VALUES ('Ahmet', 'Yılmaz', 'ahmet@email.com');
-- ❌ HATA — first_name NOT NULL ama değer verilmedi
INSERT INTO customers (last_name, email)
VALUES ('Kaya', 'zeynep@email.com');
-- ERROR: Column 'first_name' cannot be nullNe zaman NOT NULL kullanmalı?
Mutlaka olması gereken veriler: isim, e-posta, fiyat, sipariş tarihi
İş mantığı gereği zorunlu olan alanlar
Ne zaman NULL'a izin vermeli?
Opsiyonel bilgiler: telefon, ikinci adres, doğum tarihi
Henüz bilinmeyen veriler: kargo tarihi (sipariş verildi ama henüz kargoya verilmedi)
3. UNIQUE — Benzersizlik Kısıtlaması
CREATE TABLE customers (
customer_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL -- Benzersiz VE boş olamaz
);
-- ✅ Başarılı
INSERT INTO customers (first_name, last_name, email)
VALUES ('Ahmet', 'Yılmaz', 'ahmet@email.com');
-- ❌ HATA — aynı e-posta zaten var
INSERT INTO customers (first_name, last_name, email)
VALUES ('Ali', 'Demir', 'ahmet@email.com');
-- ERROR: Duplicate entry 'ahmet@email.com' for key 'email'PRIMARY KEY vs UNIQUE farkı:
| Özellik | PRIMARY KEY | UNIQUE |
|---|---|---|
| NULL olabilir mi | Hayır | Evet (bir kez) |
| Tabloda kaç tane | Sadece 1 | Birden fazla |
| Index oluşturur mu | Evet (clustered) | Evet (non-clustered) |
| Amacı | Satırı tanımlamak | Tekrarı önlemek |
-- Birden fazla sütun birlikte unique
CREATE TABLE employees (
employee_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE,
department_id INT UNSIGNED,
-- İsim + departman kombinasyonu unique olsun
UNIQUE KEY unique_name_dept (first_name, last_name, department_id)
);4. DEFAULT — Varsayılan Değer
CREATE TABLE products (
product_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(200) NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock_quantity INT UNSIGNED DEFAULT 0, -- Varsayılan: 0
is_active BOOLEAN DEFAULT TRUE, -- Varsayılan: TRUE
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- Varsayılan: şu anki zaman
);
-- stock_quantity ve is_active belirtilmezse varsayılan değer kullanılır
INSERT INTO products (product_name, price)
VALUES ('Kablosuz Mouse', 299.99);
-- stock_quantity = 0, is_active = TRUE, created_at = şu anDEFAULT ile kullanılabilecek değerler:
Sabit değer:
DEFAULT 0,DEFAULT 'pending',DEFAULT TRUEFonksiyon:
DEFAULT CURRENT_TIMESTAMP,DEFAULT (CURRENT_DATE)Expression (MySQL 8.0+):
DEFAULT (UUID()),DEFAULT (price * 1.18)
5. CHECK — Değer Kontrolü
CREATE TABLE products (
product_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(200) NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock_quantity INT UNSIGNED DEFAULT 0,
discount_percent DECIMAL(5,2) DEFAULT 0,
-- CHECK kısıtlamaları
CHECK (price > 0), -- Fiyat pozitif olmalı
CHECK (discount_percent BETWEEN 0 AND 100), -- İndirim %0-100 arası
CHECK (stock_quantity >= 0) -- Stok negatif olamaz
);
-- ✅ Başarılı
INSERT INTO products (product_name, price, discount_percent)
VALUES ('Laptop', 15999.99, 10);
-- ❌ HATA — negatif fiyat
INSERT INTO products (product_name, price)
VALUES ('Hatalı Ürün', -50);
-- ERROR: Check constraint 'products_chk_1' is violated
-- ❌ HATA — %100'den fazla indirim
INSERT INTO products (product_name, price, discount_percent)
VALUES ('Bedava Ürün', 100, 150);
-- ERROR: Check constraint is violated⚠️ Dikkat: MySQL 8.0.16 öncesinde
CHECKconstraint'i kabul edilir ama uygulanmaz (parse eder ama kontrol etmez). MySQL 8.0.16+ sürümünde düzgün çalışır. Versiyon kontrolü önemli!
📝 PostgreSQL Notu: PostgreSQL'de
CHECKconstraint'leri baştan beri tam desteklenir ve çok daha güçlüdür.
6. FOREIGN KEY — Yabancı Anahtar
Tablolar arası ilişkiyi kurar ve referans bütünlüğünü korur:
-- Önce referans verilen tablo (parent)
CREATE TABLE categories (
category_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
category_name VARCHAR(100) NOT NULL
);
-- Sonra referans veren tablo (child)
CREATE TABLE products (
product_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(200) NOT NULL,
category_id INT UNSIGNED,
price DECIMAL(10,2) NOT NULL,
-- Foreign key tanımı
FOREIGN KEY (category_id) REFERENCES categories(category_id)
);
-- Önce kategori ekle
INSERT INTO categories (category_name) VALUES ('Elektronik'); -- ID: 1
INSERT INTO categories (category_name) VALUES ('Giyim'); -- ID: 2
-- ✅ Başarılı — category_id = 1 categories tablosunda var
INSERT INTO products (product_name, category_id, price)
VALUES ('Laptop', 1, 15999.99);
-- ❌ HATA — category_id = 99 categories tablosunda yok
INSERT INTO products (product_name, category_id, price)
VALUES ('Hayalet Ürün', 99, 100);
-- ERROR: Cannot add or update a child row: a foreign key constraint failsON DELETE ve ON UPDATE davranışları:
Parent tablodaki satır silindiğinde veya güncellendiğinde child tabloda ne olacağını belirler:
CREATE TABLE products (
product_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(200) NOT NULL,
category_id INT UNSIGNED,
price DECIMAL(10,2) NOT NULL,
FOREIGN KEY (category_id) REFERENCES categories(category_id)
ON DELETE SET NULL -- Kategori silinirse, ürünün category_id'si NULL olsun
ON UPDATE CASCADE -- Kategori ID'si değişirse, ürünlerde de değişsin
);| Seçenek | ON DELETE davranışı | ON UPDATE davranışı |
|---|---|---|
RESTRICT (varsayılan) | Silmeye izin vermez | Güncellemeye izin vermez |
CASCADE | Child satırları da siler | Child'daki referansı günceller |
SET NULL | Child'daki FK'yı NULL yapar | Child'daki FK'yı NULL yapar |
SET DEFAULT | Varsayılan değere ayarlar | Varsayılan değere ayarlar |
NO ACTION | RESTRICT ile aynı (MySQL'de) | RESTRICT ile aynı |
-- RESTRICT (varsayılan) — En güvenli
-- Siparişi olan müşteriyi silmeye çalışırsan HATA alırsın
CREATE TABLE orders (
order_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
customer_id INT UNSIGNED NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
ON DELETE RESTRICT
);
-- CASCADE — Tehlikeli ama bazen gerekli
-- Siparişi silince, sipariş kalemlerini de otomatik sil
CREATE TABLE order_items (
item_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
order_id INT UNSIGNED NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(order_id)
ON DELETE CASCADE
);
-- SET NULL — Orta yol
-- Kategorisi silinince, ürün kategorisiz kalsın (NULL)
CREATE TABLE products (
product_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
category_id INT UNSIGNED,
FOREIGN KEY (category_id) REFERENCES categories(category_id)
ON DELETE SET NULL
);⚠️ Dikkat:
ON DELETE CASCADEçok güçlü ve tehlikeli bir özelliktir. Bir parent satırı sildiğinde otomatik olarak tüm ilişkili child satırlar da silinir. Eğer zincirleme foreign key'ler varsa, tek birDELETEkomutu yüzlerce satırı silebilir. Bu yüzden genellikleRESTRICTtercih edilir ve silme işlemi uygulama katmanında kontrollü yapılır.
IF NOT EXISTS — Güvenli Tablo Oluşturma
-- Tablo zaten varsa hata vermez
CREATE TABLE IF NOT EXISTS categories (
category_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
category_name VARCHAR(100) NOT NULL
);Bu özellik, script'leri tekrar tekrar çalıştırabilmen için önemlidir.
Constraint İsimlendirme
Constraint'lere isim vermek debug ve bakım açısından çok değerlidir:
CREATE TABLE products (
product_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(200) NOT NULL,
category_id INT UNSIGNED,
price DECIMAL(10,2) NOT NULL,
discount_percent DECIMAL(5,2) DEFAULT 0,
-- İsimlendirilmiş constraint'ler
CONSTRAINT fk_products_category
FOREIGN KEY (category_id) REFERENCES categories(category_id)
ON DELETE SET NULL,
CONSTRAINT chk_products_price
CHECK (price > 0),
CONSTRAINT chk_products_discount
CHECK (discount_percent BETWEEN 0 AND 100)
);İsim vermenin faydaları:
Hata mesajlarında anlamlı isim görürsün:
"Constraint 'fk_products_category' violated"vs"Constraint 'products_ibfk_1' violated"ALTER TABLE ... DROP CONSTRAINT isimile kolayca kaldırabilirsinKodun okunabilirliği artar
İsimlendirme kuralları (convention):
Foreign key:
fk_tablo_referans→fk_products_categoryCheck:
chk_tablo_kural→chk_products_priceUnique:
uq_tablo_sutun→uq_customers_email
Tablo Oluşturma Sırası — Foreign Key Dikkat Noktası
Foreign key kullanan tablolarda oluşturma sırası önemlidir. Referans verilen tablo önce oluşturulmalıdır:
-- ❌ YANLIŞ SIRA — products, categories'e referans veriyor ama categories henüz yok
CREATE TABLE products (
product_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
category_id INT UNSIGNED,
FOREIGN KEY (category_id) REFERENCES categories(category_id)
);
-- ERROR: References a table that doesn't exist
CREATE TABLE categories (
category_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
category_name VARCHAR(100)
);-- ✅ DOĞRU SIRA — önce parent, sonra child
CREATE TABLE categories (
category_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
category_name VARCHAR(100)
);
CREATE TABLE products (
product_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
category_id INT UNSIGNED,
FOREIGN KEY (category_id) REFERENCES categories(category_id)
);Dairesel referans (circular reference) durumu:
Bazen iki tablo birbirine referans verir. Bu durumda foreign key'i sonradan eklersin:
-- employees ve departments birbirine referans veriyor
-- Önce FK'sız oluştur
CREATE TABLE departments (
department_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
department_name VARCHAR(100) NOT NULL,
manager_id INT UNSIGNED NULL
);
CREATE TABLE employees (
employee_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
department_id INT UNSIGNED,
manager_id INT UNSIGNED NULL,
FOREIGN KEY (department_id) REFERENCES departments(department_id),
FOREIGN KEY (manager_id) REFERENCES employees(employee_id)
);
-- Sonradan FK ekle
ALTER TABLE departments
ADD CONSTRAINT fk_dept_manager
FOREIGN KEY (manager_id) REFERENCES employees(employee_id);Generated Columns (Hesaplanan Sütunlar)
MySQL 5.7+ sürümlerinde, diğer sütunlardan otomatik hesaplanan sütunlar tanımlayabilirsin:
CREATE TABLE order_items (
item_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
order_id INT UNSIGNED NOT NULL,
product_id INT UNSIGNED NOT NULL,
quantity INT UNSIGNED NOT NULL DEFAULT 1,
unit_price DECIMAL(10,2) NOT NULL,
discount_percent DECIMAL(5,2) DEFAULT 0,
-- Hesaplanan sütun: satır toplamı
line_total DECIMAL(10,2) GENERATED ALWAYS AS
(quantity * unit_price * (1 - discount_percent / 100)) STORED
);
INSERT INTO order_items (order_id, product_id, quantity, unit_price, discount_percent)
VALUES (1, 5, 3, 100.00, 10);
SELECT quantity, unit_price, discount_percent, line_total FROM order_items;
-- 3, 100.00, 10.00, 270.00 (3 * 100 * 0.90 = 270)VIRTUAL vs STORED:
VIRTUAL— Sorgulandığında hesaplanır, disk alanı kullanmazSTORED— INSERT/UPDATE'te hesaplanıp saklanır, index oluşturulabilir
Gerçek Dünya Örneği — E-Ticaret Veritabanı Tam Kurulum
Şimdi e-ticaret veritabanımızın tüm tablolarını doğru sırada oluşturalım:
-- E-Ticaret Veritabanı — Tam Tablo Kurulumu
-- ==========================================
DROP DATABASE IF EXISTS e_commerce;
CREATE DATABASE e_commerce CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE e_commerce;
-- 1. Kategoriler (parent referansı yok — ilk oluşturulabilir)
CREATE TABLE categories (
category_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
category_name VARCHAR(100) NOT NULL,
parent_category_id INT UNSIGNED NULL,
description TEXT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_category_parent
FOREIGN KEY (parent_category_id) REFERENCES categories(category_id)
ON DELETE SET NULL
) ENGINE=InnoDB;
-- 2. Ürünler (categories'e bağlı)
CREATE TABLE products (
product_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(200) NOT NULL,
category_id INT UNSIGNED,
price DECIMAL(10,2) NOT NULL,
stock_quantity INT UNSIGNED DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_products_category
FOREIGN KEY (category_id) REFERENCES categories(category_id)
ON DELETE SET NULL,
CONSTRAINT chk_products_price CHECK (price > 0)
) ENGINE=InnoDB;
-- 3. Müşteriler (bağımsız)
CREATE TABLE customers (
customer_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
phone VARCHAR(20),
city VARCHAR(50),
registration_date DATE DEFAULT (CURRENT_DATE),
is_active BOOLEAN DEFAULT TRUE,
CONSTRAINT uq_customers_email UNIQUE (email)
) ENGINE=InnoDB;
-- 4. Siparişler (customers'a bağlı)
CREATE TABLE orders (
order_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
customer_id INT UNSIGNED NOT NULL,
order_date DATETIME DEFAULT CURRENT_TIMESTAMP,
total_amount DECIMAL(10,2) DEFAULT 0,
status ENUM('pending','processing','shipped','delivered','cancelled')
DEFAULT 'pending',
shipping_address TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
ON DELETE RESTRICT
) ENGINE=InnoDB;
-- 5. Sipariş kalemleri (orders ve products'a bağlı)
CREATE TABLE order_items (
item_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
order_id INT UNSIGNED NOT NULL,
product_id INT UNSIGNED NOT NULL,
quantity INT UNSIGNED NOT NULL DEFAULT 1,
unit_price DECIMAL(10,2) NOT NULL,
discount_percent DECIMAL(5,2) DEFAULT 0,
CONSTRAINT fk_items_order
FOREIGN KEY (order_id) REFERENCES orders(order_id)
ON DELETE CASCADE,
CONSTRAINT fk_items_product
FOREIGN KEY (product_id) REFERENCES products(product_id)
ON DELETE RESTRICT,
CONSTRAINT chk_items_quantity CHECK (quantity > 0),
CONSTRAINT chk_items_discount CHECK (discount_percent BETWEEN 0 AND 100)
) ENGINE=InnoDB;
-- 6. Departmanlar (FK sonradan eklenecek)
CREATE TABLE departments (
department_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
department_name VARCHAR(100) NOT NULL,
manager_id INT UNSIGNED NULL
) ENGINE=InnoDB;
-- 7. Çalışanlar (departments'a ve kendine bağlı)
CREATE TABLE employees (
employee_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE,
department_id INT UNSIGNED,
manager_id INT UNSIGNED NULL,
hire_date DATE NOT NULL,
salary DECIMAL(10,2),
CONSTRAINT fk_emp_department
FOREIGN KEY (department_id) REFERENCES departments(department_id),
CONSTRAINT fk_emp_manager
FOREIGN KEY (manager_id) REFERENCES employees(employee_id)
) ENGINE=InnoDB;
-- 8. Departman yönetici FK'sini ekle (circular reference çözümü)
ALTER TABLE departments
ADD CONSTRAINT fk_dept_manager
FOREIGN KEY (manager_id) REFERENCES employees(employee_id);
-- Kontrol
SHOW TABLES;
DESC products;Bu script'te dikkat çeken noktalar:
Tablolar bağımlılık sırasına göre oluşturuldu
Her foreign key isimlendirildi (
fk_products_category,fk_orders_customervb.)Her tablo
ENGINE=InnoDBile oluşturuldu (foreign key desteği için şart)Circular reference
ALTER TABLEile çözüldüCHECK constraint'leri iş kurallarını koruyor
Storage Engine — InnoDB vs MyISAM
MySQL'de tablolar farklı storage engine'lerle oluşturulabilir:
| Özellik | InnoDB | MyISAM |
|---|---|---|
| Foreign Key | ✅ Destekler | ❌ Desteklemez |
| Transaction | ✅ Destekler | ❌ Desteklemez |
| Row-level locking | ✅ | ❌ (table-level) |
| Crash recovery | ✅ | Kısıtlı |
| Full-text search | ✅ (MySQL 5.6+) | ✅ |
| Varsayılan | ✅ (MySQL 5.5+) | Eski sürümlerde |
-- Engine belirtme
CREATE TABLE my_table (
id INT PRIMARY KEY
) ENGINE=InnoDB; -- Önerilen, zaten varsayılan💡 İpucu: MySQL 5.5'ten beri varsayılan engine InnoDB'dir. Özel bir sebebin yoksa her zaman InnoDB kullan. MyISAM'ın tek avantajı çok basit okuma ağırlıklı tablolarda marginal hız farkı — ama transaction ve foreign key desteği olmaması çok büyük dezavantaj.
Temporary Table — Geçici Tablolar
Sadece mevcut oturum süresince yaşayan tablolar:
CREATE TEMPORARY TABLE temp_bestsellers (
product_id INT UNSIGNED,
total_sold INT UNSIGNED
);
-- Bu tablo sadece senin oturumunda görünür
-- Oturum kapandığında otomatik silinir
-- Diğer kullanıcılar bu tabloyu göremezGeçici tablolar, karmaşık sorguların ara sonuçlarını tutmak için kullanılır. İleri derslerde daha detaylı göreceğiz.
DROP TABLE — Tabloyu Silme
-- Tabloyu sil
DROP TABLE products;
-- Güvenli silme
DROP TABLE IF EXISTS products;
-- Birden fazla tabloyu sil (sıra önemli — child önce!)
DROP TABLE IF EXISTS order_items;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS categories;
DROP TABLE IF EXISTS customers;⚠️ Dikkat: Foreign key ilişkisi olan tabloları silerken child tabloyu önce silmelisin. Aksi halde "Cannot drop table referenced by a foreign key constraint" hatası alırsın. Veya geçici olarak foreign key kontrolünü kapat:
-- FK kontrolünü kapat (sadece geliştirme ortamında!)
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS categories;
DROP TABLE IF EXISTS products;
-- Sıra fark etmez artık
-- FK kontrolünü tekrar aç!
SET FOREIGN_KEY_CHECKS = 1;Sıkça Yapılan Hatalar
PRIMARY KEY koymayı unutmak: Her tabloda bir primary key olmalı. Olmadan da tablo oluşabilir ama veri bütünlüğü ve performans açısından büyük dezavantaj. Her tabloya bir ID sütunu koy.
Foreign key veri tiplerinin uyuşmaması: Parent'taki
INT UNSIGNEDise child'daki daINT UNSIGNEDolmalı.INTileINT UNSIGNEDuyuşmaz ve foreign key oluşturamaz.CASCADE'i düşünmeden kullanmak:
ON DELETE CASCADErahat görünür ama yanlışlıkla bir parent satırı sildiğinde tüm child satırlar da gider. Özellikle production'daRESTRICTtercih et.Engine'i MyISAM bırakmak: Foreign key desteği olmaz, transaction desteği olmaz. InnoDB kullan.
NOT NULL'ı her yere koymak: Her sütun NOT NULL olmak zorunda değil. Opsiyonel alanlar (telefon, ikinci adres) NULL olabilmeli. Ama zorunlu alanlar (isim, e-posta, fiyat) kesinlikle NOT NULL olmalı.
Constraint isimlendirmemek: MySQL otomatik isim verir (
products_ibfk_1) ama bunlar anlaşılmaz. Constraint'lere anlamlı isim ver — hata ayıklama çok kolaylaşır.
Özet
CREATE TABLEile tablo oluşturulur, her sütunun veri tipi ve kısıtlamaları belirtilirPRIMARY KEY her satırı benzersiz tanımlar, tabloda sadece bir tane olabilir
AUTO_INCREMENT otomatik artan değer üretir, ID sütunları için ideal
NOT NULL sütunun boş bırakılmasını engeller
UNIQUE aynı değerin tekrar girmesini önler
DEFAULT varsayılan değer atar
CHECK değer aralığını kontrol eder (MySQL 8.0.16+)
FOREIGN KEY tablolar arası ilişki kurar ve referans bütünlüğünü korur
ON DELETEdavranışları: RESTRICT (güvenli), CASCADE (zincirleme sil), SET NULL (null yap)Tablo oluşturma sırası foreign key bağımlılıklarına göre belirlenmelidir
Storage engine olarak InnoDB kullan — foreign key ve transaction desteği için şart
Constraint'lere anlamlı isim ver — bakım ve debug kolaylaşır
AI Asistan
Sorularını yanıtlamaya hazır