← Kursa Dön
📄 Text · 18 min

Python ile Veritabanı Temelleri: sqlite3 Modülü

Programlarımız çalışırken veriler RAM'de yaşar. Program kapandığı anda her şey uçar — tıpkı kaydetmeden kapattığın bir Word belgesi gibi. Peki ya kullanıcı bilgilerini, notları, sipariş geçmişini kalıcı olarak saklamak istersen? İşte tam burada veritabanları devreye girer.

Bu derste Python'un yerleşik (built-in) veritabanı modülü olan sqlite3'ü sıfırdan öğreneceğiz. Hiçbir kurulum yapmadan, hiçbir sunucu ayarlamadan, doğrudan Python ile veritabanı işlemlerini keşfedeceğiz.


1. Veritabanı Nedir?

Veritabanı (database), verilerin yapılandırılmış ve kalıcı olarak saklandığı bir sistemdir. Dosyaya yazmaktan farkı şu: veritabanları verileri düzenli tablolarda tutar, hızlıca arayabilir, filtreleyebilir ve güncelleyebilirsin.

📂 Analoji: Excel Tablosu → Veritabanı Tablosu

Bir Excel dosyası düşün. Her sayfa (sheet) bir tablo (table), her sütun bir alan (column/field), her satır ise bir kayıt (row/record). Veritabanı da tam olarak böyle çalışır — ama çok daha hızlı, çok daha güvenli ve milyonlarca satırı bile rahatça yönetebilir.

Excel KavramıVeritabanı Karşılığı
Dosya (.xlsx)Veritabanı (database)
Sayfa (sheet)Tablo (table)
Sütun başlığıAlan adı (column name)
SatırKayıt (row/record)
HücreDeğer (value)

SQLite Neden Özel?

SQLite, dünyanın en yaygın kullanılan veritabanı motorudur. Telefonunda, tarayıcında, hatta uçaktaki eğlence sisteminde bile SQLite çalışıyor olabilir. Avantajları:

  • Dosya tabanlı: Tüm veritabanı tek bir .db dosyası. Taşıması, yedeklemesi kolay.

  • Kurulum gerektirmez: PostgreSQL veya MySQL gibi sunucu kurmana gerek yok.

  • Python ile yerleşik gelir: import sqlite3 — hepsi bu. Ekstra pip install yok.

  • Sıfır yapılandırma: Bağlan ve kullanmaya başla.

Küçük-orta ölçekli projeler, prototipler ve masaüstü programlar için mükemmel bir seçim.


2. sqlite3 Modülü ile Bağlantı Kurma

Veritabanıyla çalışmanın ilk adımı bağlantı (connection) kurmak. Ardından bir cursor (imleç) oluşturuyoruz — SQL komutlarını bu cursor üzerinden çalıştırıyoruz. İşimiz bitince bağlantıyı kapatıyoruz.

import sqlite3

# Veritabanına bağlan (dosya yoksa otomatik oluşturulur)
conn = sqlite3.connect("notlar.db")

# Cursor oluştur — SQL komutlarını bu nesne çalıştırır
cursor = conn.cursor()

# Basit bir SQL komutu çalıştır
cursor.execute("SELECT sqlite_version()")
version = cursor.fetchone()
print(f"SQLite sürümü: {version[0]}")

# Bağlantıyı kapat
conn.close()

Burada üç temel adım var: connectcursorclose. connect() fonksiyonuna bir dosya adı veriyoruz. O dosya yoksa SQLite otomatik olarak oluşturur. cursor nesnesi ise SQL komutlarını veritabanına gönderen bir "aracı" gibi düşünülebilir.

Eğer veritabanını dosyaya kaydetmek istemiyorsan, bellekte (in-memory) de çalışabilirsin:

import sqlite3

# Bellekte geçici veritabanı — program kapanınca kaybolur
conn = sqlite3.connect(":memory:")
cursor = conn.cursor()

cursor.execute("SELECT 1 + 1")
result = cursor.fetchone()
print(f"Sonuç: {result[0]}")  # Sonuç: 2

conn.close()

:memory: özellikle testlerde çok işe yarar. Her test için temiz bir veritabanı oluşturabilirsin.

