web-dev-qa-db-ja.com

MySQL-SELECT WHEREフィールドIN(サブクエリ)-非常に遅い理由

検査したいデータベースにいくつかの重複があるので、どの重複を確認するために何をしたか、これを行いました:

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

このようにして、relevant_fieldが複数回発生するすべての行を取得します。このクエリの実行には数ミリ秒かかります。

今、私は重複のそれぞれを検査したかったので、上記のクエリのrelevant_fieldでsome_tableの各行を選択できると思ったので、私はこれをしました:

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

これは、何らかの理由で非常に遅いことが判明しました(数分かかります)。それを遅くするために、ここで正確に何が起こっていますか? related_fieldにはインデックスが付けられます。

最終的に、最初のクエリ(SELECT relevant_field FROM some_table GROUP BY relevant_field HAVING COUNT(*) > 1)からビュー "temp_view"を作成し、次に代わりに2番目のクエリを作成しようとしました。

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

そしてそれはうまく機能します。 MySQLはこれを数ミリ秒で行います。

ここで何が起こっているのか説明できるSQLの専門家はいますか?

118
quano

サブクエリは相関クエリであるため、各行に対して実行されています。次のように、サブクエリからすべてを選択することにより、相関クエリを非相関クエリにすることができます。

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
)
95
quano

クエリをこれに書き換えます

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を使用している場合andインデックス付きフィールドのみを選択します(そして複雑ではありません)物事の方法。

IN (selectクエリの90%の一般的なソリューション

このコードを使用

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) 
109
Johan
5
edze
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;

私のデータベースの1つでクエリを試し、サブクエリへの結合として書き直しました。

これははるかに速く動作しました、試してみてください!

4
ceteras

これを試して

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;
3
user2244323

遅いsqlクエリをwww.prettysql.netで再フォーマットしました

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
 );

それは役立ちますか?

3
plang

まず、重複する行を見つけて、行の数を見つけることができる回数を使用し、このように番号順に並べます。

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
) 

最後に、重複行を削除します。Noはstart 0です。各グループの最初の番号を除き、重複行をすべて削除します。

delete from  CopyTable where No!= 0;
1
harun ugur

データが大きくなると、クエリの最適化のためにmysql WHERE INがかなり遅くなることがあります。 STRAIGHT_JOINを使用して、mysqlにクエリをそのまま実行するように指示してみてください。

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

しかし注意してください:ほとんどの場合、mysqlオプティマイザーはかなりうまく機能するので、この種の問題がある場合にのみ使用することをお勧めします

1

これは、tabel_buku_besarという名前のテーブルがある場合に似ています。必要なのは

  1. account_code='101.100'tabel_buku_besarがあり、companyarea='20000'があり、さらにIDRcurrencyであるレコードを探しています

  2. ステップ1と同じaccount_codeを持つが、ステップ1の結果にtabel_buku_besarがあるtransaction_numberからすべてのレコードを取得する必要がある

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

* relevant_fieldを、テーブルに存在するチェックする値の名前に置き換えます

* primaryKeyを比較テーブルの主キー列の名前に置き換えます。

0
Matt