MySQL-SELECT WHERE字段IN(子查询)-为什么极慢?


133

我要检查的数据库中有几个重复项,因此,为了查看哪些重复项,我执行了以下操作:

SELECT relevant_field
FROM some_table
GROUP BY relevant_field
HAVING COUNT(*) > 1

这样,我将获得与related_field一起出现的所有行不止一次。该查询需要毫秒才能执行。

现在,我想检查每个重复项,因此我想可以在上述查询中选择带有some_table的每行以及一个related_field,因此我做到了:

SELECT *
FROM some_table 
WHERE relevant_field IN
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
)

由于某种原因,这实际上是缓慢的(需要几分钟)。到底是什么使它变慢了?related_field已建立索引。

最终,我尝试从第一个查询创建视图“ temp_view” (SELECT relevant_field FROM some_table GROUP BY relevant_field HAVING COUNT(*) > 1),然后像这样进行第二个查询:

SELECT *
FROM some_table
WHERE relevant_field IN
(
    SELECT relevant_field
    FROM temp_view
)

而且效果很好。MySQL在几毫秒内完成了此操作。

这里有任何SQL专家可以解释发生了什么吗?


你到底想要什么?要删除重复的条目,除了一个?建议:请阅读自我加入
diEcho

1
显然是缓慢的分组方式……
ajreal 2011年

第一个查询以毫秒为单位执行(使用HAVING进行分组和过滤)。它仅与其他查询结合使用,这会使一切变慢(需要几分钟)。
奎诺2011年

@diEcho,我想查找重复项,检查它们,然后手动删除一些。
2011年

Answers:


112

将查询重写为此

SELECT st1.*, st2.relevant_field FROM sometable st1
INNER JOIN sometable st2 ON (st1.relevant_field = st2.relevant_field)
GROUP BY st1.id  /* list a unique sometable field here*/
HAVING COUNT(*) > 1

我认为st2.relevant_field必须在select中,因为否则该having子句将给出错误,但是我不确定100%

切勿IN与子查询一起使用;众所周知,这很慢。
仅用于IN固定值列表。

更多提示

  1. 如果您想更快地查询,请不要SELECT *只选择您真正需要的字段。
  2. 确保您有索引relevant_field以加快等联接。
  3. 确保group by在主键上。
  4. 如果您使用的是InnoDB,并且仅选择索引字段(并且事情不太复杂),那么MySQL将仅使用索引来解决您的查询,从而加快了工作速度。

90%的IN (select 查询的通用解决方案

使用此代码

SELECT * FROM sometable a WHERE EXISTS (
  SELECT 1 FROM sometable b
  WHERE a.relevant_field = b.relevant_field
  GROUP BY b.relevant_field
  HAVING count(*) > 1) 

1
您也可以使用来写HAVING COUNT(*) > 1。在MySQL中通常更快。
ypercubeᵀᴹ

@ypercube,为底部查询完成,我认为对于顶部查询,它将改变结果。
约翰

@Johan:由于st2.relevant_field没有NULL(它已经包含在ON子句中),所以不会改变结果。
ypercubeᵀᴹ

@ypercube,因此,如果您确定afield永远不会null,就可以将count(afield)更改为count(*)了。谢谢
约翰

1
@quano,是的,它列出了所有重复项,因为group byon是st1.id,不是on st1.relevant_field
约翰

110

正在为每一行运行子查询,因为它是一个相关查询。通过从子查询中选择所有内容,可以将相关查询变成不相关查询,如下所示:

SELECT * FROM
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
) AS subquery

最终查询如下所示:

SELECT *
FROM some_table
WHERE relevant_field IN
(
    SELECT * FROM
    (
        SELECT relevant_field
        FROM some_table
        GROUP BY relevant_field
        HAVING COUNT(*) > 1
    ) AS subquery
)

3
这对我来说效果非常好。我在一个IN(子查询)中有另一个IN(子查询),这花费了超过10分钟的时间,以至于我在等待时用谷歌搜索。按照您的建议将每个子查询包装在SELECT * FROM()中,将其减少到2秒!
利亚姆

谢谢,我已经尝试了好几个小时了。这工作得很好。希望我能给您更多的支持!绝对应该是答案。
thaspius

完美运作。现在,查询耗时约50秒。希望我能投票更多。有时您无法使用联接,因此这是正确的答案。
simon

我不知道为什么优化器会考虑与关联相关的查询...无论如何,此技巧像魔术一样起作用
Brian Leishman

2
您能否解释一下使该子查询相关的原因?我的理解是,当子查询使用依赖于外部查询的值时,子查询就变得相关。但是在此示例中,我看不到任何相互依赖性。对于外部查询返回的每一行,它都会给出相同的结果。我有一个在MariaDB上实现的类似示例,到目前为止,我看不到性能受到任何影响,所以我想清楚地看到何时SELECT *需要这种包装。
sbnc.eu


5
SELECT st1.*
FROM some_table st1
inner join 
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
)st2 on st2.relevant_field = st1.relevant_field;

我已经在我的一个数据库上尝试了您的查询,还尝试将其重写为对子查询的联接。

这样做的速度更快,请尝试!


是的,这可能会创建具有组结果的临时表,因此其速度与视图版本相同。但是查询计划应该说实话。
ypercubeᵀᴹ

3

试试这个

SELECT t1.*
FROM 
 some_table t1,
  (SELECT relevant_field
  FROM some_table
  GROUP BY relevant_field
  HAVING COUNT (*) > 1) t2
WHERE
 t1.relevant_field = t2.relevant_field;

2

我已经用www.prettysql.net重新格式化了您的慢速SQL查询

