mysql: группировка по идентификатору, получение наивысшего приоритета для каждого идентификатора

У меня есть следующая таблица mysql под названием «pics» со следующими полями и примерами данных:

id   vehicle_id    filename    priority
1    45            a.jpg       4
2    45            b.jpg       1
3    56            f.jpg       4
4    67            cc.jpg      4
5    45            kt.jpg      3
6    67            gg.jpg      1

Возможно ли в одном запросе получить одну строку для каждого Vehicle_id, и эта строка будет иметь наивысший приоритет?

Результат, который я ищу:

array (
  [0] => array( [id] => '2', [vehicle_id] => '45', [filename] => 'b.jpg',  [priority] => '1' ),
  [1] => array( [id] => '3', [vehicle_id] => '56', [filename] => 'f.jpg',  [priority] => '4' ),
  [2] => array( [id] => '6', [vehicle_id] => '67', [filename] => 'gg.jpg', [priority] => '1' )
);

Если это невозможно в одном запросе, что было бы лучшим подходом?

Спасибо!


person briang    schedule 22.06.2010    source источник
comment
Что делать, если у Vehicle_id есть 2 строки, привязанные к самому высокому значению. Вы хотите, чтобы оба обратно?   -  person Martin Smith    schedule 23.06.2010
comment
Нет, тогда я бы по умолчанию использовал самое раннее опубликованное. Только 1 строка на Vehicle_id.   -  person briang    schedule 23.06.2010
comment
@briang: Откуда ты знаешь, что опубликовано раньше всех? Тот, у кого самый низкий id?   -  person Mark Byers    schedule 23.06.2010
comment
Да, самый низкий идентификатор. У меня также есть поле date_posted. Но я оставил это для этого примера, чтобы попытаться упростить его.   -  person briang    schedule 23.06.2010
comment
@briang: Сколько строк и идентификаторов транспортных средств в вашей таблице? Насколько важна производительность?   -  person Mark Byers    schedule 23.06.2010
comment
Большое спасибо за помощь, ребята!   -  person briang    schedule 23.06.2010
comment
@Mark: В конце концов их будет больше тысячи. Производительность важна.   -  person briang    schedule 23.06.2010
comment
@briang: тысячи+ мало что говорят. Верхний предел более полезен, чем нижний предел. Могут ли быть миллионы строк? Сотни миллионов? Сколько транспортных средств будет сравниваться с количеством строк в таблице? Будет ли много Vehicle_id с небольшим количеством строк в каждом, или будет несколько транспортных средств с большим количеством строк для каждого транспортного средства. Кардинальность может быть важна при принятии решения о том, какой запрос является самым быстрым. Всего для нескольких тысяч строк производительность, вероятно, не проблема — независимо от того, как вы пишете запрос, он будет почти мгновенным, даже без индексов. Будете ли вы профилировать производительность?   -  person Mark Byers    schedule 23.06.2010
comment
@Mark: Да, потенциально миллионы строк. Я бы ХОТЕЛ видеть сотни миллионов, все зависит от успеха сайта. Но я хотел бы построить для этой возможности.   -  person briang    schedule 23.06.2010


Ответы (3)


Хотя это может быть «принятый» ответ, производительность Решение Марка при нормальных обстоятельствах во много раз лучше и в равной степени применимо к вопросу, поэтому во что бы то ни стало используйте его решение в продакшене!


SELECT a.id, a.vehicle_id, a.filename, a.priority
FROM pics a
LEFT JOIN pics b               -- JOIN for priority
ON b.vehicle_id = a.vehicle_id 
AND b.priority > a.priority
LEFT JOIN pics c               -- JOIN for priority ties
ON c.vehicle_id = a.vehicle_id 
AND c.priority = a.priority 
AND c.id < a.id
WHERE b.id IS NULL AND c.id IS NULL

Предполагая, что «id» является необнуляемым столбцом.

[править]: мой плохой, нужно второе соединение, не могу сделать это только с одним.

