← Back to portfolio
Data workbench · SQLite
The project numbers, queried in SQL
All three datasets loaded into one SQLite database, then queried with joins, CTEs and window functions. Every result below was produced by the query above it. To run it yourself: python sql/run.py, then open portfolio.db in any SQLite tool.
Files: schema.sql (tables) · queries.sql (these six queries) · run.py (builds the database from the CSVs and runs them).
Q1. Spend per card each month, and its month-on-month change
SELECT month,
ROUND(spend_inr_lakh_cr * 1e12 / (cards_outstanding_mn * 1e6)) AS spend_per_card_inr,
ROUND(100.0 * (spend_inr_lakh_cr / cards_outstanding_mn)
/ LAG(spend_inr_lakh_cr / cards_outstanding_mn) OVER (ORDER BY month) - 100, 1) AS mom_change_pct
FROM card_monthly
ORDER BY month;
| month | spend_per_card_inr | mom_change_pct |
|---|
| 2026-01 | 17,145 | — |
| 2026-02 | 15,038 | -12.3 |
| 2026-03 | 18,461 | 22.8 |
| 2026-04 | 16,494 | -10.7 |
| 2026-05 | 16,763 | 1.6 |
| 2026-06 | 16,530 | -1.4 |
| 2026-07 | 16,930 | 2.4 |
| 2026-08 | 16,284 | -3.8 |
| 2026-09 | 16,281 | -0 |
Q2. Net cards added each month (millions)
SELECT month,
cards_outstanding_mn,
ROUND(cards_outstanding_mn - LAG(cards_outstanding_mn) OVER (ORDER BY month), 2) AS net_added_mn
FROM card_monthly
ORDER BY month;
| month | cards_outstanding_mn | net_added_mn |
|---|
| 2026-01 | 116.65 | — |
| 2026-02 | 117.7 | 1.05 |
| 2026-03 | 118.63 | 0.93 |
| 2026-04 | 119.44 | 0.81 |
| 2026-05 | 120.5 | 1.06 |
| 2026-06 | 121.6 | 1.1 |
| 2026-07 | 122.86 | 1.26 |
| 2026-08 | 124.05 | 1.19 |
| 2026-09 | 125.3 | 1.25 |
Q3. Months where the card base grew but spend per card fell
WITH m AS (
SELECT month, cards_outstanding_mn,
spend_inr_lakh_cr / cards_outstanding_mn AS ppc,
LAG(cards_outstanding_mn) OVER (ORDER BY month) AS prev_cards,
LAG(spend_inr_lakh_cr / cards_outstanding_mn) OVER (ORDER BY month) AS prev_ppc
FROM card_monthly
)
SELECT month,
ROUND(cards_outstanding_mn - prev_cards, 2) AS cards_added_mn,
ROUND(100.0 * ppc / prev_ppc - 100, 1) AS spend_per_card_change_pct
FROM m
WHERE cards_outstanding_mn > prev_cards AND ppc < prev_ppc
ORDER BY month;
| month | cards_added_mn | spend_per_card_change_pct |
|---|
| 2026-02 | 1.05 | -12.3 |
| 2026-04 | 0.81 | -10.7 |
| 2026-06 | 1.1 | -1.4 |
| 2026-08 | 1.19 | -3.8 |
| 2026-09 | 1.25 | -0 |
Q4. Issuer ranking by spend per card, April 2026
SELECT issuer,
ROUND(spend_inr_cr * 1e7 / (cards_outstanding_mn * 1e6)) AS spend_per_card_inr,
spend_yoy_pct,
RANK() OVER (ORDER BY spend_inr_cr / cards_outstanding_mn DESC) AS rank_by_spend_per_card
FROM issuer_apr2026
ORDER BY rank_by_spend_per_card;
| issuer | spend_per_card_inr | spend_yoy_pct | rank_by_spend_per_card |
|---|
| HDFC Bank | 21,977 | 12.34 | 1 |
| SBI Card | 17,060 | 28.98 | 2 |
| ICICI Bank | 16,927 | -7.35 | 3 |
Q5. UPI average ticket and UPI value versus all card spend
SELECT u.month,
ROUND(u.upi_value_inr_lakh_cr * 1e12 / (u.upi_volume_bn * 1e9)) AS upi_avg_ticket_inr,
ROUND(u.upi_value_inr_lakh_cr / c.spend_inr_lakh_cr, 1) AS upi_to_card_value_x
FROM upi_monthly u
LEFT JOIN card_monthly c ON c.month = u.month
ORDER BY u.month;
| month | upi_avg_ticket_inr | upi_to_card_value_x |
|---|
| 2026-01 | 1,306 | 14.2 |
| 2026-02 | 1,316 | 15.2 |
| 2026-03 | 1,304 | 13.5 |
| 2026-04 | 1,299 | 14.7 |
| 2026-05 | 1,289 | 14.8 |
| 2026-06 | 1,273 | 14.4 |
| 2026-07 | 1,263 | 14.4 |
| 2026-08 | 1,217 | 14.8 |
| 2026-09 | 1,220 | 14.4 |
| 2026-10 | 1,238 | — |
Q6. Named complaint categories ranked, with running share (the residual 'All other grounds' is excluded)
SELECT category,
share_pct,
RANK() OVER (ORDER BY share_pct DESC) AS rnk,
ROUND(SUM(share_pct) OVER (ORDER BY share_pct DESC ROWS UNBOUNDED PRECEDING), 2) AS running_share_pct
FROM ombudsman_fy25
WHERE category <> 'All other grounds'
ORDER BY rnk;
| category | share_pct | rnk | running_share_pct |
|---|
| Loans and advances | 29.25 | 1 | 29.25 |
| Credit cards | 17.15 | 2 | 46.4 |
| Mobile / electronic banking | 16.86 | 3 | 63.26 |
| Deposit accounts | 16.84 | 4 | 80.1 |