如何在 SQL 中查找以相同字符串结尾的行?

How to find rows in SQL that end with the same string?

我有一个类似于此处发现的问题:How to find rows in SQL that start with the same string (similar rows)?,此解决方案适用于 MySQL 5.6,但不适用于 5.7。

我有一个包含多列的数据库 (t),重要的是 id 和文件路径,我想要完成的是检索所有最后 5 个字符相同的文件路径。以下在 MySQL5.6 中工作,第二个 SELECT 在 5.7 中工作正常:

SELECT id, filepath FROM t
WHERE SUBSTRING(filepath, -5) IN
(
  SELECT SUBSTRING(filepath, -5) 
  FROM t 
  GROUP BY SUBSTRING(filepath, -5)
  HAVING COUNT(*) > 1
)

但是当我在 5.7 上尝试 运行 它时,我收到错误

Expression #1 of HAVING clause is not in GROUP BY clause and contains 
nonaggregated column 't.filepath' which is not functionally dependent on 
columns in GROUP BY clause; this is incompatible with 
sql_mode=only_full_group_by

示例数据:

id     filepath
1      /Desktop/file1.txt
2      /Desktop/file2.txt
3      /Desktop/file1.txt

我想要 return id 为 1 和 3 的行。我如何为 MySQL5.7 解决这个问题?

编辑:还有谁能指出我 SQL 删除重复项的正确方向吗?所以我想删除 id 3 的条目,但保留 id 1 和 2 的条目。

请阅读有关 GROUP BY 和 sql_mode only_full_group_by 主题的 mysql 文档(如您的错误消息所述): https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html

我认为将内部查询更改为此可能会解决问题:

SELECT SUBSTRING(filepath, -5) AS fpath
FROM t 
GROUP BY fpath
HAVING COUNT(fpath) > 1

编辑:

关于为什么添加 "AS fpath" 有效的问题: 添加别名 "fpath" 只是一种干净的方法。 ONLY_FULL_GROUP_BY 的要点是您在 SELECT、HAVING 或 ORDER BY 中使用的每个字段也必须在 GROUP BY 中。

所以出于多种原因我添加了 fpath 别名:

  • 为了性能:您编写的查询有两次 SUBSTRING(filepath, -5),这 对性能不利。 Mysql 必须执行该 SUBSTRING 调用两次, 而在我的例子中,它只需要执行一次(每行)。
  • 解决分组问题: 您有 COUNT() 个,但“”不在您的 GROUP 中BY 语句(我什至不确定这是否可能)。你必须计算 "something",所以因为 "fpath" 在你的 SELECT 和你的 GROUP BY 中,使用它作为你的 COUNT() 会解决问题。

我不想将子查询放在 IN() 谓词中,因为 MySQL 倾向于 运行 子查询很多次。

您可以编写不同的查询,将子查询作为派生的 table 放在 FROM 子句中。这将使 MySQL 运行 子查询仅一次。

SELECT id, filepath 
FROM (
  SELECT SUBSTRING(filepath, -5) AS suffix, COUNT(*) AS count
  FROM t 
  GROUP BY suffix
  HAVING count > 1
) AS t1
JOIN t AS t2 ON SUBSTRING(t2.filepath, -5) = t1.suffix

不过这必然会进行 table 扫描,因此这将是一个代价高昂的查询。像这样进行子字符串比较时不能使用索引。

要对此进行优化,您可以创建一个带有索引的虚拟列。

ALTER TABLE t 
  ADD COLUMN filepath_last VARCHAR(10) AS (SUBSTRING_INDEX(filepath, '/', -1)),
  ADD KEY (filepath_last);

那么你可以这样查询,至少子查询使用了一个索引:

SELECT id, filepath 
FROM (
  SELECT filepath_last, COUNT(*) AS count
  FROM t 
  GROUP BY filepath_last
  HAVING count > 1
) AS t1
STRAIGHT_JOIN t AS t2 ON t2.filepath_last = t1.filepath_last

最终对我有用的解决方案是在这里找到的:Disable ONLY_FULL_GROUP_BY

I 运行 SELECT @@sql_mode 然后 SET @@sql_mode = 后跟一个包含第一个查询返回的所有值的字符串,除了 only_full_group_by,但我仍然对如何在不更改 SQL 设置的情况下完成此操作。