💡 İpucu: conn.close() çağrısını unutmak kaynak sızıntısına (resource leak) yol açabilir. Bir sonraki bölümde öğreneceğimiz context manager bu sorunu otomatik çözer.


3. Context Manager ile Bağlantı

try/finally ile close() çağırmak yerine, Python'un with ifadesini kullanmak çok daha güvenli ve temiz. Context manager bağlantıyı otomatik olarak yönetir.

import sqlite3

with sqlite3.connect("notlar.db") as conn:
    cursor = conn.cursor()
    cursor.execute("SELECT sqlite_version()")
    version = cursor.fetchone()
    print(f"SQLite sürümü: {version[0]}")
# with bloğu bitince:
# - Hata yoksa conn.commit() otomatik çağrılır
# - Hata varsa conn.rollback() otomatik çağrılır
# NOT: Bağlantı kapatılmaz, sadece transaction yönetilir

Önemli bir detay: with sqlite3.connect(...) bağlantıyı kapatmaz, sadece transaction'ı (işlemi) yönetir. Hata olmazsa commit(), hata olursa rollback() yapılır. Bu, veri bütünlüğü için harika bir güvence.

Gerçekten tam kapatma istersen contextlib.closing ile sarabilirsin, ama çoğu durumda with sqlite3.connect(...) as conn: yeterli ve temiz.


4. Tablo Oluşturma — CREATE TABLE

Verilerimizi saklamak için önce bir tablo (table) tanımlamamız gerekiyor. Her tablonun sütunları ve her sütunun bir veri tipi vardır.

import sqlite3

with sqlite3.connect("okul.db") as conn:
    cursor = conn.cursor()

    cursor.execute("""
        CREATE TABLE IF NOT EXISTS ogrenciler (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            isim TEXT NOT NULL,
            yas INTEGER,
            ortalama REAL,
            kayit_tarihi TEXT DEFAULT CURRENT_TIMESTAMP
        )
    """)
    print("Tablo oluşturuldu!")

IF NOT EXISTS ifadesi çok kritik. Bu olmadan, tabloyu ikinci kez oluşturmaya çalışırsan hata alırsın. Bu ifade "tablo zaten varsa dokunma" der.

SQLite'ın temel veri tipleri şunlar:

SQLite TipiPython KarşılığıAçıklama
INTEGERintTam sayı
REALfloatOndalıklı sayı
TEXTstrMetin
BLOBbytesİkili veri (binary)
NULLNoneBoş değer

Birden fazla tablo oluşturma örneği:

import sqlite3

with sqlite3.connect("magazin.db") as conn:
    cursor = conn.cursor()

    cursor.execute("""
        CREATE TABLE IF NOT EXISTS kategoriler (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            ad TEXT NOT NULL UNIQUE
        )
    """)

    cursor.execute("""
        CREATE TABLE IF NOT EXISTS urunler (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            ad TEXT NOT NULL,
            fiyat REAL NOT NULL,
            stok INTEGER DEFAULT 0,
            kategori_id INTEGER,
            FOREIGN KEY (kategori_id) REFERENCES kategoriler(id)
        )
    """)
    print("Tablolar oluşturuldu!")

PRIMARY KEY AUTOINCREMENT her yeni kayda otomatik artan benzersiz bir ID verir. NOT NULL o alanın boş bırakılamayacağını, UNIQUE ise değerin tekrar edemeyeceğini belirtir. FOREIGN KEY ise iki tablo arasında ilişki kurar.


5. Veri Ekleme — INSERT INTO

Tablomuza veri eklemek için INSERT INTO SQL komutunu kullanıyoruz.

Tek Kayıt Ekleme

import sqlite3

with sqlite3.connect("okul.db") as conn:
    cursor = conn.cursor()

    cursor.execute("""
        INSERT INTO ogrenciler (isim, yas, ortalama)
        VALUES (?, ?, ?)
    """, ("Elif Yılmaz", 20, 3.75))

    print(f"Eklenen kayıt ID: {cursor.lastrowid}")

Burada ? işaretlerine dikkat et — bunlar parametre yer tutucuları (parameter placeholders). Değerleri doğrudan SQL string'ine yazmak yerine tuple olarak ayrı geçiyoruz. Nedenini birazdan "SQL Injection" bölümünde göreceksin.

