Assuming clustered index on id and secondary index on price, let's think about how a query of this type can be executed:
- Either: Retrieve the correct subset of rows first (using range scan on
price index), and then sort the result on id DESC.
- Or: Scan the entire table in the reverse clustered index order (same as
id DESC), and filter-out unsuitable rows as you do. The scan produces a correctly ordered result, and the order is not disturbed by removing some rows, so no separate sort is needed.
Which of these two the optimizer chooses depends on the relative cost of retrieving a subset of rows then sorting vs. retrieving all rows and filtering, and TomTom already made some good points about that.
Let's now consider why nested loops are needed in the case (1)...
The index on price can only be used to retrieve price (because it is explicitly indexed) and id (implicitly included in every secondary index).
So if you ask for any additional fields (as you did through *), then they must be looked-up from the clustered index. In other words, constructing the full row requires not just one, but two physical retrievals (first from secondary then clustered index), hence nested loops.
The execution then goes like this:
- [outer loop iteration] Seek the first row (satisfying the condition) in the
price index. The id is retrieved together with price.
- [inner loop iteration] Fetch the row with that
id from the clustered index.
- [outer loop iteration] Move to the next row in the index on
price, and get its id.
- [inner loop iteration] Fetch the row with that
id from the clustered index.
- Etc...
To avoid the nested loop, try:
SELECT id, price
FROM Product
WHERE price > 50000
ORDER BY id DESC
Since the index on price covers both price (explicitly) and id (implicitly), it alone can satisfy the query, and there is no need for the clustered index lookup.
joins and neither query has anyjoins (assumingProductis a table and not a view).