add batch remove batch split batch comment selection show hidden batches hide batch highlight batch
db<>fiddle
donate feedback about
By using db<>fiddle, you agree to license everything you submit by Creative Commons CC0.
Help with an interesting Postgres question: Why isn't an Index Only Scan used on a partition accessed via the parent table?.
SHOW server_version;

CREATE TABLE category (id integer PRIMARY KEY, name varchar(255) NOT NULL, parent integer REFERENCES category(id));
CREATE TABLE product (id integer PRIMARY KEY, name varchar(255) NOT NULL);
CREATE TABLE category_to_product (category integer NOT NULL REFERENCES category(id), product integer NOT NULL REFERENCES product(id), PRIMARY KEY (category, product));
CREATE TABLE review (id integer PRIMARY KEY, product integer NOT NULL REFERENCES product(id), content varchar(255) NOT NULL);

server_version
10.7 (Debian 10.7-1.pgdg80+1)
INSERT INTO category (id, name) SELECT i, concat('Category ', i) FROM generate_series(1, 50) AS i;
INSERT INTO category (id, name, parent) SELECT i, concat('Sub-category ', i), i % 49 + 1 FROM generate_series(51, 130) AS i;
INSERT INTO product (id, name) SELECT i, concat('Product ', i) FROM generate_series(1, 12000) AS i;
INSERT INTO category_to_product (product, category) SELECT i % 12000 + 1, (random() * 129)::integer + 1 FROM generate_series(0, 19000) AS i ON CONFLICT DO NOTHING;

-- we want only the first 150000 reviews to have some for the searched products...
WITH
cat_prods (ids) AS (
SELECT array_agg(product)::int[]
FROM category_to_product
WHERE product NOT IN (
SELECT product
FROM category_to_product
WHERE category IN (
SELECT 27
UNION ALL
SELECT id FROM category WHERE parent = 27
)
)
),
data (id, product, content) AS (
SELECT
i,
CASE
WHEN i < 150001 THEN ((random() * 11999)::int + 1)
ELSE p.ids[(random() * (array_length(p.ids, 1) - 1))::int + 1]
END,
concat('This is my review #', i)
FROM generate_series(1, 654436) AS i
CROSS JOIN cat_prods AS p
)
INSERT INTO review (id, product, content)
SELECT * FROM data;

50 rows affected
80 rows affected
12000 rows affected
18939 rows affected
654436 rows affected
-- create missing indexes...
CREATE INDEX idx_category_parent ON category USING btree (parent);
CREATE INDEX "category_to_product_B" ON category_to_product USING btree (product);
CREATE INDEX idx_review_product ON review USING btree (product);

PREPARE so_56126280_prisma(int, int, int) AS
SELECT
"Alias"."id"
FROM "review" AS "Alias"
WHERE
"Alias"."id" IN (
SELECT "review"."id"
FROM "review"
WHERE
"review"."product" IN (
SELECT "category_to_product"."product"
FROM "category_to_product"
JOIN "category" AS "category_product_Alias" ON "category_product_Alias"."id" = "category_to_product"."category"
WHERE
"category_product_Alias"."id" = $1
OR
"category_product_Alias"."id" IN (
SELECT "category"."id"
FROM "category"
JOIN "category" AS "category_category_product_Alias" ON "category_category_product_Alias"."id" = "category"."parent"
WHERE "category_category_product_Alias"."id" = $1
)
)
)
ORDER BY "Alias"."id" DESC
LIMIT $2 OFFSET $3;

-- warmup to avoid I/O...
EXECUTE so_56126280_prisma(27, 11, 0);

-- prepare other query variants...
PREPARE so_56126280_laurenz(int, int, int) AS
SELECT
"Alias"."id"
FROM "review" AS "Alias"
WHERE
id
149962
149952
149892
149872
149815
149798
149788
149764
149758
149748
149745
ANALYZE category;
ANALYZE product;
ANALYZE category_to_product;
ANALYZE review;

EXPLAIN (ANALYZE, BUFFERS)
EXECUTE so_56126280_prisma(27, 11, 0);

