Veri Tipleri: INT, VARCHAR, TEXT, DATE, DECIMAL
Giriş — Neden Veri Tipleri Önemli?
Bir form doldurduğunu düşün: "Adınız" alanına bir metin yazarsın, "Doğum tarihiniz" alanına bir tarih girersin, "Yaşınız" alanına bir sayı. Hiç kimse yaş alanına "Merhaba" yazmaz — çünkü o alana sayı bekleniyor.
İşte veritabanındaki sütunlar da tam olarak böyle çalışır. Her sütuna bir veri tipi (data type) atarsın ve o sütun sadece o tipte veri kabul eder. Fiyat sütununa metin yazamazsın, tarih sütununa sayı giremezsin.
Veri tipleri, SQL öğrenmenin temel taşlarından biridir çünkü:
Tablo oluştururken her sütunun tipini belirlemelisin
Yanlış veri tipi seçimi performans sorunlarına yol açar
Yanlış veri tipi seçimi veri bütünlüğü sorunlarına yol açar
Doğru veri tipi, disk alanından tasarruf sağlar
Bu derste MySQL'deki tüm temel veri tiplerini öğrenecek, her birinin ne zaman kullanılacağını anlayacaksın.
Veri Tipi Kategorileri
MySQL'deki veri tiplerini üç ana kategoriye ayırabiliriz:
Veri Tipleri
/ | \
Sayısal Metin Tarih/Zaman
/ \ | \ | \
Tam Ondalık CHAR TEXT DATE DATETIME
Sayı Sayı VARCHAR TIME TIMESTAMPŞimdi her birini detaylıca inceleyelim.
1. Sayısal Veri Tipleri (Numeric Types)
Tam Sayılar (Integer Types)
Tam sayılar, ondalık kısmı olmayan sayılardır: 1, 42, -100, 0...
| Tip | Boyut | Minimum (Signed) | Maksimum (Signed) | Maksimum (Unsigned) |
|---|---|---|---|---|
TINYINT | 1 byte | -128 | 127 | 255 |
SMALLINT | 2 byte | -32,768 | 32,767 | 65,535 |
MEDIUMINT | 3 byte | -8,388,608 | 8,388,607 | 16,777,215 |
INT (veya INTEGER) | 4 byte | -2,147,483,648 | 2,147,483,647 | 4,294,967,295 |
BIGINT | 8 byte | -9.2 × 10¹⁸ | 9.2 × 10¹⁸ | 1.8 × 10¹⁹ |
🎯 Analoji: Veri tiplerini kutu boyutları gibi düşün. Bir zarfın içine kitap sığmaz, bir TIR'la tek bir mektup taşımak israf olur. Aynı şekilde, bir yaş bilgisini (0-150 arası) saklamak için
BIGINTkullanmak gereksiz yer kaplar.TINYINT UNSIGNED(0-255) fazlasıyla yeterli.
Ne zaman hangisi?
-- Yaş, stok durumu gibi küçük sayılar
age TINYINT UNSIGNED, -- 0-255 arası, 1 byte
-- Departman sayısı, kategori sayısı
department_id SMALLINT UNSIGNED, -- 0-65535, 2 byte
-- Ürün ID, müşteri ID — çoğu uygulama için yeterli
customer_id INT UNSIGNED, -- 0-4.2 milyar, 4 byte
-- Çok büyük sayılar gerektiğinde (tıklama sayısı, log ID'leri)
click_count BIGINT UNSIGNED, -- Devasa sayılar, 8 byteSIGNED vs UNSIGNED:
-- SIGNED (varsayılan): Negatif ve pozitif değer alabilir
temperature INT, -- -2.1 milyar ile +2.1 milyar arası
-- UNSIGNED: Sadece pozitif değer alır ama üst limit 2 katına çıkar
product_id INT UNSIGNED, -- 0 ile 4.2 milyar arası⚠️ Dikkat: Primary key ve ID sütunları için
UNSIGNEDkullan. Negatif ID mantıksızdır veUNSIGNEDile kullanabileceğin aralığı ikiye katlarsın.
📝 PostgreSQL Notu: PostgreSQL'de
UNSIGNEDyoktur. Bunun yerineSMALLINT,INTEGER,BIGINTkullanılır.SERIAL(otomatik artan) tipi de mevcuttur.
Ondalık Sayılar (Decimal/Floating-Point Types)
Para, ağırlık, oran gibi ondalıklı sayılar için kullanılır.
| Tip | Boyut | Hassasiyet | Kullanım |
|---|---|---|---|
FLOAT | 4 byte | ~7 basamak | Bilimsel hesaplamalar |
DOUBLE | 8 byte | ~15 basamak | Daha hassas bilimsel hesaplamalar |
DECIMAL(M,D) | Değişken | Tam hassasiyet | Para, finans |
FLOAT ve DOUBLE'ın Tuzağı:
-- ❌ FLOAT ile para hesabı — YANLIŞ!
CREATE TABLE bad_example (
price FLOAT
);
INSERT INTO bad_example VALUES (19.99);
INSERT INTO bad_example VALUES (19.99);
INSERT INTO bad_example VALUES (19.99);
SELECT SUM(price) FROM bad_example;
-- Beklenti: 59.97
-- Gerçek sonuç: 59.970001220703125 (!!!)Neden böyle oluyor? FLOAT ve DOUBLE sayıları ikili (binary) formatta saklar. Tıpkı 1/3'ün ondalık sistemde tam yazılamaması (0.333...) gibi, bazı ondalık sayılar ikili sistemde tam temsil edilemez. Bu da küçük yuvarlama hatalarına neden olur.
-- ✅ DECIMAL ile para hesabı — DOĞRU
CREATE TABLE good_example (
price DECIMAL(10, 2) -- Toplam 10 basamak, 2'si ondalık
);
INSERT INTO good_example VALUES (19.99);
INSERT INTO good_example VALUES (19.99);
INSERT INTO good_example VALUES (19.99);
SELECT SUM(price) FROM good_example;
-- Sonuç: 59.97 (TAM DOĞRU)DECIMAL(M, D) sözdizimi:
M= Toplam basamak sayısı (precision)D= Ondalık basamak sayısı (scale)
DECIMAL(10, 2) -- 99999999.99'a kadar (8 tam + 2 ondalık)
DECIMAL(5, 2) -- 999.99'a kadar (3 tam + 2 ondalık)
DECIMAL(8, 4) -- 9999.9999'a kadar (4 tam + 4 ondalık)⚠️ Dikkat: Para (fiyat, bakiye, tutar) için her zaman
DECIMALkullan, aslaFLOATveyaDOUBLEkullanma. Kuruşluk farklar milyonlarca işlemde devasa tutarlara dönüşür.
E-ticaret veritabanımızda ondalık sayı kullanımı:
CREATE TABLE products (
product_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(200) NOT NULL,
price DECIMAL(10, 2) NOT NULL, -- Fiyat: max 99,999,999.99
weight DECIMAL(8, 3), -- Ağırlık (kg): max 99999.999
discount_percent DECIMAL(5, 2) DEFAULT 0 -- İndirim yüzdesi: max 100.00
);2. Metin (String) Veri Tipleri
CHAR vs VARCHAR — En Kritik Ayrım
| Tip | Sabit/Değişken | Max Boyut | Depolama |
|---|---|---|---|
CHAR(N) | Sabit uzunluk | 255 karakter | Her zaman N byte |
VARCHAR(N) | Değişken uzunluk | 65,535 karakter | Gerçek uzunluk + 1-2 byte |
🎯 Analoji:
CHAR(10)bir kutu gibidir — her zaman 10 birimlik yer kaplar, içine 3 harflik kelime koysan bile.VARCHAR(10)ise bir lastik çanta gibidir — 3 harflik kelime koyarsan 3+1 birimlik yer kaplar, 10 harflik kelime koyarsan 10+1.
-- CHAR(5) → Sabit 5 karakter
-- 'ABC' saklanır → 'ABC ' (2 boşluk eklenir, her zaman 5 byte)
-- 'ABCDE' saklanır → 'ABCDE' (tam oturdu)
-- VARCHAR(5) → Değişken, max 5 karakter
-- 'ABC' saklanır → 'ABC' (3 byte + 1 byte uzunluk bilgisi = 4 byte)
-- 'ABCDE' saklanır → 'ABCDE' (5 byte + 1 byte = 6 byte)Ne zaman CHAR, ne zaman VARCHAR?
-- ✅ CHAR — Uzunluğu sabit olan veriler
country_code CHAR(2), -- 'TR', 'US', 'DE' — her zaman 2 karakter
gender CHAR(1), -- 'M', 'F', 'X' — her zaman 1 karakter
phone_code CHAR(3), -- '+90', '+44' — her zaman 2-3 karakter
currency_code CHAR(3), -- 'TRY', 'USD', 'EUR' — her zaman 3 karakter
-- ✅ VARCHAR — Uzunluğu değişen veriler
first_name VARCHAR(50), -- İsimler 2 ile 50 karakter arası değişir
email VARCHAR(100), -- E-postalar çok farklı uzunlukta olabilir
address VARCHAR(500), -- Adresler kısa da olabilir uzun da💡 İpucu: Emin değilsen
VARCHARkullan.CHARyalnızca verinin uzunluğunun gerçekten sabit olduğu durumlarda avantajlıdır (ve bu avantaj marginaldir). Modern MySQL'deVARCHARperformansı çok iyidir.
TEXT Tipleri
Uzun metinler için TEXT ailesini kullanırsın:
| Tip | Max Boyut | Kullanım |
|---|---|---|
TINYTEXT | 255 byte | Kısa notlar |
TEXT | 65,535 byte (~64 KB) | Açıklama, biyografi |
MEDIUMTEXT | 16,777,215 byte (~16 MB) | Makale, blog yazısı |
LONGTEXT | 4,294,967,295 byte (~4 GB) | Kitap, çok uzun dokümanlar |
CREATE TABLE products (
product_id INT PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(200) NOT NULL,
short_description VARCHAR(500), -- Kısa açıklama
full_description TEXT, -- Detaylı açıklama
specifications MEDIUMTEXT -- Teknik özellikler (uzun olabilir)
);VARCHAR vs TEXT farkı:
| Özellik | VARCHAR(N) | TEXT |
|---|---|---|
| Max boyut | 65,535 byte | 65,535 byte (TEXT), 16MB (MEDIUMTEXT) |
| Index | Doğrudan indexlenebilir | Prefix index gerekir |
| Default değer | Verilebilir | MySQL'de verilemez |
| Bellekte depolama | Satır içinde | Ayrı bir alanda (overflow) |
| Performans | Kısa metinler için daha iyi | Uzun metinler için uygun |
⚠️ Dikkat: "Ne olur ne olmaz büyük koyayım" diye her sütunu
TEXTyapma. 50 karakterlik bir isim sütununuTEXTyapmak, indexleme ve performans açısından dezavantajlıdır. Boyutu tahmin edebildiğin sütunlardaVARCHARkullan.
ENUM ve SET
ENUM — Önceden tanımlı seçeneklerden birini seçer:
CREATE TABLE orders (
order_id INT PRIMARY KEY AUTO_INCREMENT,
status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled')
DEFAULT 'pending'
);
-- ✅ Doğru — tanımlı değerlerden biri
INSERT INTO orders (status) VALUES ('shipped');
-- ❌ Hata — tanımlı olmayan değer
INSERT INTO orders (status) VALUES ('bekleniyor');
-- MySQL strict mode'da hata verirSET — Önceden tanımlı seçeneklerden birden fazlasını seçer:
CREATE TABLE products (
product_id INT PRIMARY KEY AUTO_INCREMENT,
tags SET('yeni', 'indirimli', 'cok-satan', 'sinirli-stok')
);
INSERT INTO products (tags) VALUES ('yeni,indirimli'); -- İki etiket
INSERT INTO products (tags) VALUES ('cok-satan'); -- Tek etiket💡 İpucu: ENUM sipariş durumu, cinsiyet gibi sabit ve az seçenekli durumlar için güzeldir. Ama seçenek listesi sık değişiyorsa (mesela ürün etiketleri), ENUM yerine ayrı bir referans tablosu kullanmak daha esnektir. Çünkü ENUM'a yeni değer eklemek
ALTER TABLEgerektirir.
3. Tarih ve Zaman Veri Tipleri (Date/Time Types)
| Tip | Format | Aralık | Boyut | Kullanım |
|---|---|---|---|---|
DATE | 'YYYY-MM-DD' | 1000-01-01 → 9999-12-31 | 3 byte | Doğum tarihi, kayıt tarihi |
TIME | 'HH:MM:SS' | -838:59:59 → 838:59:59 | 3 byte | Süre, saat bilgisi |
DATETIME | 'YYYY-MM-DD HH:MM:SS' | 1000-01-01 → 9999-12-31 | 8 byte | Sipariş tarihi, olay zamanı |
TIMESTAMP | 'YYYY-MM-DD HH:MM:SS' | 1970-01-01 → 2038-01-19 | 4 byte | Oluşturma/güncelleme zamanı |
YEAR | 'YYYY' | 1901 → 2155 | 1 byte | Sadece yıl bilgisi |
DATE — Sadece Tarih
CREATE TABLE customers (
customer_id INT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50),
birth_date DATE, -- Sadece tarih: '1990-05-15'
registration_date DATE DEFAULT (CURRENT_DATE) -- Bugünün tarihi
);
INSERT INTO customers (first_name, birth_date)
VALUES ('Ahmet', '1990-05-15');
-- Tarih sorguları
SELECT * FROM customers WHERE birth_date > '1990-01-01';
SELECT * FROM customers WHERE YEAR(birth_date) = 1990;DATETIME vs TIMESTAMP — Çok Önemli Fark!
Bu ikisi aynı formatta görünür ama davranışları çok farklıdır:
CREATE TABLE test_dates (
id INT PRIMARY KEY AUTO_INCREMENT,
created_datetime DATETIME,
created_timestamp TIMESTAMP
);| Özellik | DATETIME | TIMESTAMP |
|---|---|---|
| Aralık | 1000-9999 | 1970-2038 |
| Boyut | 8 byte | 4 byte |
| Timezone | Timezone bilgisi yok | UTC'ye çevrilip saklanır, okunurken session timezone'a çevrilir |
| Default | NULL (belirtilmezse) | CURRENT_TIMESTAMP (ilk sütun için) |
| Auto-update | Manuel ayarlanır | ON UPDATE CURRENT_TIMESTAMP ile otomatik |
🎯 Analoji:
DATETIMEbir fotoğraftır — "15 Ocak 2024 saat 14:30" der ve bu bilgi hiç değişmez, hangi ülkede olursan ol aynı kalır.TIMESTAMPise bir dünya saatidir — UTC'ye göre saklanır ve sen neredeysen o timezone'a göre gösterilir. İstanbul'daki "17:00" ile Londra'daki "14:00" aslında aynıTIMESTAMP'tır.
-- Gerçek dünya kullanımı
CREATE TABLE orders (
order_id INT PRIMARY KEY AUTO_INCREMENT,
order_date DATETIME NOT NULL, -- Siparişin verildiği an
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Kayıt oluşturulma
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP -- Her güncelleme otomatik
);
INSERT INTO orders (order_date) VALUES ('2024-01-15 14:30:00');
-- updated_at otomatik güncellenir
UPDATE orders SET order_date = '2024-01-15 15:00:00' WHERE order_id = 1;
-- updated_at şimdi güncelleme zamanını gösterir⚠️ Dikkat:
TIMESTAMPtipi 2038 problemi taşır. 2038-01-19 03:14:07 UTC'den sonraki tarihleri saklayamaz (32-bit integer sınırı). Uzun vadeli tarih saklamak istiyorsanDATETIMEkullan. MySQL 8.0.28+ sürümlerindeTIMESTAMPartık 64-bit destekli olmaya başladı ama yine de dikkatli ol.
Hangi durumda hangisi?
-- DATETIME kullan:
birth_date DATE, -- Doğum tarihi (timezone önemsiz)
event_date DATETIME, -- Etkinlik tarihi (belirli bir an)
invoice_date DATETIME, -- Fatura tarihi
-- TIMESTAMP kullan:
created_at TIMESTAMP, -- Kayıt oluşturulma zamanı
updated_at TIMESTAMP, -- Son güncelleme zamanı
last_login TIMESTAMP, -- Son giriş zamanı4. Boolean Veri Tipi
MySQL'de gerçek bir BOOLEAN tipi yoktur — BOOLEAN yazarsan MySQL onu TINYINT(1)'e çevirir:
CREATE TABLE products (
product_id INT PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(200),
is_active BOOLEAN DEFAULT TRUE, -- Aslında TINYINT(1)
is_featured BOOLEAN DEFAULT FALSE -- 0 = FALSE, 1 = TRUE
);
-- Değer atama
INSERT INTO products (product_name, is_active, is_featured)
VALUES ('Laptop', TRUE, FALSE);
-- Arka planda: TRUE = 1, FALSE = 0
-- Sorgulama
SELECT * FROM products WHERE is_active = TRUE;
-- veya
SELECT * FROM products WHERE is_active = 1;
-- veya (kısa yol)
SELECT * FROM products WHERE is_active; -- 0 olmayan her değer "truthy"📝 PostgreSQL Notu: PostgreSQL'de gerçek bir
BOOLEANtipi vardır.TRUE,FALSEveNULLdeğerlerini kabul eder. Ayrıca'yes','no','on','off','1','0'gibi alternatif değerler de dönüştürülür.
💡 İpucu: Boolean sütun isimlendirmesinde
is_,has_,can_prefix'leri kullan:is_active,has_discount,can_return. Bu, sütunun boolean olduğunu isimden anlaşılır kılar.
5. Binary ve Blob Veri Tipleri
Binary tipler, ikili veri (resim, dosya, şifrelenmiş veri) saklamak için kullanılır:
| Tip | Max Boyut | Kullanım |
|---|---|---|
BINARY(N) | 255 byte | Sabit uzunluklu binary (UUID, hash) |
VARBINARY(N) | 65,535 byte | Değişken uzunluklu binary |
TINYBLOB | 255 byte | Çok küçük binary veri |
BLOB | 65 KB | İkon, küçük resim |
MEDIUMBLOB | 16 MB | Resim, döküman |
LONGBLOB | 4 GB | Video, büyük dosya |
-- UUID binary olarak saklamak (16 byte — VARCHAR(36)'dan verimli)
CREATE TABLE sessions (
session_id BINARY(16) PRIMARY KEY,
user_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Şifre hash'i saklamak
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
password_hash VARBINARY(255) NOT NULL
);⚠️ Dikkat: Resim ve dosyaları veritabanında saklamak genellikle iyi bir fikir değildir. Veritabanı büyür, backup'lar yavaşlar, performans düşer. En iyi pratik: dosyaları dosya sisteminde veya cloud storage'da (S3, GCS) sakla, veritabanında sadece dosya yolunu (path) tut.
6. JSON Veri Tipi
MySQL 5.7'den itibaren JSON veri tipi destekleniyor:
CREATE TABLE products (
product_id INT PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(200),
specifications JSON
);
INSERT INTO products (product_name, specifications) VALUES (
'iPhone 15',
'{"color": "blue", "storage": "256GB", "ram": "6GB", "screen": "6.1 inch"}'
);
-- JSON içinden veri çekme
SELECT product_name,
JSON_EXTRACT(specifications, '$.color') AS color,
specifications->>'$.storage' AS storage -- Kısa yol operatörü
FROM products;💡 İpucu: JSON tipi esnek yapılar için kullanışlıdır — ürün özellikleri gibi her ürünün farklı alanları olabilecek durumlar. Ama ilişkisel veritabanında her şeyi JSON'a tıkmak, ilişkisel modelin avantajlarından vazgeçmek demektir. JSON'ı ek veri için kullan, ana veri için normal sütunlar tercih et.
Veri Tipi Seçim Rehberi
Bir tablo tasarlarken hangi veri tipini seçeceğine karar vermek için şu tabloyu kullan:
| Veri | Önerilen Tip | Neden |
|---|---|---|
| ID (primary key) | INT UNSIGNED AUTO_INCREMENT | Hızlı, kompakt, otomatik artan |
| Ad, soyad | VARCHAR(50) | Değişken uzunluk, makul sınır |
| E-posta | VARCHAR(100) | RFC standardı max 254 karakter ama 100 pratikte yeter |
| Telefon | VARCHAR(20) | Ülke kodu dahil, farklı formatlar |
| Para (fiyat, tutar) | DECIMAL(10,2) | Tam hassasiyet, kuruş desteği |
| Adet, miktar | INT UNSIGNED | Negatif adet olmaz |
| Açıklama (kısa) | VARCHAR(500) | Index desteği |
| Açıklama (uzun) | TEXT | 64KB'a kadar |
| Doğum tarihi | DATE | Sadece tarih yeterli |
| Sipariş tarihi | DATETIME | Saat de önemli |
| Kayıt zamanı | TIMESTAMP | Auto-update, timezone-aware |
| Evet/Hayır | BOOLEAN (TINYINT(1)) | is_active, has_discount |
| Durum | ENUM(...) | Belirli seçeneklerden biri |
| Ülke kodu | CHAR(2) | Sabit uzunluk, ISO 3166-1 |
| IP adresi | VARCHAR(45) | IPv6 desteği (max 45 karakter) |
Gerçek Dünya Örneği — E-Ticaret Tabloları
Öğrendiğimiz veri tiplerini kullanarak e-ticaret tablolarımızı tasarlayalım:
-- Kategoriler
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
);
-- Ürünler
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,
compare_price DECIMAL(10, 2), -- Karşılaştırma fiyatı (üzeri çizili)
cost_price DECIMAL(10, 2), -- Maliyet fiyatı
stock_quantity INT UNSIGNED DEFAULT 0,
sku VARCHAR(50) UNIQUE, -- Stok kodu
weight DECIMAL(8, 3), -- Ağırlık (kg)
is_active BOOLEAN DEFAULT TRUE,
is_featured BOOLEAN DEFAULT FALSE,
short_description VARCHAR(500),
full_description TEXT,
specifications JSON, -- Esnek özellikler
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Müşteriler
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,
phone VARCHAR(20),
birth_date DATE,
gender ENUM('male', 'female', 'other'),
city VARCHAR(50),
address TEXT,
postal_code VARCHAR(10),
is_active BOOLEAN DEFAULT TRUE,
registration_date DATE DEFAULT (CURRENT_DATE),
last_login TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Siparişler
CREATE TABLE orders (
order_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
customer_id INT UNSIGNED NOT NULL,
order_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
total_amount DECIMAL(10, 2) NOT NULL DEFAULT 0,
discount_amount DECIMAL(10, 2) DEFAULT 0,
shipping_cost DECIMAL(8, 2) DEFAULT 0,
status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled')
DEFAULT 'pending',
shipping_address TEXT,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Sipariş kalemleri
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,
line_total DECIMAL(10, 2) GENERATED ALWAYS AS
(quantity * unit_price * (1 - discount_percent / 100)) STORED
);Bu tablolarda dikkat edilmesi gereken noktalar:
Para sütunlarında
DECIMALkullandıkID'lerde
INT UNSIGNED AUTO_INCREMENTkullandıkSabit değer listelerinde
ENUMkullandıkZaman damgalarında
TIMESTAMPveON UPDATEkullandıkBoolean sütunlara
is_prefix'i koydukline_totalbir generated column — otomatik hesaplanıyor
VARCHAR(N) — N Değerini Nasıl Seçerim?
Bu, yeni başlayanların en çok sorduğu sorulardan biri. N değeri fazla büyük olursa yer israfı olur mu?
Kısa cevap: VARCHAR(255) ile VARCHAR(50) arasında disk kullanımı farkı yoktur — çünkü VARCHAR sadece gerçek veriyi saklar. Ama bazı incelikler var:
Bellek kullanımı: MySQL bazı durumlarda (temporary table, sort buffer)
VARCHAR(N)'in N değerine göre bellek ayırır. Bu yüzdenVARCHAR(10000)gereksiz yere bellek tüketebilir.Dokümantasyon:
VARCHAR(50)yazmak "bu sütuna 50 karakterden uzun veri gelmez" demektir — kodunuzu okuyanlar için değerli bir bilgidir.Doğrulama: Veritabanı seviyesinde otomatik uzunluk kontrolü sağlar.
Pratik kurallar:
İsim, soyad:
VARCHAR(50)E-posta:
VARCHAR(100)(ya daVARCHAR(255)— ama 100 pratikte yeter)Başlık:
VARCHAR(200)URL:
VARCHAR(2048)(tarayıcı limiti)Kısa açıklama:
VARCHAR(500)Belirsiz ama kısa metin:
VARCHAR(255)(geleneksel güvenli değer)
Tür Dönüşümü (Type Casting) — Kısa Bir Ön Bakış
MySQL bazı durumlarda otomatik tür dönüşümü yapar, bazılarında sen CAST() veya CONVERT() kullanırsın. Bu konuyu ilerideki derslerde detaylıca işleyeceğiz ama temel bir örnek görelim:
-- Otomatik dönüşüm (implicit casting)
SELECT '10' + 5; -- Sonuç: 15 (MySQL '10'u sayıya çevirdi)
-- Manuel dönüşüm (explicit casting)
SELECT CAST('2024-01-15' AS DATE);
SELECT CAST(19.99 AS SIGNED); -- 20 (yuvarlama)⚠️ Dikkat: Otomatik tür dönüşümüne güvenme.
WHERE price = '19.99'çalışır (MySQL string'i DECIMAL'a çevirir) amaWHERE phone = 5551234yazmak beklenmedik sonuçlar doğurabilir. Veri tipine uygun değer kullan.
Sıkça Yapılan Hatalar
Para için FLOAT kullanmak: Bu dersin en önemli mesajı: para, fiyat, tutar, bakiye gibi değerler için DECIMAL kullan.
FLOATyuvarlama hataları yapar ve bu hatalar birikerek ciddi sorunlara yol açar.Her şeyi VARCHAR(255) yapmak: Tembel yaklaşım. Her sütunun mantıklı bir sınırı olmalı. İsim alanını
VARCHAR(255)yapmak teknik olarak çalışır amaVARCHAR(50)çok daha uygun ve anlamlı.Tarih bilgisini VARCHAR'da saklamak:
VARCHAR(10)'a '2024-01-15' yazmak çalışır ama tarih karşılaştırma, tarih hesaplama yapamaz.DATEtipini kullan — veritabanı motoru tarih işlemlerini çok daha verimli yapar.Telefon numarasını INT yapmak: Telefon numaraları sayı değildir — başında sıfır olabilir (+905551234567), tire veya boşluk içerebilir.
VARCHAR(20)kullan.UNSIGNED'ı unutmak: Negatif olması mümkün olmayan değerler (ID, stok, adet) için
UNSIGNEDkullan. Hem daha güvenli, hem daha geniş aralık.TIMESTAMP'in 2038 limitini bilmemek: Uzun vadeli tarihleri
TIMESTAMP'ta saklamak 2038'de sorun çıkarır. TarihlerDATETIME, sadece otomatik oluşturulma/güncelleme zamanlarıTIMESTAMPolsun.
Özet
Sayısal tipler: Tam sayılar için
INTailesi, para içinDECIMAL, bilimsel hesap içinFLOAT/DOUBLEMetin tipleri: Kısa ve sınırlı metinler için
VARCHAR, uzun metinler içinTEXT, sabit uzunluk içinCHARTarih tipleri: Sadece tarih için
DATE, tarih+saat içinDATETIME, otomatik zaman damgası içinTIMESTAMPBoolean:
BOOLEAN(aslındaTINYINT(1))ENUM: Sabit seçenek listesi
JSON: Esnek, yarı-yapılandırılmış veri
Para, fiyat, tutar → her zaman DECIMAL
Telefon, posta kodu → her zaman VARCHAR (sayı değil!)
Primary key → INT UNSIGNED AUTO_INCREMENT
Doğru veri tipi seçimi performans, güvenlik ve veri bütünlüğü açısından kritiktir
AI Asistan
Sorularını yanıtlamaya hazır