Выберите максимальное значение для нескольких таблиц, не считая их дважды - PullRequest
0 голосов
/ 30 мая 2018

Я делаю запрос, который позволяет мне упорядочить рецепты по счету.

Структура таблиц

Структура состоит в том, что флаер содержит один или несколько flyer_items, которые могут содержать один илимного ingredients_to_flyer_item (эта таблица связывает ингредиент с элементом флаера).Другая таблица ingredient_to_recipe связывает те же ингредиенты, но с одним или несколькими рецептами.Ссылка на файл .sql включена в конце.

Пример запроса

Я хочу получить recipe_id и СУММУ МАКСИМАЛЬНОГО веса цены каждого ингредиента, являющегося частью рецепта (связан сcomponent_to_recipe), но если в рецепте есть несколько ингредиентов, принадлежащих одному и тому же элементу flyers_item, он должен учитываться один раз.

SELECT itr.recipe_id,
       SUM(itr.weight),
       SUM(max_price_weight),
       SUM(itr.weight + max_price_weight) AS score
FROM
  ( SELECT MAX(itf.max_price_weight) AS max_price_weight,
           itf.flyer_item_id,
           itf.ingredient_id
   FROM
     (SELECT ifi.ingredient_id,
             MAX(i.price_weight) AS max_price_weight,
             ifi.flyer_item_id
      FROM flyer_items i
      JOIN ingredient_to_flyer_item ifi ON i.id = ifi.flyer_item_id
      WHERE i.flyer_id IN (1,
                           2)
      GROUP BY ifi.ingredient_id ) itf
   GROUP BY itf.flyer_item_id) itf2
JOIN `ingredient_to_recipe` AS itr ON itf2.`ingredient_id` = itr.`ingredient_id`
WHERE recipe_id = 5730
GROUP BY itr.`recipe_id`
ORDER BY score DESC
LIMIT 0,10

Запрос почти работает нормально, поскольку большинство результатов хороши, но для некоторых строкнекоторые ингредиенты игнорируются и не учитываются при подсчете баллов.

Контрольные примеры

| recipe_id | 'score' with current query | what 'score' should be | explanation                                                                 |
|-----------|----------------------------|------------------------|-----------------------------------------------------------------------------|
| 8376      | 51                         | 51                     | Good result                                                                 |
| 3152      | 1                          | 18                     | Only 1 ingredient having a score of one is counted, should be 4 ingredients |
| 4771      | 41                         | 45                     | One ingredient worth score 4 is ignored                                     |
| 10230     | 40                         | 40                     | Good result                                                                 |
| 8958      | 39                         | 39                     | Good result                                                                 |
| 4656      | 28                         | 34                     | One ingredient worth 6 is ignored                                           |
| 11338     | 1                          | 10                     | 2 ingredients, worth 4 and 5 are ignored                                    |

Мне очень трудно найти простой способ объяснить это.Дайте мне знать, если что-нибудь еще может помочь.

Вот ссылка на демонстрационную базу данных для запуска запроса, тестовых примеров и тестовых случаев: https://nofile.io/f/F4YSEu8DWmT/meta.zip

Большое спасибо.

Обновление (как спросил Рик Джеймс):