person Wrikken    schedule 22.06.2010
comment
Не будет ли это слишком медленным? Вы проверяли его работоспособность? Каким образом другие ответы не являются «одним запросом»? - person Mark Byers; 23.06.2010
comment
@Mark: я немного не знаком с реальным тестированием производительности. Я только предполагал, что один запрос будет быстрее, чем несколько. Я хотел бы протестировать их. Есть ли конкретный ресурс, с которым я мог бы протестировать эти параметры? Спасибо за помощь! - person briang; 23.06.2010
comment
«Ни один запрос» действительно не очень точен, как и все ответы здесь. Это не будет медленным с правильным INDEX(vehicle_id,priority), MySQL распознает конструкцию «LEFT JOIN без результатов» с довольно оптимизированным запросом, простым, по моему опыту. Разница может варьироваться в зависимости от количества записей на идентификатор транспортного средства. Если бы мне пришлось угадывать (и таблица была правильно проиндексирована), я бы сказал, что ответ Марка Байерса, вероятно, работает лучше с небольшим количеством записей на один Vehicle_id, в то время как этот может получить больше преимуществ от индекса при большом количестве записей на идентификатор_транспортного средства. - person Wrikken; 23.06.2010
comment
То есть: по сравнению с собой. Сравнение производительности обоих этих ответов в реальной жизненной ситуации — это то, что следует сделать. - person Wrikken; 23.06.2010
comment
@Mark: Извините, я пропустил ваш завершающий вопрос. Каким образом другие ответы не являются «одним запросом»? Разве подзапрос не является дополнительным запросом? - person briang; 23.06.2010
comment
@Wrikken: Даже с индексом, который вы предложили для 10000 записей на фотографиях (100 Vehicle_ID, 7 уровней приоритета), я оцениваю это как 1,3 секунды. Мое решение занимает 0,09 с для тех же данных. Я могу опубликовать сценарии для создания тестовых данных, которые я использовал, если хотите. Я бы хотел, чтобы что-то такое простое было быстрым... может быть, я что-то упускаю, но я очень скептически отношусь к тому, что этот метод будет таким же быстрым, как два других решения. - person Mark Byers; 23.06.2010
comment
@Mark: завтра проведу несколько тестов, мне тоже интересно, как это может измениться или не измениться в зависимости от количества записей на идентификатор транспортного средства и w. Если у вас уже есть сценарий для создания тестовых данных, я был бы признателен, если бы вы могли вставить его куда-нибудь, кажется пустой тратой времени, чтобы написать свой собственный :) Конечно, я уничтожу (небольшое) преимущество, вероятно, уже имеющего записи для того, чтобы в таблице просто отметить, но я не думаю, что это значительно повлияет на результат. - person Wrikken; 23.06.2010
comment
@Mark: извините за задержку, я был занят: a.t.m, с настройкой самой предвзятой среды, которую я мог придумать для своего решения для присоединения, оно все еще примерно в 3 раза медленнее, чем ваше (версия 5.1.47). Буду иметь в виду ваше решение. - person Wrikken; 07.07.2010
comment
Очень полезный. Я знал стратегию самосоединения, направленную на оптимизацию Not Exists, но не мог придумать двойное самосоединение. Большое тебе спасибо. - person mpyw; 06.11.2018

Кажется, это типичный максимальный запрос на группу. В большинстве баз данных вы можете легко сделать это, используя ROW_NUMBER:

SELECT id, vehicle_id, filename, priority
FROM (
    SELECT 
        id, vehicle_id, filename, priority,
        ROW_NUMBER() OVER (PARTITION BY vehicle_id
                           ORDER BY priority DESC, id) AS rn
    FROM pics
) AS T1
WHERE rn = 1

Поскольку MySQL еще не поддерживает ROW_NUMBER, вы можете эмулировать его с помощью переменных:

SELECT id, vehicle_id, filename, priority
FROM (
    SELECT
        id, vehicle_id, filename, priority,
        @rn := CASE WHEN @prev_vehicle_id = vehicle_id
                    THEN @rn + 1
                    ELSE 1
               END AS rn,
        @prev_vehicle_id := vehicle_id
    FROM (SELECT @prev_vehicle_id := NULL) vars, pics T1
    ORDER BY vehicle_id, priority DESC, id
) T2
WHERE rn = 1
person Mark Byers    schedule 22.06.2010
comment
По какой-то причине люди действительно находят переменные в MySQL пугающими, я думаю, это незнакомство с '@' и ':=' по сравнению с ANSI SQL, которые их пугают. Это не должно, конечно, переменные могут значительно повысить производительность. - person Wrikken; 23.06.2010

