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.

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?

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
SİPARİŞ 1 adet · 249 TL 23 Ağustos 2026, 12:43 teslim edildi
KİTAP Nutuk Mustafa Kemal Atatürk · Bilim bugünkü fiyat: 279 TL
Bu kayıtlardan yanıt bekleyen sorular
sadeleştirSelin Aydın hangi kitapları aldı?
toplaHangi kitap kaç adet satıldı?
bölAğustos, Eylül, Ekim raporları ayrı ayrı
kişisel veriyi gizleAnalist müşteri kimliğini görmemeli

Neden Tek Tablo Değil?

Akla gelen ilk fikir: her siparişi tek bir tabloya, tüm ayrıntılarıyla yazmak.

müşteri e-posta şehir kitap yazar fiyat (kuruş)
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 tekrarSelin'in adı, e-postası ve şehri üç kez; Nutuk ve yazarı iki kez yazıldı.
Güncelleme çelişki doğururSelin taşınırsa üç satır değişmeli; biri unutulursa tablo kendisiyle çelişir.
Siparişsiz kitap eklenemezHenüz sipariş edilmemiş bir kitabı bu tabloya ekleyemeyiz.
Her bilgi bir kez, kendi tablosunda: customers+books+authors+categories+ordersyalnızca kimlikler

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
id8PK
nameSelin Aydın
emailselin.aydin@example.comUNIQUE
cityAnkara
id name email city
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

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
id3PK
nameBilimUNIQUE
id name
1 Türk Edebiyatı
2 Dünya Edebiyatı
3 Bilim

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
id14PK
nameMustafa Kemal AtatürkUNIQUE
countryTürkiye
id name country
8 Orhan Pamuk Türkiye
14 Mustafa Kemal Atatürk Türkiye
16 Abraham Silberschatz ABD

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
id18PK
titleNutukUNIQUE
category_id3FK→ categories · Bilim
price_kurus27900CHECK= 279 TL
discontinued0DEFAULT 01 olursa satıştan kalkmış
id title category_id price_kurus discontinued
2 Kürk Mantolu Madonna 1 8900 0
6 Çalıkuşu 1 12900 1
18 Nutuk 3 27900 0

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_id20FK→ books · Veritabanı Sistem Kavramları
author_id16FK→ authors · Abraham Silberschatz
PRIMARY KEY(20, 16)PKaynı çift ikinci kez yazılamaz
book_id author_id kitap yazar
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

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
id34PK
customer_id8FK→ customers · Selin Aydın
book_id18FK→ books · Nutuk
quantity1CHECK
unit_price_kurus24900CHECKNutuk bugün 27900
ordered_at2026-08-23 12:43:54DEFAULT
statusdeliveredCHECK

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

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

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 · customersid = 8
2 · ordersbook_id ∈ {18, 4, 2}
3 · booksKürk Mantolu Madonna, Huzur, Nutuk

Sorgu doğru, ancak üç SELECT ve iç içe sorgular okumayı zorlaştırıyor.

Üç Tablo, Tek Bakış

Önce sorguyu yazmadan, tabloları yan yana koyup eşleşen satırları inceleyelim.

üç ayrı tablocustomers ⋈ orders ⋈ bookspurchase_history (görünüm)
customers
idname 8Selin Aydın 18Ceren Yalçın 22Defne Yavuz 11Emre Taş
orders
customer_iddatetimebook_id 812:43:5418 1809:26:1015 2222:46:3612 1111:06:2914
books
idtitle 18Nutuk 151984 12Dönüşüm 14Veba

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';
customer city ordered_at title
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.

CREATE VIEW: Sorguya Ad Vermek

Bu sorguyu her seferinde yeniden yazmak istemiyoruz. Başına bir satır eklemek yeter:

CREATE VIEW "purchase_history" ASSELECT "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

customerstable
categoriestable
authorstable
bookstable
book_authorstable
orderstable
purchase_historyview
kitapevi.db: 57344 bayt → 57344 bayt

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';
title
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.

Görünüm Nerede Duruyor?

Görünüm şemada bir nesnedir. Tabloların yanında listelenir:

.tables
authors           books             customers         purchase_history
book_authors      categories        orders
SELECT "type", "name" FROM "sqlite_schema"
WHERE "type" = 'view';
type name
view purchase_history

