Запрос MySQL для товаров, средняя цена которых меньше X?

Я озадачен тем, как сделать следующее исключительно в MySQL, и я прибег к тому, чтобы взять свой набор результатов и впоследствии манипулировать им в ruby, что не кажется идеальным.

Вот вопрос. С набором данных, таких как:

id  state_id  price  issue_date  listed
1   5         450    2011        1
1   5         455    2011        1
1   5         490    2011        1 
1   5         510    2012        0
1   5         525    2012        1
...

Я пытаюсь получить что-то вроде:

SELECT * FROM items 
WHERE ([some conditions], e.g. issue_date >= 2011 and listed=1) 
 AND state_id = 5
GROUP BY id
HAVING AVG(price) <= 500
ORDER BY price DESC 
LIMIT 25

По сути, я хочу получить «группу» товаров, средняя цена которых ниже определенного порога. Я знаю, что приведенный выше пример «группировать по» и «иметь» неверен, поскольку он просто дает AVG(price) этого одного элемента, что на самом деле не имеет смысла. Я просто пытаюсь проиллюстрировать желаемый результат.

Здесь важно то, что мне нужны все отдельные элементы в моем наборе результатов, я не хочу просто видеть одну строку со средней ценой, общим итогом и т. Д.

В настоящее время я просто выполняю вышеуказанный запрос без HAVING AVG(price) и добавляю отдельные элементы один за другим (в рубинах), пока не достигну желаемого среднего значения. Было бы здорово, если бы я мог понять, как это сделать в SQL. Использование подзапросов или чего-то умного, например, присоединения таблицы к самой себе, безусловно, являются приемлемыми решениями, если они работают хорошо! Спасибо!

ОБНОВЛЕНИЕ: В ответ на ответ Тюдора ниже, вот некоторые пояснения. В дополнение к целевому среднему значению всегда будет целевое количество. И мы всегда сортируем результаты по цене от низкой к высокой и по дате.

Так что, если бы у нас было 10 предметов, все по цене 5 долларов, и мы хотели бы найти 5 предметов со средней стоимостью <6 долларов, мы просто вернули бы первые 5 предметов. Мы не вернем только первый, и мы не вернем первые 3, сгруппированные с последними 2. По сути, мой код в ruby ​​работает прямо сейчас.


person kaptron    schedule 05.02.2012    source источник


Ответы (3)


То, что делает MySQL, совершенно логично. То, что вы хотите сделать, не имеет смысла:

  • если у вас есть, скажем, 4 элемента, каждый с ценой 5, и вы указываете HAVING AVERAGE <= 7 то, что вы говорите, это то, что запрос должен возвращать ВСЕ перестановки, например:

    • {1} - since item with id 1, can be a group by itself
    • {1,2}
    • {1,3}
    • {1,4}
    • {1,2,3}
    • {1,2,4}

    ...

    • и так далее?

Ваш алгоритм вычисления среднего в рубине также недействителен, если у вас есть элементы со значениями 5, 1, 7, 10 - и вы ищете среднее значение меньше 7, элемент со значением 10 может быть возвращен только в группе с элемент значения 1. Но, по вашему алгоритму (если я правильно понял), элемент со значением 1 возвращается в первую группу.

Обновить

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

После поиска в Google я нашел эта статья, в которой делается попытка решить проблему ранца с ИИ, написанным на SQL.

Рассматривая цену вашего предмета как вес, имея количество предметов и желаемое среднее значение, вы можете вычислить максимальное значение, которое можно ввести в «рюкзак», умножив desired_cost на number_of_items.

person Tudor Constantin    schedule 05.02.2012
comment
Спасибо за ответ. Это хорошее объяснение того, что происходит, но, пожалуйста, просмотрите мой обновленный вопрос для уточнения. Например, у нас может быть целевое количество 3, и в этом случае будет один правильный ответ: {1,2,3}. Я действительно хочу делать такие вещи, как поиск ближайшего совпадения, если точное совпадение не найдено, но это можно сделать с помощью дополнительных запросов. - person kaptron; 05.02.2012
comment
Еще раз спасибо. Я думаю, вы действительно решили проблему. Я не уверен, был ли мой первоначальный подход правильным, но вы определенно указали мне правильное направление. - person kaptron; 06.02.2012

Я бы сделал почти противоположное тому, что предоставил Джаспер ... Начните свой запрос с ваших критериев, чтобы явно ограничить несколько элементов, которые МОГУТ квалифицироваться, вместо того, чтобы получать все элементы и запускать подвыборку для каждой записи. Могу выдать себя за более высокую производительность ... Могу ошибаться, но вот мое предложение ...

select
      i2.*
   from
      ( SELECT  i.id
           FROM items i
           WHERE 
                 i.issue_date > 2011 
             AND i.listed = 1
             AND i.state_id = 5
           GROUP BY
              i.id
           HAVING 
              AVG( i.price) <= 500 ) PreQualify

      JOIN items i2
         on PreQualify.id = i2.id
             AND i2.issue_date > 2011 
             AND i2.listed = 1
             AND i2.state_id = 5
   order by
      i2.price desc
   limit
      25

