Способ сделать MAX (evaluation_expression, return_expression) в SQL

#mysql #sql #sql-order-by #greatest-n-per-group #sql-limit

#mysql #sql #sql-order-by #наибольшее число на группу #sql-limit

Вопрос:

Я часто хочу получить значение смежной строки, когда я выполняю MIN MAX оператор or. Например, в следующем утверждении:

 WITH people AS (
    select 'Greg' as name, 20 as age union
    select 'Tom' as name, 17 as age
) SELECT MAX(age) FROM people;

# MAX(age)
20
 

MAX Функция выполняет эквивалент: MAX(eval_expression=age, return_expression=age) , где она всегда имеет одинаковое вычисление и возвращаемое значение (неявно). Тем не менее, я хотел бы найти name человека с максимальным возрастом. Итак, концептуальный синтаксис будет следующим: MAX(eval_expression=age, return_expression=name) . Это шаблон, который я использую довольно часто, и обычно в конечном итоге взламываю что-то вроде:

 WITH people AS (
    select 'Greg' as name, 20 as age union
    select 'Tom' as name, 17 as age
) SELECT name FROM people NATURAL JOIN (SELECT name, MAX(age) age FROM people) _;

# name
'Greg'
 

Есть ли общий способ сделать MAX(expr, return) то, что я пытаюсь выполнить?


Обновление: чтобы привести пример, где требуется агрегирование:

 with sales as (
    select DATE '2014-01-01' as date, 100 as sales, 'Fish' as product union
    select DATE '2014-01-01' as date, 105 as sales, 'Potatoes' as product union
    select DATE '2014-01-02' as date, 84 as sales, 'Salsa' as product
)  select date, max(sales) from sales group by date

# date, max(sales)
2014-01-01, 105
2014-01-02, 84
 

И как получить эквивалент: MAX(expr=sales, return=product) ? Что-то вроде:

 WITH sales AS (
    select DATE '2014-01-01' as d, 100 as revenue, 'Fish' as product union
    select DATE '2014-01-01' as d, 105 as revenue, 'Potatoes' as product union
    select DATE '2014-01-02' as d, 84 as revenue, 'Salsa' as product
) SELECT d AS date, product FROM sales NATURAL JOIN (SELECT d, MAX(revenue) AS revenue FROM sales GROUP BY d) _;

# date, product
2014-01-01, Potatoes
2014-01-02, Salsa
 

Ответ №1:

Если я здесь чего-то не упускаю — используйте limit с order by :

 WITH people AS (
    select 'Greg' as name, 20 as age union
    select 'Tom' as name, 17 as age
)
SELECT name
FROM people 
ORDER BY age DESC
LIMIT 1;

# name
'Greg'
 

Комментарии:

1. Я идиот, что не вижу этого … спасибо вам за это. На самом деле — позвольте мне привести лучший пример, где может потребоваться использовать агрегированное выражение.

2. @David542 . , , Если вам нужна одна строка — именно так я интерпретирую ваш вопрос — тогда это лучший ответ. Это был ваш первоначальный вопрос. Вы должны были принять этот ответ и задать новый вопрос, а не аннулировать этот ответ.

3. @GordonLinoff Спасибо. Я обсуждал с самим собой, должен ли я удалить этот ответ, поскольку на самом деле ОП предназначался для чего-то другого. Тем не менее, OP отредактировал вопрос таким образом, что не полностью аннулировал мой ответ, а скорее аннулировал сам вопрос — вместо одного сфокусированного вопроса теперь есть два разных вопроса — один отвечает с использованием order by и limit , а другой использует first_value — поэтому я решил не удалять его в конце концов.

Ответ №2:

Если вы хотите использовать first_value() , я бы рекомендовал:

 select distinct date, 
    first_value(product) over(partition by date order by sales desc) top_product
from sales
 

Здесь нет необходимости ни в агрегировании, ни в спецификации фрейма в функции window. Функция window обходит набор данных, начиная со строки с наибольшими продажами, поэтому всем строкам в разделе присваивается одинаковое top_product значение. Затем distinct сохраняется только одна строка на раздел.

Но в основном это заканчивается как проблема с наибольшим числом пользователей на группу, когда вам нужна строка с наибольшей продажей на каждую дату. first_value() Решение плохо масштабируется, если вы хотите использовать более одного столбца в этой строке. Типичным решением является ранжирование записей в подзапросе, а затем фильтрация. Опять же, агрегация не требуется, это только логика фильтрации:

 select *
from (
    select s.*
        row_number() over(partition by date order bys ales desc) rn
    from sales
) t
where rn = 1
 

Ответ №3:

Одним из решений было бы использовать неограниченную оконную функцию, такую как FIRST_VALUE , где вы можете сортировать раздел даты по продажам. Вот пример:

 ;WITH sales AS (
    select DATE '2014-01-01' as date, 100 as sales, 'Fish' as product union
    select DATE '2014-01-01' as date, 105 as sales, 'Potatoes' as product union
    select DATE '2014-01-01' as date, 103 as sales, 'Lettuce' as product union
    select DATE '2014-01-02' as date, 84 as sales, 'Salsa' as product
)  

SELECT DISTINCT date, LAST_VALUE(product) OVER (
    partition by date
    order by sales
    -- Default: https://dev.mysql.com/doc/refman/8.0/en/window-functions-frames.html
    -- rows between unbounded preceding and current row
    rows between unbounded preceding and unbounded following
) top_product
 FROM sales group by date;

# date, top_product
'2014-01-01', 'Potatoes'
'2014-01-02', 'Salsa'
 

Я думаю subselect , что это может быть проще для чтения (по крайней мере, для меня), но это другой вариант. Вам нужно было бы проверить производительность двух, но я думаю, что аналитическая функция (без неиндексируемого соединения) будет намного быстрее.

Комментарии:

1. Да, first_value это решение для этого сценария. Вам также, вероятно, следует использовать distinct .

2. @ZoharPeled Я только что заметил этот перегиб с моей стороны. На самом деле, я собирался спросить: почему аналитическая функция действует по-другому, когда есть GROUP BY vs, когда DISTINCT в предложении select есть a?

3. Я не знаю. Лично я почти никогда не работал с MySQL — большая часть моей работы связана с SQL Server — и я не помню разницы между group by distinct ними. Может быть, MySQL делает что-то по-другому?

4. @David542: концептуально функция window выполняется после агрегации, но до distinct . Смотрите Мой ответ для более простого способа выразить это.