Çoklu Kayıt Ekleme — executemany

Birden fazla kaydı tek seferde eklemek için executemany() kullanıyoruz:

import sqlite3

with sqlite3.connect("okul.db") as conn:
    cursor = conn.cursor()

    ogrenciler = [
        ("Ahmet Kaya", 21, 3.20),
        ("Zeynep Demir", 19, 3.90),
        ("Can Özkan", 22, 2.85),
        ("Selin Aksoy", 20, 3.55),
        ("Mert Çelik", 23, 3.10),
    ]

    cursor.executemany("""
        INSERT INTO ogrenciler (isim, yas, ortalama)
        VALUES (?, ?, ?)
    """, ogrenciler)

    print(f"{cursor.rowcount} öğrenci eklendi.")

executemany() her tuple için aynı SQL komutunu tekrar tekrar çalıştırır ama bunu optimize edilmiş bir şekilde yapar. 1000 kayıt eklemen gerekiyorsa, döngü yerine kesinlikle executemany() tercih et.

cursor.lastrowid son eklenen kaydın ID'sini, cursor.rowcount ise etkilenen satır sayısını verir.


6. Parameterized Queries — SQL Injection Koruması

Bu bölüm en önemli bölümlerden biri. Kullanıcıdan gelen verileri doğrudan SQL string'ine gömmek, uygulamanı ciddi güvenlik açıklarına maruz bırakır.

⚠️ Dikkat: SQL Injection Tehlikesi!

>

Kullanıcı girdisini asla doğrudan SQL string'ine ekleme. Bu, veritabanının tamamının silinmesine veya hassas verilerin çalınmasına yol açabilir. Her zaman parameterized query kullan.

❌ YANLIŞ — Asla Böyle Yapma!

import sqlite3

# TEHLİKELİ! Kullanıcı girdisi doğrudan SQL'de
kullanici_adi = input("İsim girin: ")

with sqlite3.connect("okul.db") as conn:
    cursor = conn.cursor()
    # BU SATIR GÜVENLİK AÇIĞI!
    cursor.execute(f"SELECT * FROM ogrenciler WHERE isim = '{kullanici_adi}'")

Kullanıcı isim yerine '; DROP TABLE ogrenciler; -- yazarsa ne olur? SQL komutu şöyle olur:

SELECT * FROM ogrenciler WHERE isim = ''; DROP TABLE ogrenciler; --'

Tüm tablo silinir! Buna SQL Injection saldırısı denir.

✅ DOĞRU — Positional Parameters (?)

import sqlite3

kullanici_adi = input("İsim girin: ")

with sqlite3.connect("okul.db") as conn:
    cursor = conn.cursor()
    # Güvenli: ? yer tutucusu kullan
    cursor.execute(
        "SELECT * FROM ogrenciler WHERE isim = ?",
        (kullanici_adi,)
    )
    sonuclar = cursor.fetchall()
    for row in sonuclar:
        print(row)

? yer tutucusunu kullandığında, SQLite değerleri otomatik olarak kaçışlar (escape). Kullanıcı ne yazarsa yazsın, o veri SQL komutu olarak yorumlanmaz.

✅ DOĞRU — Named Parameters (:name)

import sqlite3

with sqlite3.connect("okul.db") as conn:
    cursor = conn.cursor()

    filtre = {
        "min_yas": 19,
        "min_ort": 3.0
    }

    cursor.execute("""
        SELECT isim, yas, ortalama FROM ogrenciler
        WHERE yas >= :min_yas AND ortalama >= :min_ort
        ORDER BY ortalama DESC
    """, filtre)

    for row in cursor.fetchall():
        print(f"{row[0]} - Yaş: {row[1]}, Ort: {row[2]}")

İsimli parametreler (named parameters) özellikle çok parametreli sorgularda kodun okunabilirliğini artırır. Sözlük (dict) olarak geçiriyorsun, anahtar adları SQL'deki :name ile eşleşiyor.

💡 İpucu: Hangisini kullanmalısın? Az parametreli basit sorgularda ?, çok parametreli veya karmaşık sorgularda :name tercih et. Hangisini seçersen seç, asla f-string veya format() ile SQL oluşturma.