Не уверен в порядке, особенно если вы хотите группировку по элементам ... Кроме того, я бы обеспечил индекс по (state_id, Listed, id, issue_date)

РАЗЪЯСНЕНИЯ по комментариям

Я думаю, что я прав в этом. Не путайте предложение HAVING с предложением WHERE. ГДЕ говорит: ДЕЛАТЬ или НЕ включать в зависимости от определенных условий. HAVING означает, что после того, как все предложения where и группировка выполнены, набор результатов "ПОТЕНЦИАЛЬНО" примет ответ. ТОГДА проверяется HAVING, и, если IT STILL все еще соответствует требованиям, включается в набор результатов, в противном случае отбрасывает его. Попробуйте выполнить следующее из одного только ВНУТРЕННЕГО запроса ... Сделайте один раз БЕЗ предложения HAVING, затем еще раз С предложением HAVING ...

SELECT  i.id, avg( i.price )
   FROM items i
   WHERE i.issue_date > 2011 
     AND i.listed = 1
     AND i.state_id = 5
   GROUP BY
      i.id
   HAVING 
      AVG( i.price) <= 500

По мере того, как вы будете больше писать запросы, пробуйте отдельные части по отдельности, чтобы увидеть, что вы получаете и о чем думаете ... Вы узнаете, как и почему работают определенные вещи. Кроме того, в обновленном вопросе вы сейчас говорите о получении нескольких идентификаторов и цен в очевидном низком и высоком диапазоне ... но вы также применяете ограничение. Если бы у вас было 20 элементов, и у каждого было 10 подходящих записей, ваш лимит в 25 отобразил бы весь первый элемент, а 5 - во второй ... это НЕ то, что, я думаю, вы хотите ... вы можете захотеть 25 из каждого квалифицированного "я бы". Это перенесет этот запрос на другой уровень ...

person DRapp    schedule 05.02.2012
comment
Выглядит неплохо, но я думаю, что у него такая же проблема, как у Джаспера. AVG (i.price) на самом деле не дает AVG для общего набора элементов. Если вы группируете по state_id, возможно, это более уместно, но он не найдет вам X элементов, подпадающих под AVG 500, он просто сообщит вам положительный результат, ЕСЛИ все элементы из state_id = 5 имеют AVG ‹= 500. - person kaptron; 05.02.2012
comment
@Kaptron, затем удалите квалификатор State_ID из внутреннего ... Независимо от вашего ВНУТРЕННЕГО запроса, чтобы получить средний базис, IS вернет любой 1 ID, который вы хотите. Затем используйте ЭТО результат для повторного присоединения и укажите другие критерии, которые ДОЛЖНЫ включать ваш элемент State_ID. - person DRapp; 05.02.2012
comment
Возможно, я все еще не совсем понимаю. Кажется, ответ Тюдора понял проблему. По сути, я пытаюсь выполнить запрос, в котором общее среднее количество товаров составляет ‹= 500, что означает, что он может включать товары с ценами 490, 500 и 510. Запросы, которые я здесь видел, будут включать предметы, которые имеют индивидуальную цену ‹= 500, а не в среднем до 500 по всему набору. - person kaptron; 05.02.2012
comment
@kaptron, исправленный ответ для уточнения. - person DRapp; 05.02.2012
comment
@DRapp, я чувствую, что каптрон недостаточно ясен в своем вопросе (и его примерный набор данных также чрезвычайно предвзят). Я думаю, он мог искать запрос, который изменяет количество возвращаемых кортежей в зависимости от значений кортежей. Однако это всего лишь предположение, поэтому я думаю, нам стоит просто подождать, пока каптрон более четко объяснит свою цель. - person Jasper; 06.02.2012

Я не совсем уверен в вашем вопросе, но я думаю, что это решение вашей проблемы:

SELECT * FROM items 
WHERE (some "conditions", e.g. issue_date > 2011 and listed=1) 
 AND state_id = 5
 AND id IN (SELECT id
            FROM items
            GROUP BY id
            HAVING AVG(price) <= 500)

ORDER BY price DESC
LIMIT 25

примечание: это не в моей голове, и я давно не выполнял сложный SQL, так что это может быть неправильно. Я думаю, что это или что-то в этом роде должно сработать.

person Jasper    schedule 05.02.2012
comment
Да, я пробовал нечто подобное; проблема в том, что HAVING AVG (price) ‹= в этом запросе по существу совпадает с WHERE price‹ =, поскольку он просто группируется по отдельным элементам. - person kaptron; 05.02.2012
comment
Я создал базу данных с набором данных, который вы предоставили, и запрос возвращает то, что, как я думал, вы искали. Я не уверен, что понимаю, что вы ищете в таком случае. Не могли бы вы объяснить, что вам нужно? - person Jasper; 05.02.2012