Blok 5 · Ders 33
Veritabanı nedir? PostgreSQL'e giriş
Tablo, satır, sütun, veri tipleri ve birincil anahtar; ücretsiz bir PostgreSQL veritabanı açıp ilk tabloyu oluşturuyorsun.
Bu dersin sonunda
- Bir JSON dosyasını çok kullanıcılı bir API'de veri deposu yapmanın neden tehlikeli olduğunu çalışan bir örnekle gösterebilirsin
- Tablo, satır, sütun ve veri tipi kavramlarını bir Excel sayfasıyla karşılaştırarak anlatabilirsin
- Neon'da ücretsiz bir PostgreSQL veritabanı açıp SQL Editor'de sorgu çalıştırabilirsin
CREATE TABLEile doğru veri tipleri ve otomatik artan birincil anahtarı olan bir tablo kurabilirsinINSERTile tek ya da çok satır ekleyipSELECT *ile tabloya bakabilirsin- Tırnak, virgül, tip ve anahtar hatalarını PostgreSQL'in mesajını okuyarak düzeltebilirsin
Bu sayfada
Blok 4'te API'lerinin verisini bir JSON dosyasında tuttun ve Ders 29'da bu dosyanın zayıf yerlerini kendi gözünle gördün. Bu derste o sorunlara adlarını koyacak, sonra verini gerçek bir veritabanına taşımaya başlayacaksın: bulutta ücretsiz bir PostgreSQL veritabanı açacak, ilk tablonu kuracak ve içine satırlar ekleyeceksin. Bunun için yeni bir dil öğreniyorsun: SQL. Kuralları JavaScript'inkinden azdır ve yazdığın her komutun sonucunu hemen bir tablo olarak görürsün.
Dosya neden yetmiyor?
Ders 32'deki takım API'si verisini bir JSON dosyasında tutuyordu: bir değişiklik geldiğinde dosya okunur, değiştirilir ve geri yazılır. Sen tek başına denerken bu sorunsuz çalışır. Ama API internette; aynı anda onlarca istek gelebilir.
Node bir isteğin dosya okumasını beklerken (await) boş durmaz, öteki isteğe geçer (Ders 15'teki olay döngüsü). Ders 29'da kalabalik.js ile on isteği aynı anda gönderdiğinde ne olduğunu gördün: "eklendi" cevabı alan şampiyonların çoğu listede yoktu.
Sorunu en küçük hâline indirelim: sunucu yok, istek yok; yalnız aynı dosyaya aynı anda yazan iki fonksiyon var.
Takımın ortak bir kasası olsun. İki oyuncu aynı anda kasaya altın ekliyor: biri 100, öteki 50. Kasada başta 0 altın var, sonuç 150 olmalı. C:\kod\veritabani adında yeni bir klasör aç, VS Code'da bu klasörü aç ve kayip.mjs adında bir dosyaya şunu yaz:
import { readFile, writeFile } from "node:fs/promises" async function altinEkle(miktar) { const kasa = JSON.parse(await readFile("kasa.json", "utf8")) kasa.altin = kasa.altin + miktar await writeFile("kasa.json", JSON.stringify(kasa))} await writeFile("kasa.json", JSON.stringify({ altin: 0 }))await Promise.all([altinEkle(100), altinEkle(50)])const son = JSON.parse(await readFile("kasa.json", "utf8"))console.log("Kasadaki altın:", son.altin)PS C:\kod\veritabani> node kayip.mjsKasadaki altın: 100PS C:\kod\veritabani> node kayip.mjsKasadaki altın: 50PS C:\kod\veritabani> node kayip.mjsKasadaki altın: 50Üç çalıştırma, hiçbirinde 150 yok. Promise.all iki altinEkle çağrısını aynı anda başlatır; iki oyuncunun isteği aynı anda gelmiş gibi.
Birkaç kez daha çalıştırırsan program bazen sonucu yazamadan çöker:
PS C:\kod\veritabani> node kayip.mjs<anonymous_script>:1 SyntaxError: Unexpected end of JSON input at JSON.parse (<anonymous>) at altinEkle (file:///C:/kod/veritabani/kayip.mjs:4:23) at async Promise.all (index 0) at async file:///C:/kod/veritabani/kayip.mjs:10:1 Node.js v24.20.0Programı sen de beş on kez çalıştır; sonuç her seferinde değişebilir, çünkü hangi işin önce biteceğine işletim sistemi karar verir. Bu dersi hazırlarken programı 200 kez çalıştırdık: doğru sonuç (150) yalnız 2 kez çıktı. 172 kez altının bir kısmı kayboldu (50 ya da 100), 26 kez de program çöktü. Sende sayılar farklı çıkar; bilgisayarın o an ne kadar meşgul olduğuna bile bağlıdır.
Her sonuç ayrı bir sorunu gösteriyor:
- 50 ya da 100: İki çağrı da dosyayı neredeyse aynı anda okudu ve ikisi de
altin: 0gördü. Biri 0 + 100 yazdı, öteki 0 + 50 yazıp onun üstüne bindi. Hata mesajı yok; veri sessizce yanlış. - SyntaxError:
writeFileönce dosyayı boşaltır, sonra yazar (Ders 29'da gördün).altinEkle'nin içindeki okuma (mesajdakikayip.mjs:4) dosyayı tam o boş anda yakaladı; boş metin geçerli bir JSON değildir:Unexpected end of JSON input, yani "JSON beklenmedik yerde bitti". İki yazma iç içe geçerse dosyada iki kaydın karışımı da kalabilir ({"altin":50}}gibi); o zaman mesajUnexpected non-whitespace character after JSONolur ve bozuk dosyayı okuyan her istek aynı hatayla çöker. - 150: Ara sıra doğru sonuç da çıkar. Asıl tehlike şu: istekleri tek tek denediğin sürece kod hep doğru çalışır; sorun ancak canlıda, istekler aynı anda gelince ortaya çıkar.
Ders 29'da bu iki sorunun çaresini yazmıştın: yazmaları degistir ile sıraya koymak ve geçici dosyaya yazıp rename ile değiştirmek (atomik işlem). Bu çareler işe yarar, ama yalnız tek bir programın içinde.
Kalabalık sitelerde aynı API'nin birden çok kopyası çalışır (Ders 32'de sunucusuz barındırıcıların trafiğe göre kopya açtığını gördün) ve her kopyanın kendi sırası olur. Kopyalar aynı dosyayı paylaşıyorsa yine aynı anda yazarlar; paylaşmıyorsa her biri verinin ayrı bir hâlini görür, bu daha da kötü. Veritabanı ise sırayı, ona bağlanan bütün programlar için tek bir yerde kendisi tutar.
Aynı anda yazma, dosyanın tek zayıflığı değil:
| Sorun | JSON dosyasında | Veritabanında |
|---|---|---|
| Aynı anda yazma | Kayıp güncelleme, bozuk dosya | İstekleri düzenler; bir değişiklik ya tam yapılır ya hiç yapılmaz |
| Arama hızı | Tek kaydı bulmak için bütün dosya okunur | Aradığını indeksle doğrudan bulur |
| Bütünlük | "hp": "yüz", eksik alan, aynı id'den iki tane: hepsi girer | Tipleri ve kuralları kendisi denetler, uymayanı reddeder |
| İlişki | Şampiyonun adı her takımın içinde kopya; ad değişince Ders 32'deki gibi bütün takımları dolaşırsın | Tablolar birbirine kimlik numarasıyla bağlanır, ad tek yerde durur |
Arama hızını da ölçtük. Bir milyon maçlık bir JSON dosyası (her maçta oyuncu, şampiyon, sonuç, skor ve tarih) 142 MB tuttu. İçinden tek bir maçı bulmak, dosyayı okuyup JSON.parse ile çözmek dahil, her seferinde yaklaşık 650 milisaniye sürdü. Aynı bir milyon maçı aynı bilgisayardaki bir PostgreSQL tablosuna koyup aynı maçı numarasıyla aradığımızda cevap bir milisaniyeden kısa sürede geldi: yüzlerce kat hızlı.
Postgres bunu indeks ile yapar: kitabın sonundaki dizin gibi, bir değerin hangi satırda olduğunu tutan yardımcı bir yapı. Bu derste kuracağın birincil anahtarın indeksi kendiliğinden oluşur; başka indeksleri ve tablolar arası bağları Ders 36'da kuracaksın.
Bir sorun daha var; ısınmada hatırladın: barındırıcıda dosyaya yazdığın veri kalıcı değil. Ders 32'de API'ni koyduğun Render'ın ücretsiz servisinde disk geçicidir; servis uyuyup uyanınca ya da yeni sürüm yüklenince veri.json ilk hâline döner. Veritabanı ise kendi sunucusunda, kalıcı diskte durur; API'n kaç kez yeniden başlarsa başlasın veri yerinde kalır.
Veritabanı: veriyi yöneten program
Veritabanı (database), düzenli tutulan veri deposudur. Ama asıl işi, bu depoyu yöneten program yapar: veritabanı sunucusu. Ona "şu satırı ekle" ya da "Ahri'nin maçlarını getir" dersin; o da veriyi diske güvenle yazar, aynı anda gelen istekleri düzene koyar, kurallara uymayanı geri çevirir ve aradığını hızla bulur.
Ders 27'de bir portu dinleyip istek bekleyen kendi sunucunu yazmıştın. PostgreSQL de böyle bir sunucudur; varsayılan portu 5432'dir. Ders 37'de Node.js'ten ona bağlanacak, Ders 38'de takım API'ni ona taşıyacaksın. Bu derste ona tarayıcıdan, Neon'un SQL Editor'ü üzerinden bağlanıyorsun.
Tablo, satır, sütun
Veritabanlarının birkaç türü var; bu kursta en yaygınını, ilişkisel veritabanını (relational database) kullanacaksın. MongoDB gibi belge veritabanlarının adını da duyacaksın; onlar veriyi JSON'a benzer belgeler olarak saklar. İlişkisel veritabanında ise veri tablolarda durur. Her tablonun bir adı ve baştan belli sütunları vardır; her satır bir kayıttır.
sampiyon_havuzu tablosu. Her sütunun adı ve tipi var; her satır bir şampiyon."İlişkisel" adı aslında tablolardan gelir: matematikte böyle tablolara "ilişki" (relation) denir. Postgres de tabloya relation der; hata mesajlarında bu sözcüğü sık göreceksin. Yine de modelin en güçlü yanı, adının çağrıştırdığı şey: tabloların birbirine bağlanabilmesi. Bir maç satırı, oynayan oyuncuyu ve şampiyonu kimlik numaralarıyla gösterir. Bunu Ders 36'da kuracaksın.
Excel sayfasına benzer, ama kuralları var
Tablo ilk bakışta bir Excel sayfasına benzer: üstte sütun başlıkları, altta satır satır veri. Benzetme işe yarar, ama farklar önemli:
- Tip zorunlu. Excel'de maç sütununa
kırk sekizyazabilirsin. Postgres'temac_sayisisütunuintegerise o satır reddedilir. - Kural koyabilirsin. "Bu sütun boş olamaz", "bu değer iki kez olamaz" gibi kurallara kısıt (constraint) denir. Bu derste en önemlisini, birincil anahtarı kuracaksın; ötekiler Ders 35'te.
- Satır numarası yok. Excel'deki 5. satırın yeri sabittir. Veritabanında satırların sabit bir sırası yoktur; bir satırı "5. satır" diye değil, kimliğiyle bulursun (birazdan göreceksin).
- Formül yok. Hesabı hücreye değil, sorguya yazarsın. "Toplam kaç maç oynadım?" sorusunu Ders 34'te soracaksın.
- Çok kullanıcı, çok veri. Excel dosyasını genelde önünde oturan bir kişi kullanır. Veritabanı ise aynı anda yüzlerce programın okuyup yazması ve milyonlarca satır için yapıldı.
PostgreSQL ve SQL
Veritabanıyla SQL ile konuşursun (Structured Query Language, yapılandırılmış sorgu dili). SQL 1970'lerde IBM'de doğdu; bugün neredeyse bütün ilişkisel veritabanları onu anlar. Okunuşu iki türlü: harf harf "es-kü-el" ya da eski adı SEQUEL'den kalma "sikuıl". İkisi de doğru.
SQL'in JavaScript'ten büyük bir farkı var: nasıl yapılacağını değil, ne istediğini yazarsın. Ders 5'te bir dizide arama yaparken adımları sen kurmuştun: baştan başla, her elemana tek tek bak, aradığını bulunca dur. SQL'de bu adımları yazmazsın. "Kazanma oranı 50'nin üstündeki şampiyonları getir" dersin; nereden ve hangi sırayla bakılacağına veritabanı karar verir ve en hızlı yolu kendisi seçer.
PostgreSQL bu kursta kullanacağın veritabanı sunucusu. 1986'da California Üniversitesi Berkeley'de başlayan POSTGRES projesinden doğdu ve yaklaşık 40 yıldır geliştiriliyor. Okunuşu "post-gres-kü-el"; neredeyse herkes kısaca Postgres der. Neden Postgres?
- Ücretsiz ve açık kaynak. Lisansı onu istediğin işte kullanmana izin verir.
- En yaygını. 2025 Stack Overflow geliştirici anketinde katılımcıların %55,6'sı Postgres kullandığını söyledi; veritabanları arasında birinci. Profesyonel geliştiricilerde oran %58,2.
- Sıkı ve güçlü. Tipleri ve kuralları ciddiye alır; gerektiğinde JSON verisini de saklayıp sorgulayabilir.
- Her yerde. Neon ve Supabase gibi bulut servisleri doğrudan Postgres sunar; birinden ötekine geçmek kolaydır.
Bu kursta SQL'i şu kurallarla yazıyoruz:
- SQL'in kendi sözcüklerini (
SELECT,CREATE TABLE,INSERT INTO,VALUES…) büyük, senin verdiğin adları (tablo, sütun) ve veri tiplerini küçük harfle. Postgres için fark etmez,selectda çalışır; büyük harf yalnız okumayı kolaylaştırır. - Her komut noktalı virgülle (
;) biter. JavaScript'te noktalı virgül koymuyoruz, ama SQL'de editörde birden çok komut olduğunda Postgres nerede bittiklerini buradan anlar. - Metin tek tırnakla yazılır:
'Ahri'. JavaScript alışkanlığından en çok ayrılacağın yer burası. --ile başlayan kısım yorumdur, JavaScript'teki//gibi.
Neon'da ücretsiz bir Postgres aç
Postgres'i kendi bilgisayarına da kurabilirsin, ama bu derste kurulum yok: bulutta hazır çalışan bir Postgres kullanacaksın. Neon, Postgres'i bulutta çalıştıran bir servis. Ücretsiz planı bir deneme sürümü değil, kalıcıdır ve kredi kartı istemez.
Ücretsiz planın sınırları (Ekim 2026'daki resmî belgeye göre): 100 proje; her projede 1 GB depolama; her proje için ayda 100 CU-saat işlem süresi. CU-saat, veritabanının çalıştığı süreyi ölçer; 100 CU-saat, en küçük boyda bir veritabanını ayda 400 saat çalıştırmaya yeter. Veritabanı 5 dakika istek almazsa uyur ve süre harcamaz; sıradaki sorguda birkaç yüz milisaniyede uyanır. Ay içinde süre biterse veritabanı yeni aya kadar durur, ama verin silinmez.
- neon.com adresine git ve Sign up (ya da Get started) düğmesine bas. GitHub hesabınla (Ders 7'de açtın), Google hesabınla ya da e-postanla kaydolabilirsin.
- Neon senden bir proje oluşturmanı ister; istemezse New Project'e bas. Project name kutusuna
ogrenyaz. Region olarak sana en yakın bölgeyi seç; Türkiye'den AWS Europe (Frankfurt) iyi bir seçim. Bölge sonradan değiştirilemez. - Services bölümünde Postgres database açık kalsın, sürümünü (Postgres version) olduğu gibi bırak. Öteki servisler bu kursta gerekmiyor. Create project'e bas.
- Birkaç saniye içinde veritabanın hazır; adı
neondb. Sol menüde Postgres database altındaki SQL Editor'ü aç. Üstteki seçicilerde dal (branch) olarakproduction, veritabanı olarakneondbseçili olmalı. - Editörde Neon'un hazırladığı örnek bir kod durabilir. New Query ile boş bir sayfa aç.
- Dal ve veritabanı: sorgunun hangi veritabanında çalışacağı. Bu derste hep
productionveneondb. - Editör: SQL'i buraya yazarsın.
- Run: editördeki komutları çalıştırır. Yanındaki Explain ve Analyze'a şimdilik dokunma.
- Sonuç bölümü: editörde birden çok komut varsa her birinin sonucu, çalışma sırasıyla numaralanmış ayrı bir sekmede çıkar. Soldaki History daha önce çalıştırdığın komutları saklar.
Başka yollar. Postgres'i bilgisayarına kurmak istersen Windows için postgresql.org'daki EDB yükleyicisi sunucuyu ve pgAdmin adlı görsel yönetim aracını birlikte kurar. Docker kullanıyorsan resmî postgres imajıyla tek komutla bir sunucu açılır. Bulutta Neon'a benzer başka servisler de var (Supabase gibi). Hangisini seçersen seç SQL aynıdır; bu kurstaki komutlar hepsinde çalışır. Neon'u seçmemizin sebebi kurulum istememesi ve API'ni yayına aldığında internetten hazır ulaşılabilir olması.
İlk sorgu
Editöre şunu yaz ve Run'a bas:
SELECT 'Merhaba Sihirdar Vadisi' AS mesaj, 2 + 3 AS toplam;| mesaj | toplam |
|---|---|
| Merhaba Sihirdar Vadisi | 5 |
Sonuç bölümünde iki sütunlu, tek satırlı bir tablo görürsün. Postgres sorguların cevabını tablo olarak verir; bu derste sonuçları hep böyle tablolarla göstereceğiz.
SELECT"şunu göster" demek. Burada hiçbir tabloya bakmıyoruz, yalnız bir metni ve bir hesabı gösteriyoruz. Asıl işini, tablodan veri seçmeyi, Ders 34'te öğreneceksin.'Merhaba Sihirdar Vadisi'bir metin; tek tırnak içinde.AS mesajsonuç sütununa ad verir. Yazmazsan Postgres sütunun adını?column?koyar.;komutu bitirir.
column "Ahri" does not existNe zaman çıkar? Metni JavaScript alışkanlığıyla çift tırnakla yazınca. SQL'de çift tırnak metin değil, ad (tablo ya da sütun adı) demektir; Postgres Ahri adında bir sütun arar, bulamaz.
SELECT "Ahri" AS sampiyon;ERROR: column "Ahri" does not existNasıl düzelir? Metni tek tırnağa al: SELECT 'Ahri' AS sampiyon;. Kuralı ezberle: tek tırnak değer, çift tırnak ad.
syntax error at or near "SELECT"Ne zaman çıkar? Editörde iki komut var ve aralarındaki noktalı virgülü unuttun. Postgres ikisini tek komut sanır ve ikinci SELECT'e gelince takılır.
SELECT 'Ahri' AS sampiyonSELECT 'Lux' AS sampiyon;ERROR: syntax error at or near "SELECT"Nasıl düzelir? Her komutun sonuna ; koy. syntax error at or near "X", "X'te ya da hemen öncesinde bir yazım hatası var" demektir; hatayı çoğu zaman X'in hemen önünde bulursun. Yanlış yazılmış bir sözcük de aynı mesajı verir: SELEC yazarsan syntax error at or near "SELEC" alırsın.
Veri tipleri
Excel'den en büyük fark buydu: her sütunun bir veri tipi var ve Postgres o tipe uymayan değeri sütuna sokmaz. Sık kullanacakların:
| Tip | Ne için | Örnek değer |
|---|---|---|
integer | Tam sayı, yaklaşık ±2,1 milyara kadar | 48 |
bigint | Çok büyük tam sayı | 9000000000 |
numeric(5, 2) | Kesin ondalıklı sayı: toplam 5 basamak, 2'si virgülden sonra | 54.17 |
text | Her uzunlukta metin | 'Ahri' |
varchar(16) | En çok 16 karakterlik metin | 'Faker' |
boolean | Doğru ya da yanlış | true, false |
timestamptz | Bir an: tarih, saat ve saat dilimi | '2026-10-09 21:40:00+03' |
date | Yalnız tarih | '2026-10-09' |
jsonb | JSON verisi | '{"rol": "Orta"}' |
Birkaç not:
integermibigintmi? Maç sayısı, altın, seviye gibi değerlereintegeryeter. 2,1 milyarı geçebilecek sayılar (bütün oyuncuların bütün maçlarının numarası gibi) içinbigintseçilir.textmivarcharmı? Postgres'te ikisi aynı hızda çalışır. Gerçek bir uzunluk sınırın yoksatextyaz. Riot ID'deki oyun adı gibi, sınırı kesin bilinen bir değer içinvarchar(16)kullanabilirsin; daha uzun metin hata verir.timestamptzbir anı saklar. Yazarken saat dilimini ekle: Türkiye için+03. Postgres anı saat dilimiyle birlikte anlar, gösterirken bağlantının saat dilimine çevirir. Neon'da bu saat dilimi UTC'dir; bu yüzden21:40:00+03diye yazdığın an18:40:00+00diye görünür. İkisi aynı an. Saat dilimsiz birtimestamptipi de var; kullanma, hangi saate göre olduğu belirsiz kalır.jsonbesnek veri tutar. Ama her şeyijsonbsütununa koyarsan JSON dosyasına geri dönmüş olursun; önce sütunları düşün.
numeric kesin hesaplar. Ders 9'da JavaScript'in 0.1 + 0.2 için 0.30000000000000004 verdiğini görmüştün. Postgres'te ondalıklı sayılar varsayılan olarak numeric'tir:
SELECT 0.1 + 0.2 AS toplam;| toplam |
|---|
| 0.3 |
Para, oran, puan gibi yuvarlama hatası istemediğin her şey için numeric kullan. numeric(5, 2) en çok 999.99 tutar; virgülden sonraki fazla basamak yuvarlanır, sığmayan sayı hata verir. İkisini de görevlerde deneyeceksin.
İlk tablo: CREATE TABLE
Şimdi kendi tablonu kur. Oynadığın şampiyonları ve her biriyle ilgili istatistiklerini tutan bir şampiyon havuzu yapacağız. Bu ilk sürüm bir taslak: eksiğini birazdan kendin bulacaksın.
CREATE TABLE sampiyon_havuzu ( sampiyon text, rol text, mac_sayisi integer, kazanma_orani numeric(5, 2), favori boolean, son_mac timestamptz);| Parça | Örnek | Anlamı |
|---|---|---|
| komut | CREATE TABLE | "yeni tablo kur" |
| tablo adı | sampiyon_havuzu | tablonun adı |
| parantez | ( … ) | sütunların listesi |
| sütun | mac_sayisi integer | önce ad, sonra tip |
| virgül | , | sütunları ayırır; sonuncudan sonra yok |
| noktalı virgül | ; | komutun sonu |
Komutu birkaç satıra böldük, ama Postgres için satır sonları ve girinti önemsizdir; aynı komutu tek satıra da yazabilirdin. Böyle yazmak, her sütunu ayrı satırda görmeyi sağlar.
Run'a bas. Bu komut tablo kurar, satır döndürmez; sonuç bölümünde komutun başarıyla çalıştığını görürsün. Tablon hazır, ama içi boş.
Adlara dikkat et: sampiyon_havuzu, mac_sayisi, son_mac. JavaScript'teki camelCase yerine sözcükler alt çizgiyle bağlanıyor ve hepsi küçük harf.
Sebebi şu: Postgres tırnaksız yazılan adları küçük harfe çevirir. macSayisi adında bir sütun açarsan Postgres onu macsayisi diye kaydeder; büyük harfi korumak için o adı her yerde çift tırnakla yazman gerekir. SQL'de bu yüzden adlar küçük harf ve alt çizgiyle yazılır. Türkçe harf kullanmama kuralı JavaScript'teki gibi burada da geçerli.
syntax error at or near ")"Ne zaman çıkar? Son sütundan sonra virgül bırakınca. JavaScript'te dizinin son elemanından sonra virgül sorun olmuyordu; SQL'de oluyor.
CREATE TABLE sampiyon_havuzu ( sampiyon text, rol text,);ERROR: syntax error at or near ")"Nasıl düzelir? Mesaj kapanış parantezini gösteriyor, ama hata hemen önünde: rol text, satırının sonundaki virgülü sil. Ters durumda, iki sütun arasındaki virgülü unutursan da hata bir sonraki sütunun adında çıkar: sampiyon text ile rol text arasında virgül yoksa mesaj syntax error at or near "rol" olur.
relation "sampiyon_havuzu" already existsNe zaman çıkar? Aynı CREATE TABLE'ı ikinci kez çalıştırınca. Çoğu zaman bilerek değil: editörde eski CREATE TABLE duruyorken yeni bir komut ekleyip Run'a basınca.
CREATE TABLE sampiyon_havuzu ( sampiyon text, rol text);ERROR: relation "sampiyon_havuzu" already existsNasıl düzelir? Tablo zaten kurulu; editörden CREATE TABLE komutunu sil ve yalnız yeni komutunu çalıştır. Relation (ilişki), Postgres'in tabloya verdiği teknik ad.
INSERT: satır eklemek
Tabloya satır eklemek için INSERT INTO kullanırsın. Editörü temizle ve Ahri'yi ekle:
INSERT INTO sampiyon_havuzu (sampiyon, rol, mac_sayisi, kazanma_orani, favori, son_mac)VALUES ('Ahri', 'Orta', 48, 54.17, true, '2026-10-09 21:40:00+03');Komut iki parçadan oluşuyor. INSERT INTO'dan sonra tablonun adı ve parantez içinde değer vereceğin sütunlar gelir (satır uzamasın diye sütun listesini alt satıra aldık); VALUES'tan sonra da aynı sırayla değerler. İlk değer ilk sütuna, ikinci değer ikinci sütuna gider. Metinler ve tarih tek tırnakta; sayılar ve true/false tırnaksız. Run'a basınca Postgres eklediği satır sayısını (1) bildirir.
Tabloya bakmak için şimdilik tek bir kalıp yeter: SELECT * FROM tablo_adi;. * "bütün sütunlar" demek. Editörü temizle ve çalıştır:
SELECT * FROM sampiyon_havuzu;| sampiyon | rol | mac_sayisi | kazanma_orani | favori | son_mac |
|---|---|---|---|---|---|
| Ahri | Orta | 48 | 54.17 | true | 2026-10-09 18:40:00+00 |
İlk satırın tabloda. son_mac sütununda 21:40 yerine 18:40 görmen normal: Neon saati UTC olarak gösteriyor, an aynı. Bazı araçlar true yerine t, false yerine f gösterir; anlamı aynı. SELECT'i bütün yetenekleriyle Ders 34'te öğreneceksin.
Tek komutla birden çok satır da ekleyebilirsin; değer gruplarını virgülle ayır:
INSERT INTO sampiyon_havuzu (sampiyon, rol, mac_sayisi, kazanma_orani, favori, son_mac)VALUES ('Lux', 'Destek', 31, 51.61, false, '2026-10-08 19:05:00+03'), ('Jinx', 'Nişancı', 22, 45.45, false, '2026-10-10 22:15:00+03');SELECT * FROM sampiyon_havuzu;| sampiyon | rol | mac_sayisi | kazanma_orani | favori | son_mac |
|---|---|---|---|---|---|
| Ahri | Orta | 48 | 54.17 | true | 2026-10-09 18:40:00+00 |
| Lux | Destek | 31 | 51.61 | false | 2026-10-08 16:05:00+00 |
| Jinx | Nişancı | 22 | 45.45 | false | 2026-10-10 19:15:00+00 |
Çift tırnak hatasını ilk sorguda gördün; INSERT'te de aynısı olur: VALUES ("Ahri", …) yazarsan Postgres yine column "Ahri" does not exist der. Şimdi INSERT'e özgü tuzaklara bakalım.
Metnin içinde kesme işareti
Sıradaki şampiyon Kai'Sa. Adında kesme işareti var ve SQL'de metni bitiren işaret de tam o:
INSERT INTO sampiyon_havuzu (sampiyon, rol)VALUES ('Kai'Sa', 'Nişancı');ERROR: syntax error at or near "Sa"Postgres 'Kai' metnini okudu ve bitti sandı; ardından gelen Sa'yı anlamlandıramadı. Metnin içindeki tek tırnağı iki tek tırnak yazarak belirtirsin. Ekranda yine tek tırnak görünür:
INSERT INTO sampiyon_havuzu (sampiyon, rol, mac_sayisi, kazanma_orani, favori, son_mac)VALUES ('Kai''Sa', 'Nişancı', 9, 66.67, false, '2026-10-10 23:30:00+03');Bu komut hatasız çalışır ve tabloya Kai'Sa adıyla bir satır ekler. Cho'Gath, Kog'Maw ve Bel'Veth için de aynı kural geçerli.
INSERT has more expressions than target columnsNe zaman çıkar? Ondalık sayıyı Türkçe alışkanlıkla virgülle yazınca. 51,61 Postgres için iki ayrı değerdir: 51 ve 61. Değer sayısı sütun sayısını geçer.
INSERT INTO sampiyon_havuzu (sampiyon, rol, mac_sayisi, kazanma_orani, favori, son_mac)VALUES ('Lux', 'Destek', 31, 51,61, false, '2026-10-08 19:05:00+03');ERROR: INSERT has more expressions than target columnsNasıl düzelir? Ondalık ayırıcı noktadır: 51.61. Mesajın tersi de var: bir değeri unutursan INSERT has more target columns than expressions (sütun çok, değer az) alırsın. Çok satırlı bir INSERT'te satırlardan birinin değer sayısı farklıysa mesaj VALUES lists must all be the same length olur. Üçünde de sütunları ve değerleri parmağınla tek tek say.
invalid input syntax for type integerNe zaman çıkar? Bir sütuna tipine uymayan değer yazınca. Excel'in izin verdiği şeyi Postgres reddeder.
INSERT INTO sampiyon_havuzu (sampiyon, rol, mac_sayisi)VALUES ('Garen', 'Üst', 'kırk sekiz');ERROR: invalid input syntax for type integer: "kırk sekiz"Nasıl düzelir? Mesaj sütunun tipini (integer) ve kabul edilmeyen değeri birlikte söylüyor: sayıyı rakamla, tırnaksız yaz. boolean sütuna 'evet' yazarsan da invalid input syntax for type boolean: "evet" alırsın; doğrusu true ya da false.
Tarih yazmanın tek güvenli yolu
Garen'i ekleyelim. Maçı 9 Ekim akşamı oynadın ve tarihi alıştığın gibi gün.ay.yıl yazdın:
INSERT INTO sampiyon_havuzu (sampiyon, rol, mac_sayisi, kazanma_orani, favori, son_mac)VALUES ('Garen', 'Üst', 15, 46.67, false, '09.10.2026 21:40+03');Komut hatasız çalıştı. Sonra tabloya bakmak için altına SELECT yazdın ve Run'a bastın, ama INSERT'i silmeyi unuttun. Editörde şu duruyordu:
INSERT INTO sampiyon_havuzu (sampiyon, rol, mac_sayisi, kazanma_orani, favori, son_mac)VALUES ('Garen', 'Üst', 15, 46.67, false, '09.10.2026 21:40+03');SELECT * FROM sampiyon_havuzu;| sampiyon | rol | mac_sayisi | kazanma_orani | favori | son_mac |
|---|---|---|---|---|---|
| Ahri | Orta | 48 | 54.17 | true | 2026-10-09 18:40:00+00 |
| Lux | Destek | 31 | 51.61 | false | 2026-10-08 16:05:00+00 |
| Jinx | Nişancı | 22 | 45.45 | false | 2026-10-10 19:15:00+00 |
| Kai'Sa | Nişancı | 9 | 66.67 | false | 2026-10-10 20:30:00+00 |
| Garen | Üst | 15 | 46.67 | false | 2026-09-10 18:40:00+00 |
| Garen | Üst | 15 | 46.67 | false | 2026-09-10 18:40:00+00 |
Tabloda iki sorun var. Birincisi: Garen'in maçı 10 Eylül görünüyor. Postgres 09.10.2026'yı varsayılan olarak ay.gün.yıl sırasıyla okudu: 9. ayın 10'u. Hata mesajı yok, veri sessizce yanlış.
İkincisi: Garen iki kez eklenmiş, çünkü Run editördeki INSERT'i de yeniden çalıştırdı.
Ne zaman çıkar? Tarihi 09.10.2026 gibi gün.ay.yıl sırasıyla yazınca. Postgres bu biçimi varsayılan olarak ay.gün.yıl diye okur. Gün 12'den büyükse hata verir ('25.10.2026' için date/time field value out of range), değilse sessizce başka bir tarih kaydeder.
Nasıl düzelir? Tarihi her zaman yıl-ay-gün sırasıyla, tire ile yaz: '2026-10-09 21:40:00+03'. Bu biçim (ISO 8601) her ayarda aynı okunur. Saat dilimini (+03) de ekle; eklemezsen saat, bağlantının saat dilimine göre yorumlanır.
İkinci sorun daha derin: iki Garen satırı tıpatıp aynı. Birini silmek ya da tarihini düzeltmek istesen (Ders 35'te öğreneceksin) Postgres'e "şu satır" diyemezsin; bütün değerleri aynı. Excel'deki gibi "6. satır" da diyemezsin, çünkü satır numarası yok. Tablonun eksiği buydu.
Birincil anahtar
Her satırın, onu öteki bütün satırlardan ayıran bir kimliği olmalı. Bu işi yapan sütuna birincil anahtar (primary key) denir. İki kuralı vardır: değeri iki satırda aynı olamaz ve boş olamaz.
İlk deneme: adı anahtar yapmak
İlk akla gelen şu: bir şampiyon havuzda bir kez olur, öyleyse şampiyonun adı anahtar olsun. Bunu denemek için tabloyu silip yeniden kuracağız. DROP TABLE bir tabloyu içindeki bütün satırlarla birlikte siler:
DROP TABLE sampiyon_havuzu;Deneme için iki sütun yeter. Ad sütununun yanına PRIMARY KEY yaz:
CREATE TABLE sampiyon_havuzu ( sampiyon text PRIMARY KEY, rol text);INSERT INTO sampiyon_havuzu (sampiyon, rol)VALUES ('Ahri', 'Orta'), ('Lux', 'Destek');Şimdi Ahri'yi bir daha eklemeyi dene:
INSERT INTO sampiyon_havuzu (sampiyon, rol)VALUES ('Ahri', 'Orta');ERROR: duplicate key value violates unique constraint "sampiyon_havuzu_pkey"DETAIL: Key (sampiyon)=(Ahri) already exists.Bu sefer reddedildi. Mesajı parçalayalım: duplicate key value "tekrarlanan anahtar değeri", violates "çiğniyor", unique constraint "benzersizlik kuralı". sampiyon_havuzu_pkey, Postgres'in birincil anahtar kuralına verdiği ad: tablo adı ve pkey (primary key). DETAIL satırı hangi değerin zaten olduğunu söylüyor.
İkinci kural: anahtar boş olamaz. INSERT'te yazmadığın sütuna NULL konur. NULL SQL'de "değer yok" demektir, JavaScript'teki null gibi:
INSERT INTO sampiyon_havuzu (rol) VALUES ('Orman');ERROR: null value in column "sampiyon" of relation "sampiyon_havuzu" violates not-null constraintDETAIL: Failing row contains (null, Orman)."sampiyon sütununa boş değer yazmak, boş olamaz kuralını çiğniyor." Failing row başarısız olan satırın kendisi: adı boş, rolü Orman.
İki hata da istediğimiz şeydi: tablo aynı şampiyonu iki kez almıyor ve adsız satırı kabul etmiyor. Peki neden burada durmuyoruz?
Adlar değişir
Ad değişince ne olduğunu Ders 32'de yaşadın: takım API'sinde şampiyonun adını değiştiren route, adı havuzda ve onu seçen her takımda tek tek değiştirmek zorundaydı. Riot da aynı sorunla karşılaşır; çözümünü kendi verisinde görebilirsin.
Wukong geliştirilirken MonkeyKing diye anılıyordu (oyuncu topluluğunun derlediği bilgi). Oyuna Wukong adıyla çıktı, ama Riot'un sistemlerindeki kimliği MonkeyKing olarak kaldı. Data Dragon'da bugün de böyle görünür:
{ "id": "MonkeyKing", "key": "62", "name": "Wukong", "title": "Maymun Kral"}Nunu'nun adı 2018'de yeniden tasarlandıktan sonra "Nunu ve Willump" oldu; kimliği hâlâ Nunu. Oyuncular için de aynısı geçerli: Riot ID'ni değiştirebilirsin, ama Riot API'si seni puuid denen, değişmeyen, uzun ve anlamsız bir kimlikle tanır. Riot neden böyle yapıyor? Adı anahtar yapsaydı, ad değişince o ada bağlı her kaydı (maçlar, takımlar, istatistikler) tek tek değiştirmesi gerekirdi.
Anlamsız bir numara, Postgres versin
Çözüm: her satıra anlamı olmayan bir numara ver ve onu birincil anahtar yap. Numarayı sen seçersen bir gün iki satıra aynı numarayı verirsin ya da sırayı karıştırırsın. Numarayı Postgres versin. Deneme tablosunu sil:
DROP TABLE sampiyon_havuzu;Ve şampiyon havuzunu son hâliyle kur:
CREATE TABLE sampiyon_havuzu ( id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY, sampiyon text, rol text, mac_sayisi integer, kazanma_orani numeric(5, 2), favori boolean, son_mac timestamptz);İlk satır yeni: id adında bir integer sütun. GENERATED ALWAYS AS IDENTITY "bu sütunun değerini her zaman Postgres üretsin: 1, 2, 3…" demek; PRIMARY KEY onu birincil anahtar yapar. INSERT'te id'yi hiç yazmazsın:
INSERT INTO sampiyon_havuzu (sampiyon, rol, mac_sayisi, kazanma_orani, favori, son_mac)VALUES ('Ahri', 'Orta', 48, 54.17, true, '2026-10-09 21:40:00+03'), ('Lux', 'Destek', 31, 51.61, false, '2026-10-08 19:05:00+03'), ('Jinx', 'Nişancı', 22, 45.45, false, '2026-10-10 22:15:00+03'), ('Kai''Sa', 'Nişancı', 9, 66.67, false, '2026-10-10 23:30:00+03');SELECT * FROM sampiyon_havuzu;| id | sampiyon | rol | mac_sayisi | kazanma_orani | favori | son_mac |
|---|---|---|---|---|---|---|
| 1 | Ahri | Orta | 48 | 54.17 | true | 2026-10-09 18:40:00+00 |
| 2 | Lux | Destek | 31 | 51.61 | false | 2026-10-08 16:05:00+00 |
| 3 | Jinx | Nişancı | 22 | 45.45 | false | 2026-10-10 19:15:00+00 |
| 4 | Kai'Sa | Nişancı | 9 | 66.67 | false | 2026-10-10 20:30:00+00 |
Numaraları sen vermedin; Postgres sırayla verdi. Artık her satırın değişmeyen bir kimliği var: Ahri'nin adı bir gün değişse de satırı 1 numarasıyla bulursun. Bu numaralar sıra numarası değildir: bir satırı sildiğinde (Ders 35) onun numarası bir daha kullanılmaz ve araya boşluk girer. Bu normaldir.
ALWAYS sözcüğü "her zaman Postgres üretir" demek. Numarayı elle vermeye kalkarsan itiraz eder:
INSERT INTO sampiyon_havuzu (id, sampiyon, rol)VALUES (5, 'Garen', 'Üst');ERROR: cannot insert a non-DEFAULT value into column "id"DETAIL: Column "id" is an identity column defined as GENERATED ALWAYS.HINT: Use OVERRIDING SYSTEM VALUE to override.DETAIL sebebi söylüyor: id her zaman Postgres'in ürettiği bir kimlik sütunu. HINT bu kuralı delmenin yolunu gösteriyor, ama onu kullanma; yalnız başka bir veritabanından kayıt taşırken gerekir. Satırı id olmadan ekle.
Bir şeye dikkat: birincil anahtar artık id, ad değil. Aynı INSERT'i iki kez çalıştırırsan Ahri yine iki kez eklenir; ama bu sefer birinin id'si 1, öbürününki 5 olur ve hangisini kastettiğini söyleyebilirsin. Aynı adın iki kez girmesini de engellemeyi Ders 35'te, UNIQUE kısıtıyla öğreneceksin.
Şemanı dosyaya kaydet
Tablon Neon'da duruyor. Ama nasıl kurduğunu da bir dosyada saklamalısın: veritabanını baştan kurman gerektiğinde (yeni bir proje, başka bir bilgisayar, Ders 38'deki proje) aynı komutları çalıştırman yeter. Tabloların, sütunların, tiplerin ve kuralların tanımına şema (schema) denir; bu dosyaya da genelde schema.sql adı verilir.
VS Code'da C:\kod\veritabani klasörüne schema.sql adında bir dosya aç ve tablonun son hâlini başlangıç verisiyle birlikte yaz. VS Code .sql uzantılı dosyaları kendiliğinden renklendirir:
-- Ders 33: ilk tablom-- Şampiyon havuzum: oynadığım şampiyonlar ve istatistiklerim CREATE TABLE sampiyon_havuzu ( id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY, sampiyon text, rol text, mac_sayisi integer, kazanma_orani numeric(5, 2), favori boolean, son_mac timestamptz); INSERT INTO sampiyon_havuzu (sampiyon, rol, mac_sayisi, kazanma_orani, favori, son_mac)VALUES ('Ahri', 'Orta', 48, 54.17, true, '2026-10-09 21:40:00+03'), ('Lux', 'Destek', 31, 51.61, false, '2026-10-08 19:05:00+03'), ('Jinx', 'Nişancı', 22, 45.45, false, '2026-10-10 22:15:00+03'), ('Kai''Sa', 'Nişancı', 9, 66.67, false, '2026-10-10 23:30:00+03');Bu dosya bir kayıt: boş bir veritabanında her şeyi baştan kurmak içindir. Tablo Neon'da zaten var; dosyayı orada yeniden çalıştırırsan relation "sampiyon_havuzu" already exists alırsın.
Sık hatalar
Bu derste karşılaşabileceğin öteki hatalar. Bir hata aldığında buraya ya da bütün derslerin hatalarının toplandığı hata rehberine bak.
relation "sampiyonlar" does not existNe zaman çıkar? Tablo adını yanlış yazınca ya da tabloyu kurmadan kullanınca. Burada Ders 27–32'deki API'den kalma alışkanlıkla sampiyonlar yazılmış.
INSERT INTO sampiyonlar (sampiyon, rol)VALUES ('Garen', 'Üst');ERROR: relation "sampiyonlar" does not existNasıl düzelir? Tablonun adını kurduğun gibi yaz: sampiyon_havuzu. Ad doğruysa doğru veritabanında olduğuna bak: SQL Editor'ün üstündeki seçicide neondb seçili mi? Tabloyu hiç kurmadıysan önce CREATE TABLE'ı çalıştır.
column "macsayisi" of relation "sampiyon_havuzu" does not existNe zaman çıkar? Sütun adını JavaScript alışkanlığıyla camelCase yazınca. Mesajdaki adın küçük harfe dönmüş olduğuna dikkat et: Postgres tırnaksız adı macsayisi'ye çevirdi ve öyle bir sütun bulamadı.
INSERT INTO sampiyon_havuzu (sampiyon, rol, macSayisi)VALUES ('Garen', 'Üst', 15);ERROR: column "macsayisi" of relation "sampiyon_havuzu" does not existNasıl düzelir? Sütunu tabloda nasıl adlandırdıysan öyle yaz: mac_sayisi. Adı hatırlamıyorsan SELECT * FROM sampiyon_havuzu; ile sonuç tablosunun başlıklarına bak.
duplicate key value violates unique constraint "…_pkey"Ne zaman çıkar? Birincil anahtar sütununa tabloda zaten olan bir değeri yazınca. Birincil anahtar bölümünde şampiyon adını anahtar yaptığında gördün. DETAIL satırı hangi değerin zaten olduğunu söyler: Key (sampiyon)=(Ahri) already exists.
Nasıl düzelir? Aynı kaydı ikinci kez ekliyorsan (editörde kalan bir INSERT'i yeniden çalıştırmak gibi) eklemeyi bırak; kayıt zaten tabloda. Gerçekten farklı bir kayıtsa anahtarı da farklı olmalı. Anahtarı elle uydurma; Postgres üretsin (GENERATED ALWAYS AS IDENTITY).
null value in column "…" violates not-null constraintNe zaman çıkar? Boş olamayan bir sütunu (birincil anahtar gibi) INSERT'te yazmayınca ya da ona NULL verince. Yazmadığın her sütuna NULL konur. DETAIL satırı reddedilen satırın tamamını gösterir: Failing row contains (null, Orman).
Nasıl düzelir? Mesajdaki sütunu INSERT'in sütun listesine ekle ve ona bir değer ver.
cannot insert a non-DEFAULT value into column "id"Ne zaman çıkar? GENERATED ALWAYS AS IDENTITY olan bir sütuna elle değer yazınca. Anlamsız bir numara, Postgres versin bölümünde gördün.
Nasıl düzelir? id'yi hem sütun listesinden hem değerlerden çıkar; numarayı Postgres verir. HINT satırındaki OVERRIDING SYSTEM VALUE'yu kullanma.
Görevler
Görevleri Neon'un SQL Editor'ünde yap; her adımda editörde yalnız o adımın komutu olsun. İstersen yazdığın SQL'i C:\kod\veritabani\gorevler.sql dosyasında da sakla. Önce kendin dene, takılırsan ipuçlarını sırayla aç.
Tipi seç
Bir oyuncu profili tablosu tasarlıyorsun. Aşağıdaki her bilgi için en uygun veri tipini seç (tabloyu kurman gerekmiyor):
- Riot ID'deki oyun adı (Riot'un kuralı: en çok 16 karakter)
- Hesap seviyesi
- Mağazada bugüne kadar harcanan para (TL, kuruşlu)
- Oyuncu şu an çevrimiçi mi?
- Hesabın açıldığı an
- Doğum günü
- Telefon numarası
- Oyun ayarları (her oyuncuda farklı alanları olabilen bir JSON)
İpucu 1
Sorular sor: hesap yapılacak mı, kesin mi olmalı, saat de var mı, uzunluğun kesin bir sınırı var mı?
İpucu 2
Telefon numarasıyla toplama, çıkarma yapar mısın? Başındaki 0'a ne olur?
Çözümü göster
varchar(16)(ya datext). Sınırı veritabanı da bilsin istersenvarchar(16).integer.numeric(10, 2): para kesin olmalı. 10 basamağın 2'si kuruş, yani 99.999.999,99 TL'ye kadar.boolean.timestamptz: bir an, saat dilimiyle.date: doğum gününün saati yok.text: telefon numarası bir sayı değil, rakamlardan oluşan bir metin. Onunla hesap yapılmaz.jsonb.
Telefon numarasını sayı olarak yazarsan baştaki 0 kaybolur:
SELECT 05321234567 AS telefon;| telefon |
|---|
| 5321234567 |
Sınır denemesi: varchar(16) sınırının iki yanını dene. Tam 16 karakterlik ad girer, 17'lik ad reddedilir:
CREATE TABLE profil_deneme (oyun_adi varchar(16));INSERT INTO profil_deneme (oyun_adi)VALUES ('SihirdarAhri1234');INSERT INTO profil_deneme (oyun_adi)VALUES ('SihirdarAhri12345');ERROR: value too long for type character varying(16)character varying, varchar'ın uzun adı. Denemen bitince tabloyu DROP TABLE profil_deneme; ile silebilirsin.
Eşya dükkânı
Dükkândaki eşyalar için esyalar adında bir tablo kur. Sütunlar: Postgres'in ürettiği bir id (birincil anahtar), eşyanın adı (ad), altın fiyatı (fiyat) ve kullanınca tükenip tükenmediği (tuketilir). Sonra şu üç eşyayı tek bir INSERT ile ekle ve tabloya bak:
| ad | fiyat | tuketilir |
|---|---|---|
| Doran'ın Kılıcı | 450 | hayır |
| Can İksiri | 50 | evet |
| Görünmez Totem | 0 | hayır |
SELECT * sonucunda id'ler 1, 2, 3 olmalı.
İpucu 1
id satırı ders anlatımındakiyle aynı kalıp: id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY. "Evet/hayır" bilgisi için hangi tip?
İpucu 2
Doran'ın adında kesme işareti var. Metnin içindeki tek tırnak nasıl yazılıyordu?
Çözümü göster
CREATE TABLE esyalar ( id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY, ad text, fiyat integer, tuketilir boolean);INSERT INTO esyalar (ad, fiyat, tuketilir)VALUES ('Doran''ın Kılıcı', 450, false), ('Can İksiri', 50, true), ('Görünmez Totem', 0, false);SELECT * FROM esyalar;| id | ad | fiyat | tuketilir |
|---|---|---|---|
| 1 | Doran'ın Kılıcı | 450 | false |
| 2 | Can İksiri | 50 | true |
| 3 | Görünmez Totem | 0 | false |
Sınır denemesi: Görünmez Totem'in fiyatı 0. Sıfır da bir değerdir; NULL (değer yok) ile karıştırma. Fiyatı bilinmeyen bir eşyayı eklerken fiyat sütununu hiç yazmazsan o hücreye NULL konur, 0 değil.
Hata avı
Bir arkadaşın TFT birimleri için bir tablo kurmak istemiş, ama iki komutunda toplam üç hata var. Gözle aramadan, yalnız mesajları okuyarak düzelt. Önce CREATE TABLE'ı tek başına çalıştır; tablo kurulunca INSERT'i ayrıca çalıştır.
CREATE TABLE tft_birimleri ( id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY, ad text maliyet integer, yildiz integer);ERROR: syntax error at or near "maliyet"INSERT INTO tft_birimleri (ad, maliyet, yildiz)VALUES ("Jinx", 4, 2), ('Ahri', 'dört', 1);Hepsini düzeltince SELECT * FROM tft_birimleri; iki satır göstermeli: Jinx (4 altın, 2 yıldız) ve Ahri (4 altın, 1 yıldız).
İpucu 1
İlk mesaj maliyet sözcüğünü gösteriyor, ama hata çoğu zaman gösterilen yerin hemen önündedir. Bir üst satırın sonuna bak.
İpucu 2
INSERT'in ilk mesajı column "Jinx" does not exist olacak. Postgres Jinx'i neden bir sütun adı sandı?
İpucu 3
Son mesaj invalid input syntax for type integer: "dört". maliyet sütunu hangi tipte?
Çözümü göster
CREATE TABLE tft_birimleri ( id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY, ad text, maliyet integer, yildiz integer);INSERT INTO tft_birimleri (ad, maliyet, yildiz)VALUES ('Jinx', 4, 2), ('Ahri', 4, 1);SELECT * FROM tft_birimleri;| id | ad | maliyet | yildiz |
|---|---|---|---|
| 1 | Jinx | 4 | 2 |
| 2 | Ahri | 4 | 1 |
Sırasıyla gelen mesajlar: syntax error at or near "maliyet" (ad text'ten sonra virgül eksik), column "Jinx" does not exist (metin çift tırnakta), invalid input syntax for type integer: "dört" (sayı yazıyla yazılmış).
Sınırları dene
TFT'de bir oyuncunun ortalama sırası 1.00 ile 8.00 arasındadır. Şu tabloyu kur:
CREATE TABLE tft_istatistik ( id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY, oyuncu text, oynanan_mac integer, ortalama_sira numeric(3, 2));Sonra şu beş oyuncuyu ayrı ayrı INSERT ile eklemeyi dene. Önce her biri için tahminini yaz: girer mi, değişerek mi girer, hata mı verir?
| oyuncu | oynanan_mac | ortalama_sira |
|---|---|---|
| Deniz | 120 | 1 |
| Ece | 85 | 8 |
| Mert | 40 | 4.355 |
| Arda | 60 | 10 |
| Selin | 3000000000 | 4.5 |
En sonda SELECT * ile tabloya bak.
İpucu 1
numeric(3, 2) toplam 3 basamak tutar, 2'si virgülden sonra. Virgülden önce kaç basamak kalıyor?
İpucu 2
integer'ın sınırı yaklaşık 2,1 milyar. Virgülden sonra fazla basamak gelirse ne olduğunu ders anlatımında okudun.
Çözümü göster
İlk üçü girer. Deniz ve Ece tam sınırda (1 ve 8) ve sorunsuz. Mert'in ortalaması yuvarlanarak girer:
INSERT INTO tft_istatistik (oyuncu, oynanan_mac, ortalama_sira)VALUES ('Deniz', 120, 1);INSERT INTO tft_istatistik (oyuncu, oynanan_mac, ortalama_sira)VALUES ('Ece', 85, 8);INSERT INTO tft_istatistik (oyuncu, oynanan_mac, ortalama_sira)VALUES ('Mert', 40, 4.355);Arda'nın 10'u numeric(3, 2)'ye sığmaz; virgülden önce yalnız bir basamak var:
INSERT INTO tft_istatistik (oyuncu, oynanan_mac, ortalama_sira)VALUES ('Arda', 60, 10);ERROR: numeric field overflowDETAIL: A field with precision 3, scale 2 must round to an absolute value less than 10^1.Selin'in maç sayısı integer'ın sınırını aşıyor:
INSERT INTO tft_istatistik (oyuncu, oynanan_mac, ortalama_sira)VALUES ('Selin', 3000000000, 4.5);ERROR: integer out of rangeSELECT * FROM tft_istatistik;| id | oyuncu | oynanan_mac | ortalama_sira |
|---|---|---|---|
| 1 | Deniz | 120 | 1.00 |
| 2 | Ece | 85 | 8.00 |
| 3 | Mert | 40 | 4.36 |
4.355 sessizce 4.36 oldu; hata yok. numeric(3, 2) 9.99'a kadar her değeri kabul eder; yani TFT'de imkânsız olan 9.50 gibi bir ortalama da girer. Değeri 1–8 arasında tutan bir kural (CHECK) Ders 35'te. Selin gibi gerçekten büyük sayılar için sütun bigint olmalıydı.
Riot'un maç kimliği
TFT maçlarını Riot API'sinden çekip saklayacaksın. Riot her maça TR1_1484000001 gibi bir kimlik verir; bu kimlik değişmez ve iki maçta aynı olmaz. Bu sefer birincil anahtar olarak id yerine Riot'un maç kimliğini kullan.
tft_maclaritablosunu kur:mac_id(metin, birincil anahtar),sira(tam sayı),kompozisyon(metin),oynanma(an).- İki maç ekle:
TR1_1484000001(3. olmuşsun,Büyücüler, 10 Ekim 2026 saat 20:15, Türkiye saati) veTR1_1484000002(1. olmuşsun,Nişancılar, 11 Ekim 2026 saat 00:40, Türkiye saati). - API'den aynı maç ikinci kez gelmiş gibi, birinci maçı yeniden eklemeyi dene. Ne oluyor, bu iyi mi?
mac_id'siz bir maç eklemeyi dene.SELECT *ile bak. İkinci maçın tarihi neden 10 Ekim görünüyor?
Bonus soru: Wukong ve Nunu örneklerinde adı anahtar yapmak kötü bir fikirdi. Burada dışarıdan gelen bir kimliği anahtar yapmak neden doğru?
İpucu 1
Türkiye saatini +03 ile yaz: '2026-10-10 20:15:00+03'. Gün.ay.yıl yazma.
İpucu 2
Üçüncü ve dördüncü adımda göreceğin iki hatayı bu derste, şampiyon adını anahtar yaptığında da gördün.
İpucu 3
Gece 00:40 Türkiye saati, UTC'de hangi gün ve saat?
Çözümü göster
CREATE TABLE tft_maclari ( mac_id text PRIMARY KEY, sira integer, kompozisyon text, oynanma timestamptz);INSERT INTO tft_maclari (mac_id, sira, kompozisyon, oynanma)VALUES ('TR1_1484000001', 3, 'Büyücüler', '2026-10-10 20:15:00+03'), ('TR1_1484000002', 1, 'Nişancılar', '2026-10-11 00:40:00+03');Aynı maçı ikinci kez kaydetmek reddedilir. Bu tam istediğimiz şey: API'den aynı maç iki kez gelse de istatistiklerin şişmez.
INSERT INTO tft_maclari (mac_id, sira, kompozisyon, oynanma)VALUES ('TR1_1484000001', 3, 'Büyücüler', '2026-10-10 20:15:00+03');ERROR: duplicate key value violates unique constraint "tft_maclari_pkey"DETAIL: Key (mac_id)=(TR1_1484000001) already exists.INSERT INTO tft_maclari (sira, kompozisyon)VALUES (5, 'Savaşçılar');ERROR: null value in column "mac_id" of relation "tft_maclari" violates not-null constraintDETAIL: Failing row contains (null, 5, Savaşçılar, null).SELECT * FROM tft_maclari;| mac_id | sira | kompozisyon | oynanma |
|---|---|---|---|
| TR1_1484000001 | 3 | Büyücüler | 2026-10-10 17:15:00+00 |
| TR1_1484000002 | 1 | Nişancılar | 2026-10-10 21:40:00+00 |
İkinci maçı 11 Ekim gece 00:40'ta oynadın; UTC'de saat o an 10 Ekim 21:40'tı. Veri doğru, yalnız gösterim UTC. Sınır durumu bu: gece yarısından sonraki üç saatte oynanan her maç, UTC'de bir önceki güne düşer. Maçları güne göre sayarken bunu hatırla.
Bonus: Şampiyonun adı oyunda görünen bir addır ve Nunu'da olduğu gibi değişebilir. Maç kimliği ise hiç değişmez, benzersizliğini Riot garanti eder ve zaten anlamsız bir koddur. Dışarıdan gelen, değişmeyen kimlikler iyi birincil anahtardır. Kendi ürettiğin kayıtlar için (şampiyon havuzu gibi) ise Postgres'in ürettiği id'yi kullan.
Kendini sına
Soru 1
Ders 29'daki degistir sırası kayıp güncellemeyi bitirmişti. API birden çok kopyayla çalışınca sorun neden geri gelir?
Cevabı göster
Sıra, programın belleğinde duran bir Promise zinciriydi; yalnız o programın kendi işlerini sıraya koyar. İki kopya çalışınca her birinin ayrı bir sırası olur. A kopyasındaki istek ile B kopyasındaki istek aynı dosyayı yine aynı anda okuyup yazar; biri ötekini ezer. Kopyalar diski paylaşmıyorsa her biri kendi dosyasını görür ve veri kopyalar arasında bölünür. Veritabanında ise bütün kopyalar aynı sunucuya bağlanır ve aynı anda gelen yazmaları o sunucu düzenler.
Soru 2
Bu sorgu ne döndürür?
SELECT 7 / 2 AS sonuc;Cevabı göster
3. İki tam sayıyı böldüğünde Postgres sonucu da tam sayı yapar ve küsuratı atar; JavaScript'teki gibi 3.5 vermez:
SELECT 7 / 2 AS sonuc;| sonuc |
|---|
| 3 |
Sayılardan biri ondalıklıysa sonuç numeric olur:
SELECT 7.0 / 2 AS sonuc;| sonuc |
|---|
| 3.5000000000000000 |
Soru 3
Bu komut hata verir. Hangi mesajı alırsın, neden?
INSERT INTO sampiyon_havuzu (sampiyon, rol)VALUES ('Teemo', "Üst");Cevabı göster
column "Üst" does not exist. "Üst" çift tırnakta olduğu için Postgres onu bir değer değil, sütun adı sanar. Doğrusu 'Üst'. Tek tırnak değer, çift tırnak ad.
Soru 4
kazanma_orani sütunu numeric(5, 2). Sırasıyla 51.616 ve 1000 yazmaya kalkarsan ne olur?
Cevabı göster
51.616 hata vermez, 51.62 olarak yuvarlanıp kaydedilir. 1000 ise sığmaz: numeric(5, 2) virgülden önce en çok üç basamak tutar (en büyük değer 999.99), Postgres numeric field overflow hatası verir.
Soru 5
Oyuncular tablosunda birincil anahtar olarak hangisini seçerdin: oyuncunun Riot ID'si mi, Postgres'in ürettiği id mi?
Cevabı göster
Postgres'in ürettiği id'yi. Riot ID değiştirilebilir; anahtar olsaydı, değiştiğinde o oyuncuya bağlı bütün kayıtları güncellemen gerekirdi. Riot'un kendisi de oyuncuları değişmeyen puuid ile tanır. Riot ID'yi yine bir sütunda tutarsın, ama anahtar yapmazsın.
Soru 6
sampiyon text PRIMARY KEY olan deneme tablosuna ('', 'Orman') satırını eklemek istesen ne olur? Boş metin NULL mıdır?
Cevabı göster
Satır eklenir. '' boş bir metindir, yani uzunluğu sıfır olan bir değer; NULL ise "değer yok" demektir. Birincil anahtar yalnız NULL'u ve tekrarı reddeder, boş metni kabul eder. Boş metni de engelleyen bir kuralı (CHECK) Ders 35'te göreceksin.
Soru 7
Ahri'nin maçını '2026-10-09 21:40:00+03' olarak ekledin ama Neon 2026-10-09 18:40:00+00 gösteriyor. Veri bozuldu mu?
Cevabı göster
Hayır. timestamptz bir anı saklar. 21:40 Türkiye saatiyle (+03) ve 18:40 UTC (+00) aynı andır; Neon gösterirken UTC kullanıyor. Veriyi Ders 37'de JavaScript'e aldığında yine aynı an olarak gelecek.
Kayıt noktası
C:\kod\veritabani yeni bir proje klasörü, bu yüzden önce git init gerekiyor. VS Code'un terminalinde, bu klasördeyken:
Bir sonraki derste SELECT'i gerçekten öğreneceksin: bir maç geçmişi tablosundan istediğin satırları ve sütunları seçecek, sıralayacak, sayacak ve gruplayacaksın.