SELECT *
FROM some_table
WHERE
 relevant_field in
 (
  SELECT relevant_field
  FROM some_table
  GROUP BY relevant_field
  HAVING COUNT ( * ) > 1
 );

在查询和子查询中都使用表时,应始终对两者都使用别名,如下所示:

SELECT *
FROM some_table as t1
WHERE
 t1.relevant_field in
 (
  SELECT t2.relevant_field
  FROM some_table as t2
  GROUP BY t2.relevant_field
  HAVING COUNT ( t2.relevant_field ) > 1
 );

有帮助吗?


1
不幸的是,它没有帮助。它的执行速度同样慢。
2011年

我已经更新了答案,您可以再试一次吗?即使group by很慢,也只能执行一次...
plang 2011年

我上次无意中杀死了一个活动的mysql服务器,所以我现在无法尝试这样做。稍后我将不得不建立一个测试数据库。但是我不明白为什么这会影响查询。HAVING语句应仅适用于它所在的查询,不是吗?我真的不明白为什么“真实”查询会影响子查询。
2011年

我找到了:xaprb.com/blog/2006/04/30/…。我认为这可能是解决方案。我有时间会尝试。
夸诺2011年

2

首先,您可以找到重复的行,并查找使用的行数,并按这样的编号对其进行排序;

SELECT q.id,q.name,q.password,q.NID,(select count(*) from UserInfo k where k.NID= q.NID) as Count,
(
		CASE q.NID
		WHEN @curCode THEN
			@curRow := @curRow + 1
		ELSE
			@curRow := 1
		AND @curCode := q.NID
		END
	) AS No
FROM UserInfo q,
(
		SELECT
			@curRow := 1,
			@curCode := ''
	) rt
WHERE q.NID IN
(
    SELECT NID
    FROM UserInfo
    GROUP BY NID
    HAVING COUNT(*) > 1
) 

之后,创建一个表并向其中插入结果。

create table CopyTable 
SELECT q.id,q.name,q.password,q.NID,(select count(*) from UserInfo k where k.NID= q.NID) as Count,
(
		CASE q.NID
		WHEN @curCode THEN
			@curRow := @curRow + 1
		ELSE
			@curRow := 1
		AND @curCode := q.NID
		END
	) AS No
FROM UserInfo q,
(
		SELECT
			@curRow := 1,
			@curCode := ''
	) rt
WHERE q.NID IN
(
    SELECT NID
    FROM UserInfo
    GROUP BY NID
    HAVING COUNT(*) > 1
) 

最后,删除重复的行。编号从0开始。除每个组的第一个数字外,删除所有重复的行。

delete from  CopyTable where No!= 0;


1

有时,当数据变大时,由于查询优化,mysql WHERE IN的速度可能会非常慢。尝试使用STRAIGHT_JOIN告诉mysql按原样执行查询,例如

SELECT STRAIGHT_JOIN table.field FROM table WHERE table.id IN (...)

但要注意:在大多数情况下,mysql优化器运行良好,因此,我建议仅在遇到此类问题时才使用它


0

这与我的情况类似,我有一个名为的表tabel_buku_besar。我需要的是

  1. 寻找记录有account_code='101.100'tabel_buku_besarcompanyarea='20000',也有IDR作为currency

  2. 我需要获得所有记录,tabel_buku_besar这些记录的account_code与步骤1相同,但transaction_number在步骤1中有结果

使用时select ... from...where....transaction_number in (select transaction_number from ....),我的查询运行极其缓慢,有时会导致请求超时或使我的应用程序没有响应...

我尝试这种组合和结果...还不错...

`select DATE_FORMAT(L.TANGGAL_INPUT,'%d-%m-%y') AS TANGGAL,
      L.TRANSACTION_NUMBER AS VOUCHER,
      L.ACCOUNT_CODE,
      C.DESCRIPTION,
      L.DEBET,
      L.KREDIT 
 from (select * from tabel_buku_besar A
                where A.COMPANYAREA='$COMPANYAREA'
                      AND A.CURRENCY='$Currency'
                      AND A.ACCOUNT_CODE!='$ACCOUNT'
                      AND (A.TANGGAL_INPUT BETWEEN STR_TO_DATE('$StartDate','%d/%m/%Y') AND STR_TO_DATE('$EndDate','%d/%m/%Y'))) L 
INNER JOIN (select * from tabel_buku_besar A
                     where A.COMPANYAREA='$COMPANYAREA'
                           AND A.CURRENCY='$Currency'
                           AND A.ACCOUNT_CODE='$ACCOUNT'
                           AND (A.TANGGAL_INPUT BETWEEN STR_TO_DATE('$StartDate','%d/%m/%Y') AND STR_TO_DATE('$EndDate','%d/%m/%Y'))) R ON R.TRANSACTION_NUMBER=L.TRANSACTION_NUMBER AND R.COMPANYAREA=L.COMPANYAREA 
LEFT OUTER JOIN master_account C ON C.ACCOUNT_CODE=L.ACCOUNT_CODE AND C.COMPANYAREA=L.COMPANYAREA 
ORDER BY L.TANGGAL_INPUT,L.TRANSACTION_NUMBER`

0

我发现这是查找值是否存在的最有效方法,可以很容易地将逻辑取反以查找值是否不存在(即IS NULL)。

SELECT * FROM primary_table st1
LEFT JOIN comparision_table st2 ON (st1.relevant_field = st2.relevant_field)
WHERE st2.primaryKey IS NOT NULL

*将您要检查的值的名称替换为表中存在的related_field

*用比较表上主键列的名称替换primaryKey。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.