mirror of
https://github.com/freedbygrace/SQL.git
synced 2026-07-28 04:19:47 +00:00
b30733ccad
- Docker setup with PostgreSQL 16 and DB-UI web interface - Comprehensive 20+ table schema with fraud detection patterns - Idempotent shell scripts for data generation (no Python dependency) - Realistic geographic data (100 US cities, 210 world cities) - 5M+ transactions with embedded fraud patterns (velocity, geographic, structuring, etc.) - Progressive SQL exercises from beginner to advanced fraud detection - Complete documentation and quick start guide - Setup and verification scripts
361 lines
11 KiB
Markdown
361 lines
11 KiB
Markdown
# Level 6: Fraud Detection Scenarios
|
|
|
|
## Introduction
|
|
Now you'll apply your SQL skills to real-world fraud detection scenarios. These exercises simulate actual fraud investigation tasks.
|
|
|
|
## Learning Objectives
|
|
- Detect velocity fraud patterns
|
|
- Identify geographic anomalies
|
|
- Find money mule networks
|
|
- Detect account takeover attempts
|
|
- Identify structuring patterns
|
|
|
|
---
|
|
|
|
## Fraud Pattern Detection
|
|
|
|
### Scenario 1: Velocity Fraud Detection
|
|
**Objective:** Find accounts with more than 5 transactions in a 1-hour window
|
|
|
|
```sql
|
|
-- Detect rapid-fire transactions (velocity check)
|
|
WITH transaction_windows AS (
|
|
SELECT
|
|
t1.account_id,
|
|
t1.transaction_id,
|
|
t1.transaction_date,
|
|
t1.amount,
|
|
COUNT(t2.transaction_id) as transactions_in_hour
|
|
FROM transactions t1
|
|
JOIN transactions t2 ON t1.account_id = t2.account_id
|
|
AND t2.transaction_date BETWEEN t1.transaction_date - INTERVAL '1 hour'
|
|
AND t1.transaction_date
|
|
GROUP BY t1.account_id, t1.transaction_id, t1.transaction_date, t1.amount
|
|
)
|
|
SELECT
|
|
account_id,
|
|
transaction_date,
|
|
transactions_in_hour,
|
|
SUM(amount) as total_amount
|
|
FROM transaction_windows
|
|
WHERE transactions_in_hour > 5
|
|
GROUP BY account_id, transaction_date, transactions_in_hour
|
|
ORDER BY transactions_in_hour DESC;
|
|
```
|
|
|
|
---
|
|
|
|
### Scenario 2: Geographic Impossibility
|
|
**Objective:** Find transactions from the same card in different countries within 2 hours
|
|
|
|
```sql
|
|
-- Detect impossible travel (card used in different countries too quickly)
|
|
SELECT
|
|
t1.card_id,
|
|
t1.transaction_id as trans1_id,
|
|
t1.transaction_date as trans1_date,
|
|
c1.country_name as country1,
|
|
t1.city as city1,
|
|
t2.transaction_id as trans2_id,
|
|
t2.transaction_date as trans2_date,
|
|
c2.country_name as country2,
|
|
t2.city as city2,
|
|
EXTRACT(EPOCH FROM (t2.transaction_date - t1.transaction_date))/3600 as hours_between
|
|
FROM transactions t1
|
|
JOIN transactions t2 ON t1.card_id = t2.card_id
|
|
AND t2.transaction_date > t1.transaction_date
|
|
AND t2.transaction_date <= t1.transaction_date + INTERVAL '2 hours'
|
|
JOIN countries c1 ON t1.country_id = c1.country_id
|
|
JOIN countries c2 ON t2.country_id = c2.country_id
|
|
WHERE t1.country_id != t2.country_id
|
|
ORDER BY hours_between ASC;
|
|
```
|
|
|
|
---
|
|
|
|
### Scenario 3: Money Mule Network Detection
|
|
**Objective:** Find clusters of accounts that transfer money in a chain pattern
|
|
|
|
```sql
|
|
-- Detect potential money mule networks (rapid transfer chains)
|
|
WITH transfer_chains AS (
|
|
SELECT
|
|
tr1.from_account_id as account1,
|
|
tr1.to_account_id as account2,
|
|
tr2.to_account_id as account3,
|
|
tr1.transfer_id as transfer1,
|
|
tr2.transfer_id as transfer2,
|
|
t1.amount as amount1,
|
|
t2.amount as amount2,
|
|
t1.transaction_date as date1,
|
|
t2.transaction_date as date2,
|
|
EXTRACT(EPOCH FROM (t2.transaction_date - t1.transaction_date))/3600 as hours_between
|
|
FROM transfers tr1
|
|
JOIN transfers tr2 ON tr1.to_account_id = tr2.from_account_id
|
|
JOIN transactions t1 ON tr1.transaction_id = t1.transaction_id
|
|
JOIN transactions t2 ON tr2.transaction_id = t2.transaction_id
|
|
WHERE t2.transaction_date BETWEEN t1.transaction_date AND t1.transaction_date + INTERVAL '24 hours'
|
|
)
|
|
SELECT
|
|
account1,
|
|
account2,
|
|
account3,
|
|
amount1,
|
|
amount2,
|
|
hours_between,
|
|
CASE
|
|
WHEN ABS(amount1 - amount2) / amount1 < 0.1 THEN 'SUSPICIOUS - Similar amounts'
|
|
ELSE 'Review'
|
|
END as risk_flag
|
|
FROM transfer_chains
|
|
WHERE hours_between < 24
|
|
ORDER BY hours_between ASC;
|
|
```
|
|
|
|
---
|
|
|
|
### Scenario 4: Account Takeover Detection
|
|
**Objective:** Find accounts with sudden changes in transaction patterns
|
|
|
|
```sql
|
|
-- Detect account takeover by analyzing behavior changes
|
|
WITH customer_baseline AS (
|
|
SELECT
|
|
a.customer_id,
|
|
a.account_id,
|
|
AVG(t.amount) as avg_transaction,
|
|
STDDEV(t.amount) as stddev_transaction,
|
|
COUNT(*) as transaction_count
|
|
FROM accounts a
|
|
JOIN transactions t ON a.account_id = t.account_id
|
|
WHERE t.transaction_date < CURRENT_DATE - INTERVAL '30 days'
|
|
GROUP BY a.customer_id, a.account_id
|
|
),
|
|
recent_transactions AS (
|
|
SELECT
|
|
a.customer_id,
|
|
a.account_id,
|
|
t.transaction_id,
|
|
t.amount,
|
|
t.transaction_date,
|
|
t.country_id,
|
|
t.device_id
|
|
FROM accounts a
|
|
JOIN transactions t ON a.account_id = t.account_id
|
|
WHERE t.transaction_date >= CURRENT_DATE - INTERVAL '7 days'
|
|
)
|
|
SELECT
|
|
rt.customer_id,
|
|
rt.account_id,
|
|
rt.transaction_id,
|
|
rt.amount,
|
|
cb.avg_transaction,
|
|
(rt.amount - cb.avg_transaction) / NULLIF(cb.stddev_transaction, 0) as z_score,
|
|
CASE
|
|
WHEN ABS((rt.amount - cb.avg_transaction) / NULLIF(cb.stddev_transaction, 0)) > 3
|
|
THEN 'HIGH RISK - Amount anomaly'
|
|
WHEN ABS((rt.amount - cb.avg_transaction) / NULLIF(cb.stddev_transaction, 0)) > 2
|
|
THEN 'MEDIUM RISK'
|
|
ELSE 'Normal'
|
|
END as risk_level
|
|
FROM recent_transactions rt
|
|
JOIN customer_baseline cb ON rt.account_id = cb.account_id
|
|
WHERE cb.transaction_count > 10
|
|
ORDER BY ABS((rt.amount - cb.avg_transaction) / NULLIF(cb.stddev_transaction, 0)) DESC;
|
|
```
|
|
|
|
---
|
|
|
|
### Scenario 5: Structuring Detection (Smurfing)
|
|
**Objective:** Find patterns of transactions just under $10,000 (reporting threshold)
|
|
|
|
```sql
|
|
-- Detect structuring - multiple transactions just under reporting threshold
|
|
WITH daily_transactions AS (
|
|
SELECT
|
|
account_id,
|
|
DATE(transaction_date) as transaction_day,
|
|
COUNT(*) as num_transactions,
|
|
SUM(amount) as total_amount,
|
|
AVG(amount) as avg_amount,
|
|
MAX(amount) as max_amount
|
|
FROM transactions
|
|
WHERE amount BETWEEN 9000 AND 9999
|
|
AND transaction_date >= CURRENT_DATE - INTERVAL '30 days'
|
|
GROUP BY account_id, DATE(transaction_date)
|
|
)
|
|
SELECT
|
|
dt.account_id,
|
|
c.first_name,
|
|
c.last_name,
|
|
dt.transaction_day,
|
|
dt.num_transactions,
|
|
dt.total_amount,
|
|
dt.avg_amount,
|
|
CASE
|
|
WHEN dt.num_transactions >= 3 AND dt.total_amount > 25000
|
|
THEN 'CRITICAL - Likely structuring'
|
|
WHEN dt.num_transactions >= 2 AND dt.total_amount > 18000
|
|
THEN 'HIGH - Possible structuring'
|
|
ELSE 'Review'
|
|
END as risk_assessment
|
|
FROM daily_transactions dt
|
|
JOIN accounts a ON dt.account_id = a.account_id
|
|
JOIN customers c ON a.customer_id = c.customer_id
|
|
WHERE dt.num_transactions >= 2
|
|
ORDER BY dt.total_amount DESC, dt.num_transactions DESC;
|
|
```
|
|
|
|
---
|
|
|
|
### Scenario 6: High-Risk Merchant Analysis
|
|
**Objective:** Find customers with unusual activity at high-risk merchants
|
|
|
|
```sql
|
|
-- Analyze transactions at high-risk merchants
|
|
SELECT
|
|
c.customer_id,
|
|
c.first_name,
|
|
c.last_name,
|
|
c.risk_score as customer_risk,
|
|
mc.category_name,
|
|
m.merchant_name,
|
|
m.risk_rating as merchant_risk,
|
|
COUNT(t.transaction_id) as transaction_count,
|
|
SUM(t.amount) as total_spent,
|
|
AVG(t.amount) as avg_transaction,
|
|
MAX(t.amount) as max_transaction
|
|
FROM customers c
|
|
JOIN accounts a ON c.customer_id = a.customer_id
|
|
JOIN transactions t ON a.account_id = t.account_id
|
|
JOIN merchants m ON t.merchant_id = m.merchant_id
|
|
JOIN merchant_categories mc ON m.category_id = mc.category_id
|
|
WHERE m.risk_rating IN ('HIGH', 'CRITICAL')
|
|
AND t.transaction_date >= CURRENT_DATE - INTERVAL '90 days'
|
|
GROUP BY c.customer_id, c.first_name, c.last_name, c.risk_score,
|
|
mc.category_name, m.merchant_name, m.risk_rating
|
|
HAVING COUNT(t.transaction_id) > 5 OR SUM(t.amount) > 10000
|
|
ORDER BY total_spent DESC;
|
|
```
|
|
|
|
---
|
|
|
|
### Scenario 7: Card Testing Detection
|
|
**Objective:** Find cards with multiple small failed transactions (testing stolen cards)
|
|
|
|
```sql
|
|
-- Detect card testing patterns
|
|
SELECT
|
|
t.card_id,
|
|
c.card_last_four,
|
|
COUNT(*) as failed_attempts,
|
|
COUNT(DISTINCT t.merchant_id) as different_merchants,
|
|
MIN(t.amount) as min_amount,
|
|
MAX(t.amount) as max_amount,
|
|
MIN(t.transaction_date) as first_attempt,
|
|
MAX(t.transaction_date) as last_attempt,
|
|
EXTRACT(EPOCH FROM (MAX(t.transaction_date) - MIN(t.transaction_date)))/60 as minutes_span
|
|
FROM transactions t
|
|
JOIN cards c ON t.card_id = c.card_id
|
|
WHERE t.status = 'FAILED'
|
|
AND t.amount < 10
|
|
AND t.transaction_date >= CURRENT_DATE - INTERVAL '24 hours'
|
|
GROUP BY t.card_id, c.card_last_four
|
|
HAVING COUNT(*) >= 3
|
|
ORDER BY failed_attempts DESC, minutes_span ASC;
|
|
```
|
|
|
|
---
|
|
|
|
### Scenario 8: Dormant Account Reactivation
|
|
**Objective:** Find dormant accounts that suddenly become active (potential takeover)
|
|
|
|
```sql
|
|
-- Detect dormant account reactivation
|
|
WITH account_activity AS (
|
|
SELECT
|
|
account_id,
|
|
MIN(transaction_date) as first_transaction,
|
|
MAX(transaction_date) as last_transaction,
|
|
COUNT(*) as total_transactions
|
|
FROM transactions
|
|
GROUP BY account_id
|
|
),
|
|
dormant_accounts AS (
|
|
SELECT
|
|
account_id,
|
|
last_transaction,
|
|
total_transactions
|
|
FROM account_activity
|
|
WHERE last_transaction < CURRENT_DATE - INTERVAL '180 days'
|
|
),
|
|
recent_activity AS (
|
|
SELECT
|
|
t.account_id,
|
|
COUNT(*) as recent_transactions,
|
|
SUM(t.amount) as recent_amount,
|
|
MIN(t.transaction_date) as reactivation_date
|
|
FROM transactions t
|
|
WHERE t.transaction_date >= CURRENT_DATE - INTERVAL '7 days'
|
|
GROUP BY t.account_id
|
|
)
|
|
SELECT
|
|
da.account_id,
|
|
c.first_name,
|
|
c.last_name,
|
|
c.email,
|
|
da.last_transaction as last_active,
|
|
EXTRACT(DAY FROM (CURRENT_DATE - da.last_transaction)) as days_dormant,
|
|
ra.reactivation_date,
|
|
ra.recent_transactions,
|
|
ra.recent_amount,
|
|
'CRITICAL - Dormant account reactivated' as alert_type
|
|
FROM dormant_accounts da
|
|
JOIN recent_activity ra ON da.account_id = ra.account_id
|
|
JOIN accounts a ON da.account_id = a.account_id
|
|
JOIN customers c ON a.customer_id = c.customer_id
|
|
ORDER BY days_dormant DESC;
|
|
```
|
|
|
|
---
|
|
|
|
## Investigation Exercises
|
|
|
|
### Exercise 6.1: Full Customer Investigation
|
|
Create a comprehensive report for a suspicious customer including:
|
|
- All accounts
|
|
- All transactions
|
|
- All alerts
|
|
- All fraud cases
|
|
- Related customers (via relationships)
|
|
|
|
### Exercise 6.2: Fraud Case Summary
|
|
Generate a summary report of all open fraud cases with:
|
|
- Case details
|
|
- Associated transactions
|
|
- Total amount at risk
|
|
- Investigation status
|
|
|
|
### Exercise 6.3: Daily Fraud Dashboard
|
|
Create a daily dashboard showing:
|
|
- New alerts by severity
|
|
- High-risk transactions
|
|
- Geographic anomalies
|
|
- Velocity violations
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
Congratulations! You've completed the fraud detection scenarios. You now have the skills to:
|
|
- Detect complex fraud patterns
|
|
- Investigate suspicious activity
|
|
- Generate fraud reports
|
|
- Analyze customer behavior
|
|
|
|
Continue practicing with real data and explore advanced topics like:
|
|
- Machine learning integration
|
|
- Real-time fraud scoring
|
|
- Network analysis
|
|
- Predictive modeling
|
|
|