Оптимизация GROUP BY

chernomyrdin
Дата: 05.03.2015 14:47:50
Возможно-ли построить какой-либо индекс что-бы запрос работал быстрее:

	SELECT
		org_id,
		COUNT(id) AS img_total,
		COUNT(CASE WHEN status = 0 THEN 1 ELSE NULL END) AS status0,
		COUNT(CASE WHEN status = 1 THEN 1 ELSE NULL END) AS status1,
		COUNT(CASE WHEN status = 2 THEN 1 ELSE NULL END) AS status2,
		COUNT(CASE WHEN status = 3 THEN 1 ELSE NULL END) AS status3,
		TRUE
	FROM org_item
	GROUP BY org_id


Текущий план выполнения:

HashAggregate  (cost=34685.19..34703.57 rows=1837 width=18) (actual time=383.486..384.107 rows=1857 loops=1)
-> Seq Scan on org_item (cost=0.00..27670.77 rows=280577 width=18) (actual time=0.029..129.736 rows=280577 loops=1)
Total runtime: 384.342 ms
Ivan Durak
Дата: 05.03.2015 15:01:23
нет. Ну только если img_total вам не нужен ну и статусы высокоселективны
chernomyrdin
Дата: 05.03.2015 15:17:38
В принципе можно пожертвовать total, я его позже посчитаю как сумму статусов.
Какие в подобной ситуации возможны варианты?
Ivan Durak
Дата: 05.03.2015 15:20:41
chernomyrdin
В принципе можно пожертвовать total, я его позже посчитаю как сумму статусов.
Какие в подобной ситуации возможны варианты?

индекс на статус, если статусы высокоселективны и полно других статусов > 3
и тогда where status between 0 and 3 сгенерит поиск по индексу.
лопата
Дата: 05.03.2015 15:28:49
chernomyrdin
В принципе можно пожертвовать total, я его позже посчитаю как сумму статусов.
Какие в подобной ситуации возможны варианты?

ничего существенного вы не выиграете,
но если по мелочи на операциях -- сначала
 GROP BY org_id, status

, и только потом -- пивотинг case-ами над готовыми агрегатами.
,SUM (CASE WHEN status = 0 THEN img_total END) AS status0
...


(если есть покрывающий оба поля индекс, и визибилити мап как правило актуальна -- можно ещё немного отжать, копейки -- на IOS)
лопата
Дата: 05.03.2015 15:30:45
Ivan Durak
chernomyrdin
В принципе можно пожертвовать total, я его позже посчитаю как сумму статусов.
Какие в подобной ситуации возможны варианты?

индекс на статус, если статусы высокоселективны и полно других статусов > 3
и тогда where status between 0 and 3 сгенерит поиск по индексу.
у него тотал -- это сумма по остальным, т.ч. только на операциях при агрегировании можно чутка сэкономить. И в 9.3 -- на IOS--агрегате.
chernomyrdin
Дата: 05.03.2015 19:25:53
Возможно я не прав, возможно я в вел почтенную публику в заблуждение.
Смысл всей этой затеи в том, что есть организации у каждой их них есть некторое количество итемов пребывающих в неких состояниях, соответственно хотелось-бы получить одним запросом количество итемов в тех или иных состояниях для всех организаций.

Как таковой total не интересет так как он легко получается из суммы всех статусов организации.

Как мне показалось запрос приведенный в начале ветки, самый простой, который решает задачу "в лоб",
Возможно так будет оптимальнее:

SELECT
	org_id,
	SUM(count) AS total,
	SUM(CASE WHEN status = 0 THEN count ELSE 0 END) AS status0,
	SUM(CASE WHEN status = 1 THEN count ELSE 0 END) AS status1,
	SUM(CASE WHEN status = 2 THEN count ELSE 0 END) AS status2,
	SUM(CASE WHEN status = 3 THEN count ELSE 0 END) AS status3
FROM (
	SELECT org_id, status, count(*) as count
	FROM org_item
	GROUP BY org_id, status
) AS status
GROUP BY org_id


Но все равно план выполнения:

HashAggregate  (cost=30023.09..30025.09 rows=200 width=18) (actual time=278.918..279.648 rows=1862 loops=1)
-> HashAggregate (cost=29775.10..29830.21 rows=5511 width=10) (actual time=273.322..274.256 rows=2687 loops=1)
-> Seq Scan on org_item (cost=0.00..27670.77 rows=280577 width=10) (actual time=0.057..118.753 rows=281149 loops=1)
Total runtime: 280.044 ms