QUERY PLAN
Limit (cost=6.47..50.19 rows=11 width=4) (actual time=13742.202..13748.872 rows=11 loops=1)
  Buffers: shared hit=2391278 read=11269 written=84
  -> Merge Semi Join (cost=6.47..2158975.45 rows=543198 width=4) (actual time=13742.201..13748.867 rows=11 loops=1)
        Merge Cond: ("Alias".id = review.id)
        Buffers: shared hit=2391278 read=11269 written=84
        -> Index Only Scan Backward using review_pkey on review "Alias" (cost=0.42..22447.97 rows=654436 width=4) (actual time=0.011..106.613 rows=504692 loops=1)
              Heap Fetches: 504692
              Buffers: shared hit=6 read=5581 written=84
        -> Nested Loop Semi Join (cost=6.05..2128101.42 rows=543198 width=4) (actual time=13604.592..13611.205 rows=11 loops=1)
              Buffers: shared hit=2391272 read=5688
              -> Index Scan Backward using review_pkey on review (cost=0.42..22447.97 rows=654436 width=8) (actual time=0.005..140.526 rows=504692 loops=1)
                    Buffers: shared hit=1413 read=5552
              -> Hash Join (cost=5.62..8.83 rows=1 width=4) (actual time=0.025..0.025 rows=0 loops=504692)
                    Hash Cond: ("category_product_Alias".id = category_to_product.category)
                    Buffers: shared hit=2389859 read=136
                    -> Seq Scan on category "category_product_Alias" (cost=5.27..8.22 rows=66 width=4) (actual time=0.006..0.018 rows=3 loops=504692)
                          Filter: ((id = 27) OR (hashed SubPlan 1))
                          Rows Removed by Filter: 127
                          Buffers: shared hit=504694
                          SubPlan 1
                            -> Nested Loop (cost=0.00..5.27 rows=2 width=4) (actual time=0.015..0.024 rows=2 loops=1)
                                  Buffers: shared hit=2
                                  -> Seq Scan on category "category_category_product_Alias" (cost=0.00..2.62 rows=1 width=4) (actual time=0.006..0.011 rows=1 loops=1)
                                        Filter: (id = 27)
                                        Rows Removed by Filter: 129
                                        Buffers: shared hit=1
                                  -> Seq Scan on category (cost=0.00..2.62 rows=2 width=8) (actual time=0.008..0.012 rows=2 loops=1)
                                        Filter: (parent = 27)
                                        Rows Removed by Filter: 128
                                        Buffers: shared hit=1
                    -> Hash (cost=0.32..0.32 rows=2 width=8) (actual time=0.004..0.004 rows=2 loops=504692)
                          Buckets: 1024 Batches: 1 Memory Usage: 9kB
                          Buffers: shared hit=1885165 read=136
                          -> Index Scan using "category_to_product_B" on category_to_product (cost=0.29..0.32 rows=2 width=8) (actual time=0.002..0.003 rows=2 loops=504692)
                                Index Cond: (product = review.product)
                                Buffers: shared hit=1885165 read=136
Planning time: 1.044 ms
Execution time: 13748.917 ms
EXPLAIN (ANALYZE, BUFFERS)
EXECUTE so_56126280_laurenz(27, 11, 0);

