MySQL - 条件 MIN MAX 到 return 不同的记录
MySQL - Conditional MIN MAX to return distinct record
我有一个来自 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的secord记录表示它是一个区域
我想按地名搜索记录以构建自动完成功能。
如果记录具有相同的名称,并且如果这些记录之一是一个区域,即具有 feature_code RGN,那么我只想 return
记录否则我想 return 匹配具有最低 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
return错误的记录:
id | name
---------------------------
2652355 | Cornwall, England
在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
);
在MySQL8+中,还可以使用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;
可能存在一种方法并联合所有
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)
我认为 条件聚合 应该可以解决问题。您可以按 name
过滤记录,然后在聚合函数中应用逻辑。如果存在 feature_code = 'RGN'
的记录,那么您想要 select 它,否则您将选择匹配记录中的最小值 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
上面的结果集与原始 table:
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 |
我有一个来自 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的secord记录表示它是一个区域
我想按地名搜索记录以构建自动完成功能。 如果记录具有相同的名称,并且如果这些记录之一是一个区域,即具有 feature_code RGN,那么我只想 return 记录否则我想 return 匹配具有最低 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
return错误的记录:
id | name
---------------------------
2652355 | Cornwall, England
在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
);
在MySQL8+中,还可以使用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;
可能存在一种方法并联合所有
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)
我认为 条件聚合 应该可以解决问题。您可以按 name
过滤记录,然后在聚合函数中应用逻辑。如果存在 feature_code = 'RGN'
的记录,那么您想要 select 它,否则您将选择匹配记录中的最小值 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
上面的结果集与原始 table:
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 |