SQLite görünümün yalnızca sorgu metnini saklar; her sorguda alttaki sorgu yeniden çalışır.

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;
customer city ordered_at title
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.

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';
customerSelin Aydıncustomers.name
cityAnkaracustomers.city
ordered_at2026-08-23 12:43orders.ordered_at
titleNutukbooks.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.

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österAz önce yaptığımız: purchase_history
2 · TOPLAMAÖzet sorgusunu yeniden kullanToplam, sayı ve ortalamayı her sorguda yeniden yazma.
3 · BÖLÜMLEMETabloyu mantıksal parçalara ayırHer uygulama kendi dilimini görsün.
4 · GÜVENLİKHassas sütunları gizleBaşkasına verilen veriden kişisel bilgiyi çıkar.

Bonus: Satıştan kaldırılan kitapları görünümün sonucundan çıkarmak. Dersin sonunda.

Aynı Veri, Farklı Bakış

Aynı veritabanını kullanan üç birimin farklı bilgilere ihtiyacı var:

MuhasebeKitap başına satış adedi ve gelir.book_summary
AnalistSatış örüntüleri.orders_analysis
DepoYalnızca satıştaki kitaplar.available_books
Hepsinin altında aynı altı tablo
customers · orders · books · categories · authors · book_authors

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:

titlequantityunit_price_kurus
titleordersrevenue_kurusavg_price_kurus
Nutuk124900
Veba111900
Nutuk227900
Veba211900
Nutuk227900
Veba23570011900.0
Nutuk313650026900.0

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";
book_id avg_price
1 24900.0
2 8172.72727272727
… …

ROUND(değer, 2) iki ondalığa yuvarlar:

book_id avg_price
1 24900.0
2 8172.73
… …

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:1FROM + JOIN→2WHERE→3GROUP BY→4SELECT→5ORDER BY→6LIMIT

Kitap Başına Özet

Gelire göre ilk beş kitap:

id title orders units revenue_kurus avg_price_kurus
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.

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";

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;
title units
Nutuk 21
Veba 15
Kürk Mantolu Madonna 13

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)
id datetime quantity
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
title units revenue_kurus
Nutuk 2122 540900568800

INSERT INTO "orders" ("customer_id", "book_id", "quantity",
                      "unit_price_kurus", "ordered_at")
VALUES (3, 18, 1, 27900, '2026-11-02 08:10:00');

Bir Kitap, Birden Çok Yazar

Yazarlar ayrı tabloda; birleştirince satırlar çoğalır: 22 kitap, 25 satır.

books
id title
20 Veritabanı Sistem Kavramları
21 Pragmatik Programcı
book_authors
book_id author_id
20 16
20 17
20 18
21 19
21 20
authors
id name
16 Abraham Silberschatz
17 Henry F. Korth
18 S. Sudarshan
19 Andrew Hunt
20 David Thomas
JOIN sonucu: iki kitap, beş satır
id title author
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
id title authors
20 Veritabanı Sistem Kavramları Abraham Silberschatz, Henry F. Korth, S. Sudarshan
21 Pragmatik Programcı Andrew Hunt, David Thomas

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);
title authors
Beyaz Kale Orhan Pamuk
Veritabanı Sistem Kavramları Abraham Silberschatz, Henry F. Korth, S. Sudarshan
Pragmatik Programcı Andrew Hunt, David Thomas

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");
title authors
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.

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?

booksJOINLEFT JOINLEFT JOIN + COUNT
books.title orders tarafı COUNT("orders"."id") COUNT(*)
Yaban 6 satır 6 6
Nutuk 14 satır 14 14
Aylak Adam eşleşme yokNULL 0 1
Kozmos eşleşme yokNULL 0 1

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";
title orders
… …
Aylak Adam 0
… …
Kozmos 0

Filtreyi ON içine yazdık; WHERE’e yazsaydık eşleşmeyen satırlar yine elenirdi.

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.dbbağlantı 2 · yeniden açıldı
VIEWşemada kalıcı
CREATE VIEW book_summary
TEMP VIEWbağlantı boyunca
CREATE TEMPORARY VIEW✕
CTEtek sorgu boyunca
WITH … SELECT

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";

Geçici Görünümü Sorgulamak

SELECT * FROM "category_summary";
category units revenue_kurus avg_price_kurus
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.

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.

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ğru32943.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).

Ortalamaların Ortalaması: Ağırlık Hesabı

Farkın nereden geldiğini Bilim kategorisinin dört kitabında adım adım görelim:

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ç sorguAdın neyi temsil ettiğini söyler.
dış sorguAdı bir tablo gibi çağırır.

CTE ile Aynı Sonuç, Şemada İz Yok

Önceki slayttaki sorgu, geçici görünümle aynı sonucu verir:

