За последние 24 часа нас посетили 32843 программиста и 1821 робот. Сейчас ищут 1166 программистов ...

Join с LIMIT внутри

Тема в разделе "PHP и базы данных", создана пользователем PCSpeaker, 11 май 2013.

  1. PCSpeaker

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

    С нами с:
    26 дек 2007
    Сообщения:
    84
    Симпатии:
    0
    Здравствуйте.

    У меня есть таблица с музыкальными группами (groups) и жанрами (genres). Для связи групп и жанров используется таблица genres_groups у которой поля group_id, genre_id

    Задача: для нескольких жанров (их id я знаю) выбрать по 4 самых популярных группы. Популярность определяется столбцом avrating.
    Если бы мне нужна была одна группа, то я бы сделал так:
    Код (Text):
    1. SELECT gg.genre_id, g . *
    2. FROM genres_groups AS gg
    3. LEFT JOIN (
    4.    SELECT *
    5.    FROM  `groups`
    6.    ORDER BY avrating DESC
    7. )g ON g.id = gg.group_id
    8. WHERE gg.genre_id IN ( 1, 3, 6, 7, 10 )
    9. GROUP BY gg.genre_id
    Как же мне поставить LIMIT на мои группы? Такой вариант по понятным причинам не работает:
    Код (Text):
    1. SELECT gg.genre_id, g . *
    2. FROM genres_groups AS gg
    3. LEFT JOIN (
    4.    SELECT *
    5.    FROM  `groups`
    6.    ORDER BY avrating DESC
    7.    LIMIT 4
    8. ) g ON g.id = gg.group_id
    9. WHERE gg.genre_id IN ( 1, 3, 6, 7, 10 )
     
  2. PCSpeaker

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

    С нами с:
    26 дек 2007
    Сообщения:
    84
    Симпатии:
    0
    Долго еще сегодня пробовал разные варианты, единственное к чему пришел это к мысли, что должен быть способ сделать join к моим genre_id вот такого запроса, который выбирает 4 последних в пределах одного жанра
    Код (Text):
    1.  SELECT * FROM groups as gj
    2.     LEFT JOIN genres_groups as ggj ON ggj.group_id = gj.`id`
    3.     WHERE ggj.genre_id = 1
    4.     ORDER BY gj.avrating DESC
    5.     LIMIT 4
    только вместо цифры 1, чтобы был тот genre_id к которому выполняется join. Реально ли вообще такое?
     
  3. artoodetoo

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

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
    google помогает
    довольно частый вопрос, но как ни странно, не имеет "нормального" эффективного и красивого решения, только хаки.

    Добавлено спустя 2 минуты 6 секунд:
    если ты здесь напишешь готовые CREATE TABLE + несколько INSERT с готовыми данными, то я тебе обещаю написать подходящий SELECT.

    Добавлено спустя 2 минуты 55 секунд:
    а еще лучше создай fiddle, как в этом примере, чтобы все желающие могли поиграть с данными online
     
  4. PCSpeaker

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

    С нами с:
    26 дек 2007
    Сообщения:
    84
    Симпатии:
    0
    Да я находил в интернете такой вариант решения, но у меня id для группировки и сами данные в разных таблицах, а не в одной и я застопорился вот на таком:
    Код (Text):
    1. SELECT g.*,gg.*,
    2.   if(@typex=gg.genre_id,
    3.      @rownum:=@rownum+1,
    4.      @rownum:=1+least(0,@typex:=gg.genre_id)) AS rown
    5. FROM groups as g, genres_groups as gg, (SELECT @rownum:=1, @typex:=0) zz
    6. WHERE gg.group_id = g.id
    7. ORDER BY gg.genre_id, g.avrating DESC
    Неправильно он проставляет номера строчек почему-то.

    Дампы таблиц дам чуть позже.
     
  5. artoodetoo

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

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
  6. PCSpeaker

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

    С нами с:
    26 дек 2007
    Сообщения:
    84
    Симпатии:
    0
    Дампы будут, проблема актуальна, у меня просто доступ к базе только из дома, а домой последние дни очень поздно прихожу, не хватает сил из базы выбрать нужные данные. Спасибо, что помните об этом :)
     
  7. bkm

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

    С нами с:
    22 окт 2009
    Сообщения:
    299
    Симпатии:
    0
    я такие вещи на листке бумаги стрелочками рисую, что, куда и зачем, долго рисую, пока не пойму ;)
     
  8. PCSpeaker

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

    С нами с:
    26 дек 2007
    Сообщения:
    84
    Симпатии:
    0
    вот feddly с маленьким объемом данных
    http://sqlfiddle.com/#!2/8de75/2

    а вот sql дампы с 8000 строк если вдруг надо будет быстродействие проверить или еще что
    https://dl.dropboxusercontent.com/u/13843662/groups_temp.sql
    https://dl.dropboxusercontent.com/u/13843662/genres_groups.sql
    заранее спасибо за помощь

    p.s. в таблице groups поле id называется index ;-) надеюсь, что это никого не запутает. Просто 5 лет назад, когда я придумывал таблицу я не знал что такое index на самом деле, а в универе преподавательница по sql именно так называла primary key поля. Так оно и осталось с тех времен смущать всех своей созвучностью с индексами в базах данных.

    я тоже иногда так делаю, но тут случай, когда я понимаю что с чем должно быть связано, но на ум не приходит ни одно стандартное средство, чтобы это реализовать. Тут нужна какая-то хитрость вроде работы с переменными, как в моем примере выше...
     
  9. artoodetoo

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

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
    В данных которые в твоем fiddle ты наступил на собственные грабли: записи неуникальны, см. проверочку.
    С такими данными нельзя тестировать запросы. Вместо бесполезного поля id надо делать первичным ключем пару (genre_id, group_id) !!!

    В файлах *.sql ошибок не заметил, наверное уникальность проверялась при вводе? Короче вот рабочий запрос:
    Код (Text):
    1.  
    2. SELECT genre_id, rn, group_id, avrating
    3. FROM
    4.   (
    5.    SELECT x.group_id, x.avrating,
    6.      (@rn := IF(@prev != x.genre_id, 1, @rn + 1)) AS rn,
    7.      (@prev := x.genre_id) AS genre_id
    8.    FROM
    9.      (SELECT gg.genre_id, gg.group_id, g.avrating FROM groups AS g INNER JOIN genres_groups AS gg ON gg.group_id = g.`index`) AS x
    10.    ORDER BY x.genre_id, x.avrating DESC, x.group_id
    11.   ) AS y
    12. WHERE
    13.   rn <= 4
    fiddle

    Когда я пытался джойнить и нумеровать строки в одном под-запросе, получалась фигня, я не стал выяснять почему.
    Короче три шага: склейка - нумерация - фильтрация.

    Добавлено спустя 13 минут 37 секунд:
    P.S. что-то мне подсказывает, что если у нас одна группа может работать в разных жанрах, то рейтинг выводится в рамках жанра, а абстрактно. То есть колонка рейтинга должна поселиться в другой таблице.
     
  10. PCSpeaker

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

    С нами с:
    26 дек 2007
    Сообщения:
    84
    Симпатии:
    0
    Мой косяк. Человеческий фактор. В fiddle пришлось их вручную набивать, так как данные из таблиц были слишком большие, а просто удалить из них часть было нельзя, так как тогда бы у некоторых групп не оказалось бы связей.
    так и есть
    Спасибо болшое за запрос, в fiddle работает, но выдает пустоту на полной базе. Я дописал инициализацию переменных и всё заработало. Непонятно почему где-то работает без этого, а где-то нет. Может быть версия mysql разная. Вот итоговый вариант, мало ли кому пригодится:
    Код (Text):
    1.  
    2. SELECT genre_id, rn, group_id, avrating
    3. FROM
    4.   (
    5.    SELECT x.group_id, x.avrating,
    6.      (@rn := IF(@prev != x.genre_id, 1, @rn + 1)) AS rn,
    7.      (@prev := x.genre_id) AS genre_id
    8.    FROM
    9.      (SELECT gg.genre_id, gg.group_id, g.avrating FROM groups AS g INNER JOIN genres_groups AS gg ON gg.group_id = g.`index`) AS x, (SELECT @rn:=0,@prev:=0) as z
    10.    ORDER BY x.genre_id, x.avrating DESC, x.group_id
    11.   ) AS y
    12. WHERE
    13.   rn <= 4
    Внутрь
    Код (Text):
    1.  
    2. SELECT gg.genre_id, gg.group_id, g.avrating FROM groups AS g INNER JOIN genres_groups AS gg ON gg.group_id = g.`index`
    еще можно дописать WHERE genre_id IN (1,3,5) или любые другие условия по вкусу. Запрос кстати быстро отрабатывает даже если никаких условий не ставить и выбирать для всех жанров и из всех групп.

    artoodetoo, спасибо за помощь, воспользовался вашей подписью ;-)

    Добавлено спустя 8 минут 22 секунды:
    не совсем понял, что имеется ввиду. где-то пропущено "не".
    группы действительно работают в разных жанрах, но рейтинг у них общий и формируется из голосов людей. Люди голосуют за то нравится ли им группа в целом, а не за конкретные достижения в определенных жанрах.
     
  11. artoodetoo

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

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
    ок. рад был помочь.

    Добавлено спустя 6 минут 23 секунды:
    наверное как-то связано с "strict mode", а может с версией самой MySQL. у меня на локалхосте с полными данные всё работает.
    конечно будет правильнее явно инициализировать переменные! это мой косячек