İçeriğe geç
academia.sh

Ders 05 / 14

Boş Değerler

Boş değerin bir değer olmaması, üç değerli mantığın doğruluk tablosu, süzgeçlerin ve NOT IN yazımının davranışı, tekillik kısıtının boş değere bakışı ve boş değerden kaçınma yolları.

İçindekiler

Önceki derste iki kez “ya boştur” ifadesi geçti: yabancı anahtar boş olabilir, SET NULL eylemi sütunu boşaltır. Boş değerin ne anlama geldiği ise tanımlanmadı. Bu dersin sorusu şudur: boş değer nedir, onunla yapılan karşılaştırmalar ne döndürür, ve doğru–yanlış ikilisine üçüncü bir sonuç eklenmesi sorgu yazımında hangi tuzakları açar?

Boş Değer Bir Değer Değildir

Boş değer (null), bir sütunda değer bulunmadığını gösteren işarettir. Sıfır değildir, boş dizgi değildir, “yok” adlı özel bir değer de değildir. Anlamı “bu satır için o bilgi mevcut değildir” biçiminde okunur ve iki ayrı durumu aynı işaretle temsil eder: bilinmeyen bilgi (üyenin telefon numarası vardır ama kayıtlı değildir) ve uygulanamayan bilgi (ödünç işleminin iade tarihi henüz oluşmamıştır). Bu iki durumun aynı işareti paylaşması, ilişkisel modele yöneltilen en eski eleştirilerden biridir.

Bir değer olmadığı için, boş değer bir değerle karşılaştırılamaz. Karşılaştırmanın sonucu doğru da yanlış da değildir:

sqlite3 :memory: <<'SQL'
.headers on
.mode box
.nullvalue (bos)
SELECT NULL = NULL           AS esit_mi,
       NULL <> NULL          AS farkli_mi,
       NULL IS NULL          AS bos_mu,
       (NULL = NULL) IS NULL AS sonuc_bos_mu;
SQL
┌─────────┬───────────┬────────┬──────────────┐
│ esit_mi │ farkli_mi │ bos_mu │ sonuc_bos_mu │
├─────────┼───────────┼────────┼──────────────┤
│ (bos)   │ (bos)     │ 1      │ 1            │
└─────────┴───────────┴────────┴──────────────┘

NULL = NULL doğru değildir; NULL <> NULL de doğru değildir. İkisi de üçüncü bir sonuç verir. Boş değeri sınamanın tek doğru yolu IS NULL ve IS NOT NULL işleçleridir — bunlar karşılaştırma değil, durum sorgusudur ve her zaman doğru ya da yanlış döndürür.

Üçüncü Doğruluk Değeri

Karşılaştırmanın verdiği üçüncü sonucun adı bilinmiyor (unknown), sisteminin adı ise üç değerli mantıktır (three-valued logic). AND ve OR işleçleri bu üçüncü değeri de kapsayacak biçimde genişletilmiştir. Tabloyu ezberlemek gerekmez; hesaplatmak yeter:

sqlite3 :memory: <<'SQL'
.headers on
.mode box
.nullvalue (bos)
WITH d(ad, v) AS (VALUES ('dogru', 1), ('yanlis', 0), ('bilinmiyor', NULL))
SELECT a.ad AS sol, b.ad AS sag,
       a.v AND b.v AS ve, a.v OR b.v AS veya
FROM d a, d b;
SQL
┌────────────┬────────────┬───────┬───────┐
│    sol     │    sag     │  ve   │ veya  │
├────────────┼────────────┼───────┼───────┤
│ dogru      │ dogru      │ 1     │ 1     │
│ dogru      │ yanlis     │ 0     │ 1     │
│ dogru      │ bilinmiyor │ (bos) │ 1     │
│ yanlis     │ dogru      │ 0     │ 1     │
│ yanlis     │ yanlis     │ 0     │ 0     │
│ yanlis     │ bilinmiyor │ 0     │ (bos) │
│ bilinmiyor │ dogru      │ (bos) │ 1     │
│ bilinmiyor │ yanlis     │ 0     │ (bos) │
│ bilinmiyor │ bilinmiyor │ (bos) │ (bos) │
└────────────┴────────────┴───────┴───────┘

Tabloda okunacak kural şudur: sonucu belirleyen bir taraf varsa bilinmezlik yayılmaz. yanlis AND bilinmiyor yanlıştır, çünkü bir taraf yanlışsa sonuç ne olursa olsun yanlıştır. dogru OR bilinmiyor doğrudur, aynı gerekçeyle. Diğer bütün durumlarda bilinmezlik sonuca taşınır. Bu, Programlama Temelleri kursundaki kısa devre değerlendirmenin mantık düzeyindeki karşılığıdır.