Обратите внимание, что этот метод был опубликован до того, как стало ясно, что он необходим для обработки связей приоритетов. Я оставляю его здесь для справки (см. комментарии ниже). Ознакомьтесь с @Mark answer для решения, которое обрабатывает связи по мере необходимости:

SELECT p.id, p.vehicle_id, p.filename, p.priority
FROM   pics p
JOIN   (
           SELECT   vehicle_id, MAX(priority) max_priority
           FROM     pics
           GROUP BY vehicle_id
       ) sub_p ON (sub_p.vehicle_id = p.vehicle_id AND 
                   sub_p.max_priority = p.priority)
GROUP BY p.vehicle_id;

Это предполагает, что не может быть ни одного приоритета для одного и того же vehicle_id.

Прецедент:

CREATE TABLE pics (id int, vehicle_id int, filename varchar(10), priority int);

INSERT INTO pics VALUES ('1', '45', 'a.jpg', '4');
INSERT INTO pics VALUES ('2', '45', 'b.jpg', '1');
INSERT INTO pics VALUES ('3', '56', 'f.jpg', '4');
INSERT INTO pics VALUES ('4', '67', 'cc.jpg', '4');
INSERT INTO pics VALUES ('5', '45', 'kt.jpg', '3');
INSERT INTO pics VALUES ('6', '67', 'gg.jpg', '1');

Результат:

+------+------------+----------+----------+
| id   | vehicle_id | filename | priority |
+------+------------+----------+----------+
|    1 |         45 | a.jpg    |        4 |
|    3 |         56 | f.jpg    |        4 |
|    4 |         67 | cc.jpg   |        4 |
+------+------------+----------+----------+
3 rows in set (0.01 sec)
person Daniel Vassallo    schedule 22.06.2010
comment
Это работает только в том случае, если им нужна одна запись, мин/макс. Это не сработает, если они хотят топ 5/10/и т.д. - person OMG Ponies; 23.06.2010
comment
@OMG: Это правда. Но я думаю, что это единственный недостаток, не так ли? ... OP, похоже, не требует этого: строка имеет наивысший приоритет - person Daniel Vassallo; 23.06.2010
comment
Он прямо сказал, что ему нужна только одна строка, поэтому я не думаю, что это серьезная проблема. Более серьезная проблема заключается в том, что это может вернуть более одной строки, хотя это поправимо. - person Mark Byers; 23.06.2010
comment
Никогда не знаешь, каковы чьи-то требования, никогда не поздно для приманки. - person OMG Ponies; 23.06.2010
comment
PS: проверьте вопрос еще раз - он только что опубликовал свои требования для тай-брейка - первый разместил. Похоже, вам понадобится это исправление в конце концов ... - person Mark Byers; 23.06.2010
comment
@Mark: Да, я обновил свой ответ, чтобы было ясно, что он предполагает, что не может быть никаких приоритетных связей ... Я стараюсь избегать использования переменных в MySQL, если только это не является строго необходимым. Однако я не уверен, что эта тенденция оправдана. Если дело не будет иметь приоритетных связей, вы все равно будете использовать метод переменных? (просто любопытно) - person Daniel Vassallo; 23.06.2010
comment
@Daniel Vassallo: этот подход должен давать более высокую производительность, чем подход с переменными, так что это одна из причин, по которой следует использовать его, а не метод, который я предложил. Но этот метод труднее освоить. Количество столбцов, участвующих в разрешении конфликтов, изменяет структуру запроса. Изменить мой ответ, чтобы включить дополнительный столбец, было тривиально. Так что я думаю, что оба ответа имеют плюсы и минусы. Жаль, что нет очевидного лучшего ответа на этот простой вопрос. - person Mark Byers; 23.06.2010
comment
@Mark: Это совершенно верно. Вероятно, правильно обрабатывать правила ничьей сложнее, чем того стоит. - person Daniel Vassallo; 23.06.2010