web-dev-qa-db-fra.com

MySQL - Champ SELECT WHERE IN (sous-requête) - Extrêmement lent, pourquoi?

J'ai deux ou trois doublons dans une base de données que je veux inspecter, alors ce que j'ai fait pour voir lesquels sont des doublons, j'ai fait ceci:

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

De cette façon, toutes les lignes contenant pertinentes_field apparaîtront plus d'une fois. Cette requête prend quelques millisecondes à exécuter.

Maintenant, je voulais inspecter chacun des doublons, alors je pensais pouvoir CHOISIR chaque ligne de la table some_table avec un champ pertinent dans la requête ci-dessus.

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

Cela s'avère être extrêmement lent pour une raison quelconque (cela prend des minutes). Qu'est-ce qui se passe exactement ici pour le ralentir? relevant_field est indexé.

Finalement, j'ai essayé de créer une vue "temp_view" à partir de la première requête (SELECT relevant_field FROM some_table GROUP BY relevant_field HAVING COUNT(*) > 1), puis de créer ma deuxième requête comme suit:

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

Et ça marche très bien. MySQL le fait en quelques millisecondes.

Tous les experts SQL ici qui peuvent expliquer ce qui se passe?

113
quano

La sous-requête est en cours d'exécution pour chaque ligne car il s'agit d'une requête corrélée. On peut transformer une requête corrélée en une requête non corrélée en sélectionnant tous les éléments de la sous-requête, comme suit:

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

La requête finale ressemblerait à ceci:

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

Réécrivez la requête dans cette

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

Je pense que st2.relevant_field doit être dans la sélection, car sinon la clause having donnera une erreur, mais je ne suis pas sûr à 100%

N'utilisez jamais IN avec une sous-requête; c'est notoirement lent.
Ne jamais utiliser IN avec une liste fixe de valeurs. 

Plus de conseils  

  1. Si vous souhaitez effectuer des requêtes plus rapidement, ne modifiez pas ___SELECT * uniquement les champs dont vous avez réellement besoin.
  2. Assurez-vous d'avoir un index sur relevant_field pour accélérer l'équi-jointure.
  3. Assurez-vous de group by sur la clé primaire. 
  4. Si vous êtes sur InnoDB et vous ne sélectionnez que les champs indexés (et les choses ne sont pas trop complexes), MySQL résoudra votre requête en utilisant uniquement les index, accélérant ainsi les choses.

Solution générale pour 90% de vos requêtes IN (select

Utilisez ce code

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

J'ai essayé votre requête sur l'une de mes bases de données et je l'ai également réécrite en tant que jointure d'une sous-requête.

Cela a fonctionné beaucoup plus vite, essayez-le!

4
ceteras

Essaye ça

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

J'ai reformaté votre requête SQL lente avec www.prettysql.net

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

Lorsque vous utilisez une table à la fois dans la requête et dans la sous-requête, vous devez toujours aliaser les deux, comme ceci:

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

Est ce que ça aide?

3
plang

parfois, lorsque les données deviennent plus volumineuses, mysql WHERE IN peut être assez lent en raison de l'optimisation des requêtes. Essayez d’utiliser STRAIGHT_JOIN pour indiquer à mysql d’exécuter la requête telle quelle, par exemple. 

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

mais attention: dans la plupart des cas, l'optimiseur mysql fonctionne plutôt bien, je vous recommande donc de ne l'utiliser que lorsque vous avez ce genre de problème

1
Andrey Posudevsky

Tout d'abord, vous pouvez rechercher des lignes en double et trouver le nombre de lignes utilisé autant de fois que vous le souhaitez, et le classer par numéro comme ceci;

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
) 

après cela, créez une table et y insérez le résultat.

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
) 

Enfin, supprimez les doublons de lignes.No signifie début 0. Sauf le premier nombre de chaque groupe, supprimez toutes les rangées de doublons. 

delete from  CopyTable where No!= 0;

0
harun ugur

Ceci est similaire à mon cas, où j'ai une table nommée tabel_buku_besar. Ce dont j'ai besoin

  1. Recherche d'un enregistrement qui a account_code='101.100' dans tabel_buku_besar qui a companyarea='20000' et qui a aussi IDR comme currency

  2. Je dois obtenir tous les enregistrements de tabel_buku_besar qui ont le code account_code identique à l'étape 1 mais ont transaction_number à l'étape 1 résultat 

lors de l'utilisation de select ... from...where....transaction_number in (select transaction_number from ....), ma requête était extrêmement lente et pouvait parfois entraîner l'expiration du délai de requête ou rendre mon application ne répondant pas ...

J'essaie cette combinaison et le résultat ... pas mal ...

`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
Hilarius L. Doren

Je trouve que c'est le moyen le plus efficace de rechercher si une valeur existe, la logique peut facilement être inversée pour rechercher si une valeur n'existe pas (c'est-à-dire 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

* Remplacez pertinent_field par le nom de la valeur que vous voulez vérifier existe dans votre table

* Remplacez primaryKey par le nom de la colonne de clé primaire du tableau de comparaison.

0
Matt