7. Veri Sorgulama — SELECT

Veritabanından veri çekmek en sık yapacağın işlem. SELECT komutu ile verileri sorgulayabilir, filtreleyebilir ve sıralayabilirsin.

Temel SELECT

import sqlite3

with sqlite3.connect("okul.db") as conn:
    cursor = conn.cursor()

    # Tüm kayıtları getir
    cursor.execute("SELECT * FROM ogrenciler")
    tum_ogrenciler = cursor.fetchall()

    for ogrenci in tum_ogrenciler:
        print(ogrenci)

SELECT * tüm sütunları getirir. Performans için sadece ihtiyacın olan sütunları belirtmek daha iyi:

cursor.execute("SELECT isim, ortalama FROM ogrenciler")

WHERE ile Filtreleme

import sqlite3

with sqlite3.connect("okul.db") as conn:
    cursor = conn.cursor()

    # Ortalaması 3.5'ten yüksek öğrenciler
    cursor.execute("""
        SELECT isim, ortalama FROM ogrenciler
        WHERE ortalama > ?
    """, (3.5,))

    for row in cursor.fetchall():
        print(f"{row[0]}: {row[1]}")

ORDER BY ile Sıralama ve LIMIT

import sqlite3

with sqlite3.connect("okul.db") as conn:
    cursor = conn.cursor()

    # En yüksek ortalamalı 3 öğrenci
    cursor.execute("""
        SELECT isim, ortalama FROM ogrenciler
        ORDER BY ortalama DESC
        LIMIT ?
    """, (3,))

    print("--- En Başarılı 3 Öğrenci ---")
    for i, row in enumerate(cursor.fetchall(), 1):
        print(f"{i}. {row[0]} — Ortalama: {row[1]}")

ORDER BY ... DESC büyükten küçüğe sıralar (ASC ise küçükten büyüğe, varsayılan). LIMIT dönen kayıt sayısını sınırlar. Büyük tablolarda LIMIT kullanmak performans için çok önemli.

BETWEEN, AND, OR, LIKE, IN gibi SQL operatörlerini WHERE ile kullanabilirsin. LIKE özellikle metin aramalarında işe yarar: WHERE isim LIKE '%Kaya%' isim içinde "Kaya" geçen tüm kayıtları bulur.


8. fetchone(), fetchall(), fetchmany() Farkları

Sorgu sonuçlarını farklı şekillerde alabilirsin. Her birinin kullanım alanı farklı.

fetchone() — Tek Kayıt

import sqlite3

with sqlite3.connect("okul.db") as conn:
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM ogrenciler WHERE id = ?", (1,))

    ogrenci = cursor.fetchone()
    if ogrenci:
        print(f"İsim: {ogrenci[1]}, Yaş: {ogrenci[2]}")
    else:
        print("Öğrenci bulunamadı.")

fetchone() bir kayıt döner veya sonuç yoksa None döner. Tek bir kayıt beklediğin durumlarda (ID ile arama gibi) ideal.

fetchall() — Tüm Kayıtlar

import sqlite3

with sqlite3.connect("okul.db") as conn:
    cursor = conn.cursor()
    cursor.execute("SELECT isim FROM ogrenciler")

    tumu = cursor.fetchall()  # Liste döner: [(isim1,), (isim2,), ...]
    print(f"Toplam {len(tumu)} öğrenci var.")

fetchall() tüm sonuçları bir listeye yükler. Küçük veri setleri için sorun yok ama milyonlarca kayıt dönüyorsa belleği tüketebilir.

fetchmany(size) — Belirli Sayıda Kayıt

import sqlite3

with sqlite3.connect("okul.db") as conn:
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM ogrenciler ORDER BY id")

    while True:
        batch = cursor.fetchmany(2)  # 2'şer 2'şer al
        if not batch:
            break
        for row in batch:
            print(row)
        print("--- sonraki grup ---")

fetchmany(size) belirtilen sayıda kayıt döner. Büyük veri setlerini parça parça işlemek istediğinde kullan. Veri bittiğinde boş liste döner.

Cursor'ı Doğrudan Iterate Etme

Aslında en verimli yol cursor'ı direkt döngüde kullanmak:

