| | 4 | |
| | 5 | |
| | 6 | === Извештај за годишна финансиска активност на корисници === |
| | 7 | |
| | 8 | Анализата на перформанси се врши врз основа на моменталната состојба во базата, која има податоци кои служат за тестирање. Базата содржи: ~1000 корисници, ~2000 приходи (incomes), ~1000 finance профили. |
| | 9 | |
| | 10 | SQL: |
| | 11 | |
| | 12 | {{{ |
| | 13 | SELECT |
| | 14 | fb.user_id, |
| | 15 | fb.username, |
| | 16 | fb.email, |
| | 17 | COALESCE(ai.income_count, 0) AS income_count, |
| | 18 | COALESCE(ai.annual_total_income, 0) AS annual_total_income, |
| | 19 | COALESCE(ma.active_months, 0) AS active_months, |
| | 20 | ROUND((COALESCE(ma.active_months, 0) / 12.0)::numeric, 4) AS activity_ratio, |
| | 21 | ROUND((COALESCE(fb.spending_budget, 0) / 100.0) * COALESCE(ai.annual_total_income, 0), 2) AS spending_amount, |
| | 22 | ROUND((COALESCE(fb.saving_budget, 0) / 100.0) * COALESCE(ai.annual_total_income, 0), 2) AS saving_amount, |
| | 23 | ROUND((COALESCE(fb.investing_budget, 0) / 100.0) * COALESCE(ai.annual_total_income, 0), 2) AS investing_amount, |
| | 24 | ROUND((COALESCE(fb.donation_budget, 0) / 100.0) * COALESCE(ai.annual_total_income, 0), 2) AS donation_amount, |
| | 25 | ROUND((COALESCE(fb.credit, 0) / 100.0) * COALESCE(ai.annual_total_income, 0), 2) AS credit_amount |
| | 26 | FROM finance_users fb |
| | 27 | LEFT JOIN ( |
| | 28 | SELECT user_id, COUNT(*) AS income_count, SUM(amount) AS annual_total_income |
| | 29 | FROM incomes |
| | 30 | WHERE EXTRACT(YEAR FROM date) = 2026 |
| | 31 | GROUP BY user_id |
| | 32 | ) ai ON ai.user_id = fb.user_id |
| | 33 | LEFT JOIN ( |
| | 34 | SELECT user_id, COUNT(DISTINCT EXTRACT(MONTH FROM date)) AS active_months |
| | 35 | FROM incomes |
| | 36 | WHERE EXTRACT(YEAR FROM date) = 2026 |
| | 37 | GROUP BY user_id |
| | 38 | ) ma ON ma.user_id = fb.user_id |
| | 39 | ORDER BY annual_total_income DESC; |
| | 40 | }}} |
| | 41 | Индекси: |
| | 42 | |
| | 43 | 1. |
| | 44 | |
| | 45 | {{{ |
| | 46 | idx_incomes_user_date |
| | 47 | CREATE INDEX idx_incomes_user_date ON incomes(user_id, date); |
| | 48 | }}} |
| | 49 | |
| | 50 | Подобрување: |
| | 51 | Index Scan наместо Seq Scan + подобро филтрирање по година |
| | 52 | |
| | 53 | 2. |
| | 54 | {{{ |
| | 55 | idx_incomes_year |
| | 56 | CREATE INDEX idx_incomes_year ON incomes ((EXTRACT(YEAR FROM date))); |
| | 57 | }}} |
| | 58 | |
| | 59 | Подобрување: |
| | 60 | Избегнува full table scan за годишен филтер |
| | 61 | |
| | 62 | 3. |
| | 63 | {{{ |
| | 64 | |
| | 65 | idx_incomes_covering |
| | 66 | CREATE INDEX idx_incomes_covering ON incomes(user_id, date, amount); |
| | 67 | |
| | 68 | }}} |
| | 69 | |
| | 70 | Подобрување: |
| | 71 | Direct lookup |
| | 72 | |
| | 73 | |
| | 74 | 4. |
| | 75 | {{{ |
| | 76 | CREATE UNIQUE INDEX users_pkey |
| | 77 | ON users(user_id); |
| | 78 | |
| | 79 | }}} |
| | 80 | |
| | 81 | Подобрување: |
| | 82 | Fast join |
| | 83 | |
| | 84 | 5. |
| | 85 | {{{ |
| | 86 | CREATE INDEX idx_incomes_covering |
| | 87 | ON incomes(user_id, date, amount); |
| | 88 | |
| | 89 | }}} |
| | 90 | |
| | 91 | Подобрување: |
| | 92 | Index-only scan (без пристап до табелата) |
| | 93 | |
| | 94 | ==SQL за анализа на брзината |
| | 95 | |
| | 96 | {{{ |
| | 97 | CREATE OR REPLACE FUNCTION test_finance_report() |
| | 98 | RETURNS void AS $$ |
| | 99 | DECLARE |
| | 100 | start_time timestamptz; |
| | 101 | end_time timestamptz; |
| | 102 | duration int; |
| | 103 | BEGIN |
| | 104 | start_time := clock_timestamp(); |
| | 105 | |
| | 106 | PERFORM * |
| | 107 | FROM ( |
| | 108 | SELECT |
| | 109 | fb.user_id, |
| | 110 | COALESCE(ai.income_count, 0), |
| | 111 | COALESCE(ai.annual_total_income, 0), |
| | 112 | COALESCE(ma.active_months, 0) |
| | 113 | FROM finance_users fb |
| | 114 | LEFT JOIN ( |
| | 115 | SELECT user_id, COUNT(*), SUM(amount) |
| | 116 | FROM incomes |
| | 117 | WHERE EXTRACT(YEAR FROM date) = 2026 |
| | 118 | GROUP BY user_id |
| | 119 | ) ai ON ai.user_id = fb.user_id |
| | 120 | LEFT JOIN ( |
| | 121 | SELECT user_id, COUNT(DISTINCT EXTRACT(MONTH FROM date)) |
| | 122 | FROM incomes |
| | 123 | WHERE EXTRACT(YEAR FROM date) = 2026 |
| | 124 | GROUP BY user_id |
| | 125 | ) ma ON ma.user_id = fb.user_id |
| | 126 | ) t; |
| | 127 | |
| | 128 | end_time := clock_timestamp(); |
| | 129 | duration := round(1000 * (extract(epoch FROM end_time) - extract(epoch FROM start_time))); |
| | 130 | |
| | 131 | RAISE NOTICE 'Query executed in: % ms', duration; |
| | 132 | END; |
| | 133 | $$ LANGUAGE plpgsql; |
| | 134 | |
| | 135 | -- run 1: no indexes |
| | 136 | SELECT test_finance_report(); |
| | 137 | |
| | 138 | -- run 2: add idx_incomes_user_date |
| | 139 | CREATE INDEX idx_incomes_user_date ON incomes(user_id, date); |
| | 140 | ANALYZE incomes; |
| | 141 | SELECT test_finance_report(); |
| | 142 | |
| | 143 | -- run 3: add idx_incomes_year |
| | 144 | CREATE INDEX idx_incomes_year ON incomes ((EXTRACT(YEAR FROM date))); |
| | 145 | ANALYZE incomes; |
| | 146 | SELECT test_finance_report(); |
| | 147 | |
| | 148 | -- run 4: add covering index |
| | 149 | CREATE INDEX idx_incomes_covering ON incomes(user_id, date, amount); |
| | 150 | ANALYZE incomes; |
| | 151 | SELECT test_finance_report(); |
| | 152 | |
| | 153 | DROP FUNCTION test_finance_report(); |
| | 154 | }}} |
| | 155 | |
| | 156 | Сумарно: |
| | 157 | Без индекси: ~61ms |
| | 158 | Со индекси: ~25ms |
| | 159 | Забрзување: ~2.4x |