category units revenue_kurus
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';
0

Hangisini Ne Zaman?

VIEW TEMPORARY VIEW CTE
Ö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.

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
id ordered_at total_kurus
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
123…
orders_sep 39 satır · Eylül
4647…
orders_oct 49 satır · Ekim
868788…

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';
ordered_at strftime(‘%Y-%m’, “ordered_at”)
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.

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ü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;
id book_id quantity unit_price_kurus ordered_at status
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ı.

Bölümlemenin Sınırı ve Adlandırma

Mantıksal bölümleme: görünümVeri 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ğiPostgreSQL 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.

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)
id customer_id quantity
1 18 1
2 15 1
3 22 2
Analistsatış örüntülerini inceler
orders_analysis (görünüm)
id quantity customer
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;

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;
id book_id quantity ordered_at customer
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.

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
id customer
1 Anonim
2 Anonim
id→
orders (ham tablo)
id customer_id
1 18
2 15
customer_id→
customers
id name email
15 Yiğit Özkan yigit.ozkan@example.com
18 Ceren Yalçın ceren.yalcin@example.com

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;
id ordered_at name email
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.

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:%';
ordered_at book_id quantity
2026-10-19 03:12:45 19 1

O saatte tek sipariş var; o gece sipariş verdiğini bilen biri satırı eşleştirebilir.

Erişim Denetimi: SQLite’ta Yok, Başka Sistemlerde Var

SQLiteVeritabanı 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, OracleKullanı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.

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;

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.

id title price_kurus
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.

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
id title discontinued
6 Çalıkuşu 1
7 Aylak Adam 0
8 Yaban 01
9 Beyaz Kale 0
→discontinued = 0
available_books
id title
7 Aylak Adam
8 Yaban
9 Beyaz Kale

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.

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 01

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;
Parça Anlamı
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

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;
id title
11 Suç ve Ceza
12 Dönüşüm
13 Yabancı
14 Veba
15 1984
17 Simyacı
id title
6 Çalıkuşu
8 Yaban
16 Hayvan Çiftliği

Hata yok: Hayvan Çiftliği görünümden çıktı, satır tabloda duruyor.

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.

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ırINSERT INTO "books" ("title", "category_id", "price_kurus") VALUES (NEW."title", kategori adının kimliği, NEW."price_kurus");

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.

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.

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);
id title category
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.

Yumuşak Silmenin İki Yüzü

available_books: bugünün listesiVitrin 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ş raporuSatış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';
title orders units revenue_kurus
Yaban 6 6 51000

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.

Hepsi Bir Arada

Aynı altı tablonun üzerinde sekiz görünüm, beş ayrı ihtiyaç:

SADELEŞTİRMEpurchase_history · book_catalog
customersordersbookscategoriesauthorsbook_authors
TOPLAMAbook_summary
ordersbooks
BÖLÜMLEMEorders_aug · orders_sep · orders_oct
orders
GÜVENLİKorders_analysis
orders
YUMUŞAK SİLMEavailable_books
booksINSTEAD OF
geçici katmanTEMPORARY VIEW category_summary · CTE per_book
customersmüşteri
orderssipariş · köprü
bookskitap
categorieskategori
authorsyazar
book_authorskitap ↔ yazar · köprü

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;
customer “city” ordered_at title
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.

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%';
type name
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.

Görünümün Sınırları

Sonucu saklamazHer sorguda alttaki sorgu yeniden çalışır; büyük tabloda yavaş olabilir. MATERIALIZED VIEW (PostgreSQL, Oracle) sonucu saklar; SQLite’ta yoktur.
Doğrudan yazılamazGö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ırTablonun 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.

Alıştırmalar

Beş soru dersin dört bölümünü tarar. Her sorunun cevabı bir sonraki slayttadır.

  1. Aylık gelir: özet görünümü
  2. Ortalamanın üstündeki kitaplar: CTE
  3. Sızıntıyı bulun: güvenlik
  4. Görünümden silmek: hata ve çıkış yolları
  5. INSTEAD OF DELETE: tetikleyici

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");
month revenue_kurus
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

Amaç SQL
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:

  1. Bir görünüm ile bir tablo arasındaki en önemli fark nedir?
  2. CREATE TEMPORARY VIEW ile WITH (CTE) arasında ömür bakımından ne fark var?
  3. 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;

Dersi Toparlayalım

  1. 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.
  2. TEMPORARY VIEW bağlantı boyunca, CTE yalnızca tanımlandığı sorguda geçerlidir.
  3. 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.