Ana içeriğe geç
Kesit SQL
İleri · Büyük Sorgular

Büyük sorgu 5: bankada şüpheli işlem kural motoru

Her kuralı ayrı bir CTE olarak yazmak, UNION ALL ile alarm tablosu kurmak, hesap başına risk puanı ve öncelik sırası.

Uygulamada aç, 4 alıştırmayı çözÜcretsiz, hesap gerekmez, kurulum yok.

Bu sayfa dersin okuma sürümüdür. Metin bir kutuyu çalıştırmanı ya da adıma geçmeni söylüyorsa dersi uygulamada aç.

Soru

Ilgaz Bank uyum ekibi her sabah incelenecek hesapların listesini istiyor. Kurallar iş biriminden gelir ve sık değişir; bu yüzden sorgu, her kuralın ayrı bir blok olduğu ve kolayca eklenip çıkarılabildiği bir yapıda yazılır.

Sorgu

Büyük sorgu nasıl okunur?Yukarıdan aşağı, her CTE'yi ayrı bir tablo gibi düşün. Bir adımı anlamadıysan o CTE'nin adını en sondaki SELECT'e yazıp yalnızca onu çalıştır: SELECT * FROM adim_adi LIMIT 20;
WITH harcama AS (        -- 0) Kart harcamaları, tutar pozitif
  SELECT t.txn_id, t.account_id, t.txn_time, -t.amount AS tutar, t.merchant_category AS kat,
         CAST(strftime('%H', t.txn_time) AS INTEGER) AS saat
  FROM bank_txns t
  WHERE t.txn_type = 'kart_harcama'
),
ort AS (                 -- 1) Hesabın kendi ortalaması (kişiye göre normal)
  SELECT account_id, AVG(tutar) AS ort_tutar, COUNT(*) AS adet
  FROM harcama
  GROUP BY account_id
),
k1 AS (                  -- Kural 1: gece 01–05 arası 20.000 TL üzeri harcama
  SELECT account_id, txn_id, 'gece_yuksek' AS kural, 3 AS puan
  FROM harcama
  WHERE saat BETWEEN 1 AND 4 AND tutar > 20000
),
k2 AS (                  -- Kural 2: hesabın kendi ortalamasının 8 katından büyük harcama
  SELECT h.account_id, h.txn_id, 'ortalama_8x' AS kural, 2 AS puan
  FROM harcama h
  JOIN ort o ON o.account_id = h.account_id
  WHERE o.adet >= 5 AND h.tutar > 8 * o.ort_tutar
),
k3 AS (                  -- Kural 3: aynı hesapta 10 dakika içinde art arda e-ticaret harcaması
  SELECT DISTINCT h.account_id, h.txn_id, 'hizli_tekrar' AS kural, 1 AS puan
  FROM harcama h
  JOIN harcama p ON p.account_id = h.account_id AND p.txn_id <> h.txn_id
                AND p.kat = 'e_ticaret'
                AND p.txn_time <= h.txn_time
                AND p.txn_time >= datetime(h.txn_time, '-10 minutes')
  WHERE h.kat = 'e_ticaret'
),
alarm AS (               -- 2) Tüm kuralları alt alta topla
  SELECT * FROM k1 UNION ALL SELECT * FROM k2 UNION ALL SELECT * FROM k3
),
hesap AS (               -- 3) Hesap başına toplam risk puanı ve tetiklenen kurallar
  SELECT account_id,
         SUM(puan) AS risk,
         COUNT(DISTINCT txn_id) AS supheli_islem,
         COUNT(DISTINCT kural) AS kural_sayisi
  FROM alarm
  GROUP BY account_id
)
SELECT h.account_id, c.full_name, c.segment, h.risk, h.supheli_islem, h.kural_sayisi,
       DENSE_RANK() OVER (ORDER BY h.risk DESC) AS oncelik
FROM hesap h
JOIN bank_accounts a ON a.account_id = h.account_id
JOIN bank_customers c ON c.customer_id = a.customer_id
ORDER BY oncelik, h.account_id
LIMIT 15;

Adım adım

AdımNe yapar?Çıktının bir satırı
harcamaKart harcamaları; tutar artı, saat ayrı sütunda18802, 100231, 02:14, 27500
ortHesabın kendi harcama ortalaması ve adedi100231, 1840, 41
k1 / k2 / k3Her kural kendi koşuluyla aynı biçimde (hesap, işlem, kural, puan) satır üretir100231, 18802, gece_yuksek, 3
alarmKural çıktıları UNION ALL ile alt alta…
hesapHesap başına toplam risk ve tetiklenen kural sayısı100231, 5, 2, 2
son SELECTMüşteri bilgisi ve DENSE_RANK ile öncelik100231, …, öncelik 1

Neden bu yapı?

Kural 3'ün maliyetiKendi kendine birleştirme ve zaman aralığı koşulu büyük tablolarda pahalıdır. Gerçek sistemlerde (account_id, txn_time) üzerinde indeks ya da LAG ile yeniden yazım tercih edilir. Alıştırmalardan biri bunu istiyor.
Alıştırmalara geç (4)