import sqlite3

with sqlite3.connect("okul.db") as conn:
    cursor = conn.cursor()
    cursor.execute("SELECT isim, ortalama FROM ogrenciler")

    for isim, ortalama in cursor:  # Her seferinde 1 satır belleğe alır
        print(f"{isim}: {ortalama}")

Bu yöntem bellek dostu (memory-efficient) çünkü tüm sonuçları bir kerede belleğe yüklemez. Büyük veri setleri için en iyi seçenek.


9. Veri Güncelleme — UPDATE

Mevcut kayıtları değiştirmek için UPDATE komutunu kullanıyoruz.

import sqlite3

with sqlite3.connect("okul.db") as conn:
    cursor = conn.cursor()

    # Belirli bir öğrencinin ortalamasını güncelle
    cursor.execute("""
        UPDATE ogrenciler
        SET ortalama = ?
        WHERE id = ?
    """, (3.95, 1))

    print(f"{cursor.rowcount} kayıt güncellendi.")

cursor.rowcount kaç kaydın etkilendiğini söyler. WHERE koşulu olmadan UPDATE çalıştırırsan tüm kayıtlar güncellenir — çok dikkatli ol!

Birden fazla alanı aynı anda güncelleyebilirsin:

import sqlite3

with sqlite3.connect("okul.db") as conn:
    cursor = conn.cursor()

    cursor.execute("""
        UPDATE ogrenciler
        SET yas = ?, ortalama = ?
        WHERE isim = ?
    """, (21, 3.80, "Elif Yılmaz"))

    if cursor.rowcount == 0:
        print("Eşleşen kayıt bulunamadı!")
    else:
        print(f"{cursor.rowcount} kayıt güncellendi.")

⚠️ Dikkat: UPDATE veya DELETE komutlarında WHERE koşulunu unutma! WHERE olmadan tüm tablodaki kayıtlar etkilenir. Bu çok yaygın ve tehlikeli bir hatadır.


10. Veri Silme — DELETE

Kayıtları silmek için DELETE FROM komutunu kullanıyoruz.

import sqlite3

with sqlite3.connect("okul.db") as conn:
    cursor = conn.cursor()

    # Belirli bir kaydı sil
    cursor.execute("DELETE FROM ogrenciler WHERE id = ?", (3,))
    print(f"{cursor.rowcount} kayıt silindi.")

Koşullu silme örnekleri:

import sqlite3

with sqlite3.connect("okul.db") as conn:
    cursor = conn.cursor()

    # Ortalaması 2.0'ın altında olanları sil
    cursor.execute(
        "DELETE FROM ogrenciler WHERE ortalama < ?",
        (2.0,)
    )
    print(f"{cursor.rowcount} kayıt silindi.")

    # Tüm tabloyu temizlemek istersen (DİKKAT!)
    # cursor.execute("DELETE FROM ogrenciler")

Gerçek projelerde silmeden önce kaydın var olup olmadığını kontrol etmek iyi bir pratik.


11. Row Factory — sqlite3.Row Kullanımı

Şimdiye kadar sorgu sonuçları hep tuple olarak geldi: (1, "Elif", 20, 3.75). Hangi indeksin hangi sütun olduğunu hatırlamak zor. sqlite3.Row bunu çözer ve sonuçlara sözlük (dict) gibi erişmeni sağlar.

import sqlite3

with sqlite3.connect("okul.db") as conn:
    conn.row_factory = sqlite3.Row  # Bu satır farkı yaratır
    cursor = conn.cursor()

    cursor.execute("SELECT * FROM ogrenciler WHERE id = ?", (1,))
    ogrenci = cursor.fetchone()

    # Artık sütun adıyla erişebilirsin
    print(f"İsim: {ogrenci['isim']}")
    print(f"Yaş: {ogrenci['yas']}")
    print(f"Ortalama: {ogrenci['ortalama']}")
    print(f"Sütunlar: {ogrenci.keys()}")

sqlite3.Row hem indeks (ogrenci[1]) hem de anahtar (ogrenci['isim']) erişimini destekler. Kodu çok daha okunabilir hale getirir.

Bir Row nesnesini gerçek sözlüğe çevirmek de kolay:

