Речь идет о агрегатной функции с упорядоченным набором mode () , представленной в Postgres 9.4. Возможно, вы видели это сообщение об ошибке:
ERROR: OVER is not supported for ordered-set aggregate mode
Мы можем обойти это. Но какой именно режим?
(Все предположения group_id
и type
равны NOT NULL
, в противном случае вам нужно сделать больше.)
Режим уточняющих строк
Это вычисляет режим на основе только отфильтрованного набора (с type = 'customer'
).
Вы получаете самый популярный цвет в группе среди "клиентов".
Подзапрос в простом JOIN
(без LEFT
и LATERAL
в этом случае) сделает работу - вычисляя режим один раз для группы, а не для каждой отдельной строки:
SELECT u1.id, u1.group_id, u1.color, u2.mode_color
FROM users u1
JOIN ( -- not LATERAL
SELECT group_id, type -- propagate out for the join
, mode() WITHIN GROUP (ORDER BY color) AS mode_color
FROM users
WHERE type = 'customer' -- place condition in subquery (cheap)
GROUP BY group_id, type
) u2 USING (group_id, type); -- shorthand syntax for matching names
-- WHERE type = 'customer' -- or filter later (expensive)
Чтобы не повторять ваше условие, поместите его в подзапрос и распространите его на внешний запрос в предложении соединения - я выбрал совпадающие имена столбцов и в моем примере добавил USING
.
Вы можете переместить условие во внешний запрос или даже на более поздний шаг, пока. Однако это будет излишне дороже, поскольку необходимо рассчитать режим для каждой комбинации (group_id, type)
, прежде чем результаты для каждого другого типа будут исключены на более позднем этапе.
Существуют способы параметризации вашего запроса. Подготовленные операторы, функция PL / pgSQL, см .:
или , если базовая таблица не сильно меняется, материализованное представление со всеми предварительно вычисленными режимами в (group_id, type)
заменяет подзапрос.
Еще один вариант: сначала используйте CTE для фильтрации соответствующих строк, затем условие WHERE
может остаться вне подзапроса , как вы и просили:
WITH cte AS ( -- filter result rows first
SELECT id, group_id, color
FROM users u1
WHERE type = 'customer' -- predicate goes here
)
SELECT *
FROM cte u1
LEFT JOIN ( -- or JOIN, doesn't matter here
SELECT group_id
, mode() WITHIN GROUP (ORDER BY color) AS mode_color
FROM cte -- based on only qualifying rows
GROUP BY 1
) u2 USING (group_id);
Мы можем упростить с помощью SELECT *
, поскольку USING
удобно помещает только one group_id
в набор результатов.
Режим всех рядов
Если вы хотите основать режим на всех строках (включая те, где type = 'customer'
не соответствует действительности), вам нужен другой запрос.
Вы получаете самый популярный цвет для группы среди всех участников.
Переместить предложение WHERE
во внешний запрос:
SELECT u1.id, u1.group_id, u1.color, u2.mode_color
FROM users u1
LEFT JOIN ( -- or JOIN, doesn't matter here
SELECT group_id
, mode() WITHIN GROUP (ORDER BY color) AS mode_color
FROM users
GROUP BY group_id
) u2 USING (group_id)
WHERE u1.type = 'customer';
Если ваш предикат (type = 'customer'
) достаточно избирателен, вычисление режима для всех групп может оказаться бесполезным. Сначала отфильтруйте небольшое подмножество и рассчитайте только режим для содержащихся групп Добавьте CTE для этого:
WITH cte AS ( -- filter result rows first
SELECT id, group_id, color
FROM users u1
WHERE type = 'customer'
)
SELECT *
FROM cte u1
LEFT JOIN ( -- or JOIN
SELECT group_id
, mode() WITHIN GROUP (ORDER BY color) AS mode_color
FROM (SELECT DISTINCT group_id FROM cte) g -- only relevant groups
JOIN users USING (group_id) -- but consider all rows for those
GROUP BY 1
) u2 USING (group_id);
Аналогично приведенному выше запросу CTE, но на основе всех членов группы в базовой таблице.