Dieser Abschnitt enthält die PostgreSQL Skripts und Ausführungspläne für Kapitel 6, „Sortieren und Gruppieren“.
Indexed Order By
DROP INDEX sales_date;
CREATE INDEX sales_dt_pr ON sales (sale_date, product_id);
EXPLAIN
SELECT sale_date, product_id, quantity
FROM sales
WHERE sale_date = now() - INTERVAL '1' DAY
ORDER BY sale_date, product_id;
Die Ausführung erfolgt ohne Sort:
QUERY PLAN
-----------------------------------------------------------
Index Scan using sales_dt_pr (cost=0.01..680.86 rows=376)
Index Cond: (sale_date = (now() - '1 day'::interval day))
Derselbe Ausführungsplan wird verwendet, wenn nur nach PRODUCT_ID
sortiert wird:
EXPLAIN
SELECT sale_date, product_id, quantity
FROM sales
WHERE sale_date = now() - INTERVAL '1' DAY
ORDER BY product_id;
Wenn man den größer-gleich Operator verwendet, muss eine Sort Operation durchgeführt werden:
EXPLAIN
SELECT sale_date, product_id, quantity
FROM sales
WHERE sale_date >= now() - INTERVAL '1' DAY
ORDER BY product_id;
Die rows Schätzung ändert sich jedoch auf eine Zeile, und damit ist auch der Cost-Wert kleiner.
QUERY PLAN
--------------------------------------------------------------
Sort (cost=8.50..8.50 rows=1 width=32)
Sort Key: product_id
-> Index Scan using sales_dt_pr (cost=0.00..8.49 rows=1)
Index Cond: (sale_date >= (now() - '1 day'::interval day))
Order By ASC/DESC und NULLS FIRST/LAST
Der Index kann auch rückwärts gelesen werden:
EXPLAIN
SELECT sale_date, product_id, quantity
FROM sales
WHERE sale_date >= now() - INTERVAL '1' DAY
ORDER BY sale_date DESC, product_id DESC;
Gemischte ASC
/DESC
Angeben erzwingen jedoch eine explizite Sortierung:
EXPLAIN
SELECT sale_date, product_id, quantity
FROM sales
WHERE sale_date >= now() - INTERVAL '1' DAY
ORDER BY sale_date ASC, product_id DESC;
PostgreSQL kann jedoch auch ASC
/DESC
indizieren, sodass die Abfrage wieder als pipelined order by
durchgeführt werden kann.
DROP INDEX sales_dt_pr;
CREATE INDEX sales_dt_pr
ON sales (sale_date ASC, product_id DESC);
EXPLAIN
SELECT sale_date, product_id, quantity
FROM sales
WHERE sale_date >= now() - INTERVAL '1' DAY
ORDER BY sale_date ASC, product_id DESC;
PostgreSQL sortiert standardmäßig mit NULLS LAST
. Die Kombination mit DESC
führt jedoch dazu, dass die NULL
Einträge am Anfang landen:
EXPLAIN
SELECT sale_date, product_id, quantity
FROM sales
WHERE sale_date >= now() - INTERVAL '1' DAY
ORDER BY sale_date ASC, product_id DESC NULLS LAST;
PostgreSQL kann jedoch auch NULLS FIRST
/NULLS LAST
Spezifikationen bei der Indizierung berücksichtigen, sodass auch dafür ein pipelined order by
bewirkt werden kann:
DROP INDEX sales_dt_pr;
CREATE INDEX sales_dt_pr
ON sales (sale_date ASC, product_id DESC NULLS LAST);
EXPLAIN
SELECT sale_date, product_id, quantity
FROM sales
WHERE sale_date >= now() - INTERVAL '1' DAY
ORDER BY sale_date ASC, product_id DESC NULLS LAST;
Indexing Group By
Es scheint als hätte die PostgreSQL (zumindest 9.0-13) Datenbank ein kleines Problem mit pipelined order by
, wenn der Index eine NULLS
Sortierung entgegen der natürlichen NULLS
-Sortierung benutzt. Im oberen Beispiel war das der Fall:
EXPLAIN
SELECT product_id, SUM(eur_value)
FROM sales
WHERE sale_date = now() - INTERVAL '1' DAY
GROUP BY product_id;
QUERY PLAN
------------------------------------------------------------------------------------
HashAggregate (cost=574.21..574.53 rows=26 width=40)
Group Key: product_id
-> Index Scan using sales_dt_pr on sales (cost=0.43..572.62 rows=318 width=14)
Index Cond: (sale_date = (now() - '1 day'::interval day))
Ohne NULLS LAST
, wird ein pipelined order by
Ausgeführt:
DROP INDEX sales_dt_pr;
CREATE INDEX sales_dt_pr
ON sales (sale_date ASC, product_id DESC);
EXPLAIN
SELECT product_id, SUM(eur_value)
FROM sales
WHERE sale_date = now() - INTERVAL '1' DAY
GROUP BY product_id;
QUERY PLAN
---------------------------------------------------------------------------------------------
GroupAggregate (cost=0.43..574.53 rows=26 width=40)
Group Key: product_id
-> Index Scan Backward using sales_dt_pr on sales (cost=0.43..572.62 rows=318 width=14)
Index Cond: (sale_date = (now() - '1 day'::interval day))
Auffällig ist jedoch, dass der Index rückwärts gelesen wird. Das legt die Vermutung nahe, dass PostgreSQL intern eine ORDER BY PRODUCT_ID
durchführt. Dann könnte man mit einen gegenläufigen, expliziten order by
das pipelined order by
unterbinden:
EXPLAIN
SELECT product_id, SUM(eur_value)
FROM sales
WHERE sale_date = now() - INTERVAL '1' DAY
GROUP BY product_id
ORDER BY product_id DESC;
QUERY PLAN
------------------------------------------------------------------------------------
GroupAggregate (cost=0.43..574.53 rows=26 width=40)
Group Key: product_id
-> Index Scan using sales_dt_pr on sales (cost=0.43..572.62 rows=318 width=14)
Index Cond: (sale_date = (now() - '1 day'::interval day))
Da das Ergebnis dieser These entgegenspricht, führe ich noch einen Test mit einem NULLS LAST
Index durch:
DROP INDEX sales_dt_pr;
CREATE INDEX sales_dt_pr
ON sales (sale_date ASC, product_id DESC NULLS LAST);
EXPLAIN
SELECT product_id, SUM(eur_value)
FROM sales
WHERE sale_date = now() - INTERVAL '1' DAY
GROUP BY product_id
ORDER BY product_id DESC NULLS LAST;
QUERY PLAN
------------------------------------------------------------------------------------
GroupAggregate (cost=0.43..574.53 rows=26 width=40)
Group Key: product_id
-> Index Scan using sales_dt_pr on sales (cost=0.43..572.62 rows=318 width=14)
Index Cond: (sale_date = (now() - '1 day'::interval day))
Letztendlich scheint es also so, als würde PostgreSQL eine implizite order by
-Klausel einfügen, dabei aber die NULLS LAST
Spezifiktion des Indexes ignorieren. Wenn man jedoch eine explizite order by
-Klausel angibt, die der Indexdefinition entspricht, kann PostgreSQL ein pipelined group by
durchführen.
Durch weitere Tests stellt sich heraus, das dieses Problem auch für NULLS FIRST
-Indizes besteht, wenn die entsprechende Spalte eine ASC
-Sortierung hat:
DROP INDEX sales_dt_pr;
CREATE INDEX sales_dt_pr
ON sales (sale_date ASC, product_id ASC NULLS FIRST);
EXPLAIN
SELECT product_id, SUM(eur_value)
FROM sales
WHERE sale_date = now() - INTERVAL '1' DAY
GROUP BY product_id
ORDER BY product_id ASC NULLS FIRST;
QUERY PLAN
------------------------------------------------------------------------------------
GroupAggregate (cost=0.43..574.53 rows=26 width=40)
Group Key: product_id
-> Index Scan using sales_dt_pr on sales (cost=0.43..572.62 rows=318 width=14)
Index Cond: (sale_date = (now() - '1 day'::interval day))
Lässt man die Order by
-Klausel weg:
EXPLAIN
SELECT product_id, SUM(eur_value)
FROM sales
WHERE sale_date = now() - INTERVAL '1' DAY
GROUP BY product_id;
QUERY PLAN
------------------------------------------------------------------------------------
HashAggregate (cost=574.21..574.53 rows=26 width=40)
Group Key: product_id
-> Index Scan using sales_dt_pr on sales (cost=0.43..572.62 rows=318 width=14)
Index Cond: (sale_date = (now() - '1 day'::interval day))