import sqlite3

with sqlite3.connect("okul.db") as conn:
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()

    cursor.execute("SELECT * FROM ogrenciler")

    ogrenciler = [dict(row) for row in cursor.fetchall()]

    for ogr in ogrenciler:
        print(ogr)
    # {'id': 1, 'isim': 'Elif Yılmaz', 'yas': 20, 'ortalama': 3.75, ...}

Bu teknik özellikle JSON API'ler yaparken çok işe yarar çünkü sözlükleri doğrudan JSON'a dönüştürebilirsin.

💡 İpucu: conn.row_factory = sqlite3.Row satırını bağlantıyı açtıktan hemen sonra ayarla. Bundan sonra o bağlantıdan oluşturulan tüm cursor'lar Row nesneleri döner. Projelerinde varsayılan olarak kullanmanı tavsiye ederim.


12. Transaction Yönetimi — commit ve rollback

Bir transaction (işlem), birbirine bağlı veritabanı operasyonlarının "ya hep ya hiç" mantığıyla çalışmasını sağlar. Ya tümü başarılı olur, ya hiçbiri uygulanmaz.

Banka havalesi düşün: Bir hesaptan para çıkacak, diğerine girecek. Para çıktıktan sonra bir hata olursa, para havada kalmamalı — ilk işlem de geri alınmalı. İşte commit() ve rollback() bunu sağlar.

import sqlite3

conn = sqlite3.connect("banka.db")
cursor = conn.cursor()

cursor.execute("""
    CREATE TABLE IF NOT EXISTS hesaplar (
        id INTEGER PRIMARY KEY,
        isim TEXT,
        bakiye REAL
    )
""")
conn.commit()

try:
    # Ali'den Veli'ye 500 TL transfer
    cursor.execute("UPDATE hesaplar SET bakiye = bakiye - 500 WHERE isim = ?", ("Ali",))
    cursor.execute("UPDATE hesaplar SET bakiye = bakiye + 500 WHERE isim = ?", ("Veli",))

    # Her şey başarılıysa onayla
    conn.commit()
    print("Transfer başarılı!")

except Exception as e:
    # Hata olursa tüm değişiklikleri geri al
    conn.rollback()
    print(f"Hata! Transfer iptal edildi: {e}")

finally:
    conn.close()

SQLite'ın varsayılan davranışı: conn.commit() çağrılana kadar değişiklikler veritabanına yazılmaz. with bloğu kullandığında bu otomatik yönetilir.

Genel tavsiye: with bloğunu kullan, transaction yönetimi otomatik olsun. Manuel commit/rollback sadece karmaşık, çok adımlı senaryolarda gerekir.


13. Gerçek Dünya Projesi: Basit Not Defteri (CRUD)

Şimdi öğrendiklerimizi birleştirip CRUD (Create, Read, Update, Delete) operasyonlarını içeren gerçek bir uygulama yapalım: basit bir not defteri.

import sqlite3
from datetime import datetime


def veritabani_baglantisi(db_path="notlar.db"):
    """Veritabanı bağlantısı kur ve tabloyu oluştur."""
    conn = sqlite3.connect(db_path)
    conn.row_factory = sqlite3.Row
    conn.execute("""
        CREATE TABLE IF NOT EXISTS notlar (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            baslik TEXT NOT NULL,
            icerik TEXT NOT NULL,
            olusturma_tarihi TEXT DEFAULT CURRENT_TIMESTAMP,
            guncelleme_tarihi TEXT
        )
    """)
    conn.commit()
    return conn


def not_ekle(conn, baslik, icerik):
    """Yeni bir not ekle. (CREATE)"""
    cursor = conn.execute(
        "INSERT INTO notlar (baslik, icerik) VALUES (?, ?)",
        (baslik, icerik)
    )
    conn.commit()
    print(f"Not eklendi (ID: {cursor.lastrowid})")
    return cursor.lastrowid


def notlari_listele(conn):
    """Tüm notları listele. (READ)"""
    cursor = conn.execute(
        "SELECT id, baslik, olusturma_tarihi FROM notlar ORDER BY id DESC"
    )
    notlar = cursor.fetchall()

    if not notlar:
        print("Henüz not yok.")
        return

    print(f"\n{'ID':<5} {'Başlık':<30} {'Tarih':<20}")
    print("-" * 55)
    for n in notlar:
        print(f"{n['id']:<5} {n['baslik']:<30} {n['olusturma_tarihi']:<20}")