Süzgecin Böldüğü Küme

WHERE yalnızca doğru sonucu veren satırları geçirir; yanlış ile bilinmiyor birlikte elenir. Bunun görünür sonucu, birbirinin değili sanılan iki koşulun tüm satırları kapsamamasıdır:

sqlite3 :memory: <<'SQL'
.headers on
.mode box
CREATE TABLE odunc (odunc_no INTEGER PRIMARY KEY, uye_no INTEGER, iade_tarihi TEXT);
INSERT INTO odunc VALUES (1001, 41, '2025-03-16'), (1002, 52, NULL),
                         (1003, 41, '2025-03-19'), (1004, 41, NULL);
SELECT (SELECT COUNT(*) FROM odunc) AS toplam,
       (SELECT COUNT(*) FROM odunc WHERE iade_tarihi =  '2025-03-16') AS esit,
       (SELECT COUNT(*) FROM odunc WHERE iade_tarihi <> '2025-03-16') AS farkli,
       (SELECT COUNT(*) FROM odunc WHERE iade_tarihi IS NULL) AS bos;
SQL
┌────────┬──────┬────────┬─────┐
│ toplam │ esit │ farkli │ bos │
├────────┼──────┼────────┼─────┤
│ 4      │ 1    │ 1      │ 2   │
└────────┴──────┴────────┴─────┘

Dört satır vardır; “eşittir” bir satır, “eşit değildir” bir satır döndürür. Toplamları dörde ulaşmaz, çünkü iki satır iki süzgeçten de geçmez. İki değerli mantıkta alışılmış olan “koşul ya da değili tüm kümeyi kapsar” kuralı, boş değer bulunan bir sütunda geçerli değildir. Kapsama isteniyorsa koşul açıkça yazılır.

NOT IN Tuzağı

Aynı kuralın en pahalı sonucu alt sorgularda görülür. x NOT IN (a, b, c) yazımı, x <> a AND x <> b AND x <> c ifadesine denktir. Listede tek bir boş değer bulunuyorsa o karşılaştırma bilinmiyor döner, AND zinciri bilinmezliğe düşer ve hiçbir satır geçmez:

sqlite3 :memory: <<'SQL'
.headers on
.mode box
CREATE TABLE uye  (uye_no INTEGER PRIMARY KEY, ad TEXT NOT NULL);
CREATE TABLE ceza (ceza_no INTEGER PRIMARY KEY, uye_no INTEGER);
INSERT INTO uye  VALUES (41, 'Ayşe Demir'), (52, 'Mert Kaya'), (63, 'Selin Öztürk');
INSERT INTO ceza VALUES (9001, 52), (9002, NULL);

SELECT 'NOT IN, alt sorgu ham' AS yazim, COUNT(*) AS donen_satir
  FROM uye WHERE uye_no NOT IN (SELECT uye_no FROM ceza)
UNION ALL
SELECT 'NOT IN, bos degerler elenmis', COUNT(*)
  FROM uye WHERE uye_no NOT IN (SELECT uye_no FROM ceza WHERE uye_no IS NOT NULL)
UNION ALL
SELECT 'NOT EXISTS', COUNT(*)
  FROM uye u WHERE NOT EXISTS (SELECT 1 FROM ceza c WHERE c.uye_no = u.uye_no);
SQL
┌──────────────────────────────┬─────────────┐
│            yazim             │ donen_satir │
├──────────────────────────────┼─────────────┤
│ NOT IN, alt sorgu ham        │ 0           │
│ NOT IN, bos degerler elenmis │ 2           │
│ NOT EXISTS                   │ 2           │
└──────────────────────────────┴─────────────┘

Doğru yanıt ikidir: cezası bulunmayan iki üye vardır. İlk yazım sıfır döndürür ve bunu hata vermeden yapar — sorgu geçerlidir, sonuç sessizce yanlıştır. Bu, boş değerin en sık karşılaşılan tuzağıdır ve iki savunması vardır: ya alt sorguda boş değerler elenir, ya da NOT EXISTS yazımı seçilir. İkincisi tercih edilir, çünkü doğruluğu alt sorgunun içeriğine bağlı değildir.

Tekillik ve Boş Değer

Tekillik kısıtı da aynı mantığı izler. İki boş değer birbirine eşit sayılmadığı için, tekil bir sütun birden çok boş değer taşıyabilir:

sqlite3 :memory: <<'SQL'
.headers on
.mode box
CREATE TABLE uye (
  uye_no INTEGER PRIMARY KEY,
  ad     TEXT NOT NULL,
  tc_no  TEXT UNIQUE
);
INSERT INTO uye VALUES (41, 'Ayşe Demir', NULL);
INSERT INTO uye VALUES (52, 'Mert Kaya', NULL);
INSERT INTO uye VALUES (63, 'Selin Öztürk', NULL);
SELECT COUNT(*) AS satir FROM uye;
SQL
┌───────┐
│ satir │
├───────┤
│ 3     │
└───────┘

