---
title: 'Filtreleme, Sıralama ve Arama'
source: 'https://academia.sh/tr/kurslar/api-tasarimi/filtreleme-siralama-ve-arama'
course: 'Web API Tasarımı'
language: tr
updated: '2026-08-17T18:06:45+00:00'
license: 'CC BY-SA 4.0'
---

# Filtreleme, Sıralama ve Arama

Sorgu parametrelerinin tasarımı: süzgeç ve sıralama adlarının izin listesiyle sütuna çevrilmesi, istemci değerlerinin bağlı değişkenle taşınması, izin listesi dışındaki isteğin reddedilmesi ve sıralama bağlarının sayfalamada kayıt kaybına yol açması.

Sayfalama dersi koleksiyonun ne kadarının verileceğini çözdü. Hangi kayıtların verileceği
ve hangi sırayla verileceği açık kaldı. Kütüphane görevlisi katalogda "Aho'nun kitapları",
"1990 sonrası basımlar", "adında Ağ geçenler" diye arar; sonucu yıla ya da yazara göre
sıralı ister.

Bunların hepsi sorgu bölümünde taşınır ve hepsi SQL'e dönüşür. Dönüşüm iki yerde tasarım
kararı ister: istemciden gelen **ad** hangi sütuna karşılık gelecek, istemciden gelen
**değer** sorguya nasıl girecek. Bu iki soruyu karıştırmamak, bu dersin ana ayrımıdır.

## Ad ile Değerin Ayrılması

Bir sorgu parametresi iki farklı türde bilgi taşıyabilir. `?yazar=Aho` isteğinde `yazar`
bir addır — hangi sütuna bakılacağını söyler — `Aho` ise bir değerdir. İkisi SQL'e aynı
yoldan giremez.

Değer, sorgu metnine hiç girmez; **bağlı değişken** olarak ayrı taşınır. Motor sorgu
metnini bir kez çözümler, değeri sonra yerleştirir; değerin içeriği sorgunun yapısını
değiştiremez. İleri SQL kursundaki Dinamik SQL Riskleri dersi bunun neden zorunlu olduğunu
ayrıntılandırır: değerler dizgi birleştirmeyle sorgu metnine yazıldığında, değerin içindeki
karakterler sorgu yapısının parçası hâline gelir.

Ad ise bağlı değişken olamaz — sütun adı sorgunun yapısıdır, değeri değil. Bu yüzden adlar
için başka bir düzenek gerekir: **izin listesi**. İstemciden gelen ad, sunucudaki sabit bir
eşlemede aranır; eşlemede yoksa istek reddedilir. Böylece sorgu metnine yalnızca sunucunun
kendi yazdığı sütun adları girer.