def not_detay(conn, not_id):
    """Belirli bir notu göster. (READ)"""
    cursor = conn.execute(
        "SELECT * FROM notlar WHERE id = ?", (not_id,)
    )
    n = cursor.fetchone()

    if n is None:
        print(f"ID {not_id} ile not bulunamadı.")
        return None

    print(f"\n📝 {n['baslik']}")
    print(f"   Tarih: {n['olusturma_tarihi']}")
    if n['guncelleme_tarihi']:
        print(f"   Güncelleme: {n['guncelleme_tarihi']}")
    print(f"\n{n['icerik']}\n")
    return dict(n)


def not_guncelle(conn, not_id, baslik=None, icerik=None):
    """Bir notu güncelle. (UPDATE)"""
    mevcut = conn.execute(
        "SELECT * FROM notlar WHERE id = ?", (not_id,)
    ).fetchone()

    if mevcut is None:
        print(f"ID {not_id} ile not bulunamadı.")
        return False

    yeni_baslik = baslik if baslik else mevcut['baslik']
    yeni_icerik = icerik if icerik else mevcut['icerik']
    simdi = datetime.now().isoformat()

    conn.execute("""
        UPDATE notlar
        SET baslik = ?, icerik = ?, guncelleme_tarihi = ?
        WHERE id = ?
    """, (yeni_baslik, yeni_icerik, simdi, not_id))
    conn.commit()
    print(f"Not güncellendi (ID: {not_id})")
    return True


def not_sil(conn, not_id):
    """Bir notu sil. (DELETE)"""
    mevcut = conn.execute(
        "SELECT baslik FROM notlar WHERE id = ?", (not_id,)
    ).fetchone()

    if mevcut is None:
        print(f"ID {not_id} ile not bulunamadı.")
        return False

    conn.execute("DELETE FROM notlar WHERE id = ?", (not_id,))
    conn.commit()
    print(f"'{mevcut['baslik']}' silindi.")
    return True


def not_ara(conn, anahtar_kelime):
    """Notlarda arama yap."""
    cursor = conn.execute("""
        SELECT id, baslik, olusturma_tarihi FROM notlar
        WHERE baslik LIKE ? OR icerik LIKE ?
        ORDER BY olusturma_tarihi DESC
    """, (f"%{anahtar_kelime}%", f"%{anahtar_kelime}%"))

    sonuclar = cursor.fetchall()
    print(f"\n'{anahtar_kelime}' için {len(sonuclar)} sonuç bulundu:")
    for n in sonuclar:
        print(f"  [{n['id']}] {n['baslik']} — {n['olusturma_tarihi']}")
    return sonuclar

Kullanım örneği:

# Veritabanını hazırla
conn = veritabani_baglantisi()

# Not ekle
not_ekle(conn, "Python Dersi", "sqlite3 modülünü öğrendim, harika!")
not_ekle(conn, "Alışveriş Listesi", "Süt, ekmek, yumurta, peynir")
not_ekle(conn, "Python Projesi", "Not defteri uygulaması tamamlandı")

# Listele
notlari_listele(conn)

# Detay gör
not_detay(conn, 1)

# Güncelle
not_guncelle(conn, 2, icerik="Süt, ekmek, yumurta, peynir, zeytin")

# Ara
not_ara(conn, "Python")

# Sil
not_sil(conn, 3)

# Bağlantıyı kapat
conn.close()

Bu küçük proje, tüm temel veritabanı operasyonlarını bir arada gösteriyor. Fonksiyonları ayrı tutmak kodun test edilmesini ve bakımını kolaylaştırır. row_factory = sqlite3.Row sayesinde sütunlara isimleriyle erişiyoruz — çok daha okunabilir.


14. sqlite3 vs SQLAlchemy vs Django ORM — Ne Zaman Hangisi?

Python'da veritabanı ile çalışmanın birden fazla yolu var. Her aracın güçlü olduğu senaryolar farklı.

