За последние 24 часа нас посетил 16891 программист и 1646 роботов. Сейчас ищут 976 программистов ...

Выборка максимальных значений из объединенной таблицы

Тема в разделе "MySQL", создана пользователем Nikolai_, 23 мар 2012.

  1. Nikolai_

    Nikolai_ Активный пользователь

    С нами с:
    27 авг 2010
    Сообщения:
    133
    Симпатии:
    0
    Коллеги, подскажите, пожалуйста, как решить следующую задачу.

    Есть 2 таблицы:
    gallery_photo c полями id_photo, file – id фото, файл
    и
    gallery_month с полями viewings, date, id_photo – просмотров, месяц, id фото

    Связь один ко многим.

    Необходимо объединить таблицы и выбрать по одной фото за каждый месяц с наибольшим количеством просмотров.

    Если делать запрос к одной таблице, то выводит все, что надо:
    Код (Text):
    1. SELECT MAX(viewings) AS viewings, date, id_photo
    2.     FROM gallery_month
    3.     GROUP BY date
    4.     ORDER BY date DESC
    5.     LIMIT 12;
    Но если делать запрос к объединенной таблице, то скрипт зависает:
    Код (Text):
    1. SELECT MAX(gm.viewings) AS viewings,
    2.     gm.date AS date,
    3.     gm.id_photo AS id_photo,
    4.     gp.file AS file
    5.     FROM gallery_photo gp LEFT JOIN gallery_month gm ON (gp.id_photo = gm.id_photo)
    6.     GROUP BY gm.date
    7.     ORDER BY gm.date DESC
    8.     LIMIT 12;
    Подскажите в чем может быть проблема?
     
  2. Alexander Serkin

    Alexander Serkin Активный пользователь

    С нами с:
    23 мар 2012
    Сообщения:
    3
    Симпатии:
    0
  3. Nikolai_

    Nikolai_ Активный пользователь

    С нами с:
    27 авг 2010
    Сообщения:
    133
    Симпатии:
    0
    Alexander Serkin, составил запрос по Вашему примеру. Но... что-то не совсем то выводит. Показывает месяц (date), максимальное количество просмотров у самой популярной фото за месяц (viewings). Однако идентификатор (id_photo) выводится фото не с наибольшим, а с наименьшим кол-вом просмотров.

    Как думаете, в чем здесь может быть ошибка?

    Вот код:

    Код (Text):
    1. $query = "SELECT gm.viewings AS viewings,
    2.      gm.date AS date,
    3.     gp.id_photo AS id_photo
    4. FROM gallery_photo gp LEFT JOIN (SELECT MAX(viewings) AS viewings, date, id_photo
    5.   FROM gallery_month
    6.   GROUP BY date) AS gm
    7.   ON gp.id_photo=gm.id_photo ORDER BY gm.date DESC LIMIT 6;";
    8.  
    9. echo 'Дата: '.$q['date'].' Просмотров: '.$q['viewings'].' id фото: '.$q['id_photo'].'<br>';
    Вот что выводит:

     
  4. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.131
    Симпатии:
    1.250
    Адрес:
    там-сям
    Nikolai_, лучше бы вы привели create table для этих двух таблиц. Не совсем понятен смысл gallery_month и что там уникально.
    Если бы у меня была такая задача, я бы завел gallery_hits и не пытался хранить в ней месяцы, регистрировал бы факт просмотра как одну запись (дата_время, ид_картинки). Понятие "месяц" появилось бы только в запросе
    Код (Text):
    1.  
    2. `date` BETWEEN :d1 AND :d2
    здесь d1 и d2 первый и последний дни нужного месяца. Тогда рейтинг просмотров за месяц выглядел бы как-то так:
    Код (Text):
    1.  
    2. SELECT `photo_id`, count(*) AS `view_count`, MAX(`date`) AS `last_viewed`
    3. FROM `gallery_hits`
    4. WHERE `date` BETWEEN :d1 AND :d2
    5. GROUP BY `photo_id`
    6. ORDER BY `view_count` DESC
     
  5. Nikolai_

    Nikolai_ Активный пользователь

    С нами с:
    27 авг 2010
    Сообщения:
    133
    Симпатии:
    0
    artoodetoo, вот структура этих таблиц (см. ниже). При просмотре фото делается запрос к табл. gallery_month. Если там есть запись за текущий месяц (date) об этом фото (id_photo), то увеличивается кол-во просмотров (viewings). Если такой записи об этом фото за текущий месяц нет, то она создается.

    А теперь необходимо на странице вывести самые популярные (по просмотрам) фото по месяцам (за последние полгода - LIMIT 6). Но вот тут и проблема начинается. Составил запрос по примеру по ссылке Alexander Serkin, но он почему-то показывает наибольшее кол-во просмотров за месяц, но в то же время id фото выводит произвольное, не соответствующее фото с наибольшим кол-вом просмотром за месяц. Не пойму, где ошибка.

    Кто видит, где ошибка, подскажите, пожалуйста, что исправить.

    Код (Text):
    1. CREATE TABLE `gallery_photo` (
    2.   `id_photo` int(6) unsigned NOT NULL auto_increment,
    3.   `name` varchar(64) default NULL, # название фото
    4.   `file` file(64) default NULL, # файл фото
    5.   PRIMARY KEY  (`id_photo`),
    6. ) TYPE=MyISAM;
    7.  
    8. CREATE TABLE `gallery_month` (
    9.   `id` int(9) unsigned NOT NULL auto_increment,
    10.   `id_photo` int(6) unsigned NOT NULL, # файл фото
    11.   `date` date NOT NULL default '0000-00-00', # месяц, например 2012-03-00
    12.   `viewings` int(6) unsigned NOT NULL, # кол-во просмотров этого фото за месяц
    13.   PRIMARY KEY  (`id`)
    14. ) TYPE=MyISAM;
     
  6. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.131
    Симпатии:
    1.250
    Адрес:
    там-сям
    "Самые полулярные за последние полгода" не через limit делаются. Вам надо брать диапазон дат "полгода", группировать по id_photo и сортировать по sum(viewings). Т.е. примерно как я выше писал. Только count(*) замените на sum(viewings) — это будет ваше кол-во просмотров за период.

    Объединять таблицы при суммировании не нужно! Готовый результат уже можно заджойнить с gallery_photo.

    update: Кстати, имейте в виду: в mysql есть запрет на использование limit во вложенном запросе. limit, если он есть, должен быть "снаружи", самой последней строкой сложного запроса.

    Считаю попыткой хранить помесячно вы сами связали себе руки для расширенной статистики — храните или просто хиты или сумму за день, тогда статистику сможете собрать за произвольный период.
     
  7. Ganzal

    Ganzal Суперстар
    Команда форума Модератор

    С нами с:
    15 мар 2007
    Сообщения:
    9.893
    Симпатии:
    965
    ссылка не пашет ;)
     
  8. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.131
    Симпатии:
    1.250
    Адрес:
    там-сям
    а картинки грузятся? :D
     
  9. Ganzal

    Ganzal Суперстар
    Команда форума Модератор

    С нами с:
    15 мар 2007
    Сообщения:
    9.893
    Симпатии:
    965
    да, но без звука
     
  10. DarkElf

    DarkElf Активный пользователь

    С нами с:
    22 окт 2006
    Сообщения:
    1.632
    Симпатии:
    0
    Код (Text):
    1. SELECT id_photo, DATE_FORMAT(date, '%Y-%m) AS df, MAX(count(id_photo)) AS cnt FROM photo_month GROUP BY id_photo, df
    и уже сюда прикрутить джоин с данными.
     
  11. Nikolai_

    Nikolai_ Активный пользователь

    С нами с:
    27 авг 2010
    Сообщения:
    133
    Симпатии:
    0
    Коллеги, еще раз прошу помочь решить задачу. Перепробовал уже какие только можно решения, но все не то - выводит не то, что надо. Почему-то не совпадают кол-во просмотров (viewings) и id фото (id_photo) к нему. Не пойму в чем проблема.

    Надо выбрать фото (id_photo) с наибольшим кол-вом просмотров (viewings) за каждый месяц (date) и вывести эти данные: месяц, id и просмотры.

    Вот, что должно выводится по идее:

    А вот таблица:
    Кто может, подскажите, пожалуйста, корректный пример.
     
  12. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.131
    Симпатии:
    1.250
    Адрес:
    там-сям
    По твоим данным итог не совсем такой должен быть.
    Код (Text):
    1.  
    2. 3333    2012-01-00  6
    3. 2229    2012-02-00  10
    4. 22  2012-03-00  11
    Смотри: находим сколько максимум просмотров было по одной картинке каждый месяц.
    Код (Text):
    1.  
    2. SELECT `date`, max(`viewings`) AS `viewings`
    3. FROM `gallery_month`
    4. GROUP BY `date`
    здесь нет id_photo, т.к. его в один заход не добыть! другие диалекты SQL просто запретят сюда ставить id_photo, MySql разрешает и это засада, т.к. непонятно что оно в данном случае означает.
    и как нам написать агрегат тот-id-который-стоит-в-одной-строке-с-max-viewings ? ответ "никак"!!!

    поэтому оборачиваем предыдущий запрос в сложный запрос:
    Код (Text):
    1.  
    2. SELECT `gm`.*
    3. FROM
    4.   (SELECT `date`, max(`viewings`) AS `viewings`
    5.    FROM `gallery_month`
    6.    GROUP BY `date`) AS champ INNER JOIN
    7.   `gallery_month` AS `gm` ON `gm`.`date`=`champ`.`date` AND `gm`.`viewings`=`champ`.`viewings`
    теоретически у тебя могут быть несколько фото с одинаковым рейтингом просмотров, это нормально. тогда на каждый месяц будет выведено более одной записи
     
  13. Nikolai_

    Nikolai_ Активный пользователь

    С нами с:
    27 авг 2010
    Сообщения:
    133
    Симпатии:
    0
    Спасибо, artoodetoo!

    Все доходчиво объяснили. Теперь выводит то, что нужно.

    Подскажите, как можно объединить этот запрос с этой таблицей по id_photo:

    Составил вот такой запрос, но он не работает:

    Код (Text):
    1. $query = "SELECT gp.id_photo AS id_photo, t1.viewings AS viewings, t1.date AS date
    2. FROM gallery_photo gp LEFT JOIN (SELECT id_photo, date, max(viewings) AS viewings
    3.    FROM gallery_month
    4.    GROUP BY date) AS champ INNER JOIN
    5.   gallery_month AS gm ON gm.date=champ.date AND gm.viewings=champ.viewings) t1 ON gp.id_photo = t1.id_photo
    6.   ORDER BY date DESC
    7.   LIMIT 3;";
    Где тут может быть ошибка?
     
  14. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.131
    Симпатии:
    1.250
    Адрес:
    там-сям
    ошибка в том, что первый JOIN не имеет соответствующего ON

    не понял зачем тут LEFT JOIN. открытое (левое) объединение используется если приклеиваемая правая часть может отсутствовать. в твоем случае получается, что ты хочешь вывести инфу о всех картинках, а если вдруг картинка была чемпионом, то будет отмечен этот факт. ерунда вобщем, да еще и с ошибкой.

    вот это работает:
    Код (Text):
    1.  
    2. SELECT `gp`.*, `champ`.date, `champ`.viewings
    3. FROM
    4.   (SELECT `date`, max(`viewings`) AS `viewings`
    5.    FROM `gallery_month`
    6.    GROUP BY `date`) AS champ INNER JOIN
    7.   `gallery_month` AS `gm` ON `gm`.`date`=`champ`.`date` AND `gm`.`viewings`=`champ`.`viewings` INNER JOIN
    8.   `gallery_photo` AS `gp` ON `gm`.`id_photo`=`gp`.`id_photo`
     
  15. Nikolai_

    Nikolai_ Активный пользователь

    С нами с:
    27 авг 2010
    Сообщения:
    133
    Симпатии:
    0
    Спасибо, artoodetoo! Я все понял. Теперь работает, как было необходимо - вопрос исчерпан.