```js
// filtre-sunucusu.mjs — sorgu parametrelerini izin listesi ve bagli degiskenle SQL'e tasir
import { createServer } from "node:http";
import { DatabaseSync } from "node:sqlite";

const db = new DatabaseSync("kutuphane.db");
const yanitla = (yanit, kod, nesne) => {
  yanit.writeHead(kod, { "content-type": "application/json; charset=utf-8" });
  yanit.end(JSON.stringify(nesne));
};

// Izin listeleri: istemciden gelen ad, sutun adina burada cevrilir.
const SUZGECLER = {
  yazar:    "yazar = ?",
  yilEnAz:  "yil >= ?",
  sube:     "sube = ?",
  ara:      "ad LIKE ?",
};
const SIRALAMALAR = { yil: "yil", ad: "ad", yazar: "yazar" };

const sunucu = createServer((istek, yanit) => {
  const adres = new URL(istek.url, "http://127.0.0.1");
  if (adres.pathname !== "/kitaplar") return yanitla(yanit, 404, { hata: "yol_yok" });
  const s = adres.searchParams;
  const boyut = Math.min(Number(s.get("boyut") ?? 3), 50);

  // Suzgecler: yalnizca izin listesindeki adlar kosula donusur.
  const kosullar = [], degerler = [];
  for (const [ad, kalip] of Object.entries(SUZGECLER)) {
    const deger = s.get(ad);
    if (deger === null) continue;
    kosullar.push(kalip);
    degerler.push(ad === "ara" ? `%${deger}%` : deger);   // deger, sorgu metnine girmez
  }

  // Siralama: izin listesinde yoksa istek reddedilir, varsayilana dusulmez.
  const siralaAdi = s.get("sirala") ?? "yil";
  const sutun = SIRALAMALAR[siralaAdi];
  if (!sutun)
    return yanitla(yanit, 422, {
      hata: "dogrulama", alan: "sirala", izinli: Object.keys(SIRALAMALAR),
    });

  // Imlec: gevsek kip yalnizca siralama sutununu, kesin kip benzersizlestiriciyi de tasir.
  const imlec = s.get("imlec");
  const kesin = s.get("kip") !== "gevsek";
  if (imlec !== null) {
    if (kesin) {
      const [d, k] = imlec.split("|");
      kosullar.push(`(${sutun}, isbn) > (?, ?)`);
      degerler.push(d, k);
    } else {
      kosullar.push(`${sutun} > ?`);
      degerler.push(imlec);
    }
  }

  const nerede = kosullar.length ? `WHERE ${kosullar.join(" AND ")}` : "";
  const sira = kesin ? `${sutun}, isbn` : sutun;
  const satirlar = db.prepare(
    `SELECT isbn, ad, yazar, yil FROM kitap ${nerede} ORDER BY ${sira} LIMIT ?`
  ).all(...degerler, boyut);

  const son = satirlar.at(-1);
  yanitla(yanit, 200, {
    veri: satirlar.map((r) => `${r.yil} ${r.isbn}`),
    sayfalama: {
      sonrakiImlec: son ? (kesin ? `${son[sutun]}|${son.isbn}` : String(son[sutun])) : null,
    },
  });
});

sunucu.listen(8481, "127.0.0.1", () => console.log("filtre sunucusu 127.0.0.1:8481"));
```

Sorgu metninin nasıl kurulduğuna dikkat edin. `kosullar` dizisine giren her parça
sunucunun kendi yazdığı sabit bir dizgidir; istemciden gelen hiçbir karakter oraya
ulaşmaz. İstemcinin gönderdiği her şey `degerler` dizisindedir ve sorguya bağlı değişken
olarak verilir.

Katalog, sıralama bağlarını görebilmek için genişletilir:

```sql
-- katalog.sql — siralama baglarini iceren genisletilmis katalog
DELETE FROM kitap;
INSERT INTO kitap (isbn, ad, yazar, yil, sube) VALUES
  ('K-01','Bilgisayar Aglari','Tanenbaum',1978,'S-01'),
  ('K-02','Veri Yapilari','Aho',1983,'S-01'),
  ('K-03','Derleyiciler','Aho',1983,'S-02'),
  ('K-04','Isletim Sistemleri','Tanenbaum',1983,'S-02'),
  ('K-05','Kabuk Betikleri',"O'Reilly",1986,'S-01'),
  ('K-06','Cizge Algoritmalari','Sedgewick',1990,'S-01'),
  ('K-07','Sayisal Yontemler','Press',1990,'S-02'),
  ('K-08','Ag Programlama','Stevens',1990,'S-01'),
  ('K-09','Bicimsel Diller','Hopcroft',1995,'S-02');
```

Sayfaları dolaşan istemci de ayrı bir dosyadır; imleci kendisi kurmaz, yanıttan alır.

```js
// yuru.mjs — imleci izleyerek butun sayfalari dolasan istemci
// Kullanim: node yuru.mjs <gevsek|kesin>
const KIP = process.argv[2];
let imlec = null, sayfa = 0;
const toplananlar = [];

while (sayfa < 10) {
  const adres = new URL("http://127.0.0.1:8481/kitaplar");
  adres.searchParams.set("kip", KIP);
  adres.searchParams.set("sirala", "yil");
  if (imlec !== null) adres.searchParams.set("imlec", imlec);

  const yanit = await (await fetch(adres)).json();
  if (yanit.veri.length === 0) break;
  sayfa++;
  toplananlar.push(...yanit.veri);
  console.log(`${KIP.padEnd(7)} sayfa ${sayfa}: ${yanit.veri.join("  ")}`);
  imlec = yanit.sayfalama.sonrakiImlec;
}
console.log(`${KIP.padEnd(7)} toplam : ${toplananlar.length} satir\n`);
```

