Veritabanı Yönetim Sistemleri
Görünümler
Dr. Öğr. Üyesi Oğuz Emre Kural
2026
Öğrenme Kazanımları
Bu dersin sonunda:
Birden çok tabloyu birleştiren sorguya CREATE VIEW ile ad verip görünümü bir tablo gibi sorgulayabilirsiniz.
GROUP BY içeren bir özet sorgusunu görünüm olarak tanımlayabilir; TEMPORARY VIEW ve CTE ile geçici ara sonuçlar oluşturabilirsiniz.
Görünümü bir tabloyu mantıksal parçalara ayırmak ve hassas sütunları gizlemek için kullanabilir, bu korumanın SQLite’taki sınırını açıklayabilirsiniz.
Görünüme neden doğrudan DELETE ve INSERT uygulanamadığını açıklayabilir, INSTEAD OF tetikleyicisiyle yumuşak silmeyi görünüm üzerinden yürütebilirsiniz.
Dört kazanım dersin dört bölümüne denk geliyor: sadeleştirme ve toplama, geçici görünümler, bölümleme ve güvenlik, yumuşak silme. Dersin ana fikri tek cümle: görünüm, bir sorguya verilen addır . Bu cümleyi ders boyunca tekrar edeceğiz; öğrenci bunu yakalarsa gerisi uygulama.
Geçen Dersten Bu Derse
Dördüncü haftada kapanan bir durağı silmek yerine closed sütunuyla işaretlemeyi öğrendik; bedeli her okuma sorgusuna aynı koşulu yazmaktı. Bir sorguda koşulu unutursak kapanmış durak listeye geri döner. Sorgu uzadıkça aynı yapıyı her yerde yeniden yazmak da hata kaynağı olur.
Bugün aynı sorunları yeni bir veri setinde yaşayacağız: çevrimiçi bir kitabevi . Önce senaryoyu ve tablolarını sırayla kuralım; sonra sorgulara geçelim.
Bugünün sorusu: Bir kez yazdığımız sorguya ad verip sonradan bir tabloymuş gibi çağırabilir miyiz?
Geçen derse bağlayın: yumuşak silmenin bedeli her okuma sorgusuna aynı koşulu yazmaktı. Bugün veri seti değişiyor, fikir aynı kalıyor: işaretleme sütunu bu kez closed değil discontinued; onu birazdan kitaplar tablosunda göreceğiz. Öğrencilere “Bu koşulu bir yere yazıp unutabilsek ne olurdu?” diye sorun. Ders sonunda bu soruya cevap vereceğiz. Önce yeni veri setini tanıtıyoruz, çünkü sonraki her örnek bu tablolara dayanacak.
Senaryo: Çevrimiçi Kitabevi
Kurgusal bir internet kitabevi düşünün: üye olan müşteriler , katalogdaki kitaplar ve verilen siparişler .
MÜŞTERİ
Selin Aydın
Ankara
selin.aydin@example.com
verir
SİPARİŞ
1 adet · 249 TL
23 Ağustos 2026, 12:43
teslim edildi
içerir
KİTAP
Nutuk
Mustafa Kemal Atatürk · Bilim
bugünkü fiyat: 279 TL
Bu kayıtlardan yanıt bekleyen sorular
sadeleştir Selin Aydın hangi kitapları aldı?
topla Hangi kitap kaç adet satıldı?
böl Ağustos, Eylül, Ekim raporları ayrı ayrı
kişisel veriyi gizle Analist müşteri kimliğini görmemeli
Soldan sağa okuyun: bir müşteri sipariş verir, sipariş bir kitabı içerir. Siparişteki fiyatın (249 TL) kitabın bugünkü fiyatından (279 TL) farklı olduğuna dikkat çektirin; bunun nedenini sipariş tablosunu kurarken göreceğiz. Müşteriler, e-posta adresleri, siparişler ve fiyatlar ders için kurgulandı; kitap adları ve yazarlar gerçek. Son adımdaki dört soru dersin dört bölümüne denk geliyor: sadeleştirme (birleştirme sorgusuna ad vermek), toplama (özet görünüm), bölümleme (aylık raporlar), kişisel veriyi gizleme. Öğrencilere bu sorulardan hangisini şimdiden çözebileceklerini sorabilirsiniz; birleştirme sorgusunu bilenler ilk sorudan başlayabilir.
Neden Tek Tablo Değil?
Akla gelen ilk fikir: her siparişi tek bir tabloya, tüm ayrıntılarıyla yazmak.
Selin Aydın
selin.aydin@example.com
Ankara
Nutuk
Mustafa Kemal Atatürk
24900
Selin Aydın
selin.aydin@example.com
Ankara
Huzur
Ahmet Hamdi Tanpınar
17500
Zeynep Çelik
zeynep.celik@example.com
İstanbul
Nutuk
Mustafa Kemal Atatürk
24900
Selin Aydın
selin.aydin@example.com
Ankara
Kürk Mantolu Madonna
Sabahattin Ali
7900
Aynı bilgi tekrar tekrar Selin'in adı, e-postası ve şehri üç kez; Nutuk ve yazarı iki kez yazıldı.
Güncelleme çelişki doğurur Selin taşınırsa üç satır değişmeli; biri unutulursa tablo kendisiyle çelişir.
Siparişsiz kitap eklenemez Henüz sipariş edilmemiş bir kitabı bu tabloya ekleyemeyiz.
Her bilgi bir kez, kendi tablosunda:
customers + books + authors + categories + ordersyalnızca kimlikler
Önce tek tabloyu, ardından Selin’in bilgilerinin üç kez, Nutuk’un bilgilerinin iki kez yazıldığını gösterin. Sorunu üç cümleyle özetleyin: tekrar eden veri, güncelleme sırasında çelişki riski ve hiç sipariş edilmemiş kitabın eklenememesi. Çözümde her bilgiyi kendi tablosunda tutuyoruz: müşteri, kitap, yazar ve kategori ayrı tablolarda; sipariş ise müşteri ve kitabın kimliğini taşıyor. Böylece Atatürk’ün adı ya da “Bilim” kategorisi her kitapta yeniden yazılmıyor. Bu fikir üçüncü haftadaki şema tasarımından tanıdık. Şimdi tabloları sırayla inceleyeceğiz.
customers: Müşteriler Tablosu
Önce müşteriler: her satır bir kişi.
CREATE TABLE customers ( id INTEGER PRIMARY KEY , name TEXT NOT NULL , email TEXT NOT NULL UNIQUE , city TEXT NOT NULL );
customers · tek satır
id 8 PK
name Selin Aydın
email selin.aydin@example.com UNIQUE
city Ankara
1
Elif Yıldırım
elif.yildirim@example.com
Samsun
2
Mert Aksoy
mert.aksoy@example.com
Ankara
8
Selin Aydın
selin.aydin@example.com
Ankara
Slaytta soldaki CREATE TABLE satırları sırayla yanar; sağdaki kart aynı sütunu bir satırın içinde gösterir. Her sütun için bir cümle söyleyin: id birincil anahtar, name boş olamaz, email benzersiz, city şehir. Son adımda gerçek satırlar görünür: 24 müşteri var, tablodaki 8 numaralı satır Selin Aydın. Ad ve e-posta kurgudur; example.com alan adı gerçek bir kişiyi göstermez. Bu sütunların kişisel veri olduğunu not düşün; güvenlik bölümünde yeniden karşımıza çıkacak. Öğrenciler SELECT * FROM "customers"; yazarsa 24 satırın tamamını görür.
categories: Kategoriler Tablosu
Kitapları gruplayan küçük bir tablo. Kategori adı her kitapta yeniden yazılmasın diye ayrı tutuyoruz.
CREATE TABLE categories ( id INTEGER PRIMARY KEY , name TEXT NOT NULL UNIQUE );
categories · tek satır
id 3 PK
name Bilim UNIQUE
1
Türk Edebiyatı
2
Dünya Edebiyatı
3
Bilim
Küçük bir tablo ama önemli bir fikir: kategori adını on kitapta yeniden yazmak yerine bir kez yazıp kimlikle gösteriyoruz. Adı değiştirmek istersek (örneğin Bilim yerine Bilim ve Teknoloji) tek satırı güncellemek yeter; kitaplar tablosuna dokunmayız. UNIQUE, aynı kategorinin iki ayrı satırla tutulmasını engeller. Bir sonraki slayttaki yazarlar tablosu da aynı mantıkla kuruluyor.
authors: Yazarlar Tablosu
Yazarlar için de ayrı bir tablo var: bir yazarın birden çok kitabı olabilir ve adını her kitapta yeniden yazmak istemeyiz.
CREATE TABLE authors ( id INTEGER PRIMARY KEY , name TEXT NOT NULL UNIQUE , country TEXT NOT NULL );
authors · tek satır
id 14 PK
name Mustafa Kemal Atatürk UNIQUE
country Türkiye
8
Orhan Pamuk
Türkiye
14
Mustafa Kemal Atatürk
Türkiye
16
Abraham Silberschatz
ABD
Yazarın adı ve ülkesi kitaptan bağımsız bilgidir: Orhan Pamuk hangi kitabında olursa olsun aynı kişidir. Bu yüzden yazar bilgisini kitaplar tablosuna yazmıyoruz; her yazar bir kez burada duruyor. Kitap adları ve yazarlar gerçektir; ülke sütunu ders için yalın tutulmuştur. Tabloda 21 yazar var ama 22 kitap var: bazı yazarların birden çok kitabı, bazı kitapların birden çok yazarı olduğu için eşleşme birebir değil. Bunu books tablosundan sonra göreceğimiz köprü tablo çözecek.
books: Kitaplar Tablosu
Sonra katalog: her satır bir kitap. Kategorinin adını değil, kimliğini tutuyoruz.
CREATE TABLE books ( id INTEGER PRIMARY KEY , title TEXT NOT NULL UNIQUE , category_id INTEGER NOT NULL , price_kurus INTEGER NOT NULL CHECK (price_kurus > 0), discontinued INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (category_id) REFERENCES categories(id) );
books · tek satır
id 18 PK
title Nutuk UNIQUE
category_id 3 FK → categories · Bilim
price_kurus 27900 CHECK = 279 TL
discontinued 0 DEFAULT 0 1 olursa satıştan kalkmış
2
Kürk Mantolu Madonna
1
8900
0
6
Çalıkuşu
1
12900
1
18
Nutuk
3
27900
0
Kitap adları gerçektir; fiyatlar kurgudur. Önceki tabloyla bağı kurun: category_id bir sayı olarak durur ama anlamını FOREIGN KEY verir, yani categories tablosunda olmayan bir kategoriye kitap yazılamaz. Kategori adını görmek için iki tabloyu birleştireceğiz; bu bedelin karşılığı, adın tek yerde durması. Fiyatı kuruş cinsinden tam sayı tutuyoruz (27900 kuruş = 279 TL); para için ondalık sayı yerine tam sayı kararı üçüncü haftadan tanıdık. CHECK(price_kurus > 0) anlamsız fiyatı veritabanının kendisi engellesin diye var. En önemli sütun discontinued: geçen derse bağlayın, bu closed sütununun karşılığı. Satıştan kalkan kitabı silemeyiz çünkü siparişleri ona bağlı; bu yüzden işaretliyoruz. Çalıkuşu satıştan kaldırılmış ama eski siparişleri duruyor; bu, dersin son bölümünün başlangıç noktası. Hiç sipariş edilmemiş iki kitap da var; onları LEFT JOIN bölümünde bulacağız. Öğrenciler “yazar nerede?” diye soracak: tam da bunu bir sonraki slayt cevaplıyor.
book_authors: Kitap ve Yazar Köprüsü
Bir kitabın birden çok yazarı olabilir: her kitap-yazar çifti bir satır.
CREATE TABLE book_authors ( book_id INTEGER NOT NULL , author_id INTEGER NOT NULL , PRIMARY KEY (book_id, author_id), FOREIGN KEY (book_id) REFERENCES books(id), FOREIGN KEY (author_id) REFERENCES authors(id) );
book_authors · tek satır
book_id 20 FK → books · Veritabanı Sistem Kavramları
author_id 16 FK → authors · Abraham Silberschatz
PRIMARY KEY (20, 16) PK aynı çift ikinci kez yazılamaz
9
8
Beyaz Kale
Orhan Pamuk
10
8
Kar
Orhan Pamuk
20
16
Veritabanı Sistem Kavramları
Abraham Silberschatz
20
17
Veritabanı Sistem Kavramları
Henry F. Korth
20
18
Veritabanı Sistem Kavramları
S. Sudarshan
Önce sorunu söyletin: üç yazarlı bir kitabın yazarını nereye yazarız? author sütununa Silberschatz, Korth, Sudarshan yazarsak bir yazarın kitaplarını aramak (LIKE ile bile) kırılgan olur ve her yazar adı kitap satırlarında tekrar eder. İkinci çözüm olan author1, author2, author3 sütunları ise dördüncü yazar için tabloyu değiştirmeyi gerektirir. Doğru çözüm köprü tablo: her kitap-yazar çifti bir satır. Kartta 20 numaralı kitap (Veritabanı Sistem Kavramları) ile 16 numaralı yazar (Abraham Silberschatz) var. Anahtar iki sütundan oluşuyor: tek başına book_id ya da author_id tekrar edebilir, ama çift tekrar edemez. Son adımdaki tabloda ilk iki sütun gerçek book_authors sütunlarıdır; gri kitap ve yazar sütunları okumayı kolaylaştırmak için eklendi, tabloda yoktur. Yazarsız kitap ya da kitapsız yazar bağı kurulamaz çünkü iki FOREIGN KEY var. Toplam 25 satır: 22 kitap, 21 yazar. Sonraki tablo orders da aynı türden bir köprü: müşteri ile kitabı bağlıyor.
orders: Siparişler Tablosu
Son olarak siparişler: bir müşterinin verdiği her sipariş ayrı bir satırdır.
CREATE TABLE orders ( id INTEGER PRIMARY KEY , customer_id INTEGER NOT NULL , book_id INTEGER NOT NULL , quantity INTEGER NOT NULL CHECK (quantity > 0), unit_price_kurus INTEGER NOT NULL CHECK (unit_price_kurus > 0), ordered_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, status TEXT NOT NULL DEFAULT 'delivered' CHECK (status IN ('delivered', 'returned')), FOREIGN KEY (customer_id) REFERENCES customers(id), FOREIGN KEY (book_id) REFERENCES books(id) );
orders · tek satır
id 34 PK
customer_id 8 FK → customers · Selin Aydın
book_id 18 FK → books · Nutuk
quantity 1 CHECK
unit_price_kurus 24900 CHECK Nutuk bugün 27900
ordered_at 2026-08-23 12:43:54 DEFAULT
status delivered CHECK
Sütunları üç grupta anlatın. İlk grup kimlikler: customer_id ve book_id yalnızca birer sayı gibi durur; bu sayıların bir müşteriyi ve bir kitabı göstermesini son adımdaki FOREIGN KEY satırları sağlar, yani var olmayan bir müşteriye sipariş yazılamaz. İkinci grup quantity ve unit_price_kurus: fiyatı siparişin içinde de saklıyoruz, çünkü kitabın fiyatı değişebilir. Karttaki örnek: Selin 23 Ağustos’ta Nutuk’u 24900 kuruştan almış; kitabın fiyatı sonradan 27900 oldu. Fiyat yalnızca books tablosunda dursaydı eski sipariş yanlış tutarla görünürdü. Üçüncü grup ordered_at ve status: DEFAULT değer verilmezse otomatik atanır, CHECK yalnızca iki durumu kabul eder. 133 siparişin 130’u teslim edilmiş, 3’ü iade. Son adımda mor ve sarı oklarla siparişin müşteriye ve kitaba nasıl bağlandığını gösterin; bir sonraki slayt altı tablonun tamamını harita olarak çizecek.
Veritabanının Haritası
Kurduğumuz altı tablo ve aralarındaki beş yabancı anahtar bağı:
Çevrimiçi kitabevi veritabanının ilişki şeması
customers, orders, books, categories, authors ve book_authors tabloları. Her siparişin tam bir müşterisi ve tam bir kitabı, her kitabın tam bir kategorisi ve en az bir yazarı vardır. orders müşterileri kitaplara, book_authors kitapları yazarlara bağlayan köprü tablolardır.
customers
id
PK
name
email
city
orders
id
PK
customer_id
FK
book_id
FK
quantity
unit_price_kurus
ordered_at
status
verir
books
id
PK
title
UNIQUE
category_id
FK
price_kurus
discontinued
satılır
categories
id
PK
name
UNIQUE
türü
book_authors
book_id
PK FK
author_id
PK FK
yazarları
authors
id
PK
name
UNIQUE
country
müşteri ↔ kitap · çoktan çoğa, köprü: orders
kitap ↔ yazar · çoktan çoğa, köprü: book_authors
tam bir
sıfır veya çok
bir veya çok
Notasyonu bir kez, yavaşça anlatın; haftalar sonra aynı çizgilerle karşılaşacaklar. Çizginin ucundaki simge o ucun kaç kayıt taşıdığını söyler: iki dik çizgi “tam bir”, daire artı çatal “sıfır veya çok”. Her sipariş tam bir müşteriye ve tam bir kitaba bağlı; bu yüzden iki bağda da “tam bir” ucu müşteri ve kitap tarafında duruyor. Bir müşteri hiç sipariş vermemiş olabilir, bir kitap hiç sipariş edilmemiş olabilir; bu yüzden sipariş uçları “sıfır veya çok”. Kitabın bir kategorisi vardır, ama bir kategoride çok kitap bulunur. Yazarlar için köprü tablo gerekiyor, çünkü ilişki çoktan çoğa. Çizgileri şöyle okutun: bir ucun simgesi, o uçtaki tablonun satırlarının öbür tablodaki bir satırla nasıl ilişkili olduğunu söyler. books ucundaki iki çizgi “bu bir kitaptır” demektir; book_authors ucundaki “bir veya çok” simgesi, o kitabın bir veya birden çok book_authors satırı olduğunu söyler. Yani “kitabın tam bir yazarı var” değil, “kitap bir veya çok çift içinde yer alır” okunur. İki çizgiyi birleştirince kitap ile yazar arasındaki gerçek ilişki çoktan çoğadır; son adımdaki kesikli çizgiler bu birleşik ilişkiyi gösteriyor. Köprü tablonun uçlarında “bir veya çok” simgesi var (bir çizgi artı çatal): bir kitabın en az bir yazarı, bir yazarın da en az bir kitabı olur; sıfır olamaz. Bunu orders uçlarıyla karşılaştırın: bir kitap hiç sipariş edilmemiş olabilir (sıfır), ama yazarsız kitap katalogda anlamsızdır. Önemli sınır: FOREIGN KEY “en az bir”i zorlamaz; bu bir iş kuralıdır ve ilerleyen derslerde tetikleyici ya da uygulama katmanıyla korunur. Şemada yazarsız kitap yazılabilir; book_catalog görünümü de bu yüzden onu listelemez. Son adımda iki köprüyü yan yana koyun: orders müşteri ile kitabı, book_authors kitap ile yazarı bağlar. İkisi de çoktan çoğa ilişkiyi iki tek-çok ilişkiye böler. Sonunda şunu sorun: “Hangi müşteri hangi yazarın kitabını aldı?” Cevap beş tablo (customers, orders, books, book_authors, authors); bugünün konusu böyle zincirleri tek bakışa indirmek.
Ders Veritabanı
Altı tablo hazır hâlde bir dosyada. Boş bir klasöre indirin:
sqlite3 kitapevi.db
sqlite> .headers on
sqlite> .mode column
sqlite> .tables
authors books customers
book_authors categories orders
Veritabanında 24 müşteri, 3 kategori, 21 yazar, 22 kitap, 25 kitap-yazar bağı ve 133 sipariş var. Kitap adları ve yazarlar gerçektir; müşteriler, e-posta adresleri, siparişler ve fiyatlar ders için kurgulandı, gerçek kişilerle ilgisi yok. Siparişler 1 Ağustos ile 31 Ekim 2026 arasına yayılıyor. .headers on ve .mode column çıktıyı slayttaki gibi hizalar. Öğrencilerden .tables komutunu çalıştırmalarını isteyin: altı tablo görmeliler. Bozarsanız dosyayı silip sqlite3 yeni.db ".read kitapevi.sql" ile yeniden kurabilirler.
Selin Aydın Hangi Kitapları Aldı?
Bu sorunun yanıtını bulmak için üç tabloya bakıyoruz:
SELECT "title" FROM "books" WHERE "id" IN ( SELECT "book_id" FROM "orders" WHERE "status" = 'delivered' AND "customer_id" = ( SELECT "id" FROM "customers" WHERE "name" = 'Selin Aydın' ) );
1 · customers id = 8
2 · orders book_id ∈ {18, 4, 2}
3 · books Kürk Mantolu Madonna, Huzur, Nutuk
Sorgu doğru, ancak üç SELECT ve iç içe sorgular okumayı zorlaştırıyor.
Sorguyu öğrenciye okutun, içten dışa sırayı kendileri söylesin. Sunumda her tıklamada ilgili katman vurgulanır ve sağda o adımın ara sonucu belirir. Vurgulanacak nokta: sorunun cevabı basit, yolu karmaşık. Bu bir yazım sorunu değil, tasarım sonucu: bilgi birden çok tabloya dağıtıldı. Aynı birleştirme sorgusunu her seferinde yeniden yazmak yerine ona bir kez ad vermek istiyoruz. Selin Aydın dört sipariş verdi ama üç farklı kitap aldı: Kürk Mantolu Madonna’yı iki kez sipariş etti.
Üç Tablo, Tek Bakış
Önce sorguyu yazmadan, tabloları yan yana koyup eşleşen satırları inceleyelim.
üç ayrı tablo customers ⋈ orders ⋈ books purchase_history (görünüm)
customers
id name
8 Selin Aydın
18 Ceren Yalçın
22 Defne Yavuz
11 Emre Taş
orders
customer_id datetime book_id
8 12:43:54 18
18 09:26:10 15
22 22:46:36 12
11 11:06:29 14
books
id title
18 Nutuk
15 1984
12 Dönüşüm
14 Veba
Bu görselde adımlara zaman ayırın. Adım 1: aynı renk aynı kaydı gösteriyor; Selin’in siparişi üç tabloda aynı yükseklikte duruyor. Adım 2: customers tablosunda id sütununu sağa alıyoruz; böylece customers.id ile orders.customer_id yan yana görünüyor. Adım 3: tablolar birbirine yaklaşıp tek bir sorgu sonucu oluşturuyor. Adım 4: yalnızca eşleştirme için kullanılan kimlik sütunları gizleniyor. Adım 5: sonuç, “kim, ne zaman, hangi kitap”. Slayttaki tablolar sadeleştirildi: ordered_at yalnızca saatle gösteriliyor; gerçek orders yedi sütunlu. Gerçekte satırlar aynı sırada olmayabilir; JOIN eşleşmeyi satır sırasına göre değil, kimliklere göre yapar.
JOIN’i Yazalım
Her JOIN, bir birincil anahtarı bir yabancı anahtara bağlar:
SELECT "name" AS "customer" , "city" , "ordered_at" , "title"
FROM "customers"
JOIN "orders" ON "customers" ."id" = "orders" ."customer_id"
JOIN "books" ON "books" ."id" = "orders" ."book_id"
WHERE "orders" ."status" = 'delivered' ;
Ceren Yalçın
Samsun
2026-08-01 09:26:10
1984
Yiğit Özkan
Adana
2026-08-02 17:45:58
Saatleri Ayarlama Enstitüsü
Defne Yavuz
İzmir
2026-08-02 22:46:36
Dönüşüm
…
…
…
…
ON: hangi iki sütun aynı değeri taşıyor? WHERE: iadeleri dışarıda bırakır.
Çıktı 130 satırdır; slaytta ilk üçü gösteriliyor. JOIN yazarken en sık hata ON koşulunu unutmak ya da yanlış sütunları eşitlemektir; koşul yoksa satırlar çarpılır (her satır her satırla eşleşir). "name" AS "customer" yeni sütun adı veriyor: görünümün sütun adlarını biz belirliyoruz. WHERE koşulu iade edilen üç siparişi dışarıda bırakır; bu sorgunun amacı gerçekleşen satışları görmek.
CREATE VIEW: Sorguya Ad Vermek
Bu sorguyu her seferinde yeniden yazmak istemiyoruz. Başına bir satır eklemek yeter:
CREATE VIEW "purchase_history" AS SELECT "name" AS "customer", "city", "ordered_at", "title"
FROM "customers"
JOIN "orders" ON "customers"."id" = "orders"."customer_id"
JOIN "books" ON "books"."id" = "orders"."book_id"
WHERE "orders"."status" = 'delivered';
→
sqlite_schema
customers table
categories table
authors table
books table
book_authors table
orders table
purchase_history view
kitapevi.db: 57344 bayt → 57344 bayt
Sözdizimi: CREATE VIEW ad AS sorgu;. Başka hiçbir şey eklemiyoruz. Komut çalışınca ekrana bir şey yazılmaz; sorguyu bir kez çalıştırıp sonucu saklamıyoruz, sorgunun kendisini saklıyoruz. Dosya boyutu bu veritabanında ls -l kitapevi.db ile bakıldığında 57344 bayt olarak kalır: SQLite sayfalarında hâlâ yer vardı ve eklenen şey birkaç yüz karakterlik sorgu metni. Büyük bir tabloda aynı durum geçerli: görünüm satırları çoğaltmaz. Öğrenciler kendi bilgisayarında ls -l kitapevi.db ile ya da dosya özelliklerinden bakabilir.
Görünümü Sorgulamak
purchase_history artık bir tablo gibi sorgulanabilir. Üç SELECT içeren iç içe sorgu yerine kısa bir sorgu yazabiliriz:
SELECT DISTINCT "title" FROM "purchase_history"
WHERE "customer" = 'Selin Aydın' ;
Nutuk
Huzur
Kürk Mantolu Madonna
Aynı yanıt; üstelik üç tabloyu bilmeye gerek yok. Görünüme WHERE, ORDER BY, GROUP BY ve JOIN bile uygulanabilir.
DISTINCT ekledik çünkü Selin Kürk Mantolu Madonna’yı iki kez sipariş etti; görünümde dört satır var, ama biz hangi kitapları aldığını sorduk. Burada dikkat çekilecek nokta soyutlama: görünümü kullanan kişi altındaki üç tabloyu, kimlik bağlarını ya da status = 'delivered' filtresini bilmeden doğru sonucu alıyor. Bu, veritabanında mantıksal veri bağımsızlığı diye anılır: altta tablo yapısı değişse bile görünümün tanımı güncellendiği sürece sorgular çalışmaya devam eder. Silberschatz’ın görünümler bölümünde bu fikir resmî olarak anlatılır.
Görünüm Nerede Duruyor?
Görünüm şemada bir nesnedir. Tabloların yanında listelenir:
authors books customers purchase_history
book_authors categories orders
SELECT "type" , "name" FROM "sqlite_schema"
WHERE "type" = 'view' ;
SQLite görünümün yalnızca sorgu metnini saklar; her sorguda alttaki sorgu yeniden çalışır.
.tables çıktısında purchase_history’nin tablolarla aynı listede görünmesi önemlidir: bir uygulama için görünüm ile tablo ayırt edilemez. .schema purchase_history yazılırsa saklanan metin görünür; SQLite sonuna sütun listesini bir yorum olarak ekler. Bu yüzden görünüm sonucu “saklamaz”: tablolar değişince görünüm de değişir. Bunun artısı güncellik, eksisi her sorguda yeniden hesaplamadır; bu bedeli “Görünümün Sınırları” slaytında konuşacağız.
Görünümü Sıralamak
Görünümler de bir tablo gibi sıralanabilir:
SELECT * FROM "purchase_history"
ORDER BY "ordered_at" DESC LIMIT 4 ;
Mert Aksoy
Ankara
2026-10-31 11:26:57
Kürk Mantolu Madonna
Barış Kılıç
Bursa
2026-10-30 19:08:18
Nutuk
Deniz Arslan
Samsun
2026-10-29 19:03:54
Yaban
Can Erdoğan
İstanbul
2026-10-29 15:34:54
Nutuk
ORDER BY görünümün tanımına da yazılabilir; ama sıralamayı sorgulayanın kararına bırakmak daha sağlıklıdır.
Öğrencilerin sık sorduğu soru: “Görünümün kendisi sıralı olabilir mi?” Evet, tanıma ORDER BY yazılabilir; ama görünümü kullanan sorgu başka bir sıra isterse tanımdaki sıra kaybolur. Tanımın içinde sıralama genellikle gereksizdir. En yeni dört siparişi seçtik: bu, görünümün sorgulamaya açık gerçek bir tablo gibi davrandığının kanıtı.
Görünüme Doğrudan Yazılamaz
Görünüm veri taşımaz; satırlar altındaki tablolarda durur. Bu yüzden değiştirmeye çalışırsak:
UPDATE "purchase_history" SET "title" = 'Huzur' WHERE "customer" = 'Selin Aydın';
customer Selin Aydın customers.name
city Ankara customers.city
ordered_at 2026-08-23 12:43 orders.ordered_at
title Nutuk books.title
books.title mı değişir? Nutuk'un adı Huzur olur: kitabı alan herkes etkilenir.
orders.book_id mi değişir? Selin'in siparişi Huzur kitabına bağlanır; kitabın adı aynı kalır.
Parse error: cannot modify purchase_history because it is a view
DELETE ve INSERT aynı hatayı verir: değiştirilecek veri orders ve books tablolarındadır.
Bu kuralın bir çıkış yolu var: tetikleyici . Dersin sonunda göreceğiz.
Animasyonu adım adım izletin: her sütunun rengi, değerin hangi tablodan geldiğini gösteriyor. title sütunu books.title’dan geliyor ama UPDATE iki şeyi kastedebilir. Neden izin verilmediğini sorun: title sütunu books.title’dan geliyor; değiştirirsek books tablosundaki kitabın adını mı, orders satırının kitap bağını mı değiştirmeliyiz? Belirsiz. Görünüm üç tablodan üretildiği için bir değişikliğin hangi tabloya yazılacağı bilinemez. Bu bölümün sonunda “ya görünüme yazmak istiyorsak?” sorusuna geri döneceğiz; yumuşak silme bölümünde cevabı INSTEAD OF tetikleyicisi.
Görünüm Neye Yarar?
Görünüm tek bir fikirdir, ama dört farklı işte karşımıza çıkar:
1 · SADELEŞTİRME Çok tabloyu tek tablo gibi göster Az önce yaptığımız: purchase_history
2 · TOPLAMA Özet sorgusunu yeniden kullan Toplam, sayı ve ortalamayı her sorguda yeniden yazma.
3 · BÖLÜMLEME Tabloyu mantıksal parçalara ayır Her uygulama kendi dilimini görsün.
4 · GÜVENLİK Hassas sütunları gizle Başkasına verilen veriden kişisel bilgiyi çıkar.
Bonus: Satıştan kaldırılan kitapları görünümün sonucundan çıkarmak. Dersin sonunda.
Dört amacı kısaca açın, ayrıntıya şimdi girmeyin. Her biri için ders boyunca bir örnek göreceğiz. Sadeleştirmeyi zaten yaptık. Dört amaç da aynı mekanizmadan çıkıyor: bir sorguya ad vermek. Fark, sorgunun içine ne yazdığımızdır.
Aynı Veri, Farklı Bakış
Aynı veritabanını kullanan üç birimin farklı bilgilere ihtiyacı var:
Muhasebe Kitap başına satış adedi ve gelir.book_summary
Analist Satış örüntüleri.orders_analysis
Depo Yalnızca satıştaki kitaplar.available_books
Hepsinin altında aynı altı tablocustomers · orders · books · categories · authors · book_authors
Bugünün yol haritası bu slayt. Üç görünümün adını şimdiden söyleyin; dersin ikinci yarısında her birini kuracağız. Mesaj şu: veritabanını bölmüyoruz, çoğaltmıyoruz; aynı veriye farklı bakış açıları tanımlıyoruz. Her bakış açısı bir görünüm.
Satırları Tek Satıra İndirmek
Muhasebe sipariş satırlarını değil, kitap başına özeti görmek istiyor. GROUP BY aynı değeri taşıyan satırları gruplar; toplama fonksiyonları her grubu tek satıra indirir:
title quantity unit_price_kurus
title orders revenue_kurus avg_price_kurus
Nutuk 1 24900
Veba 1 11900
Nutuk 2 27900
Veba 2 11900
Nutuk 2 27900
Veba 2 35700 11900.0
Nutuk 3 136500 26900.0
Slayttaki beş satır gerçek tablodan seçilmiş örnek (103, 122, 126, 128 ve 130 numaralı siparişler); gerçek tabloda 130 teslim edilmiş sipariş var. Aynı renk aynı kitabı gösteriyor. İkinci tıklamada satırlar yer değiştirip gruplarına toplanıyor: GROUP BY’ın yaptığı budur. Üçüncü tıklamada her grup tek satıra iniyor. Veba’da iki sipariş var (35700 kuruş gelir, ortalama birim fiyat 11900); Nutuk’te üç (136500 kuruş gelir, ortalama 26900: iki sipariş 27900 kuruştan, biri eski fiyat 24900’den). Gelir, adet ile birim fiyatın çarpımının toplamıdır. Fonksiyonun ne yaptığını görsel olarak anlayan öğrenci COUNT, SUM, AVG farkını ezberlemeden kavrar.
AVG ve ROUND
Ortalama birim fiyatı kitap başına hesaplayalım. Bir kitabın fiyatı zamanla değiştiyse ortalama uzun bir ondalıkla çıkabilir:
SELECT "book_id" , AVG ("unit_price_kurus" ) AS "avg_price"
FROM "orders" WHERE "status" = 'delivered'
GROUP BY "book_id" ;
1
24900.0
2
8172.72727272727
…
…
ROUND(değer, 2) iki ondalığa yuvarlar:
Kürk Mantolu Madonna (2 numaralı kitap) eski fiyat olan 7900 kuruştan ve sonradan zamlanan 8900 kuruştan satıldı; ortalama 8172,727… Bu yüzden uzun bir ondalık sayı görüyoruz. Fiyatı hiç değişmeyen kitaplarda sonuç tam sayı. Yuvarlama sonucu okumayı kolaylaştırır, ancak sonraki hesaplarda yuvarlanmış değeri kullanmak hata biriktirebilir. Görünüm tanımında ortalamayı yuvarlarsak onu kullanan sorgular da bu değeri alır. Bunu birkaç slayt sonra “ortalamaların ortalaması” örneğinde göreceğiz.
Kitap Adını da Getirelim
orders yalnızca kitap kimliğini taşır. Adı görmek için books ile birleştiriyoruz. Sırayı unutmayın: önce JOIN, sonra GROUP BY çalışır. Sonucu en yüksek gelire göre sıralayıp ilk beşi alıyoruz.
SELECT "books" ."id" , "title" ,
COUNT (* ) AS "orders" ,
SUM ("quantity" ) AS "units" ,
SUM ("quantity" * "unit_price_kurus" ) AS "revenue_kurus" ,
ROUND (AVG ("unit_price_kurus" ), 2 ) AS "avg_price_kurus"
FROM "orders"
JOIN "books" ON "books" ."id" = "orders" ."book_id"
WHERE "status" = 'delivered'
GROUP BY "books" ."id"
ORDER BY "revenue_kurus" DESC LIMIT 5 ;
Sorgu yazıldığı sırada değil, şu sırayla çalışır: 1 FROM + JOIN→ 2 WHERE→ 3 GROUP BY→ 4 SELECT→ 5 ORDER BY→ 6 LIMIT
Slayttaki altı kutu, SQL’in sorguyu yazıldığı sırada değil mantıksal sırayla çalıştırdığını gösterir: önce tablolar birleşir, sonra süzülür, gruplanır, seçilen sütunlar hesaplanır, sıralanır ve kesilir. GROUP BY JOIN’den sonra çalıştığı için gruplama yapılırken kitap adı da elimizde. GROUP BY "books"."id" yazdık çünkü title her kitap için tek değer; SQLite buna izin verir (bazı sistemler title’ı da gruplamayı ister). SUM("quantity" * "unit_price_kurus") satır başına adet çarpı birim fiyatı toplar. İadeler WHERE ile dışarıda olduğu için toplam yalnızca teslim edilen siparişlerdir. ORDER BY ... LIMIT yalnızca bu denemeye ait: görünümü tanımlarken bunları dışarıda bırakacağız. Sonucu bir sonraki slaytta görüyoruz.
Kitap Başına Özet
Gelire göre ilk beş kitap:
18
Nutuk
14
21
540900
25542.86
1
Tutunamayanlar
10
12
298800
24900.0
19
Temiz Kod
7
7
285000
40714.29
21
Pragmatik Programcı
5
6
228000
38000.0
14
Veba
10
15
178500
11900.0
Yirmi kitabın gelir toplamı 2862400 kuruş: teslim edilen bütün siparişlerin geliri.
Nutuk 21 adetle çok satıyor ve fiyatı ayın ortasında 24900’den 27900 kuruşa çıktığı için ortalama birim fiyatı 25542,86. Temiz Kod daha az adetle ama yüksek birim fiyatıyla gelirde üçüncü sırada. Yirmi satırın toplamı 2862400 kuruş; bu sayıyı SELECT SUM("quantity" * "unit_price_kurus") FROM "orders" WHERE "status" = 'delivered'; ile de doğrulayabilirler. Bir sonraki adım bu sorguya ad vermek.
book_summary: Özet Sorguyu View Yapmak
Bu sorguyu her gün yazmak yerine görünüm yapalım. ORDER BY ve LIMIT görünüme girmez; onlara sorgulayan karar verir:
CREATE VIEW "book_summary" AS
SELECT "books" ."id" , "title" ,
COUNT (* ) AS "orders" ,
SUM ("quantity" ) AS "units" ,
SUM ("quantity" * "unit_price_kurus" ) AS "revenue_kurus" ,
ROUND (AVG ("unit_price_kurus" ), 2 ) AS "avg_price_kurus"
FROM "orders"
JOIN "books" ON "books" ."id" = "orders" ."book_id"
WHERE "status" = 'delivered'
GROUP BY "books" ."id" ;
Sorgunun başına yalnızca CREATE VIEW "book_summary" AS ekledik; ORDER BY ve LIMIT hariç gerisi aynı. Görünümde sütun adlarını (orders, units, revenue_kurus) biz seçtik; görünümü sorgulayan kişi bu adları kullanır.
book_summary’yi Sorgulamak
En çok satan üç kitabı artık kısa bir sorguyla bulabiliriz:
SELECT "title" , "units" FROM "book_summary"
ORDER BY "units" DESC LIMIT 3 ;
Nutuk
21
Veba
15
Kürk Mantolu Madonna
13
Görünümü, tabloda SELECT yazıyormuş gibi sorguluyoruz: ORDER BY, LIMIT hepsi çalışır. Önemli olan, görünümün içindeki toplama sorgusunu bir daha hiç yazmamamız. Muhasebeden gelen “en çok satan üç kitap” sorusu artık iki satır.
Görünüm Güncel Sonucu Verir
Görünüm veri saklamaz; her sorgulandığında tablodaki güncel verilerle sonuç üretir:
orders (Nutuk siparişleri)
122
2026-10-26 12:04:58
2
130
2026-10-29 15:34:54
2
132
2026-10-30 19:08:18
2
134
2026-11-02 08:10:00
1
→ SELECT
book_summary
Nutuk
21 22
540900 568800
INSERT INTO "orders" ("customer_id" , "book_id" , "quantity" ,
"unit_price_kurus" , "ordered_at" )
VALUES (3 , 18 , 1 , 27900 , '2026-11-02 08:10:00' );
Öğrenci aynı komutu çalıştırırsa book_summary sorgusunda Nutuk’un adedinin 22’ye, gelirin 568800 kuruşa çıktığını görür (status sütununu yazmadığımız için varsayılan delivered olur). Denemeyi geri almak için DELETE FROM "orders" WHERE "ordered_at" = '2026-11-02 08:10:00'; yazılır. İki noktayı vurgulayın: Görünüme hiçbir şey yazmadık, yine de sonuç değişti, çünkü görünüm her sorguda altındaki sorguyu yeniden çalıştırıyor. Bunun bedeli: büyük bir tabloda her çağrıda toplamayı yeniden hesaplamak yavaş olabilir. PostgreSQL ve Oracle’da bu yüzden sonucu saklayan materialized view vardır; SQLite’ta yoktur.
Bir Kitap, Birden Çok Yazar
Yazarlar ayrı tabloda; birleştirince satırlar çoğalır: 22 kitap, 25 satır.
books
20
Veritabanı Sistem Kavramları
21
Pragmatik Programcı
book_authors
20
16
20
17
20
18
21
19
21
20
authors
16
Abraham Silberschatz
17
Henry F. Korth
18
S. Sudarshan
19
Andrew Hunt
20
David Thomas
JOIN sonucu: iki kitap, beş satır
20
Veritabanı Sistem Kavramları
Abraham Silberschatz
20
Veritabanı Sistem Kavramları
Henry F. Korth
20
Veritabanı Sistem Kavramları
S. Sudarshan
21
Pragmatik Programcı
Andrew Hunt
21
Pragmatik Programcı
David Thomas
↓ GROUP BY "books"."id"GROUP_CONCAT("authors"."name", ', ')
book_catalog: iki kitap, iki satır
20
Veritabanı Sistem Kavramları
Abraham Silberschatz, Henry F. Korth, S. Sudarshan
21
Pragmatik Programcı
Andrew Hunt, David Thomas
Slaytı üç tabloyla başlatın, çünkü karışıklığın kaynağı orada. books tablosunda her kitap tek satır; book_authors köprüsünde her kitap-yazar çifti ayrı satır (20 numaralı kitap için üç, 21 numaralı için iki); authors tablosunda yazar adları duruyor. İkinci adımda aynı renkli satırlar eşleşiyor: JOIN, 20 numaralı kitap için book_authors içinde üç uyan satır buluyor. Kuralı açıkça söyleyin: JOIN her uyan satır için kitabı yeniden yazar, yani sonuçtaki satır sayısı eşleşme sayısı kadardır. Bu yüzden 20 numaralı kitap üç, 21 numaralı kitap iki satır olur; tek yazarlı kitapta bir eşleşme olduğu için bir satır çıkar. 22 kitaplık katalog 25 satır verir; 25, book_authors tablosunun satır sayısıdır. Rapor için istediğimiz ise kitap başına tek satır (ve COUNT(*) kitap sayısını 22 yerine 25 bulmasın). Çare önceki derslerden tanıdık GROUP BY ile GROUP_CONCAT birlikteliği: GROUP BY aynı kitabın satırlarını tek grupta toplar, GROUP_CONCAT o grubun yazar adlarını ayırıcıyla tek metne birleştirir (COUNT, SUM, AVG gibi bir toplama fonksiyonudur). Hesabı bir kez yazıp görünüme çevireceğiz; sonraki slayt bunu yapıyor.
book_catalog: Yazarlı Katalog
Dört tabloyu birleştiren, kitap başına tek satır üreten sorguya ad verelim:
CREATE VIEW "book_catalog" AS
SELECT "books" ."id" , "title" ,
GROUP_CONCAT("authors" ."name" , ', ' ) AS "authors" ,
"categories" ."name" AS "category" , "price_kurus"
FROM "books"
JOIN "categories" ON "categories" ."id" = "books" ."category_id"
JOIN "book_authors" ON "book_authors" ."book_id" = "books" ."id"
JOIN "authors" ON "authors" ."id" = "book_authors" ."author_id"
GROUP BY "books" ."id" ;
SELECT "title" , "authors" FROM "book_catalog"
WHERE "id" IN (9 , 20 , 21 );
Beyaz Kale
Orhan Pamuk
Veritabanı Sistem Kavramları
Abraham Silberschatz, Henry F. Korth, S. Sudarshan
Pragmatik Programcı
Andrew Hunt, David Thomas
Bu, sadeleştirme amacının ikinci örneği: purchase_history üç tabloyu, book_catalog dört tabloyu tek bir ada indiriyor. Görünümü kullanan kişi categories, book_authors ve authors tablolarını, kimlik bağlarını ve GROUP_CONCAT ayrıntısını bilmek zorunda değil; yalnızca title, authors, category ve price_kurus sütunlarını görüyor. category sütunu categories."name" değerinin takma adıdır. Görünüm 22 satır verir: kitap başına bir. Dikkat: JOIN book_authors kullandığımız için yazarı henüz girilmemiş bir kitap katalogda görünmez; yazarsız kitap istenmiyorsa bu doğru davranış, istenseydi LEFT JOIN kullanırdık (birkaç slayt sonra göreceğiz). GROUP_CONCAT içindeki sıra garanti edilmez; bu veri setinde yazar kimliği sırasıyla geliyor.
Tuzak: Hiç Satılmayan Kitaplar Nereye Gitti?
books tablosunda yirmi iki kitap var, ama book_summary yirmi satır döndürüyor. Eksik olanlar:
SELECT "title" , "authors" FROM "book_catalog"
WHERE "title" NOT IN (SELECT "title" FROM "book_summary" );
Aylak Adam
Yusuf Atılgan
Kozmos
Carl Sagan
Neden? JOIN yalnızca iki tabloda da eşleşen satırları tutar. Hiç sipariş edilmemiş kitap orders ile eşleşmez ve sonuçtan düşer; özet raporda o kitap hiç yokmuş gibi görünür.
Kitapların adını ve yazarını book_catalog görünümünden aldık; görünüm başka bir görünümün üstüne de kurulabildiği gibi sorguda da kullanılabiliyor. Bu, görünümde sık yapılan bir hatanın erken uyarısı: görünümün sonucunu tam sanmak. Raporda “hiç satılmayan kitaplar” önemliyse JOIN yerine LEFT JOIN gerekir; bir sonraki slayt bunu gösteriyor. title sütunu UNIQUE olduğu için kitapları adla karşılaştırabiliyoruz.
JOIN ve LEFT JOIN: Eşleşmeyen Satıra Ne Olur?
Dört kitaba bakalım: ikisinin siparişi var, ikisinin yok. Aynı sorgu JOIN ve LEFT JOIN ile ne yapar?
books JOIN LEFT JOIN LEFT JOIN + COUNT
Yaban
6 satır
6
6
Nutuk
14 satır
14
14
Aylak Adam
eşleşme yok NULL
0
1
Kozmos
eşleşme yok NULL
0
1
Önceki slaytta “hiç sipariş edilmemiş kitap sonuçtan düşer” demiştik; burada nedenini gösteriyoruz. Birinci adım: dört kitap ve orders tarafındaki eşleşmeler (Yaban’ın 6, Nutuk’un 14 sipariş satırı var; Aylak Adam ve Kozmos için hiç yok). İkinci adım JOIN: yalnızca iki tarafta da eşleşen satırlar kalır, eşleşmesi olmayan iki kitap kaybolur. Üçüncü adım LEFT JOIN: soldaki (books) tablonun her satırı korunur, eşleşme yoksa orders tarafı NULL olur. Dördüncü adım sayma: COUNT("orders"."id") NULL değerleri saymaz, bu yüzden eşleşmeyen kitap için 0 çıkar; COUNT(*) ise satırın kendisini sayar ve eşleşmeyen kitapları 1 saydığı için yanlıştır. Sonraki slayttaki sorgu bu yüzden COUNT("orders"."id") kullanıyor.
LEFT JOIN: Eşleşmeyeni de Tut
LEFT JOIN soldaki tablonun tüm satırlarını korur; eşleşme yoksa sağ taraf NULL kalır:
SELECT "books" ."title" , COUNT ("orders" ."id" ) AS "orders"
FROM "books"
LEFT JOIN "orders" ON "books" ."id" = "orders" ."book_id"
AND "orders" ."status" = 'delivered'
GROUP BY "books" ."id" ;
…
…
Aylak Adam
0
…
…
Kozmos
0
Filtreyi ON içine yazdık; WHERE’e yazsaydık eşleşmeyen satırlar yine elenirdi.
Çıktı yirmi iki satırdır; slaytta yalnızca iki sıfır gösteriliyor. Aylak Adam ve Kozmos için orders sütunu 0 çıkar. COUNT(*) yerine COUNT("orders"."id") yazdık: COUNT(*) eşleşmeyen satırı da 1 sayardı. ON koşuluna status = 'delivered' yazdığımıza dikkat edin; WHERE’e yazsaydık LEFT JOIN yine eşleşmeyenleri eleyip INNER JOIN’e dönerdi. Kural: soldaki tablonun tüm satırlarını korumak istiyorsak sağ tabloya ait filtreyi ON içine yazarız.
VIEW, TEMP VIEW ve CTE’nin Geçerlilik Süresi
Bazen bir ara sorguyu şemada kalıcı olarak tutmak istemeyiz. Üç yöntem farklı süreler boyunca kullanılabilir:
bağlantı 1 · sqlite3 kitapevi.db bağlantı 2 · yeniden açıldı
TEMP VIEWbağlantı boyunca
Zaman çizelgesi olarak okutun: soldan sağa iki bağlantı. Kesikli çizgi .quit anı, yani oturumu kapatıp yeniden açtığımız yer. Kalıcı görünüm iki bağlantıda da kullanılabilir; geçici görünüm yalnızca ilk bağlantıda, CTE ise tanımlandığı sorguda geçerlidir. Dördüncü tıklamada geçici görünümün yeni bağlantıda neden bulunamadığını görüyoruz. Kapanışta bu ayrımı yeniden özetleyeceğiz.
CREATE TEMPORARY VIEW
Analist kategori özeti denemek istiyor ama bunu şemaya yazıp herkese göstermek istemiyor. Görünümün üstüne bir görünüm kuruyoruz:
CREATE TEMPORARY VIEW "category_summary" AS
SELECT "categories" ."name" AS "category" ,
SUM ("units" ) AS "units" ,
SUM ("revenue_kurus" ) AS "revenue_kurus" ,
ROUND (1.0 * SUM ("revenue_kurus" ) / SUM ("units" ), 2 )
AS "avg_price_kurus"
FROM "book_summary"
JOIN "books" ON "books" ."id" = "book_summary" ."id"
JOIN "categories" ON "categories" ."id" = "books" ."category_id"
GROUP BY "categories" ."name" ;
Bir görünümün üstüne başka bir görünüm kurulabilir; üstelik başka bir tabloyla birleşebilir: book_summary kategori bilgisi taşımadığı için önce books ile id üzerinden, sonra categories ile category_id üzerinden birleştirdik: görünümün üstündeki görünüm iki tabloyla daha konuşuyor. Geçici görünüm sqlite_schema’da görünmez; sqlite_temp_schema tablosunda durur. Geçici görünüm denemeler için birebir: bir sorgunun doğru olup olmadığını denemek, kendi analizim için ara adım kurmak, ama başkalarının görmesini ya da şemayı kirletmesini istemediğim durumlar. Ağırlıklı ortalamayı bilerek kullandık (toplam gelir bölü toplam adet); iki slayt sonra neden diye soruyor.
Geçici Görünümü Sorgulamak
SELECT * FROM "category_summary" ;
Bilim
37
1218900
32943.24
Dünya Edebiyatı
57
596100
10457.89
Türk Edebiyatı
63
1047400
16625.4
Üç kategorinin toplamı: 157 adet, 2862400 kuruş. Tek tek kitapları görmeden kategori düzeyinde karşılaştırma yapabiliyoruz.
Kategori toplamları kitap toplamlarının toplamıdır: 37 + 57 + 63 = 157 adet; 1218900 + 596100 + 1047400 = 2862400 kuruş, yani az önceki toplam gelir. Bu bir doğrulama yöntemi: üst görünümün toplamı alt görünümün toplamıyla eşleşiyor mu? Bilim kategorisi az adetle ama yüksek fiyatlı kitaplarla gelirde ilk sırada.
Bağlantı Kapanınca Geçici Görünüm Kaybolur
Geçici görünüm yalnızca bu bağlantıda geçerlidir. sqlite3 oturumunu kapatıp yeniden açalım:
sqlite> .quit
$ sqlite3 kitapevi.db
sqlite> SELECT * FROM "category_summary";
Parse error: no such table: category_summary
Üstüne kurulduğu book_summary kalıcı olduğu için yeni bağlantıda hâlâ çalışır; kaybolan yalnızca üstündeki geçici katmandır.
Bu, geçerlilik süresi çizelgesinin dördüncü adımının terminaldeki karşılığı. Kalıcı görünüm şemada durduğu için iki bağlantıda da kullanılabilir; geçici görünüm .quit ile kaybolur. Öğrenciler kendi terminallerinde deneyebilir.
Tuzak: Ortalamaların Ortalaması
Kategori ortalamasını yazmanın kolay yolu, kitap ortalamalarının ortalamasını almaktır. Sonuç yanlıştır:
SELECT "categories" ."name" AS "category" ,
ROUND (AVG ("avg_price_kurus" ), 2 ) AS "yanlis"
FROM "book_summary"
JOIN "books" ON "books" ."id" = "book_summary" ."id"
JOIN "categories" ON "categories" ."id" = "books" ."category_id"
GROUP BY "categories" ."name" ;
Bilim: yanlış 39814.29 kitap ortalamalarının ortalaması
Bilim: doğru 32943.24 toplam gelir ÷ toplam adet
Dünya Edebiyatı 10014.29 ile 10457.89 aynı fark, aynı neden
Neden? Kitaplar farklı adetlerde satıldı: az satan pahalı kitap, çok satan ucuz kitapla eşit ağırlık almamalı. Doğru yöntem: SUM(gelir) / SUM(adet).
Bu tuzak yalnızca SQL’e özgü değil, istatistik. Görünümün üstüne görünüm kurmak bu hatayı kolaylaştırır: alt görünümün avg_price_kurus sütunu hazır olduğu için üstteki sorguda AVG yazmak çok doğal gelir. Kural: özet sütunlarını (ortalama, oran) bir üst düzeyde yeniden özetlemeyin; ham toplamları (adet, gelir) taşıyın ve oranı en son hesaplayın. Bilim kategorisinde Veritabanı Sistem Kavramları 55000 kuruşluk ortalamayla yalnızca 3 adet satıldı; Nutuk ise 21 adet. Yanlış yöntem pahalı ama az satan kitapların etkisini abartıyor.
Ortalamaların Ortalaması: Ağırlık Hesabı
Farkın nereden geldiğini Bilim kategorisinin dört kitabında adım adım görelim:
Bilim kategorisi: book_summary satırları
Nutuk
21
25542.86
540900
1 21
Temiz Kod
7
40714.29
285000
1 7
Pragmatik Programcı
6
38000.0
228000
1 6
Veritabanı Sistem Kavramları
3
55000.0
165000
1 3
Yanlış: ortalamaların ortalaması (25542.86 + 40714.29 + 38000 + 55000) ÷ 4 = 39814.29
Doğru: toplam gelir ÷ toplam adet 1218900 ÷ 37 = 32943.24
Fark 6871 kuruş: az satan pahalı kitaplar ortalamayı şişirdi.
Bir önceki slaytta iki sayı gösterdik; burada neden farklı olduklarını açıyoruz. Tablo book_summary görünümünden Bilim kategorisinin dört kitabı: Nutuk 21, Temiz Kod 7, Pragmatik Programcı 6, Veritabanı Sistem Kavramları 3 adet satmış (toplam 37). Birinci yol her kitabın avg_price_kurus değerini toplayıp 4’e böler; bu, her kitabı aynı ağırlıkla sayar. 3 adet satan 55000 kuruşluk kitap, 21 adet satan 25542,86 kuruşluk kitap kadar etkili olur. İkinci yol her kitabı sattığı adet kadar sayar: toplam gelir 1218900, toplam adet 37, oran 32943,24. Hesap SUM(adet * fiyat) / SUM(adet) ile aynıdır. Öğrencilere sorun: “Bir kitabevinde bir kategorinin ortalama satış fiyatı dediğimizde hangisini kastederiz?” Müşterinin gerçekten ödediği fiyatları kastederiz; bu da ağırlıklı yoldur.
CTE: Tek Sorgunun Görünümü
Ara sonuç yalnızca bu sorguya özelse: CTE .
WITH "per_book" AS (
SELECT "books"."id", "categories"."name" AS "category",
SUM("quantity") AS "units",
SUM("quantity" * "unit_price_kurus") AS "revenue_kurus"
FROM "orders"
JOIN "books" ON "books"."id" = "orders"."book_id"
JOIN "categories" ON "categories"."id" = "books"."category_id"
WHERE "status" = 'delivered'
GROUP BY "books"."id"
)
SELECT "category", SUM("units") AS "units",
SUM("revenue_kurus") AS "revenue_kurus"
FROM "per_book"
GROUP BY "category";
WITH ad AS (…) Ad yalnızca bu sorguda geçerlidir.
iç sorgu Adın neyi temsil ettiğini söyler.
dış sorgu Adı bir tablo gibi çağırır.
CTE, Common Table Expression (ortak tablo ifadesi) demektir. Sözdizimini üç parça olarak okutun: WITH ad AS ( ile tanım başlar, parantez içindeki sorgu adın içeriğidir, parantezden sonra gelen asıl sorgu adı kullanır. Virgülle birden çok CTE tanımlanabilir (slayttaki şemada tek tane var). Burada kategori özetini book_summary görünümüne dokunmadan, aynı mantığı sorgunun içine gömerek kurduk. Önemli ayrım: görünüm yeniden kullanım içindir; CTE aynı sorguda bir alt sorguyu tekrar yazmamak içindir.
CTE ile Aynı Sonuç, Şemada İz Yok
Önceki slayttaki sorgu, geçici görünümle aynı sonucu verir:
Bilim
37
1218900
Dünya Edebiyatı
57
596100
Türk Edebiyatı
63
1047400
Sorgu bitince per_book yok olur:
SELECT COUNT (* ) FROM "sqlite_schema"
WHERE "name" = 'per_book' ;
Bu sorgu geçici görünümle aynı sonucu verdi (37, 57 ve 63 adet), ama hiçbir nesne oluşturmadı; dosyaya, şemaya, bağlantıya iz bırakmadı. Hangisinin daha iyi olduğu sorusu yanlış; ihtiyaç farklı: tekrar kullanılacaksa görünüm, deneme için geçici görünüm, tek sorguluk ara adım için CTE.
Hangisini Ne Zaman?
Ömrü
Silinene kadar
Bağlantı boyunca
Tek sorgu
Şemada görünür mü?
Evet
Hayır (sqlite_temp_schema)
Hayır
Başkası kullanabilir mi?
Evet
Hayır
Hayır
Ne zaman?
Birçok kişi ya da sorgu kullanacak
Kendi denememi hızlandırmak
Tek sorguda ara adım
Silmek
DROP VIEW "ad";
DROP VIEW "ad";
Gerekmez
Kural: başkası kullanacaksa VIEW, kendim deneyeceksem TEMPORARY VIEW, bir sorguluksa CTE.
Tablo ömür çizelgesinin metin hâli. DROP VIEW hem kalıcı hem geçici görünüm için aynıdır; olmayan bir görünümü silmeye çalışırsak no such view hatası alırız, DROP VIEW IF EXISTS hata vermez. Mola öncesi son slayt: ilk yarıda görünümün sorguya ad vermek olduğunu, ikinci yarıda aynı fikirle bir tabloyu parçalamayı, gizlemeyi ve silmeyi yönetmeyi göreceğiz.
Bölümleme: Bir Tablodan Üç Ay
orders üç ayın siparişlerini bir arada tutuyor. Her ayın siparişlerini ayrı raporlamak istiyoruz. Satırları kopyalamadan bölmek için görünüm kullanırız:
orders
1
2026-08-01
10900
2
2026-08-02
19900
3
2026-08-02
13000
46
2026-09-02
19900
47
2026-09-02
17500
86
2026-10-01
42000
87
2026-10-01
6500
88
2026-10-01
24900
orders_aug 45 satır · Ağustos 1 2 3 …
orders_sep 39 satır · Eylül 46 47 …
orders_oct 49 satır · Ekim 86 87 88 …
Bölümleme, bir tabloyu mantıksal parçalara ayırmaktır. Parça ölçütü, her görünümün WHERE koşulu. Burada ölçüt ordered_at sütununun ayı; müşteri, kategori ya da şehir de ölçüt olabilirdi. Bu bölümleme mantıksaldır : veri tek tabloda duruyor, hiçbir satır kopyalanmadı; görünümler aynı satırlara farklı pencerelerden bakıyor. Slayttaki sekiz satır orders’tan seçilmiş örneklerdir; total_kurus adet ile birim fiyatın çarpımıdır ve tarih yalnızca gün olarak gösteriliyor. Sağdaki sayılar gerçek satır sayıları (45, 39, 49).
Bir Ayı Görünüm Yapalım
Ay bilgisini strftime ile alıyoruz; ay raporu için müşteri sütunu gerekmez, bu yüzden görünümde yok:
CREATE VIEW "orders_aug" AS
SELECT "id" , "book_id" , "quantity" , "unit_price_kurus" ,
"ordered_at" , "status"
FROM "orders"
WHERE strftime('%Y-%m' , "ordered_at" ) = '2026-08' ;
2026-08-01 09:26:10
2026-08
2026-09-02 12:01:57
2026-09
2026-10-01 14:06:01
2026-10
WHERE koşulu bu çıktının '2026-08' olduğu satırları bırakır.
strftime('%Y-%m', "ordered_at") bir tarihten 2026-08 biçiminde yıl ve ay çıkarır; WHERE koşulu yalnızca ağustos siparişlerini bırakır. Sütun seçimi de bölümlemenin parçasıdır: ay raporunun customer_id’ye ihtiyacı yok, bu yüzden görünümden çıkarıldı. İade edilen siparişler status sütunuyla ayırt edilebildiği için bilerek dışarıda bırakılmadı.
Eylül ve Ekim
Aynı kalıp, yalnızca ay değişir:
CREATE VIEW "orders_sep" AS
SELECT "id" , "book_id" , "quantity" , "unit_price_kurus" ,
"ordered_at" , "status"
FROM "orders"
WHERE strftime('%Y-%m' , "ordered_at" ) = '2026-09' ;
CREATE VIEW "orders_oct" AS
SELECT "id" , "book_id" , "quantity" , "unit_price_kurus" ,
"ordered_at" , "status"
FROM "orders"
WHERE strftime('%Y-%m' , "ordered_at" ) = '2026-10' ;
Üç görünümün tanımı yalnızca WHERE koşulundaki ayda farklı. Uygulamada bu tekrarı azaltmak için ayın başlangıç ve bitişini parametre olarak veren araçlar kullanılır; SQLite görünümde parametre kabul etmez, bu yüzden her ay için ayrı görünüm yazdık. Bu da mantıksal bölümlemenin bir sınırı: yeni ay geldikçe yeni görünüm gerekir.
Görünümleri Sorgulamak
Her görünüm artık kendi tablosu gibi sorgulanır:
SELECT * FROM "orders_oct"
ORDER BY "ordered_at" DESC LIMIT 3 ;
133
2
2
8900
2026-10-31 11:26:57
delivered
132
18
2
27900
2026-10-30 19:08:18
delivered
131
8
1
8500
2026-10-29 19:03:54
delivered
Satır sayıları: orders_aug 45, orders_sep 39, orders_oct 49. Toplam 133: orders’ın tamamı.
Üç görünümün satır sayıları 45, 39 ve 49: toplamı 133, yani orders’ın tamamı. Bu bir doğrulama yöntemidir: parçaların toplamı bütüne eşit mi? Eşit değilse bir ay kapsam dışında kalmış demektir. Sayıları şu sorguyla alabilirsiniz: SELECT (SELECT COUNT(*) FROM "orders_aug"), (SELECT COUNT(*) FROM "orders_sep"), (SELECT COUNT(*) FROM "orders_oct");
Bölümlemenin Sınırı ve Adlandırma
Mantıksal bölümleme: görünüm Veri tek tabloda kalır. Satırlar kopyalanmaz. Her uygulama kendi dilimini sorgular. Disk ya da hız kazancı yoktur.
Fiziksel bölümleme: DBMS özelliği PostgreSQL gibi sistemler tabloyu gerçekten parçalara böler. Büyük tabloda yalnızca ilgili parça taranır. SQLite’ta yoktur.
Ad seçerken bir denge var: orders_aug açıklayıcıdır; v1 kısadır ama anlamsızdır. Görünümün adını, onu ilk kez gören kişi içeriğini tahmin edebilsin diye seçin.
“Bölümleme” kelimesi iki farklı şey için kullanılır; ikisini ayırın. Görünümle yaptığımız, uygulamaya kolaylık sağlayan mantıksal bölümleme. Gerçek veritabanı sistemlerinde, yüz milyonlarca satırlık bir tabloyu aylara böldüğünüzde sorgu yalnızca ilgili ayın dosyasını okur; bu performans içindir ve SQLite’ın kapsamı dışındadır. Adlandırmada çift tırnak notu: "2026_08" gibi rakamla başlayan bir ad yazılabilir ama her yerde çift tırnak gerektirir; orders_aug gibi sade bir ad daha az hata verir.
Herkes Her Şeyi Görmemeli
Analist hangi kitapların ne zaman satıldığına bakacak; kimin sipariş verdiğini bilmesine gerek yok. Müşteri kaydında customer_id bir kişiyi belirler: kişisel veri .
orders (ham tablo)
1
18
1
2
15
1
3
22
2
←
Analist satış örüntülerini inceler
→
orders_analysis (görünüm)
1
1
Anonim
2
1
Anonim
3
2
Anonim
ham tabloya da erişebilir REVOKE ALL ON orders FROM analyst;
GRANT SELECT ON orders_analysis TO analyst;
Kişisel veri tanımı için KVKK’yı hatırlatın: belirli ya da belirlenebilir gerçek kişiye ilişkin her bilgi. Müşteri numarasından ad, e-posta ve şehre ulaşılabilir; bu nedenle numarayı analistle paylaşmak istemeyiz. Görünüm bu bilgiyi gizler. Üçüncü tıklamada kritik nokta: SQLite’ın erişim denetimi yok. Dosyayı açan herkes bütün tabloları okuyabilir; bu yüzden görünüm burada gizleme aracıdır, güvenlik sınırı değildir. Dördüncü tıklamada PostgreSQL gibi sistemlerin yaptığı gösteriliyor: ham tabloya erişimi kapatıp görünüme okuma izni verirler. Görünüm ancak erişim denetimiyle birlikte koruma sağlar.
orders_analysis: Sütunu Gizlemek
CREATE VIEW "orders_analysis" AS
SELECT "id" , "book_id" , "quantity" , "ordered_at" ,
'Anonim' AS "customer"
FROM "orders"
WHERE "status" = 'delivered' ;
SELECT * FROM "orders_analysis" LIMIT 3 ;
1
15
1
2026-08-01 09:26:10
Anonim
2
5
1
2026-08-02 17:45:58
Anonim
3
12
2
2026-08-02 22:46:36
Anonim
Görünümdeki customer sütununda gerçek müşteri kimliği yerine her satırda Anonim yazıyor. Yalnızca bu görünüme bakarak müşterinin kimliği görülemez.
customer_id’yi tamamen çıkarmak yerine customer adlı sabit bir sütun koyduk; bu analiste “burada bir müşteri bilgisi var ama size gösterilmiyor” mesajı verir. Bu bir tasarım tercihi: bazı ekipler sütunu tamamen kaldırır. Görünüm yalnızca teslim edilen siparişleri taşıyor; iade edilenler analiz kapsamı dışında bırakıldı. İlk bakışta temiz görünen bu görünümün iki zayıflığı var; sıradaki iki slayt onları açıyor.
Gizlenen Bilgi Nasıl Geri Gelir?
Analist yalnızca orders_analysis görünümünü bilse bile, görünümdeki id bir iz bırakıyor:
orders_analysis
1
Anonim
2
Anonim
id →
orders (ham tablo)
1
18
2
15
customer_id →
customers
15
Yiğit Özkan
yigit.ozkan@example.com
18
Ceren Yalçın
ceren.yalcin@example.com
Bir sonraki slayttaki JOIN sorgusunun ne yaptığını önce şekille gösteriyoruz. Birinci adım: analistin elinde yalnızca görünüm var ve customer sütunu her satırda Anonim. İkinci adım: görünümdeki id sütunu, ham orders tablosundaki aynı siparişin kimliğidir; SQLite’ta ham tabloya erişim kapatılamadığı için analist orada aynı id’yi arayabilir. Üçüncü adım: orders satırında customer_id = 18 yazıyor; bu sayıyı customers tablosunda arayınca ad ve e-posta çıkıyor. Dördüncü adım: gizlenen bilgi, iki basit eşleştirmeyle geri geldi. Çözümün iki parçası: görünümden id’yi çıkarmak ve ham tabloya erişimi yetkiyle kapatmak. SQLite yalnızca ilkini yapabilir.
Sızıntı 1: id ile Geri Birleştirme
Görünümde id sütunu duruyor. SQLite ham tabloyu da açık bıraktığı için analist bu kimlikle müşteriyi bulabilir:
SELECT "a" ."id" , "a" ."ordered_at" , "name" , "email"
FROM "orders_analysis" AS "a"
JOIN "orders" ON "orders" ."id" = "a" ."id"
JOIN "customers" ON "customers" ."id" = "orders" ."customer_id"
LIMIT 3 ;
1
2026-08-01 09:26:10
Ceren Yalçın
ceren.yalcin@example.com
2
2026-08-02 17:45:58
Yiğit Özkan
yigit.ozkan@example.com
3
2026-08-02 22:46:36
Defne Yavuz
defne.yavuz@example.com
Gizlediğimiz bilgi, bir JOIN ile geri geldi; üstelik ad ve e-posta ile birlikte.
Öğrencilerden önce tahmin etmelerini isteyin: “Analist elindeki görünümle kimin sipariş verdiğini bulabilir mi?” Cevap evet; çünkü görünümde siparişin kimliği var ve ham tabloya erişim kapatılmadı. Satır kimliğini görünümden çıkarmak ilk adımdır; asıl çözüm ham tabloya erişimi kapatmaktır. SQLite bunu yapamaz.
Sızıntı 2: SELECT * ve Tekil Sipariş
İki zayıflık daha:
1 · SELECT * her sütunu taşır. Görünümü SELECT *, 'Anonim' AS "customer" diye yazarsak customer_id de görünümün içindedir; 130 satırın 130’unda müşteri numarası görünür.
2 · Anonim görünüm bile kişiyi tek başına ele verebilir. Gece 03:12’de kaç sipariş var?
SELECT "ordered_at" , "book_id" , "quantity"
FROM "orders_analysis"
WHERE "ordered_at" LIKE '2026-10-19 03:%' ;
O saatte tek sipariş var; o gece sipariş verdiğini bilen biri satırı eşleştirebilir.
İkinci zayıflık “bağlantılı saldırı” (linkage attack) diye bilinir: tek başına zararsız görünen alanlar (zaman, kitap, adet), dışarıdan bilinen bir bilgiyle birleşince kişiyi ele verir. Kişisel Verilerin Korunması Kanunu’nda anonim hâle getirme, verinin başka verilerle eşleştirilse bile kişiyle ilişkilendirilememesi anlamına gelir. Bizim görünümümüz yalnızca maskeleme , anonimleştirme değil. Gerçekten anonim paylaşım için zamanı saate yuvarlamak, az sayıdaki satırları birleştirmek gibi başka önlemler gerekir. Görünüm bu işin yalnızca ilk adımıdır.
Erişim Denetimi: SQLite’ta Yok, Başka Sistemlerde Var
SQLite Veritabanı tek bir dosyadır. Dosyayı açan herkes bütün tabloları okuyabilir; “yalnızca bu görünümü ver” diyemezsiniz. Görünüm burada gizleme aracıdır, güvenlik sınırı değildir.
PostgreSQL, MySQL, Oracle Kullanıcılara tek tek yetki verilir. Ham tabloyu kapatıp yalnızca görünümü açabilirsiniz:REVOKE ALL ON orders FROM analyst;GRANT SELECT ON orders_analysis TO analyst;
Güvenli paylaşım iki parçadır: hassas veriyi görünümle gizlemek ve ham veriye erişimi yetkiyle kapatmak . SQLite yalnızca birincisini sağlar.
SQLite’ta görünümün tek başına güvenlik sağlamadığını vurgulayın. SQLite gömülü bir veritabanıdır; telefon uygulamaları, tarayıcılar ve küçük araçlarda kullanılır. Kullanıcı yönetimi yoktur. PostgreSQL gibi sunucu tabanlı sistemlerde kullanıcılar ve roller vardır; GRANT ve REVOKE komutları ders programının ilerleyen bölümlerinde ayrıca ele alınabilir. Buradaki sözdizimi yalnızca örnektir, bugün çalıştırmayacağız.
Satıştaki Kitapları Ayırmak
Dördüncü haftadaki sorun burada da karşımıza çıkıyor: satıştan kaldırılan kitabı silmek yerine discontinued = 1 ile işaretliyoruz. Satıştaki kitapları listeleyen her sorguya aynı koşulu yazmak gerekiyor:
SELECT * FROM "books" WHERE "discontinued" = 0 ;
Bu koşulu unutan her sorgu satıştan kalkmış kitabı gösterir. Görünüm, koşulu her sorguda tekrarlamamızı önler:
CREATE VIEW "available_books" AS
SELECT "books" ."id" , "title" ,
"categories" ."name" AS "category" , "price_kurus"
FROM "books"
JOIN "categories" ON "categories" ."id" = "books" ."category_id"
WHERE "discontinued" = 0 ;
Görünüm kategorinin kimliğini değil adını taşıyor; vitrin categories tablosunu bilmek zorunda değil. Ayrıca discontinued sütununu taşımadığına dikkat edin: vitrin uygulaması bu görünümü kullanırken bir kitabın satıştan kalkmış olabileceğini hiç bilmek zorunda değil. Bu, dersin başında sorduğumuz sorunun karşılığı: filtreyi bir kez yazdık, bir daha unutamayız.
available_books’ı Sorgulamak
SELECT COUNT (* ) FROM "available_books" ;
SELECT "id" , "title" , "price_kurus" FROM "available_books"
WHERE "id" BETWEEN 5 AND 9 ;
Birinci sorgu 21 döndürür: yirmi iki kitaptan biri satışta değil.
5
Saatleri Ayarlama Enstitüsü
19900
7
Aylak Adam
9900
8
Yaban
8500
9
Beyaz Kale
18900
6 numaralı kitap (Çalıkuşu) discontinued = 1 olduğu için listede yok.
Bu, dersin başında sorduğumuz sorunun karşılığı. Çalıkuşu’nu kimse silmedi; yalnızca görünüm onu göstermiyor. 5 ile 9 arasındaki kimlikleri seçtik ki boşluk gözle görülsün: 6 numara atlanıyor.
Kapat ve Gör
Yumuşak silme, books tablosundaki discontinued değerini değiştirir. Görünümü yeniden sorguladığımızda sonuç da değişir:
UPDATE "books" SET "discontinued" = 1 WHERE "title" = 'Yaban' ;
books
6
Çalıkuşu
1
7
Aylak Adam
0
8
Yaban
0 1
9
Beyaz Kale
0
→ discontinued = 0
available_books
7
Aylak Adam
8
Yaban
9
Beyaz Kale
Sağdaki tablo tek bir sorgunun sonucu; soldaki tabloyu değiştirince sağdaki yeniden sorgulanınca değişiyor. Sunumdaki ilk iki tıklamada tablo ve görünüm eş zamanlı gösterilir. Üçüncü tıklamada bir sorun belirir: vitrin uygulaması kitapları yalnızca available_books üzerinden görüyorsa bir kitabı satıştan kaldırmak için de books tablosunu bilmek zorunda. Bu bağımlılık istenmez. Görünüm üzerinden de silebilsek ne iyi olurdu; ama görünüme DELETE yazılamıyor.
Görünüme Doğrudan DELETE Yazamayız
DELETE FROM "available_books" WHERE "title" = 'Hayvan Çiftliği' ;
Parse error: cannot modify available_books because it is a view
Uygulama yalnızca available_books’ı biliyor olsun. Kitabı satıştan kaldırmak için bu görünüme DELETE yazmasını istiyoruz; ama SQLite hangi tabloyu değiştireceğini bilemiyor.
Çözüm: SQLite’a bu ifade yerine ne yapacağını biz söyleyeceğiz.
Aynı hatayı bu dersin başında UPDATE ile görmüştük. Burada soru yeni bir şey değil, bir ihtiyaç: görünümü okumak için değil, yazmak için de kullanmak istiyoruz. Çözüm tetikleyiciler; geçen hafta AFTER INSERT ve AFTER DELETE tetikleyicilerini görmüştük. Görünümlerde yalnızca bir tür tetikleyici çalışır: INSTEAD OF.
INSTEAD OF: Bunun Yerine Şunu Yap
INSTEAD OF DELETE tetikleyicisi, görünüme gelen DELETE ifadesini yakalar ve onun yerine kendi ifadesini çalıştırır:
gelen ifade DELETE FROM "available_books"
WHERE "title" = 'Hayvan Çiftliği';
→
available_books (görünüm)
15
1984
16
Hayvan Çiftliği
⚡
INSTEAD OF DELETE UPDATE "books"
SET "discontinued" = 1
WHERE "id" = OLD."id";
8
Yaban
1
16
Hayvan Çiftliği
0 1
Akışı soldan sağa izletin. Birinci tıklama: ifade görünüme geliyor, normalde burada hata alırdık. İkinci tıklama: tetikleyici araya girip DELETE’in yerine geçiyor; üzeri çizilen ifade hiç çalışmayacak. Üçüncü tıklama: tetikleyicinin kendi ifadesi books tablosunda discontinued’ı 1 yapıyor ve görünüm satırı artık göstermiyor. OLD burada silinmek istenen satır, yani Hayvan Çiftliği. Dikkat: hiçbir satır gerçekten silinmedi; uygulama DELETE yazdığını sanıyor, veritabanı bunu yumuşak silmeye çeviriyor.
CREATE TRIGGER … INSTEAD OF
CREATE TRIGGER "available_books_delete"
INSTEAD OF DELETE ON "available_books"
FOR EACH ROW
BEGIN
UPDATE "books" SET "discontinued" = 1
WHERE "id" = OLD ."id" ;
END ;
INSTEAD OF DELETE ON "available_books"
Ne zaman: görünüme DELETE yazıldığında, onun yerine
FOR EACH ROW
Silinmek istenen her satır için bir kez
UPDATE "books" ...
Bunun yerine çalışacak ifade
OLD."id"
Silinmek istenen satırın kimliği
Geçen haftaki CREATE TRIGGER sözdizimine çok benziyor; tek fark BEFORE/AFTER yerine INSTEAD OF ve tablo yerine görünüm adı. SQLite’ta INSTEAD OF yalnızca görünümlerde, BEFORE/AFTER ise yalnızca tablolarda çalışır. OLD silinmek istenen satırı, NEW (ekleme ve güncellemede) yazılmak istenen satırı temsil eder.
Tetikleyiciyi Çalıştıralım
DELETE FROM "available_books" WHERE "title" = 'Hayvan Çiftliği' ;
SELECT "id" , "title" FROM "available_books"
WHERE "category" = 'Dünya Edebiyatı' ;
SELECT "id" , "title" FROM "books" WHERE "discontinued" = 1 ;
11
Suç ve Ceza
12
Dönüşüm
13
Yabancı
14
Veba
15
1984
17
Simyacı
6
Çalıkuşu
8
Yaban
16
Hayvan Çiftliği
Hata yok: Hayvan Çiftliği görünümden çıktı, satır tabloda duruyor.
Bu tablolar, bu dersteki dizinin devamı: önce UPDATE ile Yaban satıştan kalktı, sonra DELETE ile Hayvan Çiftliği. İki yoldan da sonuç aynı: discontinued = 1. Öğrencinin kendi dosyasında bu sırayı izlemesini bekleyin. Aksi hâlde çıktıları farklı çıkar. Tüm books tablosunu görmek isteyen WHERE koşulunu kaldırabilir: yirmi iki satırın üçü satıştan kalkmış.
Ekleme de Aynı Kapıdan Geçsin
Vitrin kapalı bir kitabı yeniden satışa açmak için de görünüme INSERT yazar. Tetikleyici yoksa:
INSERT INTO "available_books"
("title" , "category" , "price_kurus" )
VALUES ('Çalıkuşu' , 'Türk Edebiyatı' , 12900 );
Parse error: cannot modify available_books because it is a view
Burada iki farklı durum var:
Kitap zaten books tablosunda var, yalnızca satıştan kalkmış: yeni satır eklemeyip discontinued’ı 0 yapmalıyız.
Kitap hiç yok : gerçekten yeni bir satır yazmalıyız.
books.title sütunu UNIQUE olduğundan aynı adlı ikinci satır eklenemez; satıştan kalkmış bir kitabı yeniden açmak için yeni satır eklemek hataya yol açardı. Bu iki durumu ayıracak bir koşul gerekiyor. Tetikleyiciler koşullu da çalışabilir: WHEN anahtar sözcüğü.
Yeniden Aç mı, Yeni Satır mı?
INSERT INTO available_books (title, category, …)
'Çalıkuşu', 'Türk Edebiyatı', …
'Küçük Prens', 'Dünya Edebiyatı', …
title books'ta zaten var mı?WHEN … IN
Evet → yeniden aç UPDATE "books" SET "discontinued" = 0 WHERE "title" = NEW."title";
Hayır → yeni satır INSERT INTO "books" ("title", "category_id", "price_kurus") VALUES (NEW."title", kategori adının kimliği , NEW."price_kurus");
Akışı bir karar ağacı olarak anlatın: ekleme görünüme gelir, tetikleyici adın books’ta olup olmadığına bakar; varsa kitap yeniden açılır, yoksa yeni satır yazılır. İkinci tıklama Çalıkuşu, üçüncü tıklama Küçük Prens ile iki yolu da gösteriyor. Bu aslında iki ayrı tetikleyicidir; her biri kendi WHEN koşuluyla çalışır. Bir sonraki slaytta kodları var.
INSERT Tetikleyicisi 1: Kitap Zaten Var
Aynı olaya iki tetikleyici bağlayacağız; hangisinin çalışacağını WHEN koşulu seçer. Birincisi satıştan kalkmış kitabı yeniden açar:
CREATE TRIGGER "available_books_insert_existing"
INSTEAD OF INSERT ON "available_books"
FOR EACH ROW
WHEN NEW ."title" IN (SELECT "title" FROM "books" )
BEGIN
UPDATE "books" SET "discontinued" = 0
WHERE "title" = NEW ."title" ;
END ;
WHEN koşulu doğruysa tetikleyici çalışır, yanlışsa atlanır.
NEW eklenmek istenen satırdır. WHEN içindeki alt sorgu books tablosundaki mevcut adlara bakıyor: ad zaten varsa yeni satır yazmak yerine yalnızca discontinued bayrağı 0 yapılır. books.title sütunu UNIQUE olduğundan aynı adlı ikinci satır zaten eklenemezdi; bu tetikleyici o durumu doğru yola çevirir.
INSERT Tetikleyicisi 2: Kitap Hiç Yok
İkincisi, adı books içinde olmayan kitaplar için yeni satır yazar:
CREATE TRIGGER "available_books_insert_new"
INSTEAD OF INSERT ON "available_books"
FOR EACH ROW
WHEN NEW ."title" NOT IN (SELECT "title" FROM "books" )
BEGIN
INSERT INTO "books"
("title" , "category_id" , "price_kurus" )
VALUES (NEW ."title" ,
(SELECT "id" FROM "categories"
WHERE "name" = NEW ."category" ),
NEW ."price_kurus" );
END ;
İki koşul birbirinin tersidir (IN, NOT IN): her INSERT için tam bir tetikleyici çalışır.
Görünümde kategori adı var ama books tablosu kategori kimliği istiyor; tetikleyici adı kimliğe çeviren alt sorguyu kendisi yazıyor. Görünüme yazan uygulama kimlikleri bilmek zorunda kalmıyor. Kitabın yazarı ise book_authors tablosuna ayrıca eklenir; kısalık için bunu derste yapmıyoruz. İki koşulun birbirini tamamlaması önemli: biri IN, diğeri NOT IN; ikisi birlikte her durumu kapsıyor. Koşul hatalıysa aynı ekleme iki kez işlenebilir ya da hiç işlenmeyebilir; bu yüzden tetikleyici yazarken “her girdi için kaç tetikleyici çalışır?” diye sormak iyi bir kontrol.
Ekleme Tetikleyicilerinin Sonucu
INSERT INTO "available_books"
("title" , "category" , "price_kurus" )
VALUES ('Çalıkuşu' , 'Türk Edebiyatı' , 12900 );
INSERT INTO "available_books"
("title" , "category" , "price_kurus" )
VALUES ('Küçük Prens' , 'Dünya Edebiyatı' , 6900 );
SELECT "id" , "title" , "category" FROM "available_books"
WHERE "id" IN (6 , 23 );
6
Çalıkuşu
Türk Edebiyatı
23
Küçük Prens
Dünya Edebiyatı
Çalıkuşu aynı kimlikle (6) döndü; yalnızca discontinued = 0 oldu.
Küçük Prens yeni satır; kimliğini SQLite verdi.
Bu çıktıda Çalıkuşu’nun kimliği hâlâ 6: tetikleyici yeni satır yazmadı, yalnızca bayrağı çevirdi. Kimlik korunduğu için orders’taki eski kayıtlar bu kitaba bağlı kalmaya devam eder: tüm geçmiş korunur. Küçük Prens ise kimlik 23 ile yeni kitap. Tetikleyiciyi iki kez deneyen öğrenci, ikinci denemede Çalıkuşu’nun zaten satışta olduğunu görür; WHEN koşulu yine doğrudur, UPDATE bir şeyi değiştirmez ama hata da vermez.
Yumuşak Silmenin İki Yüzü
available_books: bugünün listesi Vitrin yalnızca satıştaki kitapları görür. Kitap ekler, kaldırır, yeniden açar; discontinued sütununu hiç bilmez.
book_summary: geçmiş raporu Satıştan kalkmış bir kitabın eski siparişleri raporlarda durur. Yaban kaldırılmış olsa da geçmişteki 6 sipariş raporda görünür.
SELECT "title" , "orders" , "units" , "revenue_kurus"
FROM "book_summary" WHERE "title" = 'Yaban' ;
Hangi görünümün hangi soruya cevap verdiğini bilmek, tasarımın parçasıdır: güncel liste için available_books, tarihsel rapor için book_summary.
Yumuşak silmenin kazancı bu: geçmiş bozulmadan duruyor. Yaban satıştan kalkmış olduğu hâlde book_summary görünümü onu hâlâ gösteriyor çünkü bu görünüm books tablosuna discontinued filtresi uygulamadan bakıyor. Bu bir hata değil, bir karar: geçen aylarda Yaban’dan elde edilen geliri raporlarda kaybetmek istemeyiz. İki görünüm aynı tabloya farklı sorular soruyor. Kişisel veride yumuşak silme yeterli değildir; kitap gibi kişisel olmayan kayıtlar için uygundur.
Hepsi Bir Arada
Aynı altı tablonun üzerinde sekiz görünüm, beş ayrı ihtiyaç:
SADELEŞTİRME purchase_history · book_catalogcustomers orders books categories authors book_authors
TOPLAMA book_summaryorders books
BÖLÜMLEME orders_aug · orders_sep · orders_octorders
GÜVENLİK orders_analysisorders
YUMUŞAK SİLME available_booksbooks INSTEAD OF
geçici katman TEMPORARY VIEW category_summary · CTE per_book
customers müşteri
orders sipariş · köprü
books kitap
categories kategori
authors yazar
book_authors kitap ↔ yazar · köprü
Altta altı tablo, üstte sekiz görünüm var; bölümleme için üç ayrı görünüm kullanıldı. Hiçbir görünüm veri taşımıyor; hepsi aynı satırları farklı amaçlarla sorguluyor. Renkler dersin dört amacını gösteriyor. Geçici katmandaki adlar kalıcı şemaya girmiyor. Öğrencilerden .tables komutuyla kendi veritabanlarında bu haritayı doğrulamalarını isteyin: altı tablo, sekiz görünüm.
Tuzak: Alttaki Tablo Değişirse
Görünüm tabloya bağlıdır. Veri azaltma gerekçesiyle customers tablosundan city sütununu sildiğimizi düşünelim (kopya veritabanında):
ALTER TABLE "customers" DROP COLUMN "city" ;
SELECT * FROM "purchase_history" LIMIT 3 ;
Ceren Yalçın
city
2026-08-01 09:26:10
1984
Yiğit Özkan
city
2026-08-02 17:45:58
Saatleri Ayarlama Enstitüsü
Defne Yavuz
city
2026-08-02 22:46:36
Dönüşüm
Hata yok; ama şehir adı yerine city kelimesi geliyor. SQLite, çift tırnakla yazılmış ve bulunamayan bir adı metin sabiti sayar: görünüm sessizce bozuldu.
Bu tuzak dersin en sinsi olanı: hata mesajı yok. Nedeni SQLite’ın eski bir uyumluluk davranışı: çift tırnaklı bir ad çözümlenemezse onu metin olarak yorumlar. Biz bu derste tanımlayıcıları hep çift tırnakla yazdığımız için görünümler de bu davranışa açık. Tek tırnakla yazılan metin ile çift tırnakla yazılan ad arasındaki ayrım geçen haftaki kuralımızdı; burada sınır çiziliyor. Gerçek bir gerekçe de var: kişisel veriyi azaltmak (veri minimizasyonu) KVKK’nın ilkelerinden biridir. Bu örneği yalnızca kopya veritabanında deneyin. Resmî açıklama: https://www.sqlite.org/quirks.html
Sütunu Silmeden Önce Bağımlılıkları Kontrol Edin
Bir sütunu silmeden önce şemada o adı kimin kullandığını arayın:
SELECT "type" , "name" FROM "sqlite_schema"
WHERE "sql" LIKE '%city%' ;
table
customers
view
purchase_history
city yalnızca customers tablosunda ve purchase_history görünümünde geçiyor: silersek bozulacak nesne belli. Değişiklikten sonra görünümlerin sonuçlarını gözle denetlemek de iyi bir alışkanlıktır.
Koruma: sütun silmeden önce sqlite_schema içinde ad araması yapmak ve değişiklik sonrası görünümlerin sonuçlarını gözle denetlemek. Arama basit bir metin eşleşmesidir; ad başka bir nesnenin adının parçasıysa fazladan satır döndürebilir, ama kaçırmaz. Sonuç tablosu city sütunu silinmeden önceki durumdur.
Görünümün Sınırları
Sonucu saklamaz Her sorguda alttaki sorgu yeniden çalışır; büyük tabloda yavaş olabilir. MATERIALIZED VIEW (PostgreSQL, Oracle) sonucu saklar; SQLite’ta yoktur.
Doğrudan yazılamaz Görünüme INSERT, UPDATE, DELETE hata verir. Çözüm: INSTEAD OF tetikleyicisi.
Erişim denetimi yok (SQLite) Görünüm hassas sütunu gizler, ama ham tabloya erişimi kapatmaz. Güvenlik için yetkilendirme gerekir.
Alttaki tabloya bağlıdır Tablonun yapısı değişirse görünüm bozulabilir; bazen hata vermeden.
Görünüm bir bakış açısı tanımlar; veri, yetki ve hız sorunlarını tek başına çözmez.
Dört sınırı kapanıştan önce toplu söyleyin; her biri derste bir yerde geçti. Dersin başındaki cümleye dönün: “görünüm bir sorguya verilen addır”. Ad vermek güçlü bir araç, ama adın arkasında hâlâ sorgu çalışıyor, hâlâ aynı tablolar var. Performans konusu birkaç hafta sonra indeks ve sorgu planı konularıyla birlikte tekrar gündeme gelebilir.
Alıştırmalar
Beş soru dersin dört bölümünü tarar. Her sorunun cevabı bir sonraki slayttadır.
Aylık gelir: özet görünümü
Ortalamanın üstündeki kitaplar: CTE
Sızıntıyı bulun: güvenlik
Görünümden silmek: hata ve çıkış yolları
INSTEAD OF DELETE: tetikleyici
Zaman sınırlıysa 1, 3 ve 5 numaralı soruları sınıfta yapın; 2 ve 4 ders sonrasına kalabilir. Her soruda önce “bu bir görünüm mü, CTE mi, yoksa doğrudan sorgu mu?” diye sorun. Her sorunun cevabı bir sonraki slayttadır.
Soru 1: Aylık Gelir
Muhasebe, her ayın teslim edilmiş siparişlerinden elde edilen toplam geliri görmek istiyor. Ay bilgisini almak için strftime('%Y-%m', "ordered_at") kullanabilirsiniz. monthly_revenue adlı bir görünüm yazın.
Soru 1: Cevap
CREATE VIEW "monthly_revenue" AS
SELECT strftime('%Y-%m' , "ordered_at" ) AS "month" ,
SUM ("quantity" * "unit_price_kurus" ) AS "revenue_kurus"
FROM "orders"
WHERE "status" = 'delivered'
GROUP BY strftime('%Y-%m' , "ordered_at" );
2026-08
942300
2026-09
792300
2026-10
1127800
strftime zaman damgasından yalnızca yıl ve ayı alır; GROUP BY aynı ayın satırlarını toplar. Üç ayın toplamı 2862400 kuruş.
Soru 2: Ortalamanın Üstündeki Kitaplar
Satış adedi kitap başına ortalamanın üzerinde olan kitapları tek bir sorguyla bulun. Kitapların satış adetlerini sorgunun iki yerinde kullanmanız gerekecek; kalıcı bir nesne oluşturmayın. Hangi aracı kullanırdınız?
Soru 2: Cevap
WITH "per_book" AS (
SELECT "book_id" , SUM ("quantity" ) AS "units"
FROM "orders" WHERE "status" = 'delivered'
GROUP BY "book_id"
)
SELECT "title" , "units" FROM "per_book"
JOIN "books" ON "books" ."id" = "book_id"
WHERE "units" > (SELECT AVG ("units" ) FROM "per_book" );
Sonuç: sekiz kitap; en çok satanı Nutuk (21 adet). Ortalama 7,85 adet. Ara sonuç iki yerde kullanıldığı için CTE tam burada işe yarar.
Soru 3: Sızıntıyı Bulun
Bir meslektaşınız analist için şu görünümü yazdı:
CREATE VIEW "analysis" AS
SELECT * , 'Anonim' AS "customer"
FROM "orders" WHERE "status" = 'delivered' ;
Hangi bilgi hâlâ görünür? Görünümü düzeltin. Düzeltmeden sonra kalan başka bir risk var mı?
Soru 3: Cevap
SELECT * yüzünden customer_id görünümün içinde; 'Anonim' yalnızca yeni bir sütun ekledi, eskisini silmedi.
CREATE VIEW "analysis" AS
SELECT "id" , "book_id" , "quantity" , "ordered_at" ,
'Anonim' AS "customer"
FROM "orders" WHERE "status" = 'delivered' ;
Kalan risk: id ham tablodaki satırın kimliği. Ham tabloya erişim kapalı değilse id ile orders ve customers birleştirilip ad ve e-posta bulunur. Görünümden id’yi de çıkarmak ve erişimi yetkiyle kapatmak gerekir.
Soru 4: Görünümden Silmek
İade edilen siparişler returns görünümünde izleniyor. İade birimi, hatalı girilmiş bir iade kaydını görünümden silmeye çalışıyor:
CREATE VIEW "returns" AS
SELECT "id" , "customer_id" , "book_id" , "quantity" ,
"unit_price_kurus" , "ordered_at"
FROM "orders" WHERE "status" = 'returned' ;
DELETE FROM "returns" WHERE "id" = 28 ;
Ne olur? Bu işi yapabilecek iki yol söyleyin.
Soru 4: Cevap
Parse error: cannot modify returns because it is a view
Yol 1: Doğrudan asıl tablodan silmek: DELETE FROM "orders" WHERE "id" = 28;
Yol 2: Görünüme INSTEAD OF DELETE tetikleyicisi yazmak (sıradaki soru).
Her iki yolda da orders satırı kalıcı olarak silinir; iade geçmişi de kaybolur. İade gibi izlenmesi gereken kayıtlarda silmek yerine işaretlemek (yumuşak silme) çoğu zaman daha doğrudur.
Soru 5: INSTEAD OF DELETE Tetikleyicisi
returns görünümüne yazılan DELETE, asıl tablodan ilgili satırı silsin. Boşlukları doldurun:
CREATE TRIGGER "returns_delete"
INSTEAD OF ___ ON "returns"
FOR EACH ROW
BEGIN
DELETE FROM "orders" WHERE "id" = ___."id" ;
END ;
Soru 5: Cevap
CREATE TRIGGER "returns_delete"
INSTEAD OF DELETE ON "returns"
FOR EACH ROW
BEGIN
DELETE FROM "orders" WHERE "id" = OLD ."id" ;
END ;
Silinmek istenen satıra OLD ile ulaşılır. Deneyin: DELETE FROM "returns" WHERE "id" = 28; artık hata vermez ve returns iki satıra düşer.
Kısa SQL Özeti
Sorguya ad vermek
CREATE VIEW "ad" AS SELECT ...
Görünümü kaldırmak
DROP VIEW "ad"; · DROP VIEW IF EXISTS "ad";
Geçici görünüm
CREATE TEMPORARY VIEW "ad" AS ...
Tek sorguluk ara tablo
WITH "ad" AS (...) SELECT ...
Özet sorgusunu yeniden kullanmak
GROUP BY, COUNT, SUM, AVG, ROUND, GROUP_CONCAT
Eşleşmeyenleri korumak
LEFT JOIN ... ON ...
Bölümleme, gizleme
görünümün WHERE koşulu, strftime, seçilen sütunlar, sabit değer
Görünüme yazmak
CREATE TRIGGER ... INSTEAD OF DELETE / INSERT ON "görünüm", OLD, NEW, WHEN
Şemayı incelemek
.tables, .schema ad, sqlite_schema, sqlite_temp_schema
Çıkış Soruları
Üç kısa yanıt yazın:
Bir görünüm ile bir tablo arasındaki en önemli fark nedir?
CREATE TEMPORARY VIEW ile WITH (CTE) arasında ömür bakımından ne fark var?
Aşağıdaki görünüme INSERT INTO "available_books" ... yazılınca neden hata alırız ve nasıl çözülür?
CREATE VIEW "available_books" AS
SELECT "books" ."id" , "title" ,
"categories" ."name" AS "category" , "price_kurus"
FROM "books"
JOIN "categories" ON "categories" ."id" = "books" ."category_id"
WHERE "discontinued" = 0 ;
Bir dakika bireysel süre. Üçüncü sorunun cevabı iki parçalı olmalı: görünüm veri taşımaz, yazılacak tabloyu belirleyemez; çözüm INSTEAD OF INSERT tetikleyicisi. Dikkat: öğrenci “yeni satır eklemek için books’a yazarız” derse bu da doğru bir çözüm; ama uygulama görünümü bilip tabloyu bilmiyorsa tetikleyici gerekir.
Dersi Toparlayalım
Tablo veri taşır; görünüm yalnızca bir sorgunun adıdır ve her çağrıldığında altındaki sorgu yeniden çalışır.
TEMPORARY VIEW bağlantı boyunca, CTE yalnızca tanımlandığı sorguda geçerlidir.
Görünüme doğrudan yazılamaz; INSTEAD OF tetikleyicisi ifadeyi yakalayıp asıl tabloya yönlendirir.
Bugün bir sorguya ad vermeyi, özet sorgusunu yeniden kullanmayı, tabloyu mantıksal olarak bölmeyi ve hassas sütunu gizlemeyi gördük. Yumuşak silme işlemini de görünüm üzerinden yürüttük. Gelecek hafta aynı sorguların daha hızlı çalışmasını konuşacağız.
Üç cümle dersin üç ana fikri. Birincisi dersin ilk cümlesine dönüş: görünüm bir sorguya verilen addır. İkincisi ömür ayrımı: şemada kalıcı, bağlantıda geçici, sorguda anlık. Üçüncüsü görünümün yazma sınırı ve çözümü. Kapanışta “Görünümün Sınırları” slaytındaki dört maddeyi bir kez daha hatırlatabilirsiniz.