MySQL: наибольшее число на группу с объединениями и условиями - PullRequest
0 голосов
/ 02 сентября 2018

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

У меня есть таблица, подобная следующей:

* 1006 мест *

В следующей таблице приведен список предприятий

id    name
50    Nando's
60    KFC

награды

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

id    venue_id    name        points
1     50          5% off      10
2     50          10% off     20
3     50          11% off     30
4     50          15% off     40
5     50          20% off     50
6     50          30% off     50
7     60          30% off     70
8     60          60% off     100
9     60          65% off     120
10    60          70% off     130
11    60          80% off     140

points_data

В таблице указано количество баллов, оставшихся у пользователя за каждое место.

venue_id    points_remaining
50           30
60          90

Обратите внимание, что этот запрос фактически вычисляется в SQL следующим образом:

select * from (
  select venue_id, (total_points - points_redeemed) as points_remaining
  from (
         select venue_id, sum(total_points) as total_points, sum(points_redeemed) as points_redeemed
         from (
                (
                  select venue_id, sum(points) as total_points, 0 as points_redeemed
                  from check_ins
                  group by venue_id
                )
                UNION
                (
                  select venue_id, 0 as total_points, sum(points) as points_redeemed
                  from reward_redemptions rr
                    join rewards r on rr.reward_id = r.id
                  group by venue_id
                )
              ) a
         group by venue_id
       ) b
  GROUP BY venue_id
) points_data

но для этого вопроса вы, вероятно, можете просто проигнорировать этот массивный запрос и предположить, что таблица просто называется points_data.

Желаемый выход

Я хочу получить один запрос, который получает:

  • Лучшие 2 награды, которые пользователь имеет право на каждое место проведения
  • Наименьшие 2 награды, на которые пользователь еще не имеет права для каждого места

Таким образом, для приведенных выше данных результат будет:

id    venue_id    name        points
2     50          10% off     20
3     50          11% off     30
4     50          15% off     40
5     50          20% off     50
7     60          30% off     70
8     60          60% off     100
9     60          65% off     120

Что я получил до сих пор

Лучшее решение, которое я нашел до сих пор, - это сначала получить points_data, а затем использовать код (например, PHP) для динамической записи следующего:

(
  select * from rewards
  where venue_id = 50
  and points > 30
  ORDER BY points desc
  LIMIT 2
)
union all
(
  select * from rewards
  where venue_id = 50
        and points <= 30
  ORDER BY points desc
  LIMIT 2
)
UNION ALL
(
  select * from rewards
  where venue_id = 60
        and points <= 90
  ORDER BY points desc
  LIMIT 2
)
UNION ALL
(
  select * from rewards
  where venue_id = 60
        and points > 90
  ORDER BY points desc
  LIMIT 2
)
ORDER BY venue_id, points asc;

Однако я чувствую, что запрос может быть слишком длинным и неэффективным. Например, если у пользователя есть очки в 400 местах, то это 800 подзапросов.

Я тоже пытался сделать соединение, но не могу лучше, чем:

select * from points_data
INNER JOIN rewards on rewards.venue_id = points_data.venue_id
where points > points_remaining;

что далеко от того, что я хочу.

1 Ответ

0 голосов
/ 02 сентября 2018

Коррелированные подзапросы, подсчитывающие количество более высоких или более низких вознаграждений для определения верхней или нижней записей, являются односторонними.

SELECT r1.*
       FROM rewards r1
            INNER JOIN points_data pd1
                       ON pd1.venue_id = r1.venue_id
       WHERE r1.points <= pd1.points_remaining
             AND (SELECT count(*)
                         FROM rewards r2
                         WHERE r2.venue_id = r1.venue_id
                               AND r2.points <= pd1.points_remaining
                               AND (r2.points > r1.points
                                     OR r2.points = r1.points
                                        AND r2.id > r1.id)) < 2
              OR r1.points > pd1.points_remaining
                 AND (SELECT count(*)
                             FROM rewards r2
                             WHERE r2.venue_id = r1.venue_id
                                   AND r2.points > pd1.points_remaining
                                   AND (r2.points < r1.points
                                         OR r2.points = r1.points
                                            AND r2.id < r1.id)) < 2
       ORDER BY r1.venue_id,
                r1.points;

SQL Fiddle

Начиная с MySQL 8.0, решение, использующее оконную функцию row_number(), будет альтернативой. Но я полагаю, что вы находитесь на более низкой версии.

SELECT x.id,
       x.venue_id,
       x.name,
       x.points
       FROM (SELECT r.id,
                    r.venue_id,
                    r.name,
                    r.points,
                    pd.points_remaining,
                    row_number() OVER (PARTITION BY r.venue_id,
                                                    r.points <= pd.points_remaining
                                       ORDER BY r.points DESC) rntop,
                    row_number() OVER (PARTITION BY r.venue_id,
                                                    r.points > pd.points_remaining
                                       ORDER BY r.points ASC) rnbottom
                    FROM rewards r
                         INNER JOIN points_data pd
                                    ON pd.venue_id = r.venue_id) x
       WHERE x.points <= x.points_remaining
             AND x.rntop <= 2
              OR x.points > x.points_remaining
                 AND x.rnbottom <= 2
       ORDER BY x.venue_id,
                x.points;

дб <> скрипка

Сложная часть здесь для того, чтобы разделить набор также на подмножество, где очков пользователя достаточно, чтобы выкупить награду, и то, где очков недостаточно для каждого места. Но так как в MySQL логические выражения оцениваются в 0 или 1 (в не булевом контексте), для этого могут использоваться соответствующие выражения.

...