【问题标题】:MySQL group by with ordering/priority of another columnMySQL group by 与另一列的排序/优先级
【发布时间】:2013-11-26 15:06:00
【问题描述】:

这两个问题我已经看过了:

但是它们都使用聚合函数 MAX 来获得最高值或填充值,这不适用于我的情况。

出于这个问题的目的,我已经简化了我的情况。这是我目前的数据:

我想获取每条路线的运营商名称,但与行进方向有关(即排序或“首选”值)。这是我的伪代码:

if(`direction` = 'west' AND `operatorName` != '') then select `operatorName`
else if(`direction` = 'north' AND `operatorName` != '') then select `operatorName`
else if(`direction` = 'south' AND `operatorName` != '') then select `operatorName`
else if(`direction` = 'east' AND `operatorName` != '') then select `operatorName`

我当前的 SQL 查询是:

SELECT route, operatorName
FROM test
GROUP BY route

这给了我分组,但我的目的是错误的运算符:

route | operatorName
--------------------
  95  | James
  96  | Mark
  97  | Justin

我已尝试应用 ORDER BY 子句,但 GROUP BY 优先。我想要的结果是:

route | operatorName
--------------------
  95  | Richard
  96  | Andrew
  97  | Justin

我不能在这里做MAX(),因为“北”按字母顺序排在“南”之前。如何在应用 GROUP BY 子句之前明确说明我的偏好/排序?

另外请记住,空字符串不是首选。

请注意,这是一个简化的示例。实际查询选择了更多字段并与其他三个表连接,但查询中没有聚合函数。

【问题讨论】:

  • 没有聚合函数的 GROUP BY 是任意的。你想做什么没有意义(使用 GROUP BY)
  • @Mihai 我愿意接受不使用GROUP BY 以获得我想要的结果的查询的建议......
  • 在我看来,解决方案是将表与自身进行内部连接,并在子查询中放入 4 种情况stackoverflow.com/questions/14770671/… 但我不确定我能做到。

标签: mysql sql group-by sql-order-by


【解决方案1】:

您可以使用那个 MAX 示例,您只需要“伪造”它。见这里:http://sqlfiddle.com/#!2/58688/5

SELECT *
FROM test
JOIN (SELECT 'west' AS direction, 4 AS weight
      UNION
      SELECT 'north',3
      UNION
      SELECT 'south',2
      UNION
      SELECT 'east',1) AS priority
  ON priority.direction = test.direction
JOIN (
      SELECT route, MAX(weight) AS weight
      FROM test
      JOIN (SELECT 'west' AS direction, 4 AS weight
            UNION
            SELECT 'north',3
            UNION
            SELECT 'south',2
            UNION
            SELECT 'east',1) AS priority
        ON priority.direction = test.direction
      GROUP BY route
) AS t1
  ON t1.route = test.route
    AND t1.weight = priority.weight

【讨论】:

    【解决方案2】:

    我想出了这个解决方案,但是,它很难看。不管怎样,你可以试试:

    CREATE TABLE test (
      route INT,
      direction VARCHAR(20),
      operatorName VARCHAR(20)
    );
    
    INSERT INTO test VALUES(95, 'east', 'James');
    INSERT INTO test VALUES(95, 'west', 'Richard');
    INSERT INTO test VALUES(95, 'north', 'Dave');
    INSERT INTO test VALUES(95, 'south', 'Devon');
    INSERT INTO test VALUES(96, 'east', 'Mark');
    INSERT INTO test VALUES(96, 'west', 'Andrew');
    INSERT INTO test VALUES(96, 'south', 'Alex');
    INSERT INTO test VALUES(96, 'north', 'Ryan');
    INSERT INTO test VALUES(97, 'north', 'Justin');
    INSERT INTO test VALUES(97, 'south', 'Tyler');
    
    SELECT
        route,
        (SELECT operatorName
           FROM test
         WHERE route = t2.route
           AND direction =
                  CASE
                    WHEN direction_priority = 1 THEN 'west'
                    WHEN direction_priority = 2 THEN 'north'
                    WHEN direction_priority = 3 THEN 'south'
                    WHEN direction_priority = 4 THEN 'east'
                  END) AS operator_name
      FROM (
        SELECT
            route,
            MIN(direction_priority) AS direction_priority
          FROM (
            SELECT
                route,
                operatorName,
                CASE
                  WHEN direction = 'west' THEN 1
                  WHEN direction = 'north' THEN 2
                  WHEN direction = 'south' THEN 3
                  WHEN direction = 'east' THEN 4
                END AS direction_priority
              FROM test
          ) t
        GROUP BY route
      ) t2
    ;
    

    首先,我们选择所有将direction 更改为数字的记录,以便按要求的顺序排列。然后,我们通过每条路线GROUP 得到最小方向。剩下的保留在最外面的查询中 - 根据找到的最低方向选择运算符名称。

    输出:

    ROUTE OPERATOR_NAME
    95 理查德
    96 安德鲁
    97贾斯汀

    请下次不要以图片的形式附加示例数据,而是以纯文本或插入的形式(最好是SQLFiddle)。

    SQLFiddle查看此解决方案

    【讨论】:

      【解决方案3】:

      您可以使用 case 构造枚举方向,以使它们按您的顺序排序。然后对按路线划分的方向进行排序,然后只选择第一个候选。

      set @c = 1;
      set @r = '';
      
      select route
           , direction
           , operatorName
        from (
               select route
                    , direction
                    , operatorName
                    , @c := if (@r = route, @c + 1, 1) as cand
                 from (
                        select route
                             , case when direction = 'west'
                                    then 1
                                    when direction = 'north'
                                    then 2
                                    when direction = 'south'
                                    then 3
                                    when direction = 'east'
                                    then 4
                                    else 5
                               end as enum_direction
                             , direction
                             , operatorName
                      )
                order by
                      route
                    , enum_direction
             )
      

      【讨论】:

      • 谢谢@Mihai,我已经使用变量而不是窗口函数修改了我的答案。我忘记了它们在 mysql 中不可用。唉,我不完全确定这会奏效。随意编辑。
      【解决方案4】:
      select *
      from routes r1
      where exists (
          select 1
          from routes r2
          where r1.route_id = r2.route_id
          group by r2.route_id
          having min(case r1.direction
                       when 'west' then 1
                       when 'north' then 2
                       when 'south' then 3
                       when 'east' then 4 end) = min(case r2.direction
                                                        when 'west' then 1
                                                        when 'north' then 2
                                                        when 'south' then 3
                                                        when 'east' then 4 end)
      )
      

      demo

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-02-15
        • 2022-10-12
        • 2012-06-07
        • 2012-10-20
        • 2011-10-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多