Вот самое дальнее, что я мог сделать, чтобы это работало.Результаты всегда хороши, в том числе и в подзапросе, но я полностью убрал группу с помощью flyer_item_id.Таким образом, с помощью этого запроса я получаю хороший результат, но если многие ингредиенты рецепта имеют одинаковый flyer_item_item, они будут подсчитаны несколько раз (например, счет будет 59 для recipe_id = 10557 вместо хороших 56, потому что 2 ингредиента стоят 3находятся в том же flyers_item).Единственное, что мне нужно, это подсчитать один MAX (цена_тяжелости) на flyer_item_id на рецепт (который я первоначально попытался сгруппировать по 'flyer_item_id' по первому group_by ингридиенту_id.

SELECT itr.recipe_id,
       SUM(itr.weight) as total_ingredient_weight,
       SUM(itf.price_weight) as total_price_weight,
       SUM(itr.weight+itf.price_weight) as score
FROM
  (SELECT fi1.id, MAX(fi1.price_weight) as price_weight, ingredient_to_flyer_item.ingredient_id as ingredient_id, recipe_id
FROM flyer_items fi1
INNER JOIN (
    SELECT flyer_items.id as id, MAX(price_weight) as price_weight, ingredient_to_flyer_item.ingredient_id as ingredient_id
    FROM flyer_items
    JOIN ingredient_to_flyer_item ON flyer_items.id = ingredient_to_flyer_item.flyer_item_id
    GROUP BY id
) fi2 ON fi1.id = fi2.id AND fi1.price_weight = fi2.price_weight
JOIN ingredient_to_flyer_item ON fi1.id = ingredient_to_flyer_item.flyer_item_id
JOIN ingredient_to_recipe ON ingredient_to_flyer_item.ingredient_id = ingredient_to_recipe.ingredient_id
GROUP BY ingredient_to_flyer_item.ingredient_id) AS itf
INNER JOIN `ingredient_to_recipe` AS `itr` ON `itf`.`ingredient_id` = `itr`.`ingredient_id`
GROUP BY `itr`.`recipe_id`
ORDER BY `score` DESC
LIMIT 10

Вот объяснение,но я не уверен, что это полезно, так как последняя рабочая часть все еще отсутствует:

| id | select_type | table                    | partitions | type   | possible_keys                 | key           | key_len | ref                                                   | rows   | filtered | Extra                           |   |
|----|-------------|--------------------------|------------|--------|-------------------------------|---------------|---------|-------------------------------------------------------|--------|----------|---------------------------------|---|
| 1  | PRIMARY     | itr                      | NULL       | ALL    | recipe_id,ingredient_id       | NULL          | NULL    | NULL                                                  | 151800 | 100.00   | Using temporary; Using filesort |   |
| 1  | PRIMARY     | <derived2>               | NULL       | ref    | <auto_key0>                   | <auto_key0>   | 4       | metadata3.itr.ingredient_id                           | 10     | 100.00   | NULL                            |   |
| 2  | DERIVED     | ingredient_to_flyer_item | NULL       | ALL    | NULL                          | NULL          | NULL    | NULL                                                  | 249    | 100.00   | Using temporary; Using filesort |   |
| 2  | DERIVED     | fi1                      | NULL       | eq_ref | id_2,id,price_weight          | id_2          | 4       | metadata3.ingredient_to_flyer_item.flyer_item_id      | 1      | 100.00   | NULL                            |   |
| 2  | DERIVED     | <derived3>               | NULL       | ref    | <auto_key0>                   | <auto_key0>   | 9       | metadata3.ingredient_to_flyer_item.flyer_item_id,m... | 10     | 100.00   | NULL                            |   |
| 2  | DERIVED     | ingredient_to_recipe     | NULL       | ref    | ingredient_id                 | ingredient_id | 4       | metadata3.ingredient_to_flyer_item.ingredient_id      | 40     | 100.00   | NULL                            |   |
| 3  | DERIVED     | ingredient_to_flyer_item | NULL       | ALL    | NULL                          | NULL          | NULL    | NULL                                                  | 249    | 100.00   | Using temporary; Using filesort |   |
| 3  | DERIVED     | flyer_items              | NULL       | eq_ref | id_2,id,flyer_id,price_weight | id_2          | 4       | metadata3.ingredient_to_flyer_item.flyer_item_id      | 1      | 100.00   | NULL                            |   |

Обновление 2

Мне удалось найти работающий запрос, но теперь я должен сделать егобыстрее, для запуска требуется более 500 мс.

SELECT sum(ff.price_weight) as price_weight, sum(ff.weight) as weight, sum(ff.price_weight+ff.weight) as score, ff.recipe_id FROM
(
SELECT DISTINCT
       itf.flyer_item_id as flyer_item_id,
       itf.recipe_id,
       itf.weight,
       aprice_weight AS price_weight
FROM
  (SELECT itfin.flyer_item_id AS flyer_item_id,
          itfin.price_weight AS aprice_weight,
          itfin.ingredient_id,
          itr.recipe_id,
          itr.weight
   FROM
     (SELECT ifi2.flyer_item_id, ifi2.ingredient_id as ingredient_id, MAX(ifi2.price_weight) as price_weight
        FROM
          ingredient_to_flyer_item ifi1
        INNER JOIN (
                SELECT id, MAX(price_weight) as price_weight, ingredient_to_flyer_item.ingredient_id as ingredient_id, ingredient_to_flyer_item.flyer_item_id
                FROM ingredient_to_flyer_item
                GROUP BY ingredient_id
            ) ifi2 ON ifi1.price_weight = ifi2.price_weight AND ifi1.ingredient_id = ifi2.ingredient_id
        WHERE flyer_id IN (1,2)
        GROUP BY ifi1.ingredient_id) AS itfin
      INNER JOIN `ingredient_to_recipe` AS `itr` ON `itfin`.`ingredient_id` = `itr`.`ingredient_id`

     ) AS itf
) ff
GROUP BY recipe_id
ORDER BY `score` DESC
LIMIT 20

Вот ОБЪЯСНЕНИЕ:

| id | select_type | table                    | partitions | type  | possible_keys                                | key           | key_len | ref                 | rows | filtered | Extra                           |   |
|----|-------------|--------------------------|------------|-------|----------------------------------------------|---------------|---------|---------------------|------|----------|---------------------------------|---|
| 1  | PRIMARY     | <derived2>               | NULL       | ALL   | NULL                                         | NULL          | NULL    | NULL                | 1318 | 100.00   | Using temporary; Using filesort |   |
| 2  | DERIVED     | <derived4>               | NULL       | ALL   | NULL                                         | NULL          | NULL    | NULL                | 37   | 100.00   | Using temporary                 |   |
| 2  | DERIVED     | itr                      | NULL       | ref   | ingredient_id                                | ingredient_id | 4       | itfin.ingredient_id | 35   | 100.00   | NULL                            |   |
| 4  | DERIVED     | <derived5>               | NULL       | ALL   | NULL                                         | NULL          | NULL    | NULL                | 249  | 100.00   | Using temporary; Using filesort |   |
| 4  | DERIVED     | ifi1                     | NULL       | ref   | ingredient_id,itx_full,price_weight,flyer_id | ingredient_id | 4       | ifi2.ingredient_id  | 1    | 12.50    | Using where                     |   |
| 5  | DERIVED     | ingredient_to_flyer_item | NULL       | index | ingredient_id,itx_full                       | ingredient_id | 4       | NULL                | 249  | 100.00   | NULL                            |   |

Ответы [ 3 ]

0 голосов
/ 04 июня 2018

Звучит как "взорваться-взорваться". Здесь запрос имеет JOIN и GROUP BY.

  1. JOIN собирает соответствующие комбинациистроки из соединяемых таблиц; затем
  2. GROUP BY COUNTs, SUMs и т. Д., Которые дают вам завышенные значения для агрегатов.

Существует два общих исправления:оба включают агрегирование отдельно от JOIN.

Случай 1:

SELECT  ...
        ( SELECT SUM(x) FROM t2 WHERE id = ... ) AS sum_x,
        ...
    FROM t1 ...

Этот случай становится неуклюжим, если вам нужно несколько агрегатов из t2, поскольку он допускает только один за один раз.

Случай 2:

SELECT ...
    FROM ( SELECT grp,
                  SUM(x) AS sum_x,
                  COUNT(*) AS ct
           FROM t2 ) AS s
    JOIN t1 ON t1.grp = s.grp

У вас есть 2 JOINs и 3 GROUP BYs, поэтому я рекомендую вам отладить (и переписать) ваш запрос изнутри.

        SELECT  ifi.ingredient_id,
                MAX(price_weight) as max_price_weight,
                flyer_item_id
            from  flyer_items i
            join  ingredient_to_flyer_item ifi  ON i.id = ifi.flyer_item_id
            where  flyer_id in (1, 2)
            group by  ifi.ingredient_id 

Но я не могу вам помочь, поскольку вы не указали price_weight по таблице (или псевдониму), в которой он находится. (То же самое для некоторых других столбцов.)

(На самом делеMAX и MIN не получат завышенные значения; AVG получат слегка неправильные значения; COUNT и SUM получат "неправильные" значения.)

Следовательно, я оставлюотдых для читателя как «упражнение».

ИНДЕКСЫ

itr:  (ingredient_id, recipe_id)  -- for the JOIN and WHERE and GROUP BY
itr:  (recipe_id, ingredient_id, weight)  -- for 1st Update
(There is no optimization available for the ORDER BY and LIMIT)
flyer_items:  (flyer_id, price_weight) -- unless flyer_id is the PRIMARY KEY
ifi:  (flyer_item_id, ingredient_id)
ifi:  (ingredient_id, flyer_item_id)  -- for 1st Update

Пожалуйста, предоставьте `SHOW CREATE TABLE для соответствующих таблиц.

Пожалуйста предоставьте EXPLAIN SELECT ....

Если ingredient_to_flyer_item много: много таблиц сопоставления, следуйте советам здесь .То же самое для ingredient_to_recipe?

GROUP BY itf.flyer_item_id, вероятно, недопустимо, поскольку не включает неагрегированный ifi.ingredient_id.Смотрите "only_full_group_by".

Переформулируйте

После того, как вы закончите оценку INDEXes, попробуйте следующее. Внимание: я не знаю, будет ли он работать правильно.

JOIN  `ingredient_to_recipe` AS itr  ON itf2.`ingredient_id` = itr.`ingredient_id`

на

JOIN ( SELECT recipe_id,
              ingredient_id,
              SUM(weight) AS sum_weight
           FROM ingredient_to_recipe ) AS itr

И измените начальный SELECT, чтобы заменить SUMs наэти вычисленные суммы.(Я подозреваю, что не обработал ingredient_id правильно.)

Какую версию MySQL / MariaDB вы используете?

0 голосов
/ 08 июня 2018

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

SELECT recipe_id, SUM(weight) AS weight, SUM(max_price_weight) AS price_weight, SUM(weight + max_price_weight) AS score 
FROM (SELECT recipe_id, ingredient_id, MAX(weight) AS weight, MAX(price_weight) AS max_price_weight
      FROM (SELECT itr.recipe_id, MIN(itr.ingredient_id) AS ingredient_id, MAX(itr.weight) AS weight, fi.id, MAX(fi.price_weight) AS price_weight
            FROM ingredient_to_recipe itr 
            JOIN ingredient_to_flyer_item itfi ON itfi.ingredient_id = itr.ingredient_id 
            JOIN flyer_items fi ON fi.id = itfi.flyer_item_id 
            GROUP BY itr.recipe_id, fi.id) ri
      GROUP BY recipe_id, ingredient_id) r
GROUP BY recipe_id
ORDER BY score DESC
LIMIT 10

Он группируется сначала по flyer_item_id, а затем по MIN(ingredient_id), чтобы учесть ингредиенты в рецепте, которые имеют одинаковые flyer_item_id.Затем он суммирует результаты, чтобы получить желаемый результат.Если я использую запрос с предложением

HAVING recipe_id IN (8376, 3152, 4771, 10230, 8958, 4656, 11338)

, это даст следующие результаты, которые соответствуют вашему столбцу «какой счет должен быть» выше:

recipe_id   weight  price_weight    score   
8376        10      41              51
4771        5       40              45
10230       10      30              40
8958        15      24              39
4656        15      19              34
3152        0       18              18
11338       0       10              10

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

Надеюсь, это поможет вам найти более подходящее решение.

0 голосов
/ 30 мая 2018

Я не уверен, что полностью понял проблему.Мне кажется, вы группируете по неправильному столбцу flyer_items.id.Вместо этого вы должны группировать по столбцу ingredient_id.Если вы делаете это, это имеет больше смысла (для меня).Вот как я это вижу:

select
    itr.recipe_id,
    sum(itr.weight),
    sum(max_price_weight),
    sum(itr.weight + max_price_weight) as score
  from (
    select
        ifi.ingredient_id, 
        max(price_weight) as max_price_weight
      from flyer_items i
      join ingredients_to_flyer_item ifi on i.id = ifi.flyer_item_id
      where flyer_id in (1, 2)
      group by ifi.ingredient_id
    ) itf
  join `ingredient_to_recipe` as itr on itf.`ingredient_id` = itr.`ingredient_id`
  group by itr.`recipe_id`
  order by score desc
  limit 0,10;

Надеюсь, это поможет.

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