代码之家  ›  专栏  ›  技术社区  ›  adam78

mysql-返回不同记录的条件最小最大值

  •  0
  • adam78  · 技术社区  · 6 年前

    我有一个数据库转储 the geonames website for Great Britain . 它包含大约60000条记录。 示例数据如下:

    id       |     name    |   admin1   |   admin2   |  admin3  |  feature_class  |  feature_code
    -------------------------------------------------------------------------------------------
    2652355  |   Cornwall  |   ENG      |     C6     |          |      A          |    ADM2
    11609029 |   Cornwall  |   ENG      |            |          |      L          |    RGN
    6269131  |   England   |   ENG      |            |          |      A          |    ADM1
    

    功能代码为ADM2的第一条记录表示它是管理级别2 具有特征码RGN的分段记录意味着它是一个区域。

    我想按地名搜索记录以建立自动完成功能。 如果记录具有相同的名称,如果其中一个记录是一个区域,即具有特征代码RGN,那么我只想返回 否则,我想返回与具有最低id的名称匹配的记录。

    我试过以下方法,但不起作用:

       SELECT IF(t0.feature_code = 'RGN', MAX(t0.id), MIN(t0.id)) as id
           , CONCAT_WS(', ', t0.name,
                      IF(t3.name != t0.name, t3.name, NULL),
                      IF(t2.name != t0.name, t2.name, NULL),
                      IF(t1.name != t0.name, t1.name, NULL)) AS name
         FROM locations t0
      LEFT JOIN locations t1 ON t1.admin1 = t0.admin1 AND t1.feature_code = 'ADM1'
      LEFT JOIN locations t2 ON t2.admin2 = t0.admin2 AND t2.feature_code = 'ADM2'
      LEFT JOIN locations t3 ON t3.admin3 = t0.admin3 AND t3.feature_code = 'ADM3'
      WHERE 
          (t0.feature_class IN ('P', 'A') OR (t0.feature_class = 'L' AND t0.feature_code = 'RGN' ) )
          AND t0.name like 'Cornwall%' 
      GROUP BY CONCAT_WS(', ', t0.name,
                         IF(t3.name != t0.name, t3.name, NULL),
                         IF(t2.name != t0.name, t2.name, NULL),
                         IF(t1.name != t0.name, t1.name, NULL))
      ORDER BY t0.name 
    

    它返回错误的记录:

    id      | name
    ---------------------------
    2652355 | Cornwall, England
    
    2 回复  |  直到 6 年前
        1
  •  1
  •   GMB    6 年前

    我认为 条件聚合 应该会成功的。可以通过以下方式过滤记录 name ,然后在聚合函数中应用逻辑。如果记录存在于 feature_code = 'RGN' 然后选择它,否则选择最小值 id 在匹配记录中。

    SELECT IFNULL(MAX(CASE WHEN feature_code = 'RGN' THEN id END), MIN(id)) id_found
    FROM mytable
    WHERE name = @name;
    

    Demo on DB Fiddle 搜索时 'Cornwall' :

    | id_found |
    | -------- |
    | 11609029 |
    

    注意:如果您想要整个匹配记录,一个解决方案是 JOIN 以上结果与原表一致:

    SELECT t.*
    FROM mytable t
    INNER JOIN (
        SELECT IFNULL(MAX(CASE WHEN feature_code = 'RGN' THEN id END), MIN(id)) id_found
        FROM mytable
        WHERE name = @name
    ) x ON x.id_found = t.id;
    

    Demo :

    | id       | name     | admin1 | admin2 | admin3 | feature_class | feature_code |
    | -------- | -------- | ------ | ------ | ------ | ------------- | ------------ |
    | 11609029 | Cornwall | ENG    |        |        | L             | RGN          |
    
        2
  •  0
  •   Gordon Linoff    6 年前

    在MySQL中,可以使用相关的子查询:

    select l.*
    from locations l
    where l.id = (select l2.id
                  from locations l2
                  where l2.name = l.name
                  order by (feature_code = 'RGN') desc,  -- put regions first
                           id asc
                 );
    

    在MySQL 8 +中,也可以使用 row_number() :

    select l.*
    from (select l.*,
                 row_number() over (partition by name 
                                    order by (feature_code = 'RGN') desc, id
                                   ) as seqnum
          from locations l
         ) l
    where seqnum = 1;
    
        3
  •  0
  •   Zaynul Abadin Tuhin    6 年前

    一种方法可以存在并联合所有

    select t1.* from location t1
    where exists ( select 1 from location t2 where t2.name=t1.name and t2.feature_code='RGN'
                 )
     and t1.feature_code='RGN'
    union all
    
    select t1.* from location t1
    where not exists ( select 1 from location t2 where t2.name=t1.name and 
                    t2.feature_code='RGN'
                      )
      and t1.id=(select min(id) from location t2 where t2.name=t1.name)