| | 130 | |
| | 131 | }}} |
| | 132 | |
| | 133 | == Scenario 3 - Impact of Admin Discounts on Sales Numbers |
| | 134 | |
| | 135 | {{{#!div style="text-align: justify; width: 100%;" |
| | 136 | |
| | 137 | ==== Without indexes |
| | 138 | |
| | 139 | The query was tested on approximately 25,017 orders and 75,021 order-product records. Before indexing, PostgreSQL used sequential scans on both {{{orders}}} and {{{order_products}}} while calculating the 30-day periods before and after each discount. |
| | 140 | |
| | 141 | {{{ |
| | 142 | Seq Scan on order_products |
| | 143 | rows=75021 |
| | 144 | |
| | 145 | Seq Scan on orders |
| | 146 | rows=5009 |
| | 147 | Rows Removed by Filter: 20008 |
| | 148 | }}} |
| | 149 | |
| | 150 | The query was executed 10 times and the average execution time without indexes was: **103.004 ms** |
| | 151 | |
| | 152 | ==== Indexes |
| | 153 | |
| | 154 | {{{ |
| | 155 | CREATE INDEX idx_order_products_product_order |
| | 156 | ON project.order_products (product_id, order_id) |
| | 157 | INCLUDE (quantity, price_at_purchase); |
| | 158 | |
| | 159 | CREATE INDEX idx_orders_status_purchase_date |
| | 160 | ON project.orders (status, purchase_date, order_id); |
| | 161 | |
| | 162 | ANALYZE project.order_products; |
| | 163 | ANALYZE project.orders; |
| | 164 | }}} |
| | 165 | |
| | 166 | * {{{idx_order_products_product_order}}} was tested to support lookups of order products by {{{product_id}}} and provide the quantity and purchase price required by the aggregation. |
| | 167 | |
| | 168 | * {{{idx_orders_status_purchase_date}}} targets the order status filter and includes the purchase date and {{{order_id}}} needed for the sales-period joins. |
| | 169 | |
| | 170 | ==== With indexes |
| | 171 | |
| | 172 | PostgreSQL used: |
| | 173 | |
| | 174 | {{{ |
| | 175 | Index Only Scan using idx_orders_status_purchase_date on orders |
| | 176 | Heap Fetches: 0 |
| | 177 | }}} |
| | 178 | |
| | 179 | The index was used for both the pre-promotion and post-promotion order lookups. |
| | 180 | |
| | 181 | However, {{{idx_order_products_product_order}}} was not used. PostgreSQL continued using: |
| | 182 | |
| | 183 | {{{ |
| | 184 | Seq Scan on order_products |
| | 185 | }}} |
| | 186 | |
| | 187 | because scanning the table and performing the hash joins was estimated to be cheaper for the current data distribution. |
| | 188 | |
| | 189 | The query was executed 10 times with both indexes present and the average execution time was: **97.710 ms** |
| | 190 | |
| | 191 | The unused {{{idx_order_products_product_order}}} index was removed after testing. |
| | 192 | |
| | 193 | ==== Performance comparison and conclusion |
| | 194 | |
| | 195 | {{{ |
| | 196 | Without indexes: 103.004 ms |
| | 197 | With indexes: 97.710 ms |
| | 198 | Improvement: 5.14% |
| | 199 | }}} |
| | 200 | |
| | 201 | * {{{idx_orders_status_purchase_date}}} was successfully used as an Index Only Scan in both the pre-promotion and post-promotion parts of the query. |
| | 202 | |
| | 203 | * {{{idx_order_products_product_order}}} was not used by the optimizer and was removed. |
| | 204 | |
| | 205 | }}} |
| | 206 | |
| | 207 | == Scenario 4 - Customer Habits and Points Spending |
| | 208 | |
| | 209 | ==== Without indexes |
| | 210 | |
| | 211 | The query was tested on approximately 25,017 orders and 75,021 order-product records. Before indexing, PostgreSQL used a sequential scan on {{{orders}}} and scanned all {{{order_products}}} records to calculate order totals. |
| | 212 | |
| | 213 | {{{ |
| | 214 | Seq Scan on orders |
| | 215 | rows=5009 |
| | 216 | Rows Removed by Filter: 20008 |
| | 217 | |
| | 218 | Seq Scan on order_products |
| | 219 | rows=75021 |
| | 220 | }}} |
| | 221 | |
| | 222 | The {{{order_products}}} aggregation also required temporary disk usage: |
| | 223 | |
| | 224 | {{{ |
| | 225 | HashAggregate |
| | 226 | Batches: 5 |
| | 227 | Disk Usage: 760kB |
| | 228 | }}} |
| | 229 | |
| | 230 | The query was executed 10 times and the average execution time without indexes was: **93.895 ms** |
| | 231 | |
| | 232 | ==== Indexes |
| | 233 | |
| | 234 | {{{ |
| | 235 | CREATE INDEX idx_orders_status_purchase_date |
| | 236 | ON project.orders (status, purchase_date, order_id); |
| | 237 | |
| | 238 | ANALYZE project.orders; |
| | 239 | }}} |
| | 240 | |
| | 241 | The index targets the {{{status}}} and {{{purchase_date}}} filters on {{{orders}}} and includes {{{order_id}}} for the join with aggregated order totals. |
| | 242 | |
| | 243 | ==== With indexes |
| | 244 | |
| | 245 | After indexing, PostgreSQL used: |
| | 246 | |
| | 247 | {{{ |
| | 248 | Bitmap Index Scan on idx_orders_status_purchase_date |
| | 249 | |
| | 250 | Bitmap Heap Scan on orders |
| | 251 | }}} |
| | 252 | |
| | 253 | This replaced the sequential scan on {{{orders}}}. |
| | 254 | |
| | 255 | However, {{{order_products}}} was still processed using: |
| | 256 | |
| | 257 | {{{ |
| | 258 | Seq Scan on order_products |
| | 259 | rows=75021 |
| | 260 | }}} |
| | 261 | |
| | 262 | because the query needs to aggregate essentially the whole {{{order_products}}} table to calculate total spend per order. |
| | 263 | |
| | 264 | The query was executed 10 times and the average execution time with indexes was: **90.547 ms** |
| | 265 | |
| | 266 | ==== Performance comparison and conclusion |
| | 267 | |
| | 268 | {{{ |
| | 269 | Without indexes: 93.895 ms |
| | 270 | With indexes: 90.547 ms |
| | 271 | Improvement: 3.57% |
| | 272 | }}} |
| | 273 | |
| | 274 | * {{{idx_orders_status_purchase_date}}} was successfully used through a Bitmap Index Scan and reduced the cost of filtering orders. |
| | 275 | |
| | 276 | * The overall improvement was approximately 3.57% because the main remaining cost is the full aggregation of {{{order_products}}}, which still requires a sequential scan and temporary disk usage. |