【问题标题】:Select all records where last n characters in column are not unique选择列中最后 n 个字符不唯一的所有记录
【发布时间】:2019-09-07 08:51:42
【问题描述】:

我对 mysql 有一些奇怪的要求。 我应该从表中选择最后 6 个字符不是唯一的所有记录。

例如,如果我有桌子:

我应该选择第 1 行和第 3 行,因为这个值的最后 6 个字母不是唯一的。

您知道如何实现吗? 谢谢你的帮助。

【问题讨论】:

标签: mysql sql


【解决方案1】:

可能是一个快速代码,因为不涉及计数。

现场测试: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 的 answercorrelated 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,这可能比所有其他解决方案都慢。
  • @GhostGambler 并非所有相关的子查询都很慢。如果将其与COUNT 方法(认为 COUNT 具有百万行)进行对比,则 COUNT 将继续计数,无论它是否已经满足外部表的相同/唯一性。与使用EXISTS/NOT EXISTS 的相关子查询方法不同,一旦满足条件,它将停止评估
  • 只要满足至少一个标准,RDBMS 就可以利用EXISTS/NOT EXISTS 提示缩短评估
  • 也许你是对的,但我猜在检查 COUNT &gt; x 时可能会触发相同的优化,因为在这种情况下你实际上也不需要计算确切的计数。我想您的解决方案值得尝试使用真实数据和使用的 MySQL 版本。
  • @GhostGambler 你是对的。 COUNT &gt; x 方法更快。也许COUNT &gt; x 方法的表派生性质使得内部的查询可具体化为表。它是可实现的,因为它不相关/不依赖于源表。然后可以将一次性物化表与另一个表进行 JOIN'd / IN'd。与 EXISTS 方法不同,在该方法中,每一行都需要针对相关查询逐一评估
【解决方案2】:

代码更简洁一些(如果使用 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            |

【讨论】:

    【解决方案3】:

    这就是您所需要的:一个子查询来获取重复的权利(值,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
            ) 
    

    Fiddle here

    【讨论】:

    • 不确定这与我们的方法相比有多好。过去 MySQL 在使用 IN 时经常表现不佳。尽管如此,现在的执行计划似乎还可以。点赞。
    • @JoakimDanielson 这违反了哪里?外层查询没有分组,内层有一个,但只选择分组的列
    • @GhostGambler 这是 HAVING 子句的问题,它没有使用与 GROUP BY 相同的聚合函数,这是我的dbfiddle
    • @JoakimDanielson 很有趣。谢谢!
    • @JoakimDanielson 感觉 MySQL 5.7 和 8.0 的后期版本比较落后。 kiks73 的回答没有什么不寻常的地方。 MySQL 5.6 运行他的代码就好了。只有当您在 5.7 和 8.0 中运行它时,MySQL 才会抱怨。 MySQL 5.7 和 8.0 的行为感觉更像是一个错误,因为他的代码实际上将运行所有 RDBMS、Postgres、SQL Server、Oracle,我认为甚至 Microsoft Access :D 他的代码在 MySQL 5.6 中没有问题:db-fiddle.com/f/j8eq3BThgkRA1rCc5JtThS/0跨度>
    【解决方案4】:

    已编辑:我之前对这个问题的理解有误,我真的不想改变我最初的答案。但如果我之前的回答在某些环境下不能接受,可能会误导人,我还是得改正。

    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 进行了测试。 ;)

    【讨论】:

    • 当我使用 MySql 8 在 dbfiddle 上尝试时,这个查询会产生错误,你测试了吗?
    • 您在选择中混合了分组列和非分组列。这在干净的 SQL 中是不允许的:stackoverflow.com/a/5920259/3340665 MySQL 允许,但结果是不确定的。
    • 不使用 sql_mode=only_full_group_by。请参阅上面来自@GhostGambler 的评论
    • 好的,我现在明白了。而且我认为我也错误地理解了这个问题
    • 现在它工作正常,让我们看看 OP 对 GROUP_CONCAT 的看法 :)
    【解决方案5】:

    类似的东西应该可以工作:

    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而产生错误
    • @JoakimDanielson 是的,你是对的,我忘记了分组。现在修好了。
    • 嗯,现在没有错误,但它返回了我理解的相反结果,具有唯一值的行而不是具有重复值的行
    • @JoakimDanielson 是的,也注意到了。我虽然他想要唯一的值,但需要反转约束,也修复了。
    【解决方案6】:

    我对子查询使用 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
    

    【讨论】:

    • 比较执行计划,您的查询实际上比我的要好。 (还是很糟糕,因为全表扫描,但优化得更好。)
    • @GhostGambler 如果 RDBMS 有能力,您可以通过为 RIGHT(value, 2) 创建索引来最小化全表扫描,例如 Postgres
    猜你喜欢
    • 1970-01-01
    • 2019-11-06
    • 1970-01-01
    • 1970-01-01
    • 2019-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-17
    相关资源
    最近更新 更多