```bash
# Izin listesi, bagli degisken ve siralama baginin sayfalamaya etkisi.
rm -f kutuphane.db && sqlite3 kutuphane.db < sema.sql && sqlite3 kutuphane.db < katalog.sql
node filtre-sunucusu.mjs & sunucu=$!
sleep 0.4

al() { curl -sS -G "http://127.0.0.1:8481/kitaplar" "$@"; echo; }
echo "--- suzgec ve izin listesi ---"
printf 'yazar=Aho      : '; al --data-urlencode "yazar=Aho"
printf "yazar=O'Reilly : "; al --data-urlencode "yazar=O'Reilly"
printf 'ara=Ag         : '; al --data-urlencode "ara=Ag"
printf 'sirala=sube    : '; al --data-urlencode "sirala=sube"

echo "--- siralama bagi olan sutunda sayfalama (katalogda 9 kitap var) ---"
node yuru.mjs gevsek
node yuru.mjs kesin

kill $sunucu
```

```
filtre sunucusu 127.0.0.1:8481
--- suzgec ve izin listesi ---
yazar=Aho      : {"veri":["1983 K-02","1983 K-03"],"sayfalama":{"sonrakiImlec":"1983|K-03"}}
yazar=O'Reilly : {"veri":["1986 K-05"],"sayfalama":{"sonrakiImlec":"1986|K-05"}}
ara=Ag         : {"veri":["1978 K-01","1990 K-08"],"sayfalama":{"sonrakiImlec":"1990|K-08"}}
sirala=sube    : {"hata":"dogrulama","alan":"sirala","izinli":["yil","ad","yazar"]}
--- siralama bagi olan sutunda sayfalama (katalogda 9 kitap var) ---
gevsek  sayfa 1: 1978 K-01  1983 K-02  1983 K-03
gevsek  sayfa 2: 1986 K-05  1990 K-06  1990 K-07
gevsek  sayfa 3: 1995 K-09
gevsek  toplam : 7 satir

kesin   sayfa 1: 1978 K-01  1983 K-02  1983 K-03
kesin   sayfa 2: 1983 K-04  1986 K-05  1990 K-06
kesin   sayfa 3: 1990 K-07  1990 K-08  1995 K-09
kesin   toplam : 9 satir
```

## Değerin Sorgu Metnine Girmemesi

İkinci satır bu ayrımın en somut kanıtıdır. `O'Reilly` değeri kesme işareti içerir ve SQL
dizgi sabitlerini bitiren karakter tam olarak odur. Değer sorgu metnine yazılsaydı, sorgu
o noktada bölünür ve kalan bölüm SQL olarak okunurdu. Bağlı değişkenle taşındığında böyle
bir şey olmaz: değer yalnızca değerdir, kaydı bulur ve döndürür.

Bu, kesme işaretli adların "kaçırılması" sorunu değildir. Kaçış karakteri eklemek de bir
çözüm gibi görünür, ama her veri tipi ve her lehçe için ayrı kural gerektirir ve bir yerde
unutulur. Bağlı değişkende unutulacak bir şey yoktur; sorgu metni istemciden bağımsız
olarak sabittir.

Üçüncü satırdaki `ara=Ag` süzgeci aynı ilkeyi bir adım öteye taşır. Joker karakterler
sunucuda eklenir — istemcinin gönderdiği `Ag` değeri `%Ag%` hâline getirilir — böylece
istemci örüntü yazamaz, yalnızca aranacak metni verir.

## İzin Listesi Dışını Reddetmek

Dördüncü satır `sirala=sube` isteğine 422 döndü ve izinli adları yanıtta bildirdi.
Buradaki iki karar da bilinçlidir.

Birincisi, istek **reddedildi**; sessizce varsayılan sıralamaya düşülmedi. Varsayılana
düşmek, istemcinin yanlış bir ad gönderdiğini hiç öğrenmemesi demektir; ekran yanlış sırada
görünür ve kimse nedenini aramaz. Durum Kodu Seçimi dersindeki ölçüt burada da geçerlidir:
kabul edilmeyen bir alan değeri 422 ile bildirilir.