QUERY PLAN
Limit (cost=69570.37..69570.40 rows=11 width=8) (actual time=347.389..347.395 rows=11 loops=1)
  Buffers: shared hit=1383 read=9592, temp read=1709 written=1695
  -> Sort (cost=69570.37..70928.37 rows=543198 width=8) (actual time=347.388..347.388 rows=11 loops=1)
        Sort Key: (("Alias".id + 0)) DESC
        Sort Method: top-N heapsort Memory: 26kB
        Buffers: shared hit=1383 read=9592, temp read=1709 written=1695
        -> Hash Semi Join (cost=29115.27..57458.60 rows=543198 width=8) (actual time=130.527..346.606 rows=5126 loops=1)
              Hash Cond: ("Alias".id = review.id)
              Buffers: shared hit=1383 read=9592, temp read=1709 written=1695
              -> Seq Scan on review "Alias" (cost=0.00..11988.36 rows=654436 width=4) (actual time=0.007..83.956 rows=654436 loops=1)
                    Buffers: shared hit=664 read=4780
              -> Hash (cost=20203.30..20203.30 rows=543198 width=4) (actual time=130.152..130.152 rows=5126 loops=1)
                    Buckets: 131072 Batches: 8 Memory Usage: 1048kB
                    Buffers: shared hit=719 read=4812, temp written=7
                    -> Hash Semi Join (cost=453.97..20203.30 rows=543198 width=4) (actual time=3.010..128.942 rows=5126 loops=1)
                          Hash Cond: (review.product = category_to_product.product)
                          Buffers: shared hit=719 read=4812
                          -> Seq Scan on review (cost=0.00..11988.36 rows=654436 width=8) (actual time=0.006..64.633 rows=654436 loops=1)
                                Buffers: shared hit=632 read=4812
                          -> Hash (cost=333.78..333.78 rows=9615 width=4) (actual time=2.986..2.986 rows=419 loops=1)
                                Buckets: 16384 Batches: 1 Memory Usage: 143kB
                                Buffers: shared hit=87
                                -> Hash Join (cost=9.05..333.78 rows=9615 width=4) (actual time=0.069..2.924 rows=419 loops=1)
                                      Hash Cond: (category_to_product.category = "category_product_Alias".id)
                                      Buffers: shared hit=87
                                      -> Seq Scan on category_to_product (cost=0.00..273.39 rows=18939 width=8) (actual time=0.006..1.294 rows=18939 loops=1)
                                            Buffers: shared hit=84
                                      -> Hash (cost=8.22..8.22 rows=66 width=4) (actual time=0.048..0.048 rows=3 loops=1)
                                            Buckets: 1024 Batches: 1 Memory Usage: 9kB
                                            Buffers: shared hit=3
                                            -> Seq Scan on category "category_product_Alias" (cost=5.27..8.22 rows=66 width=4) (actual time=0.034..0.047 rows=3 loops=1)
                                                  Filter: ((id = 27) OR (hashed SubPlan 1))
                                                  Rows Removed by Filter: 127
                                                  Buffers: shared hit=3
                                                  SubPlan 1
                                                    -> Nested Loop (cost=0.00..5.27 rows=2 width=4) (actual time=0.014..0.022 rows=2 loops=1)
                                                          Buffers: shared hit=2
                                                          -> Seq Scan on category "category_category_product_Alias" (cost=0.00..2.62 rows=1 width=4) (actual time=0.005..0.010 rows=1 loops=1)
                                                                Filter: (id = 27)
                                                                Rows Removed by Filter: 129
                                                                Buffers: shared hit=1
                                                          -> Seq Scan on category (cost=0.00..2.62 rows=2 width=8) (actual time=0.008..0.011 rows=2 loops=1)
                                                                Filter: (parent = 27)
                                                                Rows Removed by Filter: 128
                                                                Buffers: shared hit=1
Planning time: 0.862 ms
Execution time: 347.449 ms
EXPLAIN (ANALYZE, BUFFERS)
EXECUTE so_56126280_a_horse(27, 11, 0);

QUERY PLAN
Limit (cost=0.86..209.48 rows=11 width=4) (actual time=2222.560..2223.436 rows=11 loops=1)
  Buffers: shared hit=3631477 read=5519
  -> Nested Loop Semi Join (cost=0.86..468235.05 rows=24688 width=4) (actual time=2222.560..2223.433 rows=11 loops=1)
        Buffers: shared hit=3631477 read=5519
        -> Index Scan Backward using review_pkey on review (cost=0.42..22447.97 rows=654436 width=8) (actual time=0.008..104.006 rows=504692 loops=1)
              Buffers: shared hit=1447 read=5518
        -> Nested Loop (cost=0.43..0.67 rows=1 width=4) (actual time=0.004..0.004 rows=0 loops=504692)
              Buffers: shared hit=3630030 read=1
              -> Index Scan using "category_to_product_B" on category_to_product (cost=0.29..0.32 rows=2 width=8) (actual time=0.001..0.002 rows=2 loops=504692)
                    Index Cond: (product = review.product)
                    Buffers: shared hit=1885294 read=1
              -> Index Scan using category_pkey on category (cost=0.14..0.17 rows=1 width=4) (actual time=0.001..0.001 rows=0 loops=872368)
                    Index Cond: (id = category_to_product.category)
                    Filter: ((id = 27) OR (parent = 27))
                    Rows Removed by Filter: 1
                    Buffers: shared hit=1744736
Planning time: 0.549 ms
Execution time: 2223.461 ms
-- disable nested loop join and see what PostreSQL comes up with here...
SET enable_nestloop = off;

EXPLAIN (ANALYZE, BUFFERS)
EXECUTE so_56126280_prisma(27, 11, 0);

SET enable_nestloop = on;

QUERY PLAN
Limit (cost=10000080111.64..10000080112.30 rows=11 width=4) (actual time=265.974..266.029 rows=11 loops=1)
  Buffers: shared hit=11375 read=10551
  -> Merge Semi Join (cost=10000080111.64..10000112343.24 rows=543198 width=4) (actual time=265.973..266.023 rows=11 loops=1)
        Merge Cond: ("Alias".id = review.id)
        Buffers: shared hit=11375 read=10551
        -> Index Only Scan Backward using review_pkey on review "Alias" (cost=0.42..22447.97 rows=654436 width=4) (actual time=0.011..103.627 rows=504692 loops=1)
              Heap Fetches: 504692
              Buffers: shared hit=37 read=5550
        -> Sort (cost=10000080111.22..10000081469.21 rows=543198 width=4) (actual time=132.031..132.033 rows=11 loops=1)
              Sort Key: review.id DESC
              Sort Method: quicksort Memory: 433kB
              Buffers: shared hit=11338 read=5001
              -> Hash Semi Join (cost=10000001192.24..10000020941.57 rows=543198 width=4) (actual time=6.222..130.699 rows=5126 loops=1)
                    Hash Cond: (review.product = category_to_product.product)
                    Buffers: shared hit=11338 read=5001
                    -> Seq Scan on review (cost=0.00..11988.36 rows=654436 width=8) (actual time=0.008..64.092 rows=654436 loops=1)
                          Buffers: shared hit=509 read=4935
                    -> Hash (cost=10000001072.05..10000001072.05 rows=9615 width=4) (actual time=6.197..6.197 rows=419 loops=1)
                          Buckets: 16384 Batches: 1 Memory Usage: 143kB
                          Buffers: shared hit=10829 read=66
                          -> Merge Join (cost=10000000005.72..10000001072.05 rows=9615 width=4) (actual time=1.264..6.142 rows=419 loops=1)
                                Merge Cond: (category_to_product.category = "category_product_Alias".id)
                                Buffers: shared hit=10829 read=66
                                -> Index Only Scan using category_to_product_pkey on category_to_product (cost=0.29..908.36 rows=18939 width=8) (actual time=0.009..4.910 rows=18099 loops=1)
                                      Heap Fetches: 18099
                                      Buffers: shared hit=10825 read=66
                                -> Index Only Scan using category_pkey on category "category_product_Alias" (cost=10000000005.43..10000000020.03 rows=66 width=4) (actual time=0.039..0.062 rows=3 loops=1)
                                      Filter: ((id = 27) OR (hashed SubPlan 1))
                                      Rows Removed by Filter: 127
                                      Heap Fetches: 130
                                      Buffers: shared hit=4
                                      SubPlan 1
                                        -> Nested Loop (cost=10000000000.00..10000000005.28 rows=2 width=4) (actual time=0.017..0.026 rows=2 loops=1)
                                              Buffers: shared hit=2
                                              -> Seq Scan on category (cost=0.00..2.62 rows=2 width=8) (actual time=0.009..0.012 rows=2 loops=1)
                                                    Filter: (parent = 27)
                                                    Rows Removed by Filter: 128
                                                    Buffers: shared hit=1
                                              -> Materialize (cost=0.00..2.63 rows=1 width=4) (actual time=0.003..0.006 rows=1 loops=2)
                                                    Buffers: shared hit=1
                                                    -> Seq Scan on category "category_category_product_Alias" (cost=0.00..2.62 rows=1 width=4) (actual time=0.005..0.010 rows=1 loops=1)
                                                          Filter: (id = 27)
                                                          Rows Removed by Filter: 129
                                                          Buffers: shared hit=1
Planning time: 0.859 ms
Execution time: 266.075 ms
EXPLAIN (ANALYZE, BUFFERS)
WITH
cte_reviews (id) AS (
SELECT
"Alias"."id"
FROM "review" AS "Alias"
WHERE
"Alias"."id" IN (
SELECT "review"."id"
FROM "review"
WHERE
"review"."product" IN (
SELECT "category_to_product"."product"
FROM "category_to_product"
JOIN "category" AS "category_product_Alias" ON "category_product_Alias"."id" = "category_to_product"."category"
WHERE
"category_product_Alias"."id" = 27
OR
"category_product_Alias"."id" IN (
SELECT "category"."id"
FROM "category"
JOIN "category" AS "category_category_product_Alias" ON "category_category_product_Alias"."id" = "category"."parent"
WHERE "category_category_product_Alias"."id" = 27
)
)
)
ORDER BY "Alias"."id" ASC
)
SELECT id
FROM cte_reviews
ORDER BY id DESC
LIMIT 11 OFFSET 0;

QUERY PLAN
Limit (cost=134580.70..134580.73 rows=11 width=4) (actual time=170.760..170.763 rows=11 loops=1)
  Buffers: shared hit=681 read=6502
  CTE cte_reviews
    -> Merge Semi Join (cost=79373.37..111604.97 rows=543198 width=4) (actual time=127.498..168.605 rows=5126 loops=1)
          Merge Cond: ("Alias".id = review.id)
          Buffers: shared hit=681 read=6502
          -> Index Only Scan using review_pkey on review "Alias" (cost=0.42..22447.97 rows=654436 width=4) (actual time=0.010..30.145 rows=149963 loops=1)
                Heap Fetches: 149963
                Buffers: shared hit=5 read=1647
          -> Sort (cost=79372.95..80730.94 rows=543198 width=4) (actual time=127.479..127.871 rows=5126 loops=1)
                Sort Key: review.id
                Sort Method: quicksort Memory: 433kB
                Buffers: shared hit=676 read=4855
                -> Hash Semi Join (cost=453.97..20203.30 rows=543198 width=4) (actual time=3.349..126.663 rows=5126 loops=1)
                      Hash Cond: (review.product = category_to_product.product)
                      Buffers: shared hit=676 read=4855
                      -> Seq Scan on review (cost=0.00..11988.36 rows=654436 width=8) (actual time=0.008..63.717 rows=654436 loops=1)
                            Buffers: shared hit=668 read=4776
                      -> Hash (cost=333.78..333.78 rows=9615 width=4) (actual time=3.324..3.324 rows=419 loops=1)
                            Buckets: 16384 Batches: 1 Memory Usage: 143kB
                            Buffers: shared hit=8 read=79
                            -> Hash Join (cost=9.05..333.78 rows=9615 width=4) (actual time=0.070..3.249 rows=419 loops=1)
                                  Hash Cond: (category_to_product.category = "category_product_Alias".id)
                                  Buffers: shared hit=8 read=79
                                  -> Seq Scan on category_to_product (cost=0.00..273.39 rows=18939 width=8) (actual time=0.006..1.576 rows=18939 loops=1)
                                        Buffers: shared hit=5 read=79
                                  -> Hash (cost=8.22..8.22 rows=66 width=4) (actual time=0.049..0.049 rows=3 loops=1)
                                        Buckets: 1024 Batches: 1 Memory Usage: 9kB
                                        Buffers: shared hit=3
                                        -> Seq Scan on category "category_product_Alias" (cost=5.27..8.22 rows=66 width=4) (actual time=0.035..0.048 rows=3 loops=1)
                                              Filter: ((id = 27) OR (hashed SubPlan 1))
                                              Rows Removed by Filter: 127
                                              Buffers: shared hit=3
                                              SubPlan 1
                                                -> Nested Loop (cost=0.00..5.27 rows=2 width=4) (actual time=0.014..0.022 rows=2 loops=1)
                                                      Buffers: shared hit=2
                                                      -> Seq Scan on category "category_category_product_Alias" (cost=0.00..2.62 rows=1 width=4) (actual time=0.005..0.010 rows=1 loops=1)
                                                            Filter: (id = 27)
                                                            Rows Removed by Filter: 129
                                                            Buffers: shared hit=1
                                                      -> Seq Scan on category (cost=0.00..2.62 rows=2 width=8) (actual time=0.008..0.011 rows=2 loops=1)
                                                            Filter: (parent = 27)
                                                            Rows Removed by Filter: 128
                                                            Buffers: shared hit=1
  -> Sort (cost=22975.73..24333.73 rows=543198 width=4) (actual time=170.759..170.760 rows=11 loops=1)
        Sort Key: cte_reviews.id DESC
        Sort Method: top-N heapsort Memory: 26kB
        Buffers: shared hit=681 read=6502
        -> CTE Scan on cte_reviews (cost=0.00..10863.96 rows=543198 width=4) (actual time=127.501..169.877 rows=5126 loops=1)
              Buffers: shared hit=681 read=6502
Planning time: 0.867 ms
Execution time: 170.847 ms
EXPLAIN (ANALYZE, BUFFERS)
WITH
cte_reviews (id) AS (
SELECT r.id
FROM review AS r
INNER JOIN category_to_product AS cp ON (r.product = cp.product)
WHERE
cp.category IN (
SELECT 27
UNION ALL
SELECT id FROM category WHERE parent = 27
)
ORDER BY 1 ASC
)
SELECT id
FROM cte_reviews
ORDER BY id DESC
LIMIT 11 OFFSET 0;

QUERY PLAN
Limit (cost=4546.95..4546.98 rows=11 width=4) (actual time=20.333..20.336 rows=11 loops=1)
  Buffers: shared hit=4249 read=2355
  CTE cte_reviews
    -> Sort (cost=3479.26..3538.84 rows=23834 width=4) (actual time=18.472..18.882 rows=5148 loops=1)
          Sort Key: r.id
          Sort Method: quicksort Memory: 434kB
          Buffers: shared hit=4249 read=2355
          -> Nested Loop (cost=8.52..1746.44 rows=23834 width=4) (actual time=0.071..16.937 rows=5148 loops=1)
                Buffers: shared hit=4249 read=2355
                -> Nested Loop (cost=8.09..219.32 rows=437 width=4) (actual time=0.056..1.549 rows=419 loops=1)
                      Buffers: shared hit=5 read=204
                      -> HashAggregate (cost=2.67..2.70 rows=3 width=4) (actual time=0.022..0.026 rows=3 loops=1)
                            Group Key: (27)
                            Buffers: shared hit=1
                            -> Append (cost=0.00..2.67 rows=3 width=4) (actual time=0.001..0.018 rows=3 loops=1)
                                  Buffers: shared hit=1
                                  -> Result (cost=0.00..0.01 rows=1 width=4) (actual time=0.000..0.001 rows=1 loops=1)
                                  -> Seq Scan on category (cost=0.00..2.62 rows=2 width=4) (actual time=0.013..0.017 rows=2 loops=1)
                                        Filter: (parent = 27)
                                        Rows Removed by Filter: 128
                                        Buffers: shared hit=1
                      -> Bitmap Heap Scan on category_to_product cp (cost=5.42..70.75 rows=146 width=8) (actual time=0.031..0.473 rows=140 loops=3)
                            Recheck Cond: (category = (27))
                            Heap Blocks: exact=202
                            Buffers: shared hit=4 read=204
                            -> Bitmap Index Scan on category_to_product_pkey (cost=0.00..5.38 rows=146 width=0) (actual time=0.021..0.021 rows=140 loops=3)
                                  Index Cond: (category = (27))
                                  Buffers: shared read=6
                -> Index Scan using idx_review_product on review r (cost=0.42..2.93 rows=56 width=8) (actual time=0.009..0.034 rows=12 loops=419)
                      Index Cond: (product = cp.product)
                      Buffers: shared hit=4244 read=2151
  -> Sort (cost=1008.11..1067.70 rows=23834 width=4) (actual time=20.332..20.333 rows=11 loops=1)
        Sort Key: cte_reviews.id DESC
        Sort Method: top-N heapsort Memory: 26kB
        Buffers: shared hit=4249 read=2355
        -> CTE Scan on cte_reviews (cost=0.00..476.68 rows=23834 width=4) (actual time=18.474..19.631 rows=5148 loops=1)
              Buffers: shared hit=4249 read=2355
Planning time: 0.583 ms
Execution time: 20.422 ms