Да, индекс покрывающий org_id + status имеется:

CREATE INDEX org_item_idx_org_status ON org_item USING btree (org_id, status);
chernomyrdin
Дата: 05.03.2015 19:46:01
Еще один вариант решения, оно использует индекс, но все равно работает медленнее:

SELECT
	item.org_id,
	count(*) as total,
	(SELECT count(*) FROM org_item WHERE org_id = item.org_id AND status = 0) AS status0,
	(SELECT count(*) FROM org_item WHERE org_id = item.org_id AND status = 1) AS status1,
	(SELECT count(*) FROM org_item WHERE org_id = item.org_id AND status = 2) AS status2,
	(SELECT count(*) FROM org_item WHERE org_id = item.org_id AND status = 3) AS status3
FROM org_item as item
GROUP BY org_id


План выполнения:

HashAggregate  (cost=29073.66..484895.65 rows=1837 width=8) (actual time=216.277..396.833 rows=1862 loops=1)"
-> Seq Scan on org_item item (cost=0.00..27670.77 rows=280577 width=8) (actual time=0.014..111.748 rows=281149 loops=1)
SubPlan 1
-> Aggregate (cost=40.80..40.81 rows=1 width=0) (actual time=0.020..0.020 rows=1 loops=1862)
-> Index Only Scan using org_item_idx_org_status on org_item (cost=0.42..40.74 rows=24 width=0) (actual time=0.008..0.017 rows=24 loops=1862)
Index Cond: ((org_id = item.org_id) AND (status = 0))
Heap Fetches: 16251
SubPlan 2
-> Aggregate (cost=8.44..8.45 rows=1 width=0) (actual time=0.003..0.003 rows=1 loops=1862)
-> Index Only Scan using org_item_idx_org_status on org_item org_item_1 (cost=0.42..8.44 rows=1 width=0) (actual time=0.003..0.003 rows=1 loops=1862)
Index Cond: ((org_id = item.org_id) AND (status = 1))
Heap Fetches: 495
SubPlan 3
-> Aggregate (cost=8.44..8.45 rows=1 width=0) (actual time=0.003..0.003 rows=1 loops=1862)
-> Index Only Scan using org_item_idx_org_status on org_item org_item_2 (cost=0.42..8.44 rows=1 width=0) (actual time=0.002..0.002 rows=0 loops=1862)
Index Cond: ((org_id = item.org_id) AND (status = 2))
Heap Fetches: 0
SubPlan 4
-> Aggregate (cost=190.39..190.40 rows=1 width=0) (actual time=0.068..0.068 rows=1 loops=1862)
-> Index Only Scan using org_item_idx_org_status on org_item org_item_3 (cost=0.42..190.07 rows=128 width=0) (actual time=0.007..0.056 rows=126 loops=1862)
Index Cond: ((org_id = item.org_id) AND (status = 3))
Heap Fetches: 86943
Total runtime: 397.326 ms
Alexius
Дата: 06.03.2015 06:56:41
chernomyrdin,

а можно explain analyze этого запроса с set enable_seqscan = off; ?

	SELECT org_id, status, count(*) as count
	FROM org_item
	GROUP BY org_id, status


если будет медленней (наверняка будет), чем с seq scan, то сильно улучшить скорей всего не получится.
этта
Дата: 06.03.2015 08:08:00
Alexius
chernomyrdin,

а можно explain analyze этого запроса с set enable_seqscan = off; ?

	SELECT org_id, status, count(*) as count
	FROM org_item
	GROUP BY org_id, status


если будет медленней (наверняка будет), чем с seq scan, то сильно улучшить скорей всего не получится.
сильно улучшить не получится вообще . только если записи таблицы много шире записи индекса, и, при этом, карта видимости -- актуальна почти вся. И то это бцло бы константное улучшение, а не логарифмическое или степенное. Если же сама таблица узкая -- то выигрыша практически не будет (там мелочь на операциях, и на том, что группировать по инлдексу дешевле на dort/hash или т.п.


и интересно, какая версия пж у тс.
9.3. по однопольному индексу умеет IOS count-ы группировать. по составному не проверял. а в 9.2 не задалось (на той же структуре).