Простой запрос для получения максимального значения для каждого идентификатора
-
09-09-2019 - |
Вопрос
Хорошо, у меня есть такая таблица:
ID Signal Station OwnerID
111 -120 Home 1
111 -130 Car 1
111 -135 Work 2
222 -98 Home 2
222 -95 Work 1
222 -103 Work 2
Это все за один и тот же день.Мне просто нужен запрос, чтобы вернуть максимальный сигнал для каждого идентификатора:
ID Signal Station OwnerID
111 -120 Home 1
222 -95 Work 1
Я попробовал использовать MAX(), но агрегация не удалась: Station и OwnerID различаются для каждой записи.Нужно ли мне делать JOIN?
Решение
Что-то вроде этого?Соедините таблицу сама с собой и исключите строки, для которых был найден более высокий сигнал.
select cur.id, cur.signal, cur.station, cur.ownerid
from yourtable cur
where not exists (
select *
from yourtable high
where high.id = cur.id
and high.signal > cur.signal
)
Это будет отображать одну строку для каждого самого высокого сигнала, поэтому для каждого идентификатора может быть несколько строк.
Другие советы
Вы выполняете групповую операцию максимума/минимума.Это распространенная ловушка:Кажется, что это должно быть легко сделать, но в SQL это, к сожалению, не так.
Существует ряд подходов (как стандартных ANSI, так и специфичных для конкретного поставщика) к этой проблеме, большинство из которых во многих ситуациях неоптимальны.Некоторые дадут вам несколько строк, если более чем одна строка имеет одно и то же максимальное/минимальное значение;некоторые не будут.Некоторые хорошо работают с таблицами с небольшим количеством групп;другие более эффективны для большего количества групп с меньшим количеством строк в каждой группе.
Вот обсуждение из некоторых распространенных (предвзятых к MySQL, но в целом применимых).Лично, если я знаю, что нет множественных максимумов (или меня не волнует их получение), я часто склоняюсь к методу самосоединения с нулевым левым значением, который я опубликую, поскольку еще никто не делал:
SELECT reading.ID, reading.Signal, reading.Station, reading.OwnerID
FROM readings AS reading
LEFT JOIN readings AS highersignal
ON highersignal.ID=reading.ID AND highersignal.Signal>reading.Signal
WHERE highersignal.ID IS NULL;
В классическом SQL-92 (без использования операций OLAP, используемых Quassnoi) вы можете использовать:
SELECT g.ID, g.MaxSignal, t.Station, t.OwnerID
FROM (SELECT id, MAX(Signal) AS MaxSignal
FROM t
GROUP BY id) AS g
JOIN t ON g.id = t.id AND g.MaxSignal = t.Signal;
(Непроверенный синтаксис;предполагает, что ваша таблица равна «t».)
Подзапрос в предложении FROM определяет максимальное значение сигнала для каждого идентификатора;соединение объединяет это с соответствующей строкой данных из основной таблицы.
Примечание:Если для определенного идентификатора есть несколько записей, которые имеют одинаковую мощность сигнала и эта мощность равна MAX(), то вы получите несколько выходных строк для этого идентификатора.
Протестировано на IBM Informix Dynamic Server 11.50.FC3, работающем на Solaris 10:
+ CREATE TEMP TABLE signal_info
(
id INTEGER NOT NULL,
signal INTEGER NOT NULL,
station CHAR(5) NOT NULL,
ownerid INTEGER NOT NULL
);
+ INSERT INTO signal_info VALUES(111, -120, 'Home', 1);
+ INSERT INTO signal_info VALUES(111, -130, 'Car' , 1);
+ INSERT INTO signal_info VALUES(111, -135, 'Work', 2);
+ INSERT INTO signal_info VALUES(222, -98 , 'Home', 2);
+ INSERT INTO signal_info VALUES(222, -95 , 'Work', 1);
+ INSERT INTO signal_info VALUES(222, -103, 'Work', 2);
+ SELECT g.ID, g.MaxSignal, t.Station, t.OwnerID
FROM (SELECT id, MAX(Signal) AS MaxSignal
FROM signal_info
GROUP BY id) AS g
JOIN signal_info AS t ON g.id = t.id AND g.MaxSignal = t.Signal;
111 -120 Home 1
222 -95 Work 1
Для этого теста я назвал таблицу Signal_Info, но, похоже, она дает правильный ответ.Это показывает только то, что существует хотя бы одна СУБД, поддерживающая эту нотацию.Однако я немного удивлен, что MS SQL Server этого не делает — какую версию вы используете?
Меня никогда не перестает удивлять, как часто вопросы SQL задаются без имен таблиц.
with tab(id, sig, sta, oid) as
(
select 111 as id, -120 as signal, 'Home' as station, 1 as ownerId union all
select 111, -130, 'Car', 1 union all
select 111, -135, 'Work', 2 union all
select 222, -98, 'Home', 2 union all
select 222, -95, 'Work', 1 union all
select 222, -103, 'Work', 2
) ,
tabG(id, maxS) as
(
select id, max(sig) as sig from tab group by id
)
select g.*, p.* from tabG g
cross apply ( select top(1) * from tab t where t.id=g.id order by t.sig desc ) p
WITH q AS
(
SELECT c.*, ROW_NUMBER() OVER (PARTITION BY id ORDER BY signal DESC) rn
FROM mytable
)
SELECT *
FROM q
WHERE rn = 1
Это вернет одну строку, даже если есть дубликаты MAX(signal)
для данного ID
.
Наличие индекса на (id, signal)
значительно улучшит этот запрос.
Мы можем сделать, используя самостоятельное присоединение
SELECT T1.ID,T1.Signal,T2.Station,T2.OwnerID
FROM (select ID,max(Signal) as Signal from mytable group by ID) T1
LEFT JOIN mytable T2
ON T1.ID=T2.ID and T1.Signal=T2.Signal;
Или вы также можете использовать следующий запрос
SELECT t0.ID,t0.Signal,t0.Station,t0.OwnerID
FROM mytable t0
LEFT JOIN mytable t1 ON t0.ID=t1.ID AND t1.Signal>t0.Signal
WHERE t1.ID IS NULL;
select a.id, b.signal, a.station, a.owner from
mytable a
join
(SELECT ID, MAX(Signal) as Signal FROM mytable GROUP BY ID) b
on a.id = b.id AND a.Signal = b.Signal
SELECT * FROM StatusTable WHERE Signal IN ( SELECT A.maxSignal FROM ( SELECT ID, MAX(Signal) AS maxSignal FROM StatusTable GROUP BY ID ) AS A );
Выберите ID, max_signal, владелец, владелец из (select *, rank () over (раздел по идентификатору по порядку по сигналу) как max_signal из таблицы), где max_signal = 1;