Üç satır da geçmiştir. Bu davranış standarda uygundur, ancak motora göre değişebilir; kimi sistemde tekil sütunda yalnız bir boş değere izin verilir. Bu belirsizliğin pratik sonucu şudur: bir sütunun hem tekil hem zorunlu olmasını istiyorsanız UNIQUE yeterli değildir, yanına NOT NULL yazılmalıdır. Tersi de doğrudur: tekillik kısıtı, boş değerlerin sayısını sınırlamak için kullanılamaz.

Toplama İşlevleri

Toplama işlevleri boş değerleri sayıma katmaz. COUNT(*) satır sayar, COUNT(sutun) o sütunda değer bulunan satırları sayar; SUM ve AVG boş değerleri atlar:

sqlite3 :memory: <<'SQL'
.headers on
.mode box
.nullvalue (bos)
CREATE TABLE ceza (ceza_no INTEGER PRIMARY KEY, tutar INTEGER);
INSERT INTO ceza VALUES (9001, 30), (9002, NULL), (9003, 10);
SELECT COUNT(*) AS satir, COUNT(tutar) AS dolu_tutar,
       SUM(tutar) AS toplam, AVG(tutar) AS ortalama FROM ceza;
SQL
┌───────┬────────────┬────────┬──────────┐
│ satir │ dolu_tutar │ toplam │ ortalama │
├───────┼────────────┼────────┼──────────┤
│ 3     │ 2          │ 40     │ 20.0     │
└───────┴────────────┴────────┴──────────┘

Ortalama, üç satırın değil iki değerin ortalamasıdır. Boş değeri sıfır saymak isteniyorsa bu açıkça yazılmalıdır; işlevin kendiliğinden yaptığı şey atlamaktır. Sıralamada boş değerlerin başa mı sona mı geleceği ise standartta belirtilmemiştir ve motora göre değişir; belirli bir yer isteniyorsa sıralama ifadesinde açıkça bildirilmelidir.

Boş Değerden Kaçınmak

Boş değer bir araçtır, hedef değildir. Üç yaygın durumda gereksiz yere ortaya çıkar ve üçünün de modelleme karşılığı vardır.

  • Çok değerli olgu için ayrılmış sütunlar. telefon_1, telefon_2, telefon_3 sütunlarının ikisi çoğu satırda boştur. Karşılığı, telefonları ayrı bir bağıntıya taşımaktır.
  • Yalnız bazı satırlar için anlamlı sütunlar. Süreli üyeliğe özgü bir bitiş tarihi, süresiz üyelerde boş durur. Karşılığı, alt türü ayrı bir bağıntıda tutmaktır.
  • Henüz gerçekleşmemiş olay. İade tarihinin boş olması, ödünç işleminin sürdüğü anlamına gelir. Bu, boş değerin yerinde kullanımıdır — olayın olmadığını bildiren başka bir işaret yoktur.

Genel ölçüt şudur: boş değer bir durum bildiriyorsa yerindedir; eksik modellemeyi kapatıyorsa şema düzeltilmelidir.

Özet

  • Boş değer bir değer değildir; sıfırdan ve boş dizgiden ayrıdır ve yalnız IS NULL ile sınanır.
  • Karşılaştırmalar üçüncü bir sonuç üretir; WHERE yalnız doğru olanı geçirir, bu yüzden bir koşul ile değili tüm satırları kapsamaz.
  • NOT IN alt sorgusunda tek bir boş değer sonucu sessizce boşaltır; NOT EXISTS bu duyarlılığı taşımaz.
  • Tekillik kısıtı boş değerleri birbirinden farklı sayar; zorunluluk isteniyorsa NOT NULL ayrıca yazılır.
  • Toplama işlevleri boş değerleri atlar; ortalama, satır sayısına değil değer sayısına bölünür.

Sonraki Adım

Bu ders bir sütunun boş olabilmesinin sonuçlarını gösterdi; sütunun ne tuttuğu ise hâlâ belirsiz. Kurs boyunca tarihler metin olarak, tutarlar tam sayı olarak yazıldı ve bu seçimler gerekçelendirilmedi. Sıradaki ders sayı, metin, tarih ve ikili tipleri ele alır; para tutarının neden kayan noktalı sayıda tutulmadığını, metin karşılaştırmasının hangi ayara bağlı olduğunu ve büyük ikili verinin veritabanında durup durmayacağını tartışır.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat