İçeriğe geç

Pencere Fonksiyonları — Ekstrede Yürüyen Bakiye Nasıl Hesaplanır?

Orta 8 dk Sık karşılaşılır

Önce şunu oku: SQL İndeksleme ve EXPLAIN

30 saniyede özet

Ekstrede her hareketin yanına o ana kadarki bakiye yazılır. Her satırda öncekileri yeniden toplamak karesel büyür; hepsini çekip Java'da toplamak da son sayfa için bütün geçmişi taşır. SUM() OVER tek geçişte, veritabanında hesaplar.

Eski banka cüzdanlarında her satırın yanında bakiye yazardı. Bir memur her yeni satırda sayfanın başından itibaren yeniden toplar. Öbürü bir cetveli aşağı kaydırır ve toplamı satırdan satıra taşır.

  1. Bayt: Ekstre ekranında her hareketin yanına bakiye yazmamız gerekiyor.

  2. Sen: Her satır için öncekileri toplayan bir alt sorgu yazdım. Çalışıyor!

  3. Bayt: Ama on yıllık bir hesapta ekran dakikalarca bekliyor. Her satır bütün geçmişi yeniden okuyor.

  4. Bayt: Cetveli aşağı kaydıran memur gibi tek geçişte toplamamız gerekiyor.

Gruplamadan hesaplamak

GROUP BY bir hesabın bütün hareketlerini tek bir toplama indirir. Ekstrede ise her hareket yerinde kalmalı, yanına o ana kadarki bakiye yazılmalıdır.

Bir pencere fonksiyonuSatırları gruplamadan, her satırın yanına bir pencere üzerinden hesap ekleyen SQL fonksiyonu: SUM() OVER, ROW_NUMBER() OVER gibi.Sözlükte gör → tam olarak bunu yapar: satırları gruplamadan, her satırın yanına bir pencere üzerinden hesap ekler. SUM(amount) OVER (ORDER BY booked_at) yürüyen toplamı verir.

Kafam karıştı, daha basit anlat

Pencere fonksiyonu satırları korur ve her birinin yanına bir toplam ekler.

Hızlı kontrolBaşlangıç

Pencere fonksiyonu ile GROUP BY arasındaki temel fark nedir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Ekstrede her satırın yanına o ana kadarki bakiyeyi yazan ifade hangisi?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Üç yol, aynı bakiye

Ekstrede sekiz hareket var. Her satır için kendisinden önceki bütün satırları toplayan bir alt sorgu yazdık. Toplam kaç satır okunur? Cevabı göster

36 satır. Birinci satır 1, ikinci 2, sekizinci 8 satır okur: 1’den 8’e kadar toplam 36. Bin hareketlik bir hesapta bu sayı yarım milyonu geçer.

Tek geçiş, her satıra yürüyen toplam.
Adım adım oku
  1. Cüzdan defteri: her satırın yanına bakiye.
  2. Bir memur her satırda baştan topluyor.
  3. Öbürü cetveli aşağı kaydırıyor.
  4. Tek geçiş, her satıra yürüyen toplam.

Satır başına çalışan bağlı alt sorguDıştaki sorgunun her satırı için yeniden çalışan alt sorgu. Satır başına yeniden okuma yaptığı için büyüyen tablolarda pahalanır.Sözlükte gör → doğru sonucu verir ama karesel büyür. Bütün satırları çekip Java’da toplamak tek geçiştir; ama son sayfayı göstermek için bile bütün geçmiş uygulamaya taşınır.

Pencere fonksiyonu satırları bir kez dolaşır ve süzmeyi veritabanında bırakır. Ekrana yalnızca gösterilecek satırlar gider.

Kafam karıştı, daha basit anlat

Alt sorgu tekrar tekrar okur. Java bütün satırları taşır. Pencere fonksiyonu bir kez okur, gerekeni gönderir.

Hızlı kontrolOrta

Her satır için öncekilerin toplamını alt sorguyla almak neden pahalanır?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Ekranda yalnızca son 3 hareket var. Bakiyeyi Java'da hesaplamak için kaç satır çekmek gerekir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kendin gör

Ekstrede yürüyen bakiye

Tohum 413208
Hesap ekstresi
TarihAçıklamaTutarBakiye
01.10Maaş42.000 TL…
02.10Kira-15.000 TL…
03.10Market-1.850 TL…
05.10Elektrik-920 TL…
08.10Arkadaştan havale600 TL…
12.10Kart ödemesi-7.400 TL…
15.10Eczane-310 TL…
20.10Kira iadesi1.500 TL…

Okunan satır: 36 · uygulamaya gönderilen: 8 · ekranda: 8

Hız
Adım 0

Şu an ne oldu?

Her satır için öncekileri yeniden topla (alt sorgu)

Her hareketin yanına o ana kadarki bakiye yazılıyor.

Görevler0/3

  • Aynı bakiye için 36 satır okunsunaçık

    İpucu

    Alt sorgu, bütün sayfa.

  • Üç satır göstermek için sekiz satır taşınsınaçık

    İpucu

    Java’da topla, son sayfa.

  • Son sayfa: sekiz satır oku, yalnızca üçünü gönderaçık

    İpucu

    Pencere fonksiyonu.

Olay günlüğü (0)

Henüz olay yok. Oynat veya adımla.

  1. Varsayılanla oynat. Alt sorgu: sekiz satırlık bakiye için 36 satır okundu.
  2. “Java’da topla” ve “son sayfa” seç. Ekranda üç satır var; uygulamaya sekiz satır taşındı.
  3. “Pencere fonksiyonu” seç. Sekiz satır okundu, yalnızca üçü gönderildi; bakiyeler aynı.
Hızlı kontrolOrta

PARTITION BY account_id ne yapar?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Her hesabın en son hareketini bulmak için hangisi kullanılır?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Tuzaklar

Belirsiz sıralama. Aynı tarihli iki hareketin sırası garanti değildir; ara bakiyeler çalıştırmadan çalıştırmaya değişebilir. Sıralamaya id gibi benzersiz bir alan ekle.

Varsayılan çerçeve. ORDER BY içeren bir pencerede varsayılan çerçeve aynı değere sahip satırları birlikte toplar. Satır satır yürümek için ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW yaz.

WHERE içinde kullanmak. Pencere fonksiyonu WHERE’den sonra hesaplanır. Önce bir CTE içinde hesapla, sonra dışarıda süz.

Kafam karıştı, daha basit anlat

Sırayı benzersiz yap, çerçeveyi açıkça yaz, süzmeyi dışarıda yap.

Hızlı kontrolOrta

Aynı tarihte iki hareket varsa ORDER BY booked_at ile yürüyen toplam neden belirsiz olabilir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.
Derinleş · Hesap ekstresi: yürüyen bakiye veritabanında 4 dosya · ~52 satır · ilk okumada atlayabilirsin
Proje dosyaları

statements/src/main/resources/sql/ statement-page.sql Sorgu: hesap başına pencere, benzersiz sıralama, satır satır çerçeve; son sayfa dışarıda süzülüyor.

statements/src/main/resources/sql/statement-page.sql
WITH running AS (
SELECT id,
booked_at,
description,
amount,
SUM(amount) OVER (
PARTITION BY account_id
ORDER BY booked_at, id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS balance,
ROW_NUMBER() OVER (PARTITION BY account_id ORDER BY booked_at DESC, id DESC) AS from_end
FROM transactions
WHERE account_id = :accountId
)
SELECT id, booked_at, description, amount, balance
FROM running
WHERE from_end BETWEEN :offset + 1 AND :offset + :pageSize
ORDER BY booked_at, id;

statements/src/main/java/com/bank/statements/ StatementRepository.java Repository: sorgu Spring Data JDBC ile çağrılıyor; uygulamaya yalnızca sayfa satırları geliyor.

statements/src/main/java/com/bank/statements/StatementRepository.java
@Repository
class StatementRepository {
private final NamedParameterJdbcTemplate jdbc;
private final String pageSql;
StatementRepository(NamedParameterJdbcTemplate jdbc, @Value("classpath:sql/statement-page.sql") Resource sql) throws IOException {
this.jdbc = jdbc;
this.pageSql = sql.getContentAsString(StandardCharsets.UTF_8);
}
/** Only the page's rows travel to the app; the balance was computed in the database. */
List<StatementLine> page(UUID accountId, int offset, int pageSize) {
return jdbc.query(pageSql,
Map.of("accountId", accountId, "offset", offset, "pageSize", pageSize),
(rs, i) -> new StatementLine(
rs.getObject("booked_at", OffsetDateTime.class),
rs.getString("description"),
rs.getBigDecimal("amount"),
rs.getBigDecimal("balance")));
}
}

statements/src/main/resources/db/migration/ V4__transactions_window_index.sql İndeks: pencerenin sırasıyla aynı; veritabanı satırları sıralı okuyabilsin.

statements/src/main/resources/db/migration/V4__transactions_window_index.sql
-- Same order as the window, so rows are read already sorted.
CREATE INDEX transactions_account_time ON transactions (account_id, booked_at, id);

statements/src/main/resources/sql/ statement-slow.sql Şöyle de yazılabilirdi, ama bak ne oluyor: her satır için bütün geçmişi yeniden toplayan alt sorgu.

statements/src/main/resources/sql/statement-slow.sql
-- Tempting, but look what happens:
-- each row re-reads every earlier row; work grows with the square of the history.
SELECT t.id, t.booked_at, t.description, t.amount,
(SELECT SUM(p.amount)
FROM transactions p
WHERE p.account_id = t.account_id
AND (p.booked_at, p.id) <= (t.booked_at, t.id)) AS balance
FROM transactions t
WHERE t.account_id = :accountId
ORDER BY t.booked_at, t.id;

Kendini sına

Şimşek turu1/4

Pencere fonksiyonu satırları tek satıra indirir.

Soru 1/3İleri

SUM() OVER (ORDER BY x) varsayılan çerçevesi eşit x değerlerinde ne yapar?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Aklında kalacak üç şey

  1. 1 Pencere fonksiyonu satırları gruplamaz; her satırı yerinde bırakır ve yanına bir hesap ekler.
  2. 2 Her satır için öncekileri yeniden toplayan alt sorgu, satır sayısı arttıkça karesiyle pahalanır.
  3. 3 SUM(amount) OVER (PARTITION BY hesap ORDER BY tarih, id) yürüyen bakiyeyi tek geçişte hesaplar; yalnızca gösterilecek satırlar gönderilir.
Sonraki kapı Saklama süresi dolan bir yıllık hareketi silmek için gece bir DELETE çalıştırıldı. Sabah iş hâlâ bitmemişti ve tablo küçülmemişti. Ne oldu? Tablo Bölümleme — Eski Yılı Silmek Neden Bütün Geceyi Aldı? · 8 dk

4 kart sonraki derste seni bekliyor

0/4 kart bu dersten toplandı

Bu dersin üstüne kurulanlar

Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.