sqlite3 — Ham SQL, Tam Kontrol

# sqlite3: Doğrudan SQL yazıyorsun
cursor.execute("SELECT * FROM users WHERE age > ?", (18,))
  • ✅ Yerleşik, ek kurulum yok

  • ✅ SQL öğrenmek için ideal

  • ✅ Küçük projeler, scriptler, prototipler

  • ❌ SQL string'lerini elle yönetmek zahmetli

  • ❌ Farklı veritabanına geçmek zor (SQL dialektleri farklı)

SQLAlchemy — Profesyonel ORM

# SQLAlchemy: Python nesneleriyle çalışıyorsun
from sqlalchemy import create_engine
from sqlalchemy.orm import Session

engine = create_engine("sqlite:///okul.db")
with Session(engine) as session:
    ogrenciler = session.query(Ogrenci).filter(
        Ogrenci.yas > 18
    ).all()
  • ✅ Hem ORM hem ham SQL desteği

  • ✅ Veritabanı bağımsız — PostgreSQL, MySQL, SQLite arası geçiş kolay

  • ✅ Migration (veritabanı versiyonlama) desteği (Alembic ile)

  • ✅ Büyük ve orta ölçekli projeler için ideal

  • ❌ Öğrenme eğrisi daha yüksek

  • ❌ Küçük scriptler için overkill olabilir

Django ORM — Web Framework'ün Parçası

# Django ORM: Model tanımla, Django gerisini halleder
from myapp.models import Ogrenci

ogrenciler = Ogrenci.objects.filter(yas__gt=18).order_by("-ortalama")
  • ✅ Django web projelerinde doğal entegrasyon

  • ✅ Admin paneli, migration, form entegrasyonu

  • ✅ Çok hızlı geliştirme

  • ❌ Django dışında kullanmak pratik değil

  • ❌ Karmaşık sorgularda ham SQL'e düşmek gerekebilir

Karar Tablosu

SenaryoÖnerilen Araç
Python öğreniyorum, SQL pratiği yapacağımsqlite3
Küçük script, veri analizi, otomasyonsqlite3
Orta-büyük proje, birden fazla veritabanı desteğiSQLAlchemy
REST API (FastAPI, Flask)SQLAlchemy
Django web projesiDjango ORM
Mobil uygulama backend'iSQLAlchemy veya Django ORM
Tek seferlik veri işleme scriptisqlite3

Tavsiyem: önce sqlite3 ile SQL'in temellerini öğren. SQL'i anlamadan ORM kullanmak, araba kullanmayı bilmeden otomatik vites sürmek gibi — çalışır ama ne yaptığını bilmezsin.


Özet

  • 📌 SQLite dosya tabanlı, kurulum gerektirmeyen, Python ile yerleşik gelen bir veritabanı motorudur. import sqlite3 ile hemen kullanmaya başlayabilirsin.

  • 📌 Temel iş akışı: connect()cursor()execute()commit()close(). Context manager (with) kullanarak transaction yönetimini otomatikleştirebilirsin.

  • 📌 Parameterized queries (? veya :name) her zaman kullan. Kullanıcı girdisini asla doğrudan SQL string'ine ekleme — SQL injection saldırılarına karşı bu tek savunma hattın.

  • 📌 fetchone(), fetchall(), fetchmany() ve cursor iteration ile sorgu sonuçlarını farklı şekillerde alabilirsin. Büyük veri setlerinde cursor'ı doğrudan iterate etmek en verimli yöntem.

  • 📌 conn.row_factory = sqlite3.Row ayarı ile sorgu sonuçlarına sözlük gibi (row['isim']) erişebilirsin. Kodun okunabilirliğini ciddi ölçüde artırır.

  • 📌 Proje büyüdükçe sqlite3'ten SQLAlchemy'ye veya Django ORM'e geçmeyi düşün. Ama önce SQL temellerini sqlite3 ile öğrenmek, ilerideki tüm veritabanı çalışmalarını kolaylaştırır.


*Bu ders, Python V2 müfredatının "Veritabanı Temelleri" konusunu destekleyen ek bir kaynaktır. Örnekleri kendi bilgisayarında deneyerek öğrenmeyi pekiştirebilirsin.*