| | 14 | WITH |
| | 15 | holding_values AS ( |
| | 16 | SELECT |
| | 17 | ph.portfolio_id AS hv_portfolio_id, |
| | 18 | ph.stock_id AS hv_stock_id, |
| | 19 | s.symbol AS hv_symbol, |
| | 20 | ph.quantity::NUMERIC * s.current_price::NUMERIC AS market_value, |
| | 21 | ph.quantity::NUMERIC * (s.current_price::NUMERIC - ph.avg_price::NUMERIC) AS unrealized_pnl |
| | 22 | FROM portfolio_holdings ph |
| | 23 | JOIN stock s ON ph.stock_id = s.id |
| | 24 | WHERE ph.quantity > 0 |
| | 25 | ), |
| | 26 | stock_weights AS ( |
| | 27 | SELECT |
| | 28 | hv.hv_portfolio_id AS sw_portfolio_id, |
| | 29 | hv.hv_stock_id AS sw_stock_id, |
| | 30 | hv.hv_symbol AS sw_symbol, |
| | 31 | SUM(hv.market_value) AS stock_market_value, |
| | 32 | SUM(hv.unrealized_pnl) AS stock_unrealized_pnl, |
| | 33 | COUNT(*) AS lots_count |
| | 34 | FROM holding_values hv |
| | 35 | GROUP BY hv.hv_portfolio_id, hv.hv_stock_id, hv.hv_symbol |
| | 36 | ), |
| | 37 | portfolio_totals AS ( |
| | 38 | SELECT |
| | 39 | sw.sw_portfolio_id AS pt_portfolio_id, |
| | 40 | SUM(sw.stock_market_value) AS total_holdings_value, |
| | 41 | SUM(sw.stock_unrealized_pnl) AS total_unrealized_pnl, |
| | 42 | COUNT(DISTINCT sw.sw_stock_id) AS num_stocks, |
| | 43 | SUM(sw.lots_count) AS total_lots |
| | 44 | FROM stock_weights sw |
| | 45 | GROUP BY sw.sw_portfolio_id |
| | 46 | ), |
| | 47 | hhi_calc AS ( |
| | 48 | SELECT |
| | 49 | sw.sw_portfolio_id AS hhi_portfolio_id, |
| | 50 | SUM(POWER(sw.stock_market_value / NULLIF(pt.total_holdings_value, 0::NUMERIC), 2::NUMERIC)) AS hhi, |
| | 51 | MAX(sw.stock_market_value / NULLIF(pt.total_holdings_value, 0::NUMERIC) * 100::NUMERIC) AS max_weight_pct, |
| | 52 | MIN(sw.stock_market_value / NULLIF(pt.total_holdings_value, 0::NUMERIC) * 100::NUMERIC) AS min_weight_pct |
| | 53 | FROM stock_weights sw |
| | 54 | JOIN portfolio_totals pt ON sw.sw_portfolio_id = pt.pt_portfolio_id |
| | 55 | GROUP BY sw.sw_portfolio_id |
| | 56 | ), |
| | 57 | dominant_stock AS ( |
| | 58 | SELECT DISTINCT ON (sw.sw_portfolio_id) |
| | 59 | sw.sw_portfolio_id AS ds_portfolio_id, |
| | 60 | sw.sw_symbol AS dominant_symbol, |
| | 61 | ROUND(sw.stock_market_value / NULLIF(pt.total_holdings_value, 0::NUMERIC) * 100::NUMERIC, 2) AS dominant_weight_pct |
| | 62 | FROM stock_weights sw |
| | 63 | JOIN portfolio_totals pt ON sw.sw_portfolio_id = pt.pt_portfolio_id |
| | 64 | ORDER BY sw.sw_portfolio_id, sw.stock_market_value / NULLIF(pt.total_holdings_value, 0::NUMERIC) DESC |
| | 65 | ) |
| 18 | | ph.portfolio_id, |
| 19 | | ph.stock_id, |
| 20 | | s.symbol, |
| 21 | | ph.quantity::NUMERIC * s.current_price::NUMERIC AS market_value, |
| 22 | | ph.quantity::NUMERIC * (s.current_price::NUMERIC - ph.avg_price::NUMERIC) AS unrealized_pnl |
| 23 | | FROM portfolio_holdings ph |
| 24 | | JOIN stock s ON ph.stock_id = s.id |
| 25 | | WHERE quantity > 0 AND portfolio_id = 1; |
| 26 | | |
| 27 | | |
| 28 | | |
| 29 | | QUERY PLAN | |
| 30 | | --------------------------------------------------------------------------------------------------------------------------------+ |
| 31 | | Nested Loop (cost=0.15..1418.59 rows=50 width=85) (actual time=0.085..8.009 rows=50 loops=1) | |
| 32 | | -> Seq Scan on portfolio_holdings ph (cost=0.00..1400.00 rows=50 width=26) (actual time=0.037..7.462 rows=50 loops=1) | |
| 33 | | Filter: ((quantity > 0) AND (portfolio_id = 1)) | |
| 34 | | Rows Removed by Filter: 59950 | |
| 35 | | -> Memoize (cost=0.15..0.49 rows=1 width=21) (actual time=0.005..0.005 rows=1 loops=50) | |
| 36 | | Cache Key: ph.stock_id | |
| 37 | | Cache Mode: logical | |
| 38 | | Hits: 0 Misses: 50 Evictions: 0 Overflows: 0 Memory Usage: 7kB | |
| 39 | | -> Index Scan using stock_pkey on stock s (cost=0.14..0.48 rows=1 width=21) (actual time=0.003..0.003 rows=1 loops=50)| |
| 40 | | Index Cond: (id = ph.stock_id) | |
| 41 | | Planning Time: 0.799 ms | |
| 42 | | Execution Time: 8.064 ms | |
| | 67 | u.id::INTEGER AS user_id, |
| | 68 | u.username::TEXT, |
| | 69 | p.id::INTEGER AS portfolio_id, |
| | 70 | p.balance::NUMERIC AS portfolio_cash_balance, |
| | 71 | COALESCE(pt.total_holdings_value, 0::NUMERIC) AS total_holdings_value, |
| | 72 | (p.balance::NUMERIC + COALESCE(pt.total_holdings_value, 0::NUMERIC)) AS total_portfolio_value, |
| | 73 | COALESCE(pt.num_stocks, 0)::BIGINT AS num_distinct_stocks, |
| | 74 | COALESCE(pt.total_lots, 0)::BIGINT AS total_lots, |
| | 75 | COALESCE(pt.total_unrealized_pnl, 0::NUMERIC) AS unrealized_pnl, |
| | 76 | ROUND(COALESCE(pt.total_unrealized_pnl, 0::NUMERIC) / NULLIF(pt.total_holdings_value - COALESCE(pt.total_unrealized_pnl, 0::NUMERIC), 0::NUMERIC) * 100::NUMERIC, 2) AS unrealized_pnl_pct, |
| | 77 | ROUND(COALESCE(hhi.hhi, 0::NUMERIC), 4) AS hhi_score, |
| | 78 | CASE WHEN hhi.hhi >= 0.25 THEN 'HIGH CONCENTRATION RISK' WHEN hhi.hhi >= 0.10 THEN 'MEDIUM CONCENTRATION RISK' ELSE 'WELL DIVERSIFIED' END::TEXT AS risk_classification, |
| | 79 | COALESCE(ds.dominant_symbol, 'N/A')::TEXT AS dominant_stock_symbol, |
| | 80 | COALESCE(ds.dominant_weight_pct, 0::NUMERIC) AS dominant_stock_weight_pct, |
| | 81 | COALESCE(hhi.max_weight_pct, 0::NUMERIC) AS max_single_weight_pct, |
| | 82 | COALESCE(hhi.min_weight_pct, 0::NUMERIC) AS min_single_weight_pct, |
| | 83 | RANK() OVER (ORDER BY COALESCE(hhi.hhi, 1::NUMERIC) ASC)::BIGINT AS diversification_rank |
| | 84 | FROM users u |
| | 85 | JOIN portfolios p ON u.id = p.user_id |
| | 86 | LEFT JOIN portfolio_totals pt ON p.id = pt.pt_portfolio_id |
| | 87 | LEFT JOIN hhi_calc hhi ON p.id = hhi.hhi_portfolio_id |
| | 88 | LEFT JOIN dominant_stock ds ON p.id = ds.ds_portfolio_id |
| | 89 | WHERE u.role = 'USER' |
| | 90 | ORDER BY hhi_score ASC, total_portfolio_value DESC; |
| | 91 | |
| | 92 | |
| | 93 | QUERY PLAN | |
| | 94 | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | 95 | Sort (cost=10000076265.17..10000076267.67 rows=1000 width=448) (actual time=2950.286..2950.368 rows=1000 loops=1) | |
| | 96 | Sort Key: (round(COALESCE(hhi.hhi, '0'::numeric), 4)), (((p.balance)::numeric + COALESCE(pt.total_holdings_value, '0'::numeric))) DESC | |
| | 97 | Sort Method: quicksort Memory: 212kB | |
| | 98 | CTE stock_weights | |
| | 99 | -> GroupAggregate (cost=10000007600.41..10000013734.23 rows=50009 width=93) (actual time=913.089..1157.834 rows=50000 loops=1) | |
| | 100 | Group Key: ph.stock_id, ph.portfolio_id, s.symbol | |
| | 101 | -> Incremental Sort (cost=10000007600.41..10000011358.81 rows=50009 width=39) (actual time=913.012..941.655 rows=50000 loops=1) | |
| | 102 | Sort Key: ph.stock_id, ph.portfolio_id, s.symbol | |
| | 103 | Presorted Key: ph.stock_id | |
| | 104 | Full-sort Groups: 50 Sort Method: quicksort Average Memory: 29kB Peak Memory: 29kB | |
| | 105 | Pre-sorted Groups: 50 Sort Method: quicksort Average Memory: 87kB Peak Memory: 87kB | |
| | 106 | -> Merge Join (cost=10000007547.32..10000008306.30 rows=50009 width=39) (actual time=912.479..927.879 rows=50000 loops=1) | |
| | 107 | Merge Cond: (ph.stock_id = s.id) | |
| | 108 | -> Sort (cost=10000007547.18..10000007672.20 rows=50009 width=26) (actual time=912.375..919.789 rows=50000 loops=1) | |
| | 109 | Sort Key: ph.stock_id | |
| | 110 | Sort Method: external merge Disk: 1824kB | |
| | 111 | -> Seq Scan on portfolio_holdings ph (cost=10000000000.00..10000001250.00 rows=50009 width=26) (actual time=0.062..13.649 rows=50000 loops=1) | |
| | 112 | Filter: (quantity > 0) | |
| | 113 | Rows Removed by Filter: 10000 | |
| | 114 | -> Index Scan using stock_pkey on stock s (cost=0.14..14.64 rows=100 width=21) (actual time=0.025..0.196 rows=50 loops=1) | |
| | 115 | CTE portfolio_totals | |
| | 116 | -> GroupAggregate (cost=9691.36..10444.99 rows=200 width=112) (actual time=1232.448..1261.387 rows=1000 loops=1) | |
| | 117 | Group Key: sw_2.sw_portfolio_id | |
| | 118 | -> Sort (cost=9691.36..9816.38 rows=50009 width=88) (actual time=1232.344..1246.691 rows=50000 loops=1) | |
| | 119 | Sort Key: sw_2.sw_portfolio_id, sw_2.sw_stock_id | |
| | 120 | Sort Method: external merge Disk: 2704kB | |
| | 121 | -> CTE Scan on stock_weights sw_2 (cost=0.00..1000.18 rows=50009 width=88) (actual time=913.102..1191.852 rows=50000 loops=1) | |
| | 122 | -> WindowAgg (cost=51785.63..51833.12 rows=1000 width=448) (actual time=2946.255..2948.249 rows=1000 loops=1) | |
| | 123 | -> Sort (cost=51785.62..51788.12 rows=1000 width=817) (actual time=2946.215..2946.303 rows=1000 loops=1) | |
| | 124 | Sort Key: (COALESCE(hhi.hhi, '1'::numeric)) | |
| | 125 | Sort Method: quicksort Memory: 187kB | |
| | 126 | -> Hash Join (cost=46946.33..51371.79 rows=1000 width=817) (actual time=2815.414..2945.323 rows=1000 loops=1) | |
| | 127 | Hash Cond: (p.user_id = u.id) | |
| | 128 | -> Hash Left Join (cost=46879.05..51301.88 rows=1000 width=772) (actual time=1628.954..1758.394 rows=1000 loops=1) | |
| | 129 | Hash Cond: (p.id = hhi.hhi_portfolio_id) | |
| | 130 | -> Merge Left Join (cost=42364.74..46784.93 rows=1000 width=676) (actual time=1263.716..1392.616 rows=1000 loops=1) | |
| | 131 | Merge Cond: (p.id = sw.sw_portfolio_id) | |
| | 132 | -> Merge Left Join (cost=0.28..55.77 rows=1000 width=128) (actual time=1232.543..1234.126 rows=1000 loops=1) | |
| | 133 | Merge Cond: (p.id = pt.pt_portfolio_id) | |
| | 134 | -> Index Scan using portfolios_pkey on portfolios p (cost=0.28..46.27 rows=1000 width=24) (actual time=0.048..0.814 rows=1000 loops=1) | |
| | 135 | -> Materialize (cost=0.00..4.50 rows=200 width=112) (actual time=1232.456..1232.755 rows=1000 loops=1) | |
| | 136 | -> CTE Scan on portfolio_totals pt (cost=0.00..4.00 rows=200 width=112) (actual time=1232.452..1232.606 rows=1000 loops=1) | |
| | 137 | -> Unique (cost=42364.47..46722.15 rows=200 width=588) (actual time=31.143..158.023 rows=1000 loops=1) | |
| | 138 | -> Incremental Sort (cost=42364.47..46597.13 rows=50009 width=588) (actual time=31.141..154.964 rows=49951 loops=1) | |
| | 139 | Sort Key: sw.sw_portfolio_id, ((sw.stock_market_value / NULLIF(pt_1.total_holdings_value, '0'::numeric))) DESC | |
| | 140 | Presorted Key: sw.sw_portfolio_id | |
| | 141 | Full-sort Groups: 1000 Sort Method: quicksort Average Memory: 27kB Peak Memory: 27kB | |
| | 142 | -> Merge Join (cost=42346.36..43976.15 rows=50009 width=588) (actual time=30.962..100.854 rows=50000 loops=1) | |
| | 143 | Merge Cond: (pt_1.pt_portfolio_id = sw.sw_portfolio_id) | |
| | 144 | -> CTE Scan on portfolio_totals pt_1 (cost=0.00..4.00 rows=200 width=40) (actual time=0.001..0.332 rows=1000 loops=1) | |
| | 145 | -> Materialize (cost=42346.36..42596.40 rows=50009 width=556) (actual time=30.913..45.619 rows=50000 loops=1) | |
| | 146 | -> Sort (cost=42346.36..42471.38 rows=50009 width=556) (actual time=30.910..39.318 rows=50000 loops=1) | |
| | 147 | Sort Key: sw.sw_portfolio_id | |
| | 148 | Sort Method: external merge Disk: 1616kB | |
| | 149 | -> CTE Scan on stock_weights sw (cost=0.00..1000.18 rows=50009 width=556) (actual time=0.046..9.117 rows=50000 loops=1)| |
| | 150 | -> Hash (cost=4511.81..4511.81 rows=200 width=104) (actual time=365.175..365.180 rows=1000 loops=1) | |
| | 151 | Buckets: 1024 Batches: 1 Memory Usage: 94kB | |
| | 152 | -> Subquery Scan on hhi (cost=4507.31..4511.81 rows=200 width=104) (actual time=237.945..364.912 rows=1000 loops=1) | |
| | 153 | -> HashAggregate (cost=4507.31..4509.81 rows=200 width=104) (actual time=237.936..364.756 rows=1000 loops=1) | |
| | 154 | Group Key: sw_1.sw_portfolio_id | |
| | 155 | Batches: 5 Memory Usage: 625kB Disk Usage: 232kB | |
| | 156 | -> Hash Join (cost=6.50..2882.02 rows=50009 width=72) (actual time=29.760..53.495 rows=50000 loops=1) | |
| | 157 | Hash Cond: (sw_1.sw_portfolio_id = pt_2.pt_portfolio_id) | |
| | 158 | -> CTE Scan on stock_weights sw_1 (cost=0.00..1000.18 rows=50009 width=40) (actual time=0.011..9.383 rows=50000 loops=1) | |
| | 159 | -> Hash (cost=4.00..4.00 rows=200 width=40) (actual time=29.701..29.703 rows=1000 loops=1) | |
| | 160 | Buckets: 1024 Batches: 1 Memory Usage: 63kB | |
| | 161 | -> CTE Scan on portfolio_totals pt_2 (cost=0.00..4.00 rows=200 width=40) (actual time=0.014..29.427 rows=1000 loops=1) | |
| | 162 | -> Hash (cost=54.77..54.77 rows=1000 width=21) (actual time=1186.429..1186.430 rows=1000 loops=1) | |
| | 163 | Buckets: 1024 Batches: 1 Memory Usage: 61kB | |
| | 164 | -> Index Scan using users_pkey on users u (cost=0.28..54.77 rows=1000 width=21) (actual time=1185.685..1186.197 rows=1000 loops=1) | |
| | 165 | Filter: ((role)::text = 'USER'::text) | |
| | 166 | Planning Time: 3.006 ms | |
| | 167 | JIT: | |
| | 168 | Functions: 83 | |
| | 169 | Options: Inlining true, Optimization true, Expressions true, Deforming true | |
| | 170 | Timing: Generation 8.317 ms (Deform 3.321 ms), Inlining 71.671 ms, Optimization 773.085 ms, Emission 545.906 ms, Total 1398.978 ms | |
| | 171 | Execution Time: 2960.889 ms | |
| 57 | | SELECT |
| 58 | | ph.portfolio_id, |
| 59 | | ph.stock_id, |
| 60 | | s.symbol, |
| 61 | | ph.quantity::NUMERIC * s.current_price::NUMERIC AS market_value, |
| 62 | | ph.quantity::NUMERIC * (s.current_price::NUMERIC - ph.avg_price::NUMERIC) AS unrealized_pnl |
| 63 | | FROM portfolio_holdings ph |
| 64 | | JOIN stock s ON ph.stock_id = s.id |
| 65 | | WHERE quantity > 0 AND portfolio_id = 1; |
| 66 | | |
| 67 | | QUERY PLAN | |
| 68 | | --------------------------------------------------------------------------------------------------------------------------------+ |
| 69 | | Nested Loop (cost=0.15..1418.59 rows=50 width=85) (actual time=0.052..5.758 rows=50 loops=1) | |
| 70 | | -> Seq Scan on portfolio_holdings ph (cost=0.00..1400.00 rows=50 width=26) (actual time=0.018..5.389 rows=50 loops=1) | |
| 71 | | Filter: ((quantity > 0) AND (portfolio_id = 1)) | |
| 72 | | Rows Removed by Filter: 59950 | |
| 73 | | -> Memoize (cost=0.15..0.49 rows=1 width=21) (actual time=0.003..0.003 rows=1 loops=50) | |
| 74 | | Cache Key: ph.stock_id | |
| 75 | | Cache Mode: logical | |
| 76 | | Hits: 0 Misses: 50 Evictions: 0 Overflows: 0 Memory Usage: 7kB | |
| 77 | | -> Index Scan using stock_pkey on stock s (cost=0.14..0.48 rows=1 width=21) (actual time=0.002..0.002 rows=1 loops=50)| |
| 78 | | Index Cond: (id = ph.stock_id) | |
| 79 | | Planning Time: 0.471 ms | |
| 80 | | Execution Time: 5.802 ms | |
| 81 | | |
| 82 | | }}} |
| | 193 | ... |
| | 194 | |
| | 195 | |
| | 196 | QUERY PLAN | |
| | 197 | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | 198 | Sort (cost=20008.39..20010.89 rows=1000 width=448) (actual time=1159.420..1159.488 rows=1000 loops=1) | |
| | 199 | Sort Key: (round(COALESCE((sum(power((sw.stock_market_value / NULLIF(pt_1.total_holdings_value, '0'::numeric)), '2'::numeric))), '0'::numeric), 4)), (((p.balance)::numeric + COALESCE(pt.total_holdings_value, '0'::numeric))) DESC| |
| | 200 | Sort Method: quicksort Memory: 212kB | |
| | 201 | CTE stock_weights | |
| | 202 | -> GroupAggregate (cost=1.53..8892.19 rows=50009 width=93) (actual time=0.297..343.555 rows=50000 loops=1) | |
| | 203 | Group Key: ph.portfolio_id, ph.stock_id, s.symbol | |
| | 204 | -> Incremental Sort (cost=1.53..6516.76 rows=50009 width=39) (actual time=0.261..101.899 rows=50000 loops=1) | |
| | 205 | Sort Key: ph.portfolio_id, ph.stock_id, s.symbol | |
| | 206 | Presorted Key: ph.portfolio_id, ph.stock_id | |
| | 207 | Full-sort Groups: 1563 Sort Method: quicksort Average Memory: 27kB Peak Memory: 27kB | |
| | 208 | -> Nested Loop (cost=0.57..5006.72 rows=50009 width=39) (actual time=0.083..84.356 rows=50000 loops=1) | |
| | 209 | -> Index Scan using idx_ph_portfolio_stock_qty on portfolio_holdings ph (cost=0.41..3754.09 rows=50009 width=26) (actual time=0.051..56.091 rows=50000 loops=1) | |
| | 210 | -> Memoize (cost=0.15..0.17 rows=1 width=21) (actual time=0.000..0.000 rows=1 loops=50000) | |
| | 211 | Cache Key: ph.stock_id | |
| | 212 | Cache Mode: logical | |
| | 213 | Hits: 49950 Misses: 50 Evictions: 0 Overflows: 0 Memory Usage: 7kB | |
| | 214 | -> Index Scan using stock_pkey on stock s (cost=0.14..0.16 rows=1 width=21) (actual time=0.001..0.001 rows=1 loops=50) | |
| | 215 | Index Cond: (id = ph.stock_id) | |
| | 216 | CTE portfolio_totals | |
| | 217 | -> GroupAggregate (cost=0.00..1628.79 rows=200 width=112) (actual time=0.702..862.171 rows=1000 loops=1) | |
| | 218 | Group Key: sw_2.sw_portfolio_id | |
| | 219 | -> CTE Scan on stock_weights sw_2 (cost=0.00..1000.18 rows=50009 width=88) (actual time=0.299..837.648 rows=50000 loops=1) | |
| | 220 | -> WindowAgg (cost=9187.09..9234.58 rows=1000 width=448) (actual time=1154.807..1157.221 rows=1000 loops=1) | |
| | 221 | -> Sort (cost=9187.08..9189.58 rows=1000 width=817) (actual time=1154.785..1154.927 rows=1000 loops=1) | |
| | 222 | Sort Key: (COALESCE((sum(power((sw.stock_market_value / NULLIF(pt_1.total_holdings_value, '0'::numeric)), '2'::numeric))), '1'::numeric)) | |
| | 223 | Sort Method: quicksort Memory: 187kB | |
| | 224 | -> Hash Join (cost=90.03..8773.25 rows=1000 width=817) (actual time=2.268..1152.842 rows=1000 loops=1) | |
| | 225 | Hash Cond: (p.user_id = u.id) | |
| | 226 | -> Merge Left Join (cost=22.76..8703.34 rows=1000 width=772) (actual time=1.460..1150.243 rows=1000 loops=1) | |
| | 227 | Merge Cond: (p.id = sw_1.sw_portfolio_id) | |
| | 228 | -> Merge Left Join (cost=0.28..3445.38 rows=1000 width=224) (actual time=1.312..995.965 rows=1000 loops=1) | |
| | 229 | Merge Cond: (p.id = sw.sw_portfolio_id) | |
| | 230 | -> Merge Left Join (cost=0.28..55.77 rows=1000 width=128) (actual time=0.789..5.346 rows=1000 loops=1) | |
| | 231 | Merge Cond: (p.id = pt.pt_portfolio_id) | |
| | 232 | -> Index Scan using portfolios_pkey on portfolios p (cost=0.28..46.27 rows=1000 width=24) (actual time=0.075..2.856 rows=1000 loops=1) | |
| | 233 | -> Materialize (cost=0.00..4.50 rows=200 width=112) (actual time=0.707..1.297 rows=1000 loops=1) | |
| | 234 | -> CTE Scan on portfolio_totals pt (cost=0.00..4.00 rows=200 width=112) (actual time=0.704..0.942 rows=1000 loops=1) | |
| | 235 | -> GroupAggregate (cost=0.00..3382.61 rows=200 width=104) (actual time=0.520..989.374 rows=1000 loops=1) | |
| | 236 | Group Key: sw.sw_portfolio_id | |
| | 237 | -> Merge Join (cost=0.00..1754.82 rows=50009 width=72) (actual time=0.004..887.199 rows=50000 loops=1) | |
| | 238 | Merge Cond: (pt_1.pt_portfolio_id = sw.sw_portfolio_id) | |
| | 239 | -> CTE Scan on portfolio_totals pt_1 (cost=0.00..4.00 rows=200 width=40) (actual time=0.000..862.598 rows=1000 loops=1) | |
| | 240 | -> Materialize (cost=0.00..1125.20 rows=50009 width=40) (actual time=0.002..12.323 rows=50000 loops=1) | |
| | 241 | -> CTE Scan on stock_weights sw (cost=0.00..1000.18 rows=50009 width=40) (actual time=0.000..5.927 rows=50000 loops=1) | |
| | 242 | -> Unique (cost=22.48..5250.95 rows=200 width=588) (actual time=0.146..152.842 rows=1000 loops=1) | |
| | 243 | -> Incremental Sort (cost=22.48..5125.93 rows=50009 width=588) (actual time=0.145..148.672 rows=49951 loops=1) | |
| | 244 | Sort Key: sw_1.sw_portfolio_id, ((sw_1.stock_market_value / NULLIF(pt_2.total_holdings_value, '0'::numeric))) DESC | |
| | 245 | Presorted Key: sw_1.sw_portfolio_id | |
| | 246 | Full-sort Groups: 1000 Sort Method: quicksort Average Memory: 27kB Peak Memory: 27kB | |
| | 247 | -> Merge Join (cost=0.00..2504.95 rows=50009 width=588) (actual time=0.006..91.344 rows=50000 loops=1) | |
| | 248 | Merge Cond: (pt_2.pt_portfolio_id = sw_1.sw_portfolio_id) | |
| | 249 | -> CTE Scan on portfolio_totals pt_2 (cost=0.00..4.00 rows=200 width=40) (actual time=0.000..0.344 rows=1000 loops=1) | |
| | 250 | -> Materialize (cost=0.00..1125.20 rows=50009 width=556) (actual time=0.001..28.674 rows=50000 loops=1) | |
| | 251 | -> CTE Scan on stock_weights sw_1 (cost=0.00..1000.18 rows=50009 width=556) (actual time=0.000..21.606 rows=50000 loops=1) | |
| | 252 | -> Hash (cost=54.77..54.77 rows=1000 width=21) (actual time=0.793..0.795 rows=1000 loops=1) | |
| | 253 | Buckets: 1024 Batches: 1 Memory Usage: 61kB | |
| | 254 | -> Index Scan using users_pkey on users u (cost=0.28..54.77 rows=1000 width=21) (actual time=0.017..0.598 rows=1000 loops=1) | |
| | 255 | Filter: ((role)::text = 'USER'::text) | |
| | 256 | Planning Time: 186.270 ms | |
| | 257 | Execution Time: 1160.749 ms | |
| | 258 | |
| | 259 | }}} |
| | 260 | |
| | 261 | idx_ph_portfolio_stock_qty => Index Scan using idx_ph_portfolio_stock_qty on portfolio_holdings ph |
| 92 | | SELECT |
| 93 | | ph.portfolio_id, |
| 94 | | ph.stock_id, |
| 95 | | s.symbol, |
| 96 | | ph.quantity::NUMERIC * s.current_price::NUMERIC AS market_value, |
| 97 | | ph.quantity::NUMERIC * (s.current_price::NUMERIC - ph.avg_price::NUMERIC) AS unrealized_pnl |
| 98 | | FROM portfolio_holdings ph |
| 99 | | JOIN stock s ON ph.stock_id = s.id |
| 100 | | WHERE quantity > 0 AND portfolio_id = 1; |
| 101 | | |
| 102 | | |
| 103 | | QUERY PLAN | |
| 104 | | ----------------------------------------------------------------------------------------------------------------------------------+ |
| 105 | | Hash Join (cost=9.00..181.17 rows=50 width=85) (actual time=0.245..0.515 rows=50 loops=1) | |
| 106 | | Hash Cond: (ph.stock_id = s.id) | |
| 107 | | -> Bitmap Heap Scan on portfolio_holdings ph (cost=4.75..175.92 rows=50 width=26) (actual time=0.156..0.265 rows=50 loops=1) | |
| 108 | | Recheck Cond: (portfolio_id = 1) | |
| 109 | | Filter: (quantity > 0) | |
| 110 | | Rows Removed by Filter: 10 | |
| 111 | | Heap Blocks: exact=60 | |
| 112 | | -> Bitmap Index Scan on idx_ph_portfolio_id (cost=0.00..4.74 rows=60 width=0) (actual time=0.136..0.137 rows=60 loops=1)| |
| 113 | | Index Cond: (portfolio_id = 1) | |
| 114 | | -> Hash (cost=3.00..3.00 rows=100 width=21) (actual time=0.059..0.060 rows=100 loops=1) | |
| 115 | | Buckets: 1024 Batches: 1 Memory Usage: 14kB | |
| 116 | | -> Seq Scan on stock s (cost=0.00..3.00 rows=100 width=21) (actual time=0.019..0.035 rows=100 loops=1) | |
| 117 | | Planning Time: 0.526 ms | |
| 118 | | Execution Time: 0.554 ms | |
| 119 | | |
| 120 | | }}} |
| 121 | | |
| 122 | | |
| 123 | | Подобрување: Index Scan наместо Seq Scan при групирање по портфолио |
| | 278 | ... |
| | 279 | |
| | 280 | QUERY PLAN | |
| | 281 | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | 282 | Sort (cost=20008.39..20010.89 rows=1000 width=448) (actual time=743.813..743.880 rows=1000 loops=1) | |
| | 283 | Sort Key: (round(COALESCE((sum(power((sw.stock_market_value / NULLIF(pt_1.total_holdings_value, '0'::numeric)), '2'::numeric))), '0'::numeric), 4)), (((p.balance)::numeric + COALESCE(pt.total_holdings_value, '0'::numeric))) DESC| |
| | 284 | Sort Method: quicksort Memory: 212kB | |
| | 285 | CTE stock_weights | |
| | 286 | -> GroupAggregate (cost=1.53..8892.19 rows=50009 width=93) (actual time=0.732..359.488 rows=50000 loops=1) | |
| | 287 | Group Key: ph.portfolio_id, ph.stock_id, s.symbol | |
| | 288 | -> Incremental Sort (cost=1.53..6516.76 rows=50009 width=39) (actual time=0.700..108.625 rows=50000 loops=1) | |
| | 289 | Sort Key: ph.portfolio_id, ph.stock_id, s.symbol | |
| | 290 | Presorted Key: ph.portfolio_id, ph.stock_id | |
| | 291 | Full-sort Groups: 1563 Sort Method: quicksort Average Memory: 27kB Peak Memory: 27kB | |
| | 292 | -> Nested Loop (cost=0.57..5006.72 rows=50009 width=39) (actual time=0.084..89.347 rows=50000 loops=1) | |
| | 293 | -> Index Scan using idx_ph_portfolio_stock_qty on portfolio_holdings ph (cost=0.41..3754.09 rows=50009 width=26) (actual time=0.055..59.156 rows=50000 loops=1) | |
| | 294 | -> Memoize (cost=0.15..0.17 rows=1 width=21) (actual time=0.000..0.000 rows=1 loops=50000) | |
| | 295 | Cache Key: ph.stock_id | |
| | 296 | Cache Mode: logical | |
| | 297 | Hits: 49950 Misses: 50 Evictions: 0 Overflows: 0 Memory Usage: 7kB | |
| | 298 | -> Index Only Scan using idx_stock_id_covering on stock s (cost=0.14..0.16 rows=1 width=21) (actual time=0.003..0.003 rows=1 loops=50) | |
| | 299 | Index Cond: (id = ph.stock_id) | |
| | 300 | Heap Fetches: 50 | |
| | 301 | CTE portfolio_totals | |
| | 302 | -> GroupAggregate (cost=0.00..1628.79 rows=200 width=112) (actual time=1.455..434.830 rows=1000 loops=1) | |
| | 303 | Group Key: sw_2.sw_portfolio_id | |
| | 304 | -> CTE Scan on stock_weights sw_2 (cost=0.00..1000.18 rows=50009 width=88) (actual time=0.733..409.152 rows=50000 loops=1) | |
| | 305 | -> WindowAgg (cost=9187.09..9234.58 rows=1000 width=448) (actual time=739.406..741.751 rows=1000 loops=1) | |
| | 306 | -> Sort (cost=9187.08..9189.58 rows=1000 width=817) (actual time=739.382..739.512 rows=1000 loops=1) | |
| | 307 | Sort Key: (COALESCE((sum(power((sw.stock_market_value / NULLIF(pt_1.total_holdings_value, '0'::numeric)), '2'::numeric))), '1'::numeric)) | |
| | 308 | Sort Method: quicksort Memory: 187kB | |
| | 309 | -> Hash Join (cost=90.03..8773.25 rows=1000 width=817) (actual time=2.938..737.368 rows=1000 loops=1) | |
| | 310 | Hash Cond: (p.user_id = u.id) | |
| | 311 | -> Merge Left Join (cost=22.76..8703.34 rows=1000 width=772) (actual time=2.190..734.798 rows=1000 loops=1) | |
| | 312 | Merge Cond: (p.id = sw_1.sw_portfolio_id) | |
| | 313 | -> Merge Left Join (cost=0.28..3445.38 rows=1000 width=224) (actual time=2.043..573.048 rows=1000 loops=1) | |
| | 314 | Merge Cond: (p.id = sw.sw_portfolio_id) | |
| | 315 | -> Merge Left Join (cost=0.28..55.77 rows=1000 width=128) (actual time=1.518..6.548 rows=1000 loops=1) | |
| | 316 | Merge Cond: (p.id = pt.pt_portfolio_id) | |
| | 317 | -> Index Scan using portfolios_pkey on portfolios p (cost=0.28..46.27 rows=1000 width=24) (actual time=0.053..3.198 rows=1000 loops=1) | |
| | 318 | -> Materialize (cost=0.00..4.50 rows=200 width=112) (actual time=1.459..2.160 rows=1000 loops=1) | |
| | 319 | -> CTE Scan on portfolio_totals pt (cost=0.00..4.00 rows=200 width=112) (actual time=1.457..1.720 rows=1000 loops=1) | |
| | 320 | -> GroupAggregate (cost=0.00..3382.61 rows=200 width=104) (actual time=0.522..565.120 rows=1000 loops=1) | |
| | 321 | Group Key: sw.sw_portfolio_id | |
| | 322 | -> Merge Join (cost=0.00..1754.82 rows=50009 width=72) (actual time=0.004..460.251 rows=50000 loops=1) | |
| | 323 | Merge Cond: (pt_1.pt_portfolio_id = sw.sw_portfolio_id) | |
| | 324 | -> CTE Scan on portfolio_totals pt_1 (cost=0.00..4.00 rows=200 width=40) (actual time=0.000..434.549 rows=1000 loops=1) | |
| | 325 | -> Materialize (cost=0.00..1125.20 rows=50009 width=40) (actual time=0.002..12.786 rows=50000 loops=1) | |
| | 326 | -> CTE Scan on stock_weights sw (cost=0.00..1000.18 rows=50009 width=40) (actual time=0.000..6.167 rows=50000 loops=1) | |
| | 327 | -> Unique (cost=22.48..5250.95 rows=200 width=588) (actual time=0.145..160.204 rows=1000 loops=1) | |
| | 328 | -> Incremental Sort (cost=22.48..5125.93 rows=50009 width=588) (actual time=0.144..155.872 rows=49951 loops=1) | |
| | 329 | Sort Key: sw_1.sw_portfolio_id, ((sw_1.stock_market_value / NULLIF(pt_2.total_holdings_value, '0'::numeric))) DESC | |
| | 330 | Presorted Key: sw_1.sw_portfolio_id | |
| | 331 | Full-sort Groups: 1000 Sort Method: quicksort Average Memory: 27kB Peak Memory: 27kB | |
| | 332 | -> Merge Join (cost=0.00..2504.95 rows=50009 width=588) (actual time=0.006..96.233 rows=50000 loops=1) | |
| | 333 | Merge Cond: (pt_2.pt_portfolio_id = sw_1.sw_portfolio_id) | |
| | 334 | -> CTE Scan on portfolio_totals pt_2 (cost=0.00..4.00 rows=200 width=40) (actual time=0.000..0.330 rows=1000 loops=1) | |
| | 335 | -> Materialize (cost=0.00..1125.20 rows=50009 width=556) (actual time=0.002..31.566 rows=50000 loops=1) | |
| | 336 | -> CTE Scan on stock_weights sw_1 (cost=0.00..1000.18 rows=50009 width=556) (actual time=0.000..24.237 rows=50000 loops=1) | |
| | 337 | -> Hash (cost=54.77..54.77 rows=1000 width=21) (actual time=0.737..0.738 rows=1000 loops=1) | |
| | 338 | Buckets: 1024 Batches: 1 Memory Usage: 61kB | |
| | 339 | -> Index Scan using users_pkey on users u (cost=0.28..54.77 rows=1000 width=21) (actual time=0.017..0.546 rows=1000 loops=1) | |
| | 340 | Filter: ((role)::text = 'USER'::text) | |
| | 341 | Planning Time: 1.743 ms | |
| | 342 | Execution Time: 745.179 ms | |
| | 343 | |
| | 344 | |
| | 345 | |
| | 346 | }}} |
| | 347 | |
| | 348 | |
| | 349 | idx_stock_id_covering => Index Only Scan using idx_stock_id_covering on stock s + Heap Fetches: 50 (наместо претходниот Index Scan using stock_pkey кој правеше heap fetch за секој ред) |
| 137 | | SELECT |
| 138 | | ph.portfolio_id, |
| 139 | | ph.stock_id, |
| 140 | | s.symbol, |
| 141 | | ph.quantity::NUMERIC * s.current_price::NUMERIC AS market_value, |
| 142 | | ph.quantity::NUMERIC * (s.current_price::NUMERIC - ph.avg_price::NUMERIC) AS unrealized_pnl |
| 143 | | FROM portfolio_holdings ph |
| 144 | | JOIN stock s ON ph.stock_id = s.id |
| 145 | | WHERE quantity > 0 AND portfolio_id = 1; |
| 146 | | |
| 147 | | |
| 148 | | QUERY PLAN | |
| 149 | | ---------------------------------------------------------------------------------------------------------------------------------------+ |
| 150 | | Hash Join (cost=8.93..158.07 rows=50 width=85) (actual time=0.315..0.573 rows=50 loops=1) | |
| 151 | | Hash Cond: (ph.stock_id = s.id) | |
| 152 | | -> Bitmap Heap Scan on portfolio_holdings ph (cost=4.68..152.81 rows=50 width=26) (actual time=0.225..0.320 rows=50 loops=1) | |
| 153 | | Recheck Cond: ((portfolio_id = 1) AND (quantity > 0)) | |
| 154 | | Heap Blocks: exact=50 | |
| 155 | | -> Bitmap Index Scan on idx_ph_quantity_positive (cost=0.00..4.67 rows=50 width=0) (actual time=0.209..0.209 rows=50 loops=1)| |
| 156 | | Index Cond: (portfolio_id = 1) | |
| 157 | | -> Hash (cost=3.00..3.00 rows=100 width=21) (actual time=0.059..0.060 rows=100 loops=1) | |
| 158 | | Buckets: 1024 Batches: 1 Memory Usage: 14kB | |
| 159 | | -> Seq Scan on stock s (cost=0.00..3.00 rows=100 width=21) (actual time=0.021..0.037 rows=100 loops=1) | |
| 160 | | Planning Time: 0.401 ms | |
| 161 | | Execution Time: 0.409 ms | |
| 162 | | |
| 163 | | }}} |
| | 365 | ... |
| | 366 | |
| | 367 | QUERY PLAN | |
| | 368 | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | 369 | Sort (cost=20008.39..20010.89 rows=1000 width=448) (actual time=714.206..714.272 rows=1000 loops=1) | |
| | 370 | Sort Key: (round(COALESCE((sum(power((sw.stock_market_value / NULLIF(pt_1.total_holdings_value, '0'::numeric)), '2'::numeric))), '0'::numeric), 4)), (((p.balance)::numeric + COALESCE(pt.total_holdings_value, '0'::numeric))) DESC| |
| | 371 | Sort Method: quicksort Memory: 212kB | |
| | 372 | CTE stock_weights | |
| | 373 | -> GroupAggregate (cost=1.53..8892.19 rows=50009 width=93) (actual time=0.974..347.230 rows=50000 loops=1) | |
| | 374 | Group Key: ph.portfolio_id, ph.stock_id, s.symbol | |
| | 375 | -> Incremental Sort (cost=1.53..6516.76 rows=50009 width=39) (actual time=0.941..105.364 rows=50000 loops=1) | |
| | 376 | Sort Key: ph.portfolio_id, ph.stock_id, s.symbol | |
| | 377 | Presorted Key: ph.portfolio_id, ph.stock_id | |
| | 378 | Full-sort Groups: 1563 Sort Method: quicksort Average Memory: 27kB Peak Memory: 27kB | |
| | 379 | -> Nested Loop (cost=0.57..5006.72 rows=50009 width=39) (actual time=0.102..87.212 rows=50000 loops=1) | |
| | 380 | -> Index Scan using idx_ph_portfolio_stock_qty on portfolio_holdings ph (cost=0.41..3754.09 rows=50009 width=26) (actual time=0.071..57.958 rows=50000 loops=1) | |
| | 381 | -> Memoize (cost=0.15..0.17 rows=1 width=21) (actual time=0.000..0.000 rows=1 loops=50000) | |
| | 382 | Cache Key: ph.stock_id | |
| | 383 | Cache Mode: logical | |
| | 384 | Hits: 49950 Misses: 50 Evictions: 0 Overflows: 0 Memory Usage: 7kB | |
| | 385 | -> Index Only Scan using idx_stock_id_covering on stock s (cost=0.14..0.16 rows=1 width=21) (actual time=0.003..0.003 rows=1 loops=50) | |
| | 386 | Index Cond: (id = ph.stock_id) | |
| | 387 | Heap Fetches: 50 | |
| | 388 | CTE portfolio_totals | |
| | 389 | -> GroupAggregate (cost=0.00..1628.79 rows=200 width=112) (actual time=1.730..416.989 rows=1000 loops=1) | |
| | 390 | Group Key: sw_2.sw_portfolio_id | |
| | 391 | -> CTE Scan on stock_weights sw_2 (cost=0.00..1000.18 rows=50009 width=88) (actual time=0.976..392.621 rows=50000 loops=1) | |
| | 392 | -> WindowAgg (cost=9187.09..9234.58 rows=1000 width=448) (actual time=709.858..712.200 rows=1000 loops=1) | |
| | 393 | -> Sort (cost=9187.08..9189.58 rows=1000 width=817) (actual time=709.836..709.963 rows=1000 loops=1) | |
| | 394 | Sort Key: (COALESCE((sum(power((sw.stock_market_value / NULLIF(pt_1.total_holdings_value, '0'::numeric)), '2'::numeric))), '1'::numeric)) | |
| | 395 | Sort Method: quicksort Memory: 187kB | |
| | 396 | -> Hash Join (cost=90.03..8773.25 rows=1000 width=817) (actual time=3.217..707.863 rows=1000 loops=1) | |
| | 397 | Hash Cond: (p.user_id = u.id) | |
| | 398 | -> Merge Left Join (cost=22.76..8703.34 rows=1000 width=772) (actual time=2.453..705.425 rows=1000 loops=1) | |
| | 399 | Merge Cond: (p.id = sw_1.sw_portfolio_id) | |
| | 400 | -> Merge Left Join (cost=0.28..3445.38 rows=1000 width=224) (actual time=2.306..550.820 rows=1000 loops=1) | |
| | 401 | Merge Cond: (p.id = sw.sw_portfolio_id) | |
| | 402 | -> Merge Left Join (cost=0.28..55.77 rows=1000 width=128) (actual time=1.793..6.307 rows=1000 loops=1) | |
| | 403 | Merge Cond: (p.id = pt.pt_portfolio_id) | |
| | 404 | -> Index Scan using portfolios_pkey on portfolios p (cost=0.28..46.27 rows=1000 width=24) (actual time=0.052..2.793 rows=1000 loops=1) | |
| | 405 | -> Materialize (cost=0.00..4.50 rows=200 width=112) (actual time=1.735..2.412 rows=1000 loops=1) | |
| | 406 | -> CTE Scan on portfolio_totals pt (cost=0.00..4.00 rows=200 width=112) (actual time=1.732..1.985 rows=1000 loops=1) | |
| | 407 | -> GroupAggregate (cost=0.00..3382.61 rows=200 width=104) (actual time=0.510..543.124 rows=1000 loops=1) | |
| | 408 | Group Key: sw.sw_portfolio_id | |
| | 409 | -> Merge Join (cost=0.00..1754.82 rows=50009 width=72) (actual time=0.005..441.070 rows=50000 loops=1) | |
| | 410 | Merge Cond: (pt_1.pt_portfolio_id = sw.sw_portfolio_id) | |
| | 411 | -> CTE Scan on portfolio_totals pt_1 (cost=0.00..4.00 rows=200 width=40) (actual time=0.000..416.394 rows=1000 loops=1) | |
| | 412 | -> Materialize (cost=0.00..1125.20 rows=50009 width=40) (actual time=0.003..12.318 rows=50000 loops=1) | |
| | 413 | -> CTE Scan on stock_weights sw (cost=0.00..1000.18 rows=50009 width=40) (actual time=0.000..5.926 rows=50000 loops=1) | |
| | 414 | -> Unique (cost=22.48..5250.95 rows=200 width=588) (actual time=0.144..153.167 rows=1000 loops=1) | |
| | 415 | -> Incremental Sort (cost=22.48..5125.93 rows=50009 width=588) (actual time=0.144..149.002 rows=49951 loops=1) | |
| | 416 | Sort Key: sw_1.sw_portfolio_id, ((sw_1.stock_market_value / NULLIF(pt_2.total_holdings_value, '0'::numeric))) DESC | |
| | 417 | Presorted Key: sw_1.sw_portfolio_id | |
| | 418 | Full-sort Groups: 1000 Sort Method: quicksort Average Memory: 27kB Peak Memory: 27kB | |
| | 419 | -> Merge Join (cost=0.00..2504.95 rows=50009 width=588) (actual time=0.006..91.481 rows=50000 loops=1) | |
| | 420 | Merge Cond: (pt_2.pt_portfolio_id = sw_1.sw_portfolio_id) | |
| | 421 | -> CTE Scan on portfolio_totals pt_2 (cost=0.00..4.00 rows=200 width=40) (actual time=0.000..0.324 rows=1000 loops=1) | |
| | 422 | -> Materialize (cost=0.00..1125.20 rows=50009 width=556) (actual time=0.002..28.890 rows=50000 loops=1) | |
| | 423 | -> CTE Scan on stock_weights sw_1 (cost=0.00..1000.18 rows=50009 width=556) (actual time=0.000..21.838 rows=50000 loops=1) | |
| | 424 | -> Hash (cost=54.77..54.77 rows=1000 width=21) (actual time=0.752..0.753 rows=1000 loops=1) | |
| | 425 | Buckets: 1024 Batches: 1 Memory Usage: 61kB | |
| | 426 | -> Index Scan using users_pkey on users u (cost=0.28..54.77 rows=1000 width=21) (actual time=0.018..0.522 rows=1000 loops=1) | |
| | 427 | Filter: ((role)::text = 'USER'::text) | |
| | 428 | Planning Time: 1.951 ms | |
| | 429 | Execution Time: 715.560 ms | |
| | 430 | |
| | 431 | }}} |
| | 432 | |
| | 433 | idx_portfolios_user_id => Овој не се гледа во планот — планерот продолжи да го користи portfolios_pkey. Причина: сите 1.000 портфолија се читаат (нема селективен филтер), па PK скенот е поефикасен. |