Blok 5 · Ders 34
SELECT: veriyi sorgulamak
SELECT, WHERE, ORDER BY, LIMIT, LIKE, NULL, toplama fonksiyonları ve GROUP BY ile maç istatistiklerini sorgulamak.
Bu dersin sonunda
SELECTile bir tablodan istediğin sütunları alabilir,*'ın neden sakıncalı olduğunu açıklayabilirsinWHEREiçindeAND,OR,NOT,INveBETWEENkullanarak satırları süzebilirsin- Sonucu
ORDER BYile birden çok sütuna göre sıralayıpLIMITveOFFSETile sayfalara bölebilirsin LIKEveILIKEile metin arayabilir, boş değerleriIS NULLile doğru bulabilirsinCOUNT,SUM,AVG,MIN,MAX,GROUP BYveHAVINGile oyuncu ve şampiyon istatistiği çıkarabilirsin- Bir sorgunun hangi sırayla çalıştığını bilerek SQL hata mesajlarını okuyup düzeltebilirsin
Bu sayfada
- Örnek veri: maç geçmişi tablosu
- SELECT: hangi sütunlar gelsin?
- WHERE: hangi satırlar gelsin?
- Koşulları birleştirmek: AND, OR, NOT
- IN ve BETWEEN: kısa yollar
- ORDER BY: sıralamak
- LIMIT ve OFFSET: ilk birkaç satır ve sayfalar
- SELECT'te hesap ve takma ad
- LIKE ve ILIKE: metin içinde aramak
- NULL: bilinmeyen değer
- Toplama fonksiyonları: COUNT, SUM, AVG, MIN, MAX
- GROUP BY: her oyuncu için ayrı hesap
- HAVING: grupları süzmek
- Sorgu hangi sırayla çalışır?
- Sorgularını bir dosyada sakla
- Sık hatalar
- Görevler
- Kendini sına
- Kayıt noktası
Ders 33'te veriyi bir tabloya koydun. Veritabanının asıl gücü ise o veriye soru sorabilmekte: "Hangi şampiyonla en çok kazandım?", "Deniz maç başına kaç kill alıyor?", "En az iki kez oynanan şampiyonlardan hangisi daha iyi gidiyor?" Bu soruların hepsi tek bir SQL komutuyla sorulur: SELECT. Bu derste onu bir maç geçmişi tablosu üzerinde adım adım kuracaksın: sütun seçmek, satır süzmek, sıralamak, saymak ve gruplamak. Sonunda istatistik sitelerinin gösterdiği tabloları kendi sorgunla üretebileceksin.
SELECT yalnız okur, tabloyu değiştirmez. İstediğin kadar dene; yanlış yazılmış bir sorgu verini bozamaz.
Örnek veri: maç geçmişi tablosu
Dersin bütün sorguları aynı tabloda çalışacak: üç arkadaşın (Deniz, Ece ve Mert) son 20 maçı. Her satır bir maç, her sütun o maçın bir bilgisi. Sütunlar ve tipleri:
id(integer): maçın numarası; veritabanı kendisi verir (birincil anahtar).oyuncu(text): maçı oynayan,Deniz,Eceya daMert.sampiyon(text): seçilen şampiyon.rol(text):üst,orman,orta,altya dadestek; ARAM maçlarında boş.sonuc(text):zaferya dayenilgi.kill,olum,asist(integer): maç sonu skoru,8/2/11gibi.sure_dk(integer): maçın süresi, dakika olarak.tarih(timestamptz): maçın başladığı an.
Aşağıdaki betik tabloyu kurar ve 20 maçı ekler. Bu dersin amacı veri girmek değil sorgu yazmak; bu yüzden betiği kopyalayabilirsin. Neon'da SQL Editor'ü aç (Ders 33), New Query ile boş bir sayfa aç, betiği yapıştır ve Run'a bas. Dersin sonunda bu betiği maclar.sql adıyla bir dosyaya da kaydedeceksin.
-- Maç geçmişi tablosu. Yeniden çalıştırırsan tabloyu sıfırdan kurar.DROP TABLE IF EXISTS maclar; CREATE TABLE maclar ( id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY, oyuncu text, sampiyon text, rol text, sonuc text, kill integer, olum integer, asist integer, sure_dk integer, tarih timestamptz); INSERT INTO maclar (oyuncu, sampiyon, rol, sonuc, kill, olum, asist, sure_dk, tarih) VALUES ('Deniz', 'Ahri', 'orta', 'zafer', 8, 2, 11, 31, '2026-09-28 20:40+03'), ('Ece', 'Jinx', 'alt', 'zafer', 12, 3, 8, 32, '2026-09-28 21:30+03'), ('Mert', 'Garen', 'üst', 'zafer', 9, 3, 5, 34, '2026-09-29 19:10+03'), ('Deniz', 'Ahri', 'orta', 'yenilgi', 3, 6, 4, 27, '2026-09-29 22:05+03'), ('Ece', 'Thresh', 'destek', 'zafer', 1, 4, 19, 36, '2026-09-30 18:20+03'), ('Mert', 'Lee Sin', 'orman', 'yenilgi', 4, 7, 10, 29, '2026-09-30 21:45+03'), ('Deniz', 'Lux', 'destek', 'zafer', 2, 3, 15, 35, '2026-10-01 20:15+03'), ('Ece', 'Kai''Sa', 'alt', 'yenilgi', 5, 7, 4, 28, '2026-10-01 22:40+03'), ('Deniz', 'Jinx', NULL, 'zafer', 14, 6, 22, 19, '2026-10-02 19:55+03'), ('Mert', 'Darius', 'üst', 'yenilgi', 3, 8, 3, 26, '2026-10-02 22:20+03'), ('Ece', 'Jinx', 'alt', 'zafer', 9, 2, 10, 30, '2026-10-03 17:25+03'), ('Deniz', 'Yasuo', 'orta', 'yenilgi', 6, 9, 2, 24, '2026-10-03 20:50+03'), ('Mert', 'Lee Sin', 'orman', 'zafer', 6, 4, 14, 31, '2026-10-04 16:30+03'), ('Ece', 'Lux', NULL, 'yenilgi', 6, 8, 25, 17, '2026-10-04 19:00+03'), ('Deniz', 'Ahri', 'orta', 'zafer', 11, 1, 7, 29, '2026-10-05 21:20+03'), ('Ece', 'Ashe', 'alt', 'zafer', 7, 0, 12, 25, '2026-10-06 20:05+03'), ('Mert', 'Teemo', 'üst', 'yenilgi', 2, 7, 6, 35, '2026-10-07 22:30+03'), ('Deniz', 'Annie', 'orta', 'zafer', 7, 4, 9, 33, '2026-10-08 21:00+03'), ('Ece', 'Jinx', 'alt', 'yenilgi', 3, 5, 6, 40, '2026-10-09 23:15+03'), ('Mert', 'Garen', NULL, 'zafer', 10, 9, 18, 21, '2026-10-10 18:45+03');Betikte dört şeye dikkat et:
--ile başlayan satır yorumdur. JavaScript'teki//gibi; veritabanı onu okumaz.DROP TABLE IF EXISTS maclar;"maclar adında bir tablo varsa sil" demek. Betiği ikinci kez çalıştırırsan tablo baştan kurulur ve maçlar iki kez eklenmez. İlk çalıştırmadaNOTICE: table "maclar" does not exist, skippinggibi bir bilgi satırı görebilirsin; hata değil.DROP TABLEgeri alınamaz (Ders 33), ama bu tabloda yalnız deneme verisi var.- Üç maçta
rolyerine tırnaksızNULLyazıyor. Bunlar ARAM maçları: orada koridor seçilmez, rol bilgisi yok. NULL'u dersin ortasında ayrıntılı göreceksin. 'Kai''Sa'Ders 33'teki gibi yazıldı: metnin içindeki kesme işareti iki kez yazılır. Saatler de Ders 33'teki gibi Türkiye saatiyle (+03) girildi; Neon onları UTC'ye çevirip gösterir.
SELECT: hangi sütunlar gelsin?
İlk sorgu, Ders 33'te tabloya bakmak için kullandığın kalıp: tablonun tamamını getirir. Editöre kendin yaz ve Run'a bas; altta şu sonuç tablosu belirir:
SELECT * FROM maclar;| id | oyuncu | sampiyon | rol | sonuc | kill | olum | asist | sure_dk | tarih |
|---|---|---|---|---|---|---|---|---|---|
| 1 | Deniz | Ahri | orta | zafer | 8 | 2 | 11 | 31 | 2026-09-28 17:40:00+00 |
| 2 | Ece | Jinx | alt | zafer | 12 | 3 | 8 | 32 | 2026-09-28 18:30:00+00 |
| 3 | Mert | Garen | üst | zafer | 9 | 3 | 5 | 34 | 2026-09-29 16:10:00+00 |
| 4 | Deniz | Ahri | orta | yenilgi | 3 | 6 | 4 | 27 | 2026-09-29 19:05:00+00 |
| 5 | Ece | Thresh | destek | zafer | 1 | 4 | 19 | 36 | 2026-09-30 15:20:00+00 |
| 6 | Mert | Lee Sin | orman | yenilgi | 4 | 7 | 10 | 29 | 2026-09-30 18:45:00+00 |
| 7 | Deniz | Lux | destek | zafer | 2 | 3 | 15 | 35 | 2026-10-01 17:15:00+00 |
| 8 | Ece | Kai'Sa | alt | yenilgi | 5 | 7 | 4 | 28 | 2026-10-01 19:40:00+00 |
| 9 | Deniz | Jinx | NULL | zafer | 14 | 6 | 22 | 19 | 2026-10-02 16:55:00+00 |
| 10 | Mert | Darius | üst | yenilgi | 3 | 8 | 3 | 26 | 2026-10-02 19:20:00+00 |
| 11 | Ece | Jinx | alt | zafer | 9 | 2 | 10 | 30 | 2026-10-03 14:25:00+00 |
| 12 | Deniz | Yasuo | orta | yenilgi | 6 | 9 | 2 | 24 | 2026-10-03 17:50:00+00 |
| 13 | Mert | Lee Sin | orman | zafer | 6 | 4 | 14 | 31 | 2026-10-04 13:30:00+00 |
| 14 | Ece | Lux | NULL | yenilgi | 6 | 8 | 25 | 17 | 2026-10-04 16:00:00+00 |
| 15 | Deniz | Ahri | orta | zafer | 11 | 1 | 7 | 29 | 2026-10-05 18:20:00+00 |
| 16 | Ece | Ashe | alt | zafer | 7 | 0 | 12 | 25 | 2026-10-06 17:05:00+00 |
| 17 | Mert | Teemo | üst | yenilgi | 2 | 7 | 6 | 35 | 2026-10-07 19:30:00+00 |
| 18 | Deniz | Annie | orta | zafer | 7 | 4 | 9 | 33 | 2026-10-08 18:00:00+00 |
| 19 | Ece | Jinx | alt | yenilgi | 3 | 5 | 6 | 40 | 2026-10-09 20:15:00+00 |
| 20 | Mert | Garen | NULL | zafer | 10 | 9 | 18 | 21 | 2026-10-10 15:45:00+00 |
Sorgunun dört parçası var:
SELECT: "seç, getir". Arkasına hangi sütunları istediğini yazarsın.*(yıldız): "bütün sütunlar".FROM maclar: "maclar tablosundan".;komutun bittiğini söyler. Editörde tek komut varken unutsan da çalışır, ama komutları birbirinden ayıran odur; her sorgunun sonuna koymayı alışkanlık edin.
NULL yazan hücreler boş hücreler; başka araçlar onları boş da gösterebilir. Tarihler Ders 33'te gördüğün gibi UTC'ye çevrilmiş gelir (+00): 20:40+03 diye girilen maç 17:40:00+00 görünür, ikisi aynı an.
Anahtar sözcükleri büyük harfle yazmak bir gelenek: SQL, anahtar sözcüklerde ve tırnaksız adlarda büyük-küçük harfe bakmaz; select * from maclar; da çalışır. Büyük yazınca hangi sözcüğün SQL'e, hangisinin senin tablona ait olduğu bir bakışta ayrılır.
Çoğu zaman bütün sütunlar gerekmez. İstediğin sütunları virgülle ayırarak yaz; sonuç o sütunlarla, senin yazdığın sırayla gelir:
SELECT oyuncu, sampiyon, sonuc FROM maclar;| oyuncu | sampiyon | sonuc |
|---|---|---|
| Deniz | Ahri | zafer |
| Ece | Jinx | zafer |
| Mert | Garen | zafer |
| Deniz | Ahri | yenilgi |
| Ece | Thresh | zafer |
| Mert | Lee Sin | yenilgi |
| Deniz | Lux | zafer |
| Ece | Kai'Sa | yenilgi |
| Deniz | Jinx | zafer |
| Mert | Darius | yenilgi |
| Ece | Jinx | zafer |
| Deniz | Yasuo | yenilgi |
| Mert | Lee Sin | zafer |
| Ece | Lux | yenilgi |
| Deniz | Ahri | zafer |
| Ece | Ashe | zafer |
| Mert | Teemo | yenilgi |
| Deniz | Annie | zafer |
| Ece | Jinx | yenilgi |
| Mert | Garen | zafer |
Neden her zaman * yazmıyoruz?
* bir tabloyu ilk kez tanırken çok kullanışlıdır. Ama bir programın içinde her gün çalışacak sorgularda onu alışkanlık hâline getirme:
- Gereksiz veri taşır. Ekranda yalnız şampiyon adı gösterecekken tarih, süre ve skor da gelir. 20 satırda fark etmez; iki milyon satırda veritabanı, ağ ve sunucun boşuna yorulur.
- Tablo değişince sürpriz yapar. Tabloya yeni bir sütun eklenince (Ders 35'te
ALTER TABLEile yapacaksın)*onu da getirmeye başlar ve kodun beklemediği bir alanla karşılaşır. - Gizli bilgiyi sızdırabilir. Ders 31'deki gibi bir
kullanicilartablosundasifre_hashsütunu varsa ve API'nSELECT *sonucunu olduğu gibi JSON'a çevirip gönderiyorsa, hash'ler herkesin tarayıcısına gider. - Okuyanı yorar.
SELECT oyuncu, sampiyonne istediğini kendisi söyler;SELECT *okuyanı tabloya bakmaya zorlar.
syntax error at or near "FROM"Ne zaman çıkar? Sütun listesinin sonunda virgül bırakınca. SQL virgülden sonra bir sütun daha bekler, karşısına FROM çıkar.
SELECT oyuncu, sampiyon, FROM maclar;ERROR: syntax error at or near "FROM"Nasıl düzelir? Son sütundan sonra virgül koyma. syntax error at or near "X" mesajı "X'in yakınında dilbilgisi hatası" demek; sorun ya X'in kendisinde ya da hemen önündedir. Hataları Ders 33'teki gibi psql biçiminde gösteriyoruz; Neon'da biçim biraz farklı görünebilir, ama asıl cümle aynıdır.
WHERE: hangi satırlar gelsin?
SELECT sütunları seçer, ama satırların hepsi yine gelir. Yalnız Ahri maçlarını görmek istiyorsan satırları süzmen gerekir. Bu iş WHERE'in:
SELECT sampiyon, sonuc, kill FROM maclar WHERE sampiyon = 'Ahri';| sampiyon | sonuc | kill |
|---|---|---|
| Ahri | zafer | 8 |
| Ahri | yenilgi | 3 |
| Ahri | zafer | 11 |
WHERE'den sonra bir koşul gelir. PostgreSQL tablonun her satırı için koşula bakar: doğruysa satır sonuca girer, değilse elenir. Ders 11'de öğrendiğin filter ile aynı fikir, maclar.filter((mac) => mac.sampiyon === "Ahri") gibi; tek fark, işi bu kez veritabanının yapması.
SELECT dikey bir dilim alır (sütunlar), WHERE yatay bir dilim (satırlar). Sonuç, ikisinin kesiştiği hücrelerdir.Koşullarda kullanacağın karşılaştırma işaretleri:
| İşaret | Anlamı | Örnek |
|---|---|---|
= | eşit | sonuc = 'zafer' |
<> ya da != | eşit değil | rol <> 'orta' |
<, > | küçük, büyük | kill > 10 |
<=, >= | küçük ya da eşit, büyük ya da eşit | sure_dk >= 30 |
SQL'de eşitlik tek = ile sorulur. JavaScript'te = "koy", === "eşit mi" demekti; SQL'de WHERE içindeki = her zaman karşılaştırmadır. Sayılar tırnaksız yazılır. 10 ya da daha fazla kill alınan maçlar:
SELECT oyuncu, sampiyon, kill FROM maclar WHERE kill >= 10;| oyuncu | sampiyon | kill |
|---|---|---|
| Ece | Jinx | 12 |
| Deniz | Jinx | 14 |
| Deniz | Ahri | 11 |
| Mert | Garen | 10 |
Metin tek tırnakla yazılır
Ders 33'teki kural WHERE'de de geçerli: metin değerleri tek tırnak içinde yazılır, 'Ahri'. JavaScript'te hep çift tırnak kullandığın için parmakların koşul yazarken de ona gidecek:
column "Ahri" does not existNe zaman çıkar? WHERE koşulunda metni çift tırnakla yazınca. SQL'de çift tırnak bir ad (sütun ya da tablo adı) demektir; PostgreSQL Ahri adında bir sütun arar, bulamaz.
SELECT sampiyon, sonuc, kill FROM maclar WHERE sampiyon = "Ahri";ERROR: column "Ahri" does not existNasıl düzelir? Metni tek tırnakla yaz: WHERE sampiyon = 'Ahri'. Tek tırnak değer, çift tırnak ad.
Metin karşılaştırması büyük-küçük harfe duyarlıdır: WHERE sampiyon = 'ahri' hiç satır döndürmez, çünkü tablodaki değer büyük A ile Ahri. Harf büyüklüğüne bakmayan aramayı LIKE bölümünde göreceksin.
Koşulları birleştirmek: AND, OR, NOT
Gerçek sorular çoğu zaman tek koşulla bitmez: "Deniz'in kazandığı maçlar" iki koşul demek. Koşullar eklendikçe sorgu da uzar ve tek satıra sığmaz olur.
Sorgunun SELECT …, FROM …, WHERE … gibi bir anahtar sözcükle başlayan her parçasına cümlecik (clause) denir. SQL satır sonlarına ve girintiye bakmaz, komutu yalnız ; bitirir. Bu yüzden bundan sonra uzun sorguları cümleciklerine bölüp alt alta yazacağız; editörde de böyle yaz, okuması kolaylaşır.
AND iki koşulun ikisi de doğruysa satırı geçirir:
SELECT oyuncu, sampiyon, sonuc, killFROM maclarWHERE oyuncu = 'Deniz' AND sonuc = 'zafer';| oyuncu | sampiyon | sonuc | kill |
|---|---|---|---|
| Deniz | Ahri | zafer | 8 |
| Deniz | Lux | zafer | 2 |
| Deniz | Jinx | zafer | 14 |
| Deniz | Ahri | zafer | 11 |
| Deniz | Annie | zafer | 7 |
OR için koşullardan birinin doğru olması yeter. Ahri ya da Yasuo oynanan maçlar:
SELECT oyuncu, sampiyon, sonucFROM maclarWHERE sampiyon = 'Ahri' OR sampiyon = 'Yasuo';| oyuncu | sampiyon | sonuc |
|---|---|---|
| Deniz | Ahri | zafer |
| Deniz | Ahri | yenilgi |
| Deniz | Yasuo | yenilgi |
| Deniz | Ahri | zafer |
NOT bir koşulu tersine çevirir. Mert'in kazanamadığı maçlar:
SELECT oyuncu, sampiyon, sonucFROM maclarWHERE oyuncu = 'Mert' AND NOT sonuc = 'zafer';| oyuncu | sampiyon | sonuc |
|---|---|---|
| Mert | Lee Sin | yenilgi |
| Mert | Darius | yenilgi |
| Mert | Teemo | yenilgi |
Bunlar Ders 3'teki mantık işaretlerinin (&&, ||, !) SQL'deki adları. Kuralları da aynı, tuzakları da.
AND, OR'dan önce bağlanır
Ahri ya da Lux oynanan maçlardan kazanılanları istiyorsun ve aklındaki cümleyi sırasıyla yazıyorsun:
SELECT oyuncu, sampiyon, sonucFROM maclarWHERE sampiyon = 'Ahri' OR sampiyon = 'Lux' AND sonuc = 'zafer';| oyuncu | sampiyon | sonuc |
|---|---|---|
| Deniz | Ahri | zafer |
| Deniz | Ahri | yenilgi |
| Deniz | Lux | zafer |
| Deniz | Ahri | zafer |
Listede bir yenilgi var. Hata mesajı yok, sorgu çalıştı, ama sonuç yanlış. Sebebi Ders 3'tekiyle aynı: AND, OR'dan önce bağlanır. PostgreSQL bu koşulu "Ahri ya da (Lux ve zafer)" diye okudu ve Ahri'nin bütün maçları geçti. Ne kastettiğini parantezle söyle:
SELECT oyuncu, sampiyon, sonucFROM maclarWHERE (sampiyon = 'Ahri' OR sampiyon = 'Lux') AND sonuc = 'zafer';| oyuncu | sampiyon | sonuc |
|---|---|---|
| Deniz | Ahri | zafer |
| Deniz | Lux | zafer |
| Deniz | Ahri | zafer |
IN ve BETWEEN: kısa yollar
Aynı sütun için uzun bir OR zinciri yazmak yerine IN ile bir liste verebilirsin. Yukarıdaki parantezli sorgu kısaca şöyle yazılır:
SELECT oyuncu, sampiyon, sonucFROM maclarWHERE sampiyon IN ('Ahri', 'Lux') AND sonuc = 'zafer';| oyuncu | sampiyon | sonuc |
|---|---|---|
| Deniz | Ahri | zafer |
| Deniz | Lux | zafer |
| Deniz | Ahri | zafer |
IN "bu listedekilerden biri" demek; Ders 5'teki takim.includes("Lux") kalıbının SQL'deki karşılığı. NOT IN ise "listedekilerin hiçbiri".
BETWEEN bir aralık verir ve iki ucu da dahildir. 6 ile 9 arasında kill alınan maçlar:
SELECT oyuncu, sampiyon, kill FROM maclar WHERE kill BETWEEN 6 AND 9;| oyuncu | sampiyon | kill |
|---|---|---|
| Deniz | Ahri | 8 |
| Mert | Garen | 9 |
| Ece | Jinx | 9 |
| Deniz | Yasuo | 6 |
| Mert | Lee Sin | 6 |
| Ece | Lux | 6 |
| Ece | Ashe | 7 |
| Deniz | Annie | 7 |
Sonuçta tam 6 ve tam 9 kill'li maçlar da var. Aynı koşulu uzun yoldan kill >= 6 AND kill <= 9 diye de yazabilirdin.
ORDER BY: sıralamak
Şimdiye kadarki sonuçlar tabloya eklendikleri sırayla geldi. Bu bir tesadüf: ORDER BY yazmadığın sürece PostgreSQL sıra sözü vermez. Tablo büyüyüp değiştikçe aynı sorgu satırları başka bir sırayla getirebilir. Sıra önemliyse, ki neredeyse her zaman önemlidir, ORDER BY yaz. Deniz'in maçları, en çok kill alınan en üstte:
SELECT sampiyon, sonuc, killFROM maclarWHERE oyuncu = 'Deniz'ORDER BY kill DESC;| sampiyon | sonuc | kill |
|---|---|---|
| Jinx | zafer | 14 |
| Ahri | zafer | 11 |
| Ahri | zafer | 8 |
| Annie | zafer | 7 |
| Yasuo | yenilgi | 6 |
| Ahri | yenilgi | 3 |
| Lux | zafer | 2 |
DESC (descending, azalan) büyükten küçüğe demek. Hiçbir şey yazmazsan ya da ASC (ascending, artan) yazarsan küçükten büyüğe sıralar. Metinler alfabetik, tarihler eskiden yeniye dizilir.
ORDER BY'ın yeri WHERE'den sonradır. Türkçe harfli metinlerin sırası veritabanının dil ayarına bağlıdır; bazı ayarlarda Çağla, Zeynep'ten sonra bile gelebilir.
Birden çok sütuna göre
Kazanılan maçlar; önce oyuncuya göre, aynı oyuncunun maçları da kill'e göre:
SELECT oyuncu, sampiyon, killFROM maclarWHERE sonuc = 'zafer'ORDER BY oyuncu, kill DESC;| oyuncu | sampiyon | kill |
|---|---|---|
| Deniz | Jinx | 14 |
| Deniz | Ahri | 11 |
| Deniz | Ahri | 8 |
| Deniz | Annie | 7 |
| Deniz | Lux | 2 |
| Ece | Jinx | 12 |
| Ece | Jinx | 9 |
| Ece | Ashe | 7 |
| Ece | Thresh | 1 |
| Mert | Garen | 10 |
| Mert | Garen | 9 |
| Mert | Lee Sin | 6 |
Her sütunun yönü ayrı yazılır: oyuncu'nun yanında DESC olmadığı için o artan (A'dan Z'ye) sıralandı, kill ise azalan.
LIMIT ve OFFSET: ilk birkaç satır ve sayfalar
Bütün listeye değil, en iyi üç maça bakmak istiyorsun. LIMIT sonucun ilk birkaç satırını alır:
SELECT oyuncu, sampiyon, kill FROM maclar ORDER BY kill DESC LIMIT 3;| oyuncu | sampiyon | kill |
|---|---|---|
| Deniz | Jinx | 14 |
| Ece | Jinx | 12 |
| Deniz | Ahri | 11 |
LIMIT sorgunun en sonuna yazılır ve ORDER BY ile birlikte anlam kazanır. ORDER BY olmadan LIMIT 3 "herhangi üç satır" demektir; hangilerinin geleceği belli değildir.
OFFSET ile sayfa sayfa
Maç geçmişi ekranları bütün maçları bir anda yüklemez: önce birkaç maç gelir, "daha fazla göster"e basınca sonrakiler. Buna sayfalama denir.
OFFSET n "ilk n satırı atla" demek; LIMIT ile birlikte sonuçtan bir sayfa keser. Kill sıralamasını 5'er maçlık sayfalara böldüğünü düşün. 2. sayfa, ilk 5 satırı atlayıp sonraki 5'ini alır:
SELECT id, oyuncu, sampiyon, killFROM maclarORDER BY kill DESC, idLIMIT 5 OFFSET 5;| id | oyuncu | sampiyon | kill |
|---|---|---|---|
| 11 | Ece | Jinx | 9 |
| 1 | Deniz | Ahri | 8 |
| 16 | Ece | Ashe | 7 |
| 18 | Deniz | Annie | 7 |
| 12 | Deniz | Yasuo | 6 |
Üçüncü sayfa için OFFSET 10, dördüncü sayfa için OFFSET 15 yazarsın: her sayfada önceki sayfaların bütün satırları atlanır. Son sayfadan sonrasını istersen boş bir tablo gelir; bu hata değil.
SELECT'te hesap ve takma ad
SELECT'in arkasına yalnız sütun adı değil, hesap da yazabilirsin. Kill ile asistin toplamı, oyuncunun takım savaşlarına ne kadar katıldığını kabaca gösterir. Bu toplamın en yüksek olduğu üç maç:
SELECT oyuncu, sampiyon, kill + asistFROM maclarORDER BY kill + asist DESCLIMIT 3;| oyuncu | sampiyon | ?column? |
|---|---|---|
| Deniz | Jinx | 36 |
| Ece | Lux | 31 |
| Mert | Garen | 28 |
Hesap doğru, ama sütunun adı ?column? oldu. Ders 33'te söylenen buydu: hesaba ad vermezsen PostgreSQL ona kendisi bir ad uyduramaz.
Ders 33'teki ilk sorguda yaptığın gibi AS ile ad ver. Sonuç sütununa böyle verilen ada takma ad (alias) denir. Takma adı ORDER BY'da da kullanabilirsin:
SELECT oyuncu, sampiyon, kill + asist AS katkiFROM maclarORDER BY katki DESCLIMIT 3;| oyuncu | sampiyon | katki |
|---|---|---|
| Deniz | Jinx | 36 |
| Ece | Lux | 31 |
| Mert | Garen | 28 |
LIKE ve ILIKE: metin içinde aramak
Şampiyon seçim ekranındaki arama kutusunu düşün: a yazınca yalnız adında a geçenler kalır. = bunu yapamaz; satırı ancak metnin tamamı eşitse bulur. Metnin bir kısmına göre aramak için LIKE ve bir kalıp kullanırsın:
SELECT oyuncu, sampiyon FROM maclar WHERE sampiyon LIKE 'A%';| oyuncu | sampiyon |
|---|---|
| Deniz | Ahri |
| Deniz | Ahri |
| Deniz | Ahri |
| Ece | Ashe |
| Deniz | Annie |
Kalıptaki % "burada herhangi sayıda karakter olabilir, hiç olmayabilir de" demek; 'A%' A ile başlayan her şeyi tutar. Kalıplarda iki joker karakter var: % ve tam bir karakterin yerini tutan _ (alt çizgi). Bu tablodaki şampiyonlarla:
'A%': A ile başlayan. Ahri, Ashe, Annie.'%e': e ile biten. Ashe, Annie.'%in%': içinde "in" geçen. Jinx, Lee Sin.'____'(dört alt çizgi): tam dört karakterli. Ahri, Jinx, Ashe.'_e%': ikinci harfi e olan. Lee Sin, Teemo.
Kalıpta joker karakter yoksa LIKE, = gibi davranır: sampiyon LIKE 'Lee' hiçbir şey bulmaz, çünkü tam ad Lee Sin.
Büyük-küçük harf: ILIKE
LIKE da = gibi büyük-küçük harfe duyarlıdır. Kullanıcı arama kutusuna küçük a yazarsa:
SELECT oyuncu, sampiyon FROM maclar WHERE sampiyon LIKE 'a%';| oyuncu | sampiyon |
|---|
Boş tablo, çünkü bütün adlar büyük harfle başlıyor. PostgreSQL'in ILIKE'ı aynı kalıbı harf büyüklüğüne bakmadan uygular:
SELECT oyuncu, sampiyon FROM maclar WHERE sampiyon ILIKE 'a%';| oyuncu | sampiyon |
|---|---|
| Deniz | Ahri |
| Deniz | Ahri |
| Deniz | Ahri |
| Ece | Ashe |
| Deniz | Annie |
Arama kutuları neredeyse her zaman ILIKE ister. İki not: ILIKE PostgreSQL'e özgüdür, başka veritabanlarında adı ve davranışı farklıdır. Türkçedeki İ, ı gibi harflerde büyük-küçük eşleştirmesi veritabanının dil ayarına bağlıdır; Ders 9'daki toLocaleUpperCase("tr") tuzağının bir benzeri.
NULL: bilinmeyen değer
Üç ARAM maçında rol boş. Onları bulmak için aklına gelen ilk sorgu muhtemelen şudur:
WHERE rol = NULL hiç satır döndürmüyorNe zaman çıkar? Boş (NULL) değerleri = ile aramaya kalkınca. Hata mesajı yok; sorgu çalışır ve boş bir tablo döner. Bu yüzden fark etmesi zordur.
SELECT id, oyuncu, sampiyon, rol FROM maclar WHERE rol = NULL;| id | oyuncu | sampiyon | rol |
|---|
Sütun başlıkları geldi, ama tek satır yok: sorgu 0 satır döndürdü.
Nasıl düzelir? NULL'u IS NULL ile, NULL olmayanları IS NOT NULL ile ara. Aynı tuzak <> için de geçerli: rol <> 'orta' rolü boş maçları da dışarıda bırakır.
Neden boş? Ders 33'te NULL'u "değer yok" diye tanıdın. Karşılaştırmalarda PostgreSQL onu "bilinmiyor" diye ele alır. rol = NULL sorusu "bu maçın rolü, bilinmeyen bir şeye eşit mi?" olur; cevabı da "bilinmiyor"dur.
Bunu kendin görebilirsin. Ders 33'teki ilk sorgun gibi SELECT'i tablosuz çalıştır; tek satırlık bir hesap makinesi gibi davranır:
SELECT NULL = NULL AS bos_bosa_esit_mi, 'orta' = 'orta' AS orta_ortaya_esit_mi;| bos_bosa_esit_mi | orta_ortaya_esit_mi |
|---|---|
NULL | true |
İki NULL'un eşitliği bile NULL çıktı, yani bilinmiyor. (Bazı araçlar true yerine t yazar.) WHERE ise yalnız sonucu doğru (true) olan satırları geçirir; yanlış çıkanlar da bilinmeyenler de elenir.
WHERE yalnız koşulu doğru çıkan satırları geçirir. NULL ile = karşılaştırması hep "bilinmiyor" verir; NULL'u yalnız IS NULL yakalar.Doğru sorgu:
SELECT id, oyuncu, sampiyon, rol FROM maclar WHERE rol IS NULL;| id | oyuncu | sampiyon | rol |
|---|---|---|---|
| 9 | Deniz | Jinx | NULL |
| 14 | Ece | Lux | NULL |
| 20 | Mert | Garen | NULL |
Rolü belli maçları da WHERE rol IS NOT NULL getirir. NULL'la yapılan hesaplar da NULL verir: NULL + 5 sonucu NULL'dur, çünkü bilinmeyen bir sayıya beş eklersen sonuç yine bilinmez.
Toplama fonksiyonları: COUNT, SUM, AVG, MIN, MAX
Şimdiye kadarki her sorgu satır döndürdü. Bazen satırların kendisini değil özetini istersin: "Kaç maç oynandı?" COUNT(*) satırları sayar:
SELECT COUNT(*) FROM maclar;| count |
|---|
| 20 |
20 satır tek bir sayıya indi. Sütunun adı count oldu; ona da AS ile daha anlamlı bir ad verebilirsin. COUNT gibi çok satırı alıp tek bir değer üreten fonksiyonlara toplama fonksiyonu denir. Beş temel toplama fonksiyonu var:
| Fonksiyon | Ne yapar? |
|---|---|
COUNT(*) | satırları sayar |
SUM(kill) | değerleri toplar |
AVG(kill) | ortalamayı hesaplar |
MIN(kill) | en küçüğü bulur |
MAX(kill) | en büyüğü bulur |
SELECT SUM(kill) AS toplam_kill, AVG(kill) AS ortalama_kill, MIN(kill) AS en_az, MAX(kill) AS en_cokFROM maclar;| toplam_kill | ortalama_kill | en_az | en_cok |
|---|---|---|---|
| 128 | 6.4000000000000000 | 1 | 14 |
Ortalama 6.4000000000000000 çıktı, çünkü PostgreSQL ortalamayı tam hassasiyetle verir. ROUND(değer, basamak) onu istediğin basamağa yuvarlar. Toplama fonksiyonları WHERE ile birlikte de çalışır; o zaman yalnız süzülen satırlar özetlenir. Deniz'in karnesi:
SELECT COUNT(*) AS mac, SUM(kill) AS toplam_kill, ROUND(AVG(kill), 2) AS ortalama_kill, MAX(kill) AS en_cok_killFROM maclarWHERE oyuncu = 'Deniz';| mac | toplam_kill | ortalama_kill | en_cok_kill |
|---|---|---|---|
| 7 | 51 | 7.29 | 14 |
MIN ve MAX yalnız sayılarla sınırlı değil; tarihte en eskiyi ve en yeniyi, metinde alfabetik ilk ve sonu bulur:
SELECT MIN(tarih) AS ilk_mac, MAX(tarih) AS son_mac FROM maclar;| ilk_mac | son_mac |
|---|---|
| 2026-09-28 17:40:00+00 | 2026-10-10 15:45:00+00 |
Toplama fonksiyonları NULL'u atlar
COUNT(*) satırları sayar. COUNT(sütun) ise o sütunda NULL olmayan değerleri sayar:
SELECT COUNT(*) AS tum_maclar, COUNT(rol) AS rolu_belli FROM maclar;| tum_maclar | rolu_belli |
|---|---|
| 20 | 17 |
Aradaki fark, rolü boş üç ARAM maçı. SUM, AVG, MIN ve MAX da NULL'ları görmezden gelir.
NULL bölümündeki <> tuzağını da şimdi sayılarla görebilirsin. Rolü orta olan 5 maç var; demek ki rolü orta olmayan 15 maç olmalı:
SELECT COUNT(*) AS orta_olmayanFROM maclarWHERE rol <> 'orta';| orta_olmayan |
|---|
| 12 |
12 geldi: üç ARAM maçında NULL <> 'orta' bilinmiyor çıktı ve elendi. ARAM maçlarını da saymak istiyorsan bunu açıkça söylemelisin:
SELECT COUNT(*) AS orta_olmayanFROM maclarWHERE rol <> 'orta' OR rol IS NULL;| orta_olmayan |
|---|
| 15 |
Tam sayı bölmesi
Ders 33'ün sonunda gördüğün tuzak, KDA hesaplarken kesin karşına çıkacak: JavaScript'te 7 / 2 sonucu 3.5'ti, SQL'de ise iki tam sayının bölümü yine tam sayıdır ve küsurat atılır.
Orada çözüm sayıyı 7.0 diye yazmaktı. Sütunlarda bunu yapamazsın; kill ve olum tam sayı (integer) sütunları. Onun yerine bölmeden önce 1.0 ile çarparsın:
SELECT 7 / 2 AS tam_bolme, 7 * 1.0 / 2 AS ondalikli;| tam_bolme | ondalikli |
|---|---|
| 3 | 3.5000000000000000 |
* 1.0 değeri değiştirmez ama hesabı ondalıklı sayılara taşır; sütunla da aynı çalışır: kill * 1.0 / olum. AVG bunu kendisi yapar; elle bölerken unutma.
division by zeroNe zaman çıkar? Bir sütuna bölerken o sütunda 0 olan bir satır varsa. Ece, Ashe maçında hiç ölmemiş (olum 0); her maçın KDA'sını hesaplamaya kalkınca sorgu o satırda durur. JavaScript sıfıra bölünce Infinity veriyordu (Ders 1); PostgreSQL hata verir.
SELECT sampiyon, (kill + asist) / olum AS kda FROM maclar;ERROR: division by zeroNasıl düzelir? olum yerine NULLIF(olum, 0) ile böl. NULLIF(a, b) "a, b'ye eşitse NULL ver, değilse a'nın kendisini ver" demek: olum 0 ise NULL olur, değilse olduğu gibi kalır. NULL'a bölmek hata değil, NULL verir: Ders 9'daki "Kusursuz" maç gibi, KDA tanımsız kalır.
SELECT sampiyon, olum, ROUND((kill + asist) * 1.0 / NULLIF(olum, 0), 2) AS kdaFROM maclarWHERE oyuncu = 'Ece'ORDER BY id;| sampiyon | olum | kda |
|---|---|---|
| Jinx | 3 | 6.67 |
| Thresh | 4 | 5.00 |
| Kai'Sa | 7 | 1.29 |
| Jinx | 2 | 9.50 |
| Lux | 8 | 3.88 |
| Ashe | 0 | NULL |
| Jinx | 5 | 1.80 |
GROUP BY: her oyuncu için ayrı hesap
Her oyuncunun kaç maç oynadığını bilmek istiyorsun. COUNT(*)'un yanına oyuncu yazınca:
SELECT oyuncu, COUNT(*) AS mac_sayisi FROM maclar;ERROR: column "maclar.oyuncu" must appear in the GROUP BY clause or be used in an aggregate functionCOUNT(*) 20 satırı tek satıra indiriyor. O tek satırın oyuncu hücresine kimin adı yazılacak, Deniz'in mi Ece'nin mi? PostgreSQL tahmin yürütmez, hata verir. Önce satırları oyunculara ayırman gerekiyor:
SELECT oyuncu, COUNT(*) AS mac_sayisi FROM maclar GROUP BY oyuncu;| oyuncu | mac_sayisi |
|---|---|
| Mert | 6 |
| Deniz | 7 |
| Ece | 7 |
GROUP BY oyuncu satırları oyuncu adına göre kovalara ayırdı, COUNT(*) her kovayı ayrı saydı ve her kova sonuçta tek satır oldu. Sıraya dikkat et: Mert, Deniz, Ece. GROUP BY sıralamaz; senin ekranında sıra başka olabilir. Sıra istiyorsan yine ORDER BY yazarsın:
SELECT oyuncu, COUNT(*) AS mac_sayisi, SUM(kill) AS toplam_killFROM maclarGROUP BY oyuncuORDER BY oyuncu;| oyuncu | mac_sayisi | toplam_kill |
|---|---|---|
| Deniz | 7 | 51 |
| Ece | 7 | 43 |
| Mert | 6 | 34 |
GROUP BY oyuncu 20 satırı üç kovaya ayırır. Toplama fonksiyonları her kovayı ayrı hesaplar ve her kova sonuçta tek satır olur.Buradan bir kural çıkıyor: GROUP BY'lı bir sorguda SELECT'e yazdığın her sütun ya GROUP BY'da olmalı ya da bir toplama fonksiyonunun içinde. Sonuçtaki her satır bir kovadır; Deniz kovasının tek bir oyuncu adı vardır, ama tek bir şampiyonu yoktur.
column "maclar.rol" must appear in the GROUP BY clause or be used in an aggregate functionNe zaman çıkar? Gruplarken SELECT'e, grupta tek bir değeri olmayan bir sütun yazınca. Jinx'in dört maçı var ve rolleri farklı (alt ve boş); PostgreSQL Jinx satırına hangisini yazacağını bilemez.
SELECT sampiyon, rol, COUNT(*) AS macFROM maclarGROUP BY sampiyon;ERROR: column "maclar.rol" must appear in the GROUP BY clause or be used in an aggregate functionNasıl düzelir? Mesaj iki yol söylüyor ("GROUP BY'da görünmeli ya da bir toplama fonksiyonunda kullanılmalı"): sütunu gruba ekle (GROUP BY sampiyon, rol; artık her şampiyon ve rol çifti ayrı satır olur) ya da toplama fonksiyonunun içine al (COUNT(rol) gibi). GROUP BY hiç yazmadan SELECT oyuncu, COUNT(*) yazmak da aynı hatayı verir.
Aynı kalıpla şampiyon istatistiği:
SELECT sampiyon, COUNT(*) AS mac, ROUND(AVG(kill), 1) AS ort_killFROM maclarGROUP BY sampiyonORDER BY mac DESC, sampiyon;| sampiyon | mac | ort_kill |
|---|---|---|
| Jinx | 4 | 9.5 |
| Ahri | 3 | 7.3 |
| Garen | 2 | 9.5 |
| Lee Sin | 2 | 5.0 |
| Lux | 2 | 4.0 |
| Annie | 1 | 7.0 |
| Ashe | 1 | 7.0 |
| Darius | 1 | 3.0 |
| Kai'Sa | 1 | 5.0 |
| Teemo | 1 | 2.0 |
| Thresh | 1 | 1.0 |
| Yasuo | 1 | 6.0 |
Birden çok sütuna göre de gruplayabilirsin; o zaman her farklı çift ayrı bir kova olur. Her oyuncunun zafer ve yenilgi sayısı:
SELECT oyuncu, sonuc, COUNT(*) AS macFROM maclarGROUP BY oyuncu, sonucORDER BY oyuncu, sonuc;| oyuncu | sonuc | mac |
|---|---|---|
| Deniz | yenilgi | 2 |
| Deniz | zafer | 5 |
| Ece | yenilgi | 3 |
| Ece | zafer | 4 |
| Mert | yenilgi | 3 |
| Mert | zafer | 3 |
NULL'lar da kendi kovalarında toplanır. Rol başına maç sayısında ARAM maçları ayrı bir NULL grubu olur; ORDER BY artan sıralamada NULL'u en sona koyar:
SELECT rol, COUNT(*) AS mac FROM maclar GROUP BY rol ORDER BY rol;| rol | mac |
|---|---|
| alt | 5 |
| destek | 2 |
| orman | 2 |
| orta | 5 |
| üst | 3 |
NULL | 3 |
Ders 11'de reduce ile bir diziden gruplanmış bir obje çıkarmıştın; biriktiriciyi kurup her elemanı doğru anahtara eklemek birkaç satır sürüyordu. GROUP BY aynı işi tek satırda yapıyor.
HAVING: grupları süzmek
Şampiyon tablosuna tekrar bak: Annie'nin kill ortalaması 7.0, ama tek maçtan. Tek maçlık bir ortalama şans eseri olabilir. Yalnız en az iki kez oynanan şampiyonları görmek istiyorsun. Bir koşul olduğuna göre WHERE yazarsın:
aggregate functions are not allowed in WHERENe zaman çıkar? WHERE içine COUNT, SUM, AVG gibi bir toplama fonksiyonu yazınca.
SELECT sampiyon, COUNT(*) AS macFROM maclarWHERE COUNT(*) >= 2GROUP BY sampiyon;ERROR: aggregate functions are not allowed in WHERENasıl düzelir? WHERE tek tek satırlara bakar; o çalışırken henüz grup yoktur, sayılacak bir şey de yoktur. Grupların üzerindeki koşulu GROUP BY'dan sonra gelen HAVING'e taşı: GROUP BY sampiyon HAVING COUNT(*) >= 2.
SELECT sampiyon, COUNT(*) AS mac, ROUND(AVG(kill), 1) AS ort_killFROM maclarGROUP BY sampiyonHAVING COUNT(*) >= 2ORDER BY ort_kill DESC, sampiyon;| sampiyon | mac | ort_kill |
|---|---|---|
| Garen | 2 | 9.5 |
| Jinx | 4 | 9.5 |
| Ahri | 3 | 7.3 |
| Lee Sin | 2 | 5.0 |
| Lux | 2 | 4.0 |
HAVING grupları süzer: en az iki maçı olan beş şampiyon kaldı. Garen ile Jinx'in ortalaması eşit; sıralamanın sonundaki sampiyon onları alfabetik dizdi.
WHERE | HAVING | |
|---|---|---|
| Neye bakar? | Tek tek satırlara | Gruplara |
| Ne zaman çalışır? | Gruplamadan önce | Gruplamadan sonra |
| Toplama fonksiyonu | Yazılamaz | Yazılır: COUNT(*) >= 2 |
| Örnek | WHERE sonuc = 'zafer' | HAVING SUM(kill) > 30 |
İkisi aynı sorguda birlikte de kullanılır: önce WHERE satırları süzer, kalanlar gruplanır, sonra HAVING grupları süzer. Bir sonraki bölümdeki sorgu tam olarak bunu yapıyor.
Sorgu hangi sırayla çalışır?
Şimdi hepsini bir araya getir. ARAM dışındaki maçlarda, en az iki kez oynanan şampiyonlardan kill ortalaması en yüksek iki tanesi:
SELECT sampiyon, COUNT(*) AS mac, ROUND(AVG(kill), 1) AS ort_killFROM maclarWHERE rol IS NOT NULLGROUP BY sampiyonHAVING COUNT(*) >= 2ORDER BY ort_kill DESCLIMIT 2;| sampiyon | mac | ort_kill |
|---|---|---|
| Jinx | 3 | 8.0 |
| Ahri | 3 | 7.3 |
Sorguyu yukarıdan aşağı yazıyorsun, ama PostgreSQL onu bu sırayla çalıştırmaz. SELECT en üstte yazılır, oysa beşinci sırada çalışır:
Bu sırayı bilince dersteki hataların çoğu kendiliğinden anlam kazanır:
WHERE(2) gruplamadan (3) önce çalışır; bu yüzden içineCOUNT(*)yazılamaz. Grup koşuluHAVING'e (4) gider.SELECT(5),WHERE'den sonra çalışır.ASile verdiğin takma adlarWHEREçalışırken henüz yoktur. PostgreSQL'deHAVINGde takma adı tanımaz; orada da hesabın kendisini (COUNT(*)) yazarsın.ORDER BY(6),SELECT'ten sonra çalışır; takma adı (ort_kill) orada rahatça kullanabilirsin.LIMIT(7) en sonda çalışır. "İlk 2" her zaman sıralanmış sonucun ilk ikisidir.
column "katki" does not existNe zaman çıkar? SELECT'te verdiğin takma adı WHERE'de (ya da HAVING'de) kullanınca. WHERE çalışırken katki adı henüz verilmemiştir.
SELECT oyuncu, sampiyon, kill + asist AS katkiFROM maclarWHERE katki >= 20;ERROR: column "katki" does not existNasıl düzelir? WHERE'de hesabın kendisini yaz; takma adı ORDER BY'da kullanmaya devam edebilirsin:
SELECT oyuncu, sampiyon, kill + asist AS katkiFROM maclarWHERE kill + asist >= 20ORDER BY katki DESC, sampiyon;| oyuncu | sampiyon | katki |
|---|---|---|
| Deniz | Jinx | 36 |
| Ece | Lux | 31 |
| Mert | Garen | 28 |
| Ece | Jinx | 20 |
| Mert | Lee Sin | 20 |
| Ece | Thresh | 20 |
Sorgularını bir dosyada sakla
Sorgular Neon'un History listesinde de durur, ama asıl yerleri bir dosya: Git'e kaydedilir, istediğin an yeniden çalıştırırsın. Ders 33'teki C:\kod\veritabani klasörünü VS Code'da aç ve iki dosya ekle: dersin başındaki betiği maclar.sql adıyla kaydet, işine yarayan sorguları ders34.sql'e yaz. VS Code .sql uzantısını tanır ve anahtar sözcükleri renklendirir.
-- Ders 34: maç geçmişini sorgulamak-- Sorguları Neon'da tek tek çalıştır. -- Deniz'in kazandığı maçlarSELECT sampiyon, kill, olum, asistFROM maclarWHERE oyuncu = 'Deniz' AND sonuc = 'zafer'ORDER BY kill DESC; -- Oyuncu başına maç sayısı ve killSELECT oyuncu, COUNT(*) AS mac_sayisi, SUM(kill) AS toplam_killFROM maclarGROUP BY oyuncuORDER BY oyuncu; -- En az 2 kez oynanan şampiyonlarSELECT sampiyon, COUNT(*) AS mac, ROUND(AVG(kill), 1) AS ort_killFROM maclarGROUP BY sampiyonHAVING COUNT(*) >= 2ORDER BY ort_kill DESC, sampiyon;-- ile başlayan yorumlar her sorgunun ne yaptığını anlatıyor ve her sorgu ; ile bitiyor. Sorgular dersteki gibi cümleciklerine bölünmüş; uzun bir SELECT listesini de virgülden sonra alt satıra geçip 4 boşluk içeri alarak sürdürebilirsin. Birini çalıştırmak için seçip SQL Editor'e kopyala.
Sık hatalar
Dersteki hataların çoğunu ilgili bölümlerde gördün. Birkaçı daha; hepsi hata rehberinde de duruyor.
syntax error at or near "WHERE"Ne zaman çıkar? Cümlecikleri yanlış sırayla yazınca. Aşağıda WHERE, ORDER BY'dan sonra yazılmış.
SELECT * FROM maclar ORDER BY kill DESC WHERE oyuncu = 'Ece';ERROR: syntax error at or near "WHERE"Nasıl düzelir? Yazım sırası sabit: SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET. Kullanmadığın cümleciği atlarsın, ama sıralarını değiştiremezsin. (Tek istisna sondaki ikili: PostgreSQL OFFSET'i LIMIT'ten önce yazmana da izin verir.)
column "sampion" does not existNe zaman çıkar? Sütun adını yanlış yazınca.
SELECT oyuncu, sampion FROM maclar;ERROR: column "sampion" does not existHINT: Perhaps you meant to reference the column "maclar.sampiyon".Nasıl düzelir? HINT (ipucu) satırını oku: PostgreSQL adı benzeyen sütunu öneriyor, "maclar.sampiyon sütununu mu kastettin?" diyor. Sütun adlarından emin değilsen SELECT * FROM maclar LIMIT 1; ile tabloya bir göz at; bu, *'ın yerinde kullanımlarından biri.
invalid input syntax for type boolean: "Lux"Ne zaman çıkar? "Ahri ya da Lux" cümlesini sözcük sözcük çevirince.
SELECT oyuncu, sampiyonFROM maclarWHERE sampiyon = 'Ahri' OR 'Lux';ERROR: invalid input syntax for type boolean: "Lux"Nasıl düzelir? OR'un iki yanı da ayrı birer koşul olmalı. Sağdaki 'Lux' tek başına bir koşul değil; PostgreSQL onu doğru/yanlış değerine (boolean) çevirmeye çalışıyor ve başaramıyor. Doğrusu sampiyon = 'Ahri' OR sampiyon = 'Lux' ya da kısaca sampiyon IN ('Ahri', 'Lux').
Görevler
Görevleri SQL Editor'de yap; çözümlerini ders34.sql'e de ekleyebilirsin. Her görevde beklenen sonuç var: seninki satır satır aynı olmalı. Önce kendin dene, takılırsan ipuçlarını sırayla aç, çözüme en son bak.
Uzun maçlar
35 dakika ya da daha uzun süren maçları listele. Sütunlar: oyuncu, sampiyon, sure_dk. En uzun maç en üstte olsun; süresi eşit maçlar oyuncu adına göre A'dan Z'ye dizilsin. Beklenen sonuç:
| oyuncu | sampiyon | sure_dk |
|---|---|---|
| Ece | Jinx | 40 |
| Ece | Thresh | 36 |
| Deniz | Lux | 35 |
| Mert | Teemo | 35 |
İpucu 1
Hangi karşılaştırma işareti 35'in kendisini de içine alır: > mı, >= mi?
İpucu 2
ORDER BY'a iki sütun yaz: önce süre (büyükten küçüğe), sonra oyuncu.
Çözümü göster
SELECT oyuncu, sampiyon, sure_dkFROM maclarWHERE sure_dk >= 35ORDER BY sure_dk DESC, oyuncu;| oyuncu | sampiyon | sure_dk |
|---|---|---|
| Ece | Jinx | 40 |
| Ece | Thresh | 36 |
| Deniz | Lux | 35 |
| Mert | Teemo | 35 |
Sınır denemesi: >= yerine > yazarsan 35 dakikalık iki maç kaybolur ve 2 satır kalır; "35 ve üstü" ile "35'ten uzun" aynı şey değil. İkinci sıralama sütununu silersen Deniz ile Mert'in 35 dakikalık maçları sende ters sırada gelebilir.
Destek ve orman
Koçun, destek ya da orman oynanan maçlardan asist sayısı 10 ile 15 arasında olanları istiyor; 10 ve 15 de dahil. Sütunlar: oyuncu, sampiyon, rol, asist; en çok asist üstte. Beklenen sonuç:
| oyuncu | sampiyon | rol | asist |
|---|---|---|---|
| Deniz | Lux | destek | 15 |
| Mert | Lee Sin | orman | 14 |
| Mert | Lee Sin | orman | 10 |
İpucu 1
Rol için "şu ikisinden biri" diyen kısa yolu, asist için iki ucu dahil olan aralığı kullan.
İpucu 2
rol IN ('destek', 'orman') ve asist BETWEEN 10 AND 15; ikisini AND ile bağla.
İpucu 3
Sonuçta Ece'nin Thresh maçı (19 asist) da varsa rol = 'destek' OR rol = 'orman' AND ... yazmışsın: AND önce bağlandı. IN kullan ya da OR'lu kısmı paranteze al.
Çözümü göster
SELECT oyuncu, sampiyon, rol, asistFROM maclarWHERE rol IN ('destek', 'orman') AND asist BETWEEN 10 AND 15ORDER BY asist DESC;| oyuncu | sampiyon | rol | asist |
|---|---|---|---|
| Deniz | Lux | destek | 15 |
| Mert | Lee Sin | orman | 14 |
| Mert | Lee Sin | orman | 10 |
Sınır denemesi: listedeki iki maç tam sınırda (15 ve 10). BETWEEN iki ucu da kapsadığı için geldiler; asist > 10 AND asist < 15 yazsaydın yalnız 14 asistli maç kalırdı.
Şampiyon arama
Bir arama kutusu yapıyorsun ve kullanıcı kutuya JI yazdı. Adında bu harfler geçen şampiyonların maçlarını büyük-küçük harf fark etmeden bul, ama ARAM maçlarını (rolü boş olanlar) alma. Sütunlar: id, oyuncu, sampiyon, rol; en yeni maç üstte. Beklenen sonuç 3 satır; ilki 19 · Ece · Jinx · alt, sonuncusu 2 · Ece · Jinx · alt.
İpucu 1
Büyük-küçük harfe bakmayan arama ILIKE. Harflerin adın herhangi bir yerinde olabileceğini iki yanlarına koyduğun % ile söyle.
İpucu 2
ARAM maçlarında rol NULL. NULL'u = ya da <> ile değil, IS ile denetle.
İpucu 3
"En yeni üstte" için tarih'e göre büyükten küçüğe sırala.
Çözümü göster
SELECT id, oyuncu, sampiyon, rolFROM maclarWHERE sampiyon ILIKE '%ji%' AND rol IS NOT NULLORDER BY tarih DESC;| id | oyuncu | sampiyon | rol |
|---|---|---|---|
| 19 | Ece | Jinx | alt |
| 11 | Ece | Jinx | alt |
| 2 | Ece | Jinx | alt |
Sınır denemeleri: rol IS NOT NULL'u unutursan Deniz'in ARAM'daki Jinx maçı (id 9) da gelir ve 4 satır olur. ILIKE yerine LIKE '%JI%' boş tablo döndürür. Kutu boşken kalıp '%%' olur ve ARAM dışındaki 17 maçın hepsi gelir; çoğu uygulamada istenen budur, ama bilerek seç.
Sayfalı maç geçmişi
Maç geçmişi ekranın her sayfada 6 maç gösteriyor, en yeni maç en üstte. 2. sayfayı getiren sorguyu yaz. Sütunlar: id, oyuncu, sampiyon, sonuc. Beklenen sonuç 6 satır: ilki 14 · Ece · Lux · yenilgi, sonuncusu 9 · Deniz · Jinx · zafer.
İpucu 1
İlk sayfa en yeni 6 maç. İkinci sayfa için o 6 maçı atlaman gerekiyor.
İpucu 2
ORDER BY tarih DESC, ardından LIMIT 6 OFFSET ve atlanacak satır sayısı.
Çözümü göster
SELECT id, oyuncu, sampiyon, sonucFROM maclarORDER BY tarih DESCLIMIT 6 OFFSET 6;| id | oyuncu | sampiyon | sonuc |
|---|---|---|---|
| 14 | Ece | Lux | yenilgi |
| 13 | Mert | Lee Sin | zafer |
| 12 | Deniz | Yasuo | yenilgi |
| 11 | Ece | Jinx | zafer |
| 10 | Mert | Darius | yenilgi |
| 9 | Deniz | Jinx | zafer |
Sınır denemesi: 20 maç 6'şarlık sayfalara bölününce son sayfa eksik kalır. 4. sayfa yalnız 2 satır getirir:
SELECT id, oyuncu, sampiyon, sonucFROM maclarORDER BY tarih DESCLIMIT 6 OFFSET 18;| id | oyuncu | sampiyon | sonuc |
|---|---|---|---|
| 2 | Ece | Jinx | zafer |
| 1 | Deniz | Ahri | zafer |
Beşinci sayfa (OFFSET 24) boş bir tablo döndürür; hata değil. 6'dan az satır geldiyse son sayfadasın; uygulaman "daha fazla göster" düğmesini buna bakarak gizleyebilir. Burada her maçın tarih'i farklı, yani sıra benzersiz; aynı anda başlayan maçlar olabilseydi ORDER BY tarih DESC, id DESC yazardın.
En çok kazanılan şampiyonlar
Zafer sayısına göre ilk 3 şampiyonu bul. Her biri için zafer sayısını, bu zaferlerin ortalama süresini (bir basamağa yuvarlanmış) ve en kısa zaferin süresini göster. Sütunlar: sampiyon, zafer_sayisi, ort_sure, en_kisa; zafer sayısı eşit olanlar adlarına göre A'dan Z'ye dizilsin. Beklenen sonuç:
| sampiyon | zafer_sayisi | ort_sure | en_kisa |
|---|---|---|---|
| Jinx | 3 | 27.0 | 19 |
| Ahri | 2 | 30.0 | 29 |
| Garen | 2 | 27.5 | 21 |
İpucu 1
Önce yalnız zaferleri süz, sonra kalan satırları şampiyona göre grupla. Her kova için üç toplama fonksiyonu gerekiyor: sayan, ortalama alan ve en küçüğü bulan.
İpucu 2
COUNT(*) AS zafer_sayisi, ROUND(AVG(sure_dk), 1) AS ort_sure, MIN(sure_dk) AS en_kisa. Sıralamada zafer_sayisi adını kullanabilirsin, çünkü ORDER BY, SELECT'ten sonra çalışır.
İpucu 3
ORDER BY zafer_sayisi DESC, sampiyon ve en sonda LIMIT 3.
Çözümü göster
SELECT sampiyon, COUNT(*) AS zafer_sayisi, ROUND(AVG(sure_dk), 1) AS ort_sure, MIN(sure_dk) AS en_kisaFROM maclarWHERE sonuc = 'zafer'GROUP BY sampiyonORDER BY zafer_sayisi DESC, sampiyonLIMIT 3;| sampiyon | zafer_sayisi | ort_sure | en_kisa |
|---|---|---|---|
| Jinx | 3 | 27.0 | 19 |
| Ahri | 2 | 30.0 | 29 |
| Garen | 2 | 27.5 | 21 |
Sınır denemeleri: WHERE satırını unutursan yine Jinx, Ahri ve Garen gelir, ama sayılar 4, 3 ve 2 olur: onlar zafer değil, maç sayısı. Süreler de bütün maçlardan hesaplanır; Ahri'nin en kısa maçı 27 dakikalık yenilgisi olur. Adlara bakıp "doğru" deme, sayıları da denetle. Sıralamanın sonundaki sampiyon'u silersen Garen, Ahri'nin önüne geçebilir; LIMIT 2 yazarsan da ikisinden biri, eşit oldukları hâlde düşer. Eşitlik sınıra denk gelirse kimin kalacağını senin yazdığın sıralama belirler.
Oyuncu karnesi: KDA
En az 7 maç oynamış oyuncuların maç sayısını ve genel KDA'sını hesapla. Genel KDA: bütün maçlardaki kill ve asist toplamının ölüm toplamına bölümü, iki basamağa yuvarlanmış. En yüksek KDA üstte. Sütunlar: oyuncu, mac, kda. Beklenen sonuç:
| oyuncu | mac | kda |
|---|---|---|
| Ece | 7 | 4.38 |
| Deniz | 7 | 3.90 |
İpucu 1
Oyuncu başına hesap: GROUP BY oyuncu. "En az 7 maç" bir grup koşulu; hangi cümleciğe yazılır?
İpucu 2
(SUM(kill) + SUM(asist)) / SUM(olum) tam sayı bölmesi yapar ve 4 ile 3 verir. Bölmeden önce * 1.0 ile ondalığa geç, sonucu ROUND(..., 2) ile yuvarla.
İpucu 3
HAVING COUNT(*) >= 7, sonra ORDER BY kda DESC.
Çözümü göster
SELECT oyuncu, COUNT(*) AS mac, ROUND((SUM(kill) + SUM(asist)) * 1.0 / SUM(olum), 2) AS kdaFROM maclarGROUP BY oyuncuHAVING COUNT(*) >= 7ORDER BY kda DESC;| oyuncu | mac | kda |
|---|---|---|
| Ece | 7 | 4.38 |
| Deniz | 7 | 3.90 |
Sınır denemeleri: HAVING COUNT(*) > 7 boş tablo döndürür, çünkü kimse 7'den fazla oynamamış; sınır tam 7'de. Mert 6 maçla listede yok; HAVING satırını silersen o da 2.37 ile gelir. Her maçın KDA'sını ayrı hesaplamaya kalkarsan Ece'nin ölümsüz Ashe maçında division by zero hatasına çarparsın; toplamlarla hesaplamak bunu da önler, çünkü kimsenin ölüm toplamı 0 değil.
Kendini sına
Soru 1
Bu sorgu kaç satır döndürür? Neden?
SELECT * FROM maclar WHERE rol = NULL;Cevabı göster
Sıfır. NULL "bilinmiyor" demek; bir değerin bilinmeyen bir şeye eşit olup olmadığı da bilinmez, karşılaştırmanın sonucu NULL olur. WHERE yalnız sonucu doğru (true) çıkan satırları geçirir. Doğrusu WHERE rol IS NULL; 3 satır gelir.
Soru 2
sampiyon LIKE '_a%' bu tablodaki hangi şampiyonları bulur?
Cevabı göster
İkinci harfi a olanları: Garen, Darius, Kai'Sa ve Yasuo. _ tam bir karakterin yerini tutar (ilk harf ne olursa olsun), ardından a gelmeli, % da geri kalanı karşılar.
Soru 3
Bu sorgu neden hata verir? Her sonuç için ayrı sayı istiyorsan nasıl düzeltirsin?
SELECT sonuc, COUNT(*) FROM maclar;Cevabı göster
COUNT(*) bütün tabloyu tek satıra indirir, sonuc ise 20 satırdan gelir; tek satıra hangi sonucun yazılacağı belli değildir. PostgreSQL column "maclar.sonuc" must appear in the GROUP BY clause or be used in an aggregate function der. Sonuna GROUP BY sonuc eklersen zafer ve yenilgi için birer satır gelir.
Soru 4
ORDER BY tarih DESC LIMIT 5 OFFSET 10 hangi maçları getirir?
Cevabı göster
En yeniden eskiye sıralı listede 11. ile 15. maçları: ilk 10 satırı atlar, sonraki 5'ini alır. 5'erli sayfalarda bu 3. sayfadır.
Soru 5
WHERE mi, HAVING mi? (a) Yalnız Ece'nin maçlarını say. (b) En az 4 zaferi olan oyuncuları göster.
Cevabı göster
(a) WHERE oyuncu = 'Ece': tek tek satırlara bakan bir koşul, gruplamadan önce çalışır. (b) Zaferleri önce WHERE sonuc = 'zafer' ile süzer, GROUP BY oyuncu ile gruplarsın; "en az 4" ise grubun sayısına bakan bir koşul, yani HAVING:
SELECT oyuncu, COUNT(*) AS zaferFROM maclarWHERE sonuc = 'zafer'GROUP BY oyuncuHAVING COUNT(*) >= 4ORDER BY oyuncu;| oyuncu | zafer |
|---|---|
| Deniz | 5 |
| Ece | 4 |
Soru 6
Bu sorgu çalışır mı? Çalışmazsa hangi satır yüzünden?
SELECT sampiyon, kill + asist AS katkiFROM maclarWHERE katki > 25ORDER BY katki DESC;Cevabı göster
Çalışmaz: column "katki" does not exist. Suçlu 3. satır. WHERE, SELECT'ten önce çalışır; takma ad o anda henüz verilmemiştir. 4. satırdaki ORDER BY ise sorunsuz, çünkü SELECT'ten sonra çalışır. Düzeltme: WHERE kill + asist > 25.
Soru 7
Bu sorgu maclar tablosunda hangi üç sayıyı döndürür?
SELECT COUNT(*) AS a, COUNT(rol) AS b, SUM(kill) / COUNT(*) AS cFROM maclar;Cevabı göster
a 20: COUNT(*) bütün satırları sayar. b 17: COUNT(rol) yalnız rolü NULL olmayanları sayar, üç ARAM maçı dışarıda kalır. c 6: toplam kill 128, maç sayısı 20, ama ikisi de tam sayı olduğu için bölümün küsuratı atılır ve 6.4 yerine 6 çıkar. Doğru ortalama için AVG(kill) ya da SUM(kill) * 1.0 / COUNT(*) yazarsın.
Kayıt noktası
C:\kod\veritabani klasörüne maclar.sql ve ders34.sql eklendi. Git'i bu klasörde Ders 33'te başlattın; şimdi yalnız yeni bir kayıt alacaksın. VS Code terminalinde, bu klasördeyken:
Bir sonraki derste veriyi okumakla kalmayıp değiştireceksin: INSERT, UPDATE ve DELETE ile maç ekleyip düzelteceksin, kısıtlarla tabloyu bozuk veriye karşı koruyacaksın ve WHERE'siz bir DELETE'in neden felaket olduğunu güvenli bir ortamda göreceksin.