İkincisi, yanıt izinli adları sayıyor. Bu, sözleşmenin kendini anlatan bir parçasıdır;
istemciyi belgeye gitmeden düzeltmeye yönlendirir.

İzin listesi aynı zamanda sıralamanın maliyetini denetim altında tutar. Her sütuna göre
sıralamaya izin verilirse, dizini olmayan bir sütuna göre sıralama isteği tabloyu baştan
sona okutur. Listeye yalnızca desteklenmesi kararlaştırılmış — ve gerekiyorsa dizinlenmiş —
sütunlar konur.

## Sıralama Bağı Kayıt Kaybettirir

Çıktının son bölümü sayfalama dersine geri bağlanır. Katalogda dokuz kitap var; gevşek kip
yedisini gösterdi. K-04 ve K-08 hiçbir sayfada görünmedi.

Neden ölçülebilir biçimde ortadadır. Gevşek kipte imleç yalnızca yılı taşır. Birinci sayfa
1983 yılında biter ve imleç `1983` olur; ikinci sayfa `yil > 1983` koşuluyla gelir ve
aynı yıla sahip K-04 kaydı bu koşulun dışında kalır. Aynı şey 1990 yılında yinelenir ve
K-08 kaybolur. Sıralama anahtarı benzersiz olmadığı sürece, "bu değerden sonrası" ölçütü
bağların bir bölümünü hep dışarıda bırakır.

Kesin kipte imleç iki alan taşır: sıralama sütunu ve benzersiz kimlik. Koşul da iki alan
üzerinde çalışır, sıralama da. Aynı yıla sahip kayıtlar arasında kesin bir sıra kurulduğu
için "bu noktadan sonrası" tanımlıdır ve dokuz kaydın dokuzu da görünür.

Buradan iki kural çıkar. Sıralama ölçütü ne olursa olsun, sonuna benzersiz bir alan
eklenir — **belirlenimci sıralama** koşulu budur. İmleç de sıralama ölçütünün tamamını
taşır: tek alanlık bir imleç, tek alanlık bir sıralamayı anlatabilir.

## Özet

- Sorgu parametresi iki tür bilgi taşır: ad ve değer. Ad izin listesiyle sütuna çevrilir,
  değer bağlı değişkenle taşınır; ikisi SQL'e aynı yoldan girmez.
- Sorgu metnine yalnızca sunucunun yazdığı sabit parçalar girer; istemciden gelen hiçbir
  karakter sorgu yapısına ulaşmaz. Kesme işareti içeren bir yazar adı bu yüzden sorunsuz
  bulunur.
- Arama örüntüleri sunucuda kurulur; istemci aranacak metni verir, joker karakteri değil.
- İzin listesi dışındaki sıralama isteği 422 ile reddedilir ve izinli adlar yanıtta
  bildirilir; sessizce varsayılana düşmek hatayı görünmez kılar.
- İzin listesi sıralamanın maliyetini de sınırlar: listeye yalnızca desteklenmesi
  kararlaştırılmış sütunlar konur.
- Sıralama anahtarı benzersiz değilse imleç tabanlı sayfalama bağların bir bölümünü atlar;
  ölçümde dokuz kayıttan yedisi göründü. Sıralamaya ve imlece benzersiz bir alan eklenmesi
  bu kaybı ortadan kaldırır.

## Sonraki Adım

Hangi kayıtların döneceği ve hangi sırayla döneceği çözüldü. Geriye her kaydın **ne
kadarının** döneceği kaldı. Katalog listesi ekranı yalnızca kitap adı ve yazarı gösterirken
sunucu her satırda tüm alanları gönderiyor; ödünç geçmişi ekranı ise tek bir yanıtta
bulamadığı bilgiler için ayrı ayrı istek atıyor. İki sorun da aynı yerden doğar: gösterimin
ayrıntı düzeyine sunucu tek başına karar veriyor. Sonraki ders istemcinin alan seçmesine
izin veren düzeneği kurar ve aktarılan bayt miktarındaki farkı ölçer.
