代码之家  ›  专栏  ›  技术社区  ›  Yahya Uddin

MySQL:每个组最大n个连接和条件

  •  1
  • Yahya Uddin  · 技术社区  · 6 年前

    我有一张类似如下的桌子:

    场馆

    下表描述了业务列表

    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
    

    点\u数据

    下表描述了用户在每个场地的剩余积分数。

    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
    

    到目前为止,我找到的最佳解决方案是首先获取点数据,然后使用代码(即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 回复  |  直到 6 年前
        1
  •  2
  •   sticky bit    6 年前

    计算较高或较低奖励的数量以确定最高或最低项目的相关子查询是一种方法。

    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

    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;
    

    db<>fiddle

    在这里,最棘手的部分是将场景划分为用户积分足以兑换奖励的子集和积分不足的子集。但是在MySQL中,逻辑表达式的值为0或1(在非布尔上下文中),相应的表达式可以用于此目的。