【发布时间】:2019-09-07 08:51:42
【问题描述】:
我对 mysql 有一些奇怪的要求。 我应该从表中选择最后 6 个字符不是唯一的所有记录。
例如,如果我有桌子:
我应该选择第 1 行和第 3 行,因为这个值的最后 6 个字母不是唯一的。
您知道如何实现吗? 谢谢你的帮助。
【问题讨论】:
-
使用子字符串获取最后 6 个字符。 dev.mysql.com/doc/refman/8.0/en/…
我对 mysql 有一些奇怪的要求。 我应该从表中选择最后 6 个字符不是唯一的所有记录。
例如,如果我有桌子:
我应该选择第 1 行和第 3 行,因为这个值的最后 6 个字母不是唯一的。
您知道如何实现吗? 谢谢你的帮助。
【问题讨论】:
可能是一个快速代码,因为不涉及计数。
现场测试:https://www.db-fiddle.com/f/dBdH9tZd4W6Eac1TCRXZ8U/0
select *
from tbl outr
where not exists
(
select 1 / 0 -- just a proof that this is not evaluated. won't cause division by zero
from tbl inr
where
inr.id <> outr.id
and right(inr.value, 6) = right(outr.value, 6)
)
输出:
| id | value |
| --- | --------------- |
| 2 | aaaaaaaaaaaaaa |
| 4 | aaaaaaaaaaaaaaB |
| 5 | Hello |
逻辑是测试其他不等于外行id相同的行。如果其他行与外行有相同的右 6 个字符,则不显示该外行。
更新
我误解了 OP 的意图。是反了无论如何,只是颠倒逻辑。使用 EXISTS 而不是 NOT EXISTS
现场测试:https://www.db-fiddle.com/f/dBdH9tZd4W6Eac1TCRXZ8U/3
select *
from tbl outr
where exists
(
select 1 / 0 -- just a proof that this is not evaluated. won't cause division by zero
from tbl inr
where
inr.id <> outr.id
and right(inr.value, 6) = right(outr.value, 6)
)
输出:
| id | value |
| --- | ----------- |
| 1 | abcdePuzzle |
| 3 | abcPuzzle |
更新
测试了查询。我的答案(相关EXISTS 方法)的性能不是最佳的。只是保留我的答案,这样其他人就会知道要避免什么方法:)
GhostGambler 的 answer 比 correlated EXISTS 方法更快。对于 500 万行,他的回答只需要 2.762 秒:
explain analyze
SELECT
tbl.*
FROM
(
SELECT
RIGHT(value, 6) AS ending
FROM
tbl
GROUP BY
ending
HAVING
COUNT(*) > 1
) grouped
JOIN tbl ON grouped.ending = RIGHT(value, 6)
我的回答(相关EXISTS)需要 4.08 秒:
explain analyze
select *
from tbl outr
where exists
(
select 1 / 0 -- just a proof that this is not evaluated. won't cause division by zero
from tbl inr
where
inr.id <> outr.id
and right(inr.value, 6) = right(outr.value, 6)
)
直接查询是最快的,没有连接,只是简单的 IN 查询。 2.722 秒。它具有与 JOIN 方法几乎相同的性能,因为它们具有相同的执行计划。这是 kiks73 的answer。我只是不知道他为什么让他的第二个答案变得不必要地复杂。
所以这只是一个品味问题,或者选择哪个代码更易读select from in vs select from join
explain analyze
SELECT *
FROM tbl
where right(value, 6) in
(
SELECT
RIGHT(value, 6) AS ending
FROM
tbl
GROUP BY
ending
HAVING
COUNT(*) > 1
)
结果:
使用的测试数据:
CREATE TABLE tbl (
id INTEGER primary key,
value VARCHAR(20)
);
INSERT INTO tbl
(id, value)
VALUES
('1', 'abcdePuzzle'),
('2', 'aaaaaaaaaaaaaa'),
('3', 'abcPuzzle'),
('4', 'aaaaaaaaaaaaaaB'),
('5', 'Hello');
insert into tbl(id, value)
select x.y, 'Puzzle'
from generate_series(6, 5000000) as x(y);
create index ix_tbl__right on tbl(right(value, 6));
没有索引的性能,以及tbl(right(value, 6))上的索引:
JOIN 方法:
无索引:3.805 秒
带索引:2.762 秒
IN 方法:
无索引:3.719 秒
带索引:2.722 秒
【讨论】:
DEPENDENT SUBQUERY,这可能比所有其他解决方案都慢。
COUNT 方法(认为 COUNT 具有百万行)进行对比,则 COUNT 将继续计数,无论它是否已经满足外部表的相同/唯一性。与使用EXISTS/NOT EXISTS 的相关子查询方法不同,一旦满足条件,它将停止评估
EXISTS/NOT EXISTS 提示缩短评估
COUNT > x 时可能会触发相同的优化,因为在这种情况下你实际上也不需要计算确切的计数。我想您的解决方案值得尝试使用真实数据和使用的 MySQL 版本。
COUNT > x 方法更快。也许COUNT > x 方法的表派生性质使得内部的查询可具体化为表。它是可实现的,因为它不相关/不依赖于源表。然后可以将一次性物化表与另一个表进行 JOIN'd / IN'd。与 EXISTS 方法不同,在该方法中,每一行都需要针对相关查询逐一评估
代码更简洁一些(如果使用 MySQL 8.0)。虽然不能保证性能
现场测试:https://www.db-fiddle.com/f/dBdH9tZd4W6Eac1TCRXZ8U/1
select x.*
from
(
select
*,
count(*) over(partition by right(value, 6)) as unique_count
from tbl
) as x
where x.unique_count = 1
输出:
| id | value | unique_count |
| --- | --------------- | ------------ |
| 2 | aaaaaaaaaaaaaa | 1 |
| 4 | aaaaaaaaaaaaaaB | 1 |
| 5 | Hello | 1 |
更新
我误解了 OP 的意图。是反了只需更改计数:
select x.*
from
(
select
*,
count(*) over(partition by right(value, 6)) as unique_count
from tbl
) as x
where x.unique_count > 1
输出:
| id | value | unique_count |
| --- | ----------- | ------------ |
| 1 | abcdePuzzle | 2 |
| 3 | abcPuzzle | 2 |
【讨论】:
这就是您所需要的:一个子查询来获取重复的权利(值,6),而主查询则根据该条件获取行。
SELECT t.* FROM t WHERE RIGHT(`value`,6) IN (
SELECT RIGHT(`value`,6)
FROM t
GROUP BY RIGHT(`value`,6) HAVING COUNT(*) > 1);
更新
这是在你有sql_mode=only_full_group_by的情况下避免mysql错误的解决方案
SELECT t.* FROM t WHERE RIGHT(`value`,6) IN (
SELECT DISTINCT right_value FROM (
SELECT RIGHT(`value`,6) AS right_value,
COUNT(*) AS TOT
FROM t
GROUP BY RIGHT(`value`,6) HAVING COUNT(*) > 1) t2
)
【讨论】:
IN 时经常表现不佳。尽管如此,现在的执行计划似乎还可以。点赞。
已编辑:我之前对这个问题的理解有误,我真的不想改变我最初的答案。但如果我之前的回答在某些环境下不能接受,可能会误导人,我还是得改正。
SELECT GROUP_CONCAT(id),RIGHT(VALUE,6)
FROM table1
GROUP BY RIGHT(VALUE,6) HAVING COUNT(RIGHT(VALUE,6)) > 1;
由于这个问题已经有了很好的答案,所以我以稍微不同的方式进行了查询。我已经用sql_mode=ONLY_FULL_GROUP_BY 进行了测试。 ;)
【讨论】:
类似的东西应该可以工作:
SELECT `mytable`.*
FROM (SELECT RIGHT(`value`, 6) AS `ending` FROM `mytable` GROUP BY `ending` HAVING COUNT(*) > 1) `grouped`
INNER JOIN `mytable` ON `grouped`.`ending` = RIGHT(`value`, 6)
但它并不快。这需要全表扫描。也许你应该重新考虑你的问题。
【讨论】:
sql_mode=only_full_group_bysql_mode=only_full_group_by时不正确使用group by/have而产生错误
我对子查询使用 JOIN,在其中计算 n(在我的示例中为 2 个)最后一个字符的每个唯一组合的出现次数
SELECT t.*
FROM t
JOIN (SELECT RIGHT(value, 2) r, COUNT(RIGHT(value, 2)) rc
FROM t
GROUP BY r) c ON c.r = RIGHT(value, 2) AND c.rc > 1
【讨论】:
RIGHT(value, 2) 创建索引来最小化全表扫描,例如 Postgres