MySQL - SELECT WHERE Feld IN (Unterabfrage) - Extrem langsam warum?


133

Ich habe ein paar Duplikate in einer Datenbank, die ich überprüfen möchte. Was ich also getan habe, um zu sehen, welche Duplikate sind, habe ich folgendermaßen gemacht:

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

Auf diese Weise werden alle Zeilen mit relevantem_Feld mehr als einmal angezeigt. Die Ausführung dieser Abfrage dauert Millisekunden.

Jetzt wollte ich jedes der Duplikate untersuchen, also dachte ich, ich könnte jede Zeile in einer Tabelle mit einem relevanten Feld in der obigen Abfrage AUSWÄHLEN, also gefiel mir Folgendes:

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

Dies stellt sich aus irgendeinem Grund als äußerst langsam heraus (es dauert Minuten). Was genau ist hier los, um es so langsam zu machen? relevant_field ist indiziert.

Schließlich habe ich versucht, aus der ersten Abfrage eine Ansicht "temp_view" zu erstellen (SELECT relevant_field FROM some_table GROUP BY relevant_field HAVING COUNT(*) > 1)und stattdessen meine zweite Abfrage wie folgt durchzuführen :

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

Und das funktioniert gut. MySQL erledigt dies in einigen Millisekunden.

Gibt es hier SQL-Experten, die erklären können, was los ist?


Was genau willst du? Möchten Sie doppelte Einträge außer einem löschen? Vorschlag: Bitte lesen Sie Self Join
diEcho


Die erste Abfrage wird in Millisekunden ausgeführt (die eine Gruppierung und Filterung mit HAVING). Nur in Kombination mit der anderen Abfrage wird alles langsam (es dauert Minuten).
Quano

@diEcho, ich möchte Duplikate finden, untersuchen und einige manuell löschen.
Quano

Antworten:


112

Schreiben Sie die Abfrage in diese um

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

Ich denke, st2.relevant_fieldmuss in der Auswahl sein, weil sonst die havingKlausel einen Fehler gibt, aber ich bin nicht 100% sicher

Niemals INmit einer Unterabfrage verwenden. das ist notorisch langsam.
Nur INmit einer festen Werteliste verwenden.

Mehr Tipps

  1. Wenn Sie Abfragen schneller durchführen möchten, SELECT *wählen Sie nicht nur die Felder aus, die Sie wirklich benötigen.
  2. Stellen Sie sicher, dass Sie einen Index relevant_fieldaktiviert haben, um den Equi-Join zu beschleunigen.
  3. Stellen Sie sicher, dass Sie group byden Primärschlüssel verwenden.
  4. Wenn Sie sich in InnoDB befinden und nur indizierte Felder auswählen (und die Dinge nicht zu komplex sind), löst MySQL Ihre Abfrage nur anhand der Indizes auf, was die Dinge erheblich beschleunigt.

Allgemeine Lösung für 90% Ihrer IN (select Anfragen

Verwenden Sie diesen 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) 

1
Sie können das auch mit schreiben HAVING COUNT(*) > 1. In MySQL ist es normalerweise schneller.
Ypercubeᵀᴹ

@ypercube, fertig für die untere Abfrage, ich denke, dass es für die obere Abfrage das Ergebnis verändern wird.
Johan

@Johan: Da dies st2.relevant_fieldnicht der Fall ist NULL(es ist bereits in der ONKlausel enthalten), wird das Ergebnis nicht geändert .
Ypercubeᵀᴹ

@ypercube, also können Sie count (afield) in count (*) ändern, wenn Sie sicher sind, dass dies afieldniemals der Fall sein nullwird. Danke
Johan

1
@quano, ja, es listet alle Duplikate auf, weil das eingeschaltet group byist st1.id, nicht eingeschaltet st1.relevant_field.
Johan

110

Die Unterabfrage wird für jede Zeile ausgeführt, da es sich um eine korrelierte Abfrage handelt. Sie können eine korrelierte Abfrage in eine nicht korrelierte Abfrage verwandeln, indem Sie alles aus der Unterabfrage auswählen, wie folgt:

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

Die letzte Abfrage würde folgendermaßen aussehen:

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
Das hat bei mir erstaunlich gut funktioniert. Ich hatte eine weitere IN (Unterabfrage) innerhalb einer IN (Unterabfrage) und es dauerte mehr als 10 Minuten, so lange, dass ich googelte, während ich wartete. Wenn Sie jede Unterabfrage in SELECT * FROM () einschließen, wie Sie vorgeschlagen haben, reduzieren Sie sie auf 2 Sekunden!
Liam

DANKE, ich habe seit ein paar Stunden versucht, einen guten Weg zu finden, dies zu tun. Das hat perfekt funktioniert. Ich wünschte, ich könnte dir mehr Stimmen geben! Dies sollte definitiv die Antwort sein.
Thaspius

Funktioniert perfekt. Eine Abfrage, deren Ausführung ~ 50 Sekunden dauerte, erfolgt jetzt sofort. Ich wünschte, ich könnte mehr stimmen. Manchmal können Sie keine Joins verwenden, daher ist dies die richtige Antwort.
Simon

Ich frage mich, warum der Optimierer Abfragen mit Gewerkschaften als korreliert betrachtet ... Wie auch immer, dieser Trick hat wie Zauberei funktioniert
Brian Leishman

2
Könnten Sie bitte erklären, was das zu einer korrelierten Unterabfrage macht? Mein Verständnis, dass Unterabfragen korrelieren, wenn sie einen Wert verwenden, der von der äußeren Abfrage abhängt. In diesem Beispiel sehe ich jedoch keine Abhängigkeiten. Es würde das gleiche Ergebnis für jede Zeile geben, die von der äußeren Abfrage zurückgegeben wird. Ich habe ein ähnliches Beispiel, das in MariaDB implementiert ist, und ich kann (bisher) keinen Leistungseinbruch feststellen. Daher möchte ich klar sehen, wann diese SELECT *Umhüllung erforderlich ist.
sbnc.eu

6

Ich vermutete so etwas, dass die Unterabfrage für jede Zeile ausgeführt wird.
Quano

Einige MySQL-Versionen verwenden in IN sogar keinen Index. Ich habe einen weiteren Link hinzugefügt.
Edze

1
MySQL 6 ist noch nicht stabil, das würde ich für die Produktion nicht empfehlen!
Johan

1
Ich würde es nicht empfehlen. Hier wird aber erklärt, wie es intern läuft (4.1 / 5.x -> 6). Dies zeigt einige Fallstricke der aktuellen Versionen.
Edze

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;

Ich habe Ihre Abfrage in einer meiner Datenbanken ausprobiert und auch versucht, sie als Join zu einer Unterabfrage umzuschreiben.

Das hat viel schneller geklappt, probieren Sie es aus!


Ja, dies wird wahrscheinlich eine temporäre Tabelle mit den Gruppenergebnissen erstellen, sodass diese Geschwindigkeit der Ansichtsversion entspricht. Aber die Abfragepläne sollten die Wahrheit sagen.
Ypercubeᵀᴹ

3

Versuche dies

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

Ich habe Ihre langsame SQL-Abfrage mit www.prettysql.net neu formatiert

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

Wenn Sie eine Tabelle sowohl in der Abfrage als auch in der Unterabfrage verwenden, sollten Sie beide immer wie folgt aliasisieren:

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

Hilft das?


1
Es hilft leider nicht. Es wird genauso langsam ausgeführt.
Quano

Ich habe meine Antwort aktualisiert. Können Sie es erneut versuchen? Selbst wenn die Gruppe langsam ist, sollte sie nur einmal ausgeführt werden ...
Plang

Ich habe beim letzten Mal versehentlich einen Live-MySQL-Server getötet, daher kann ich das momentan leider nicht ausprobieren. Ich muss später eine Testdatenbank einrichten. Aber ich verstehe nicht, warum dies die Abfrage beeinflussen sollte. Die HAVING-Anweisung sollte nur für die Abfrage gelten, in der sie sich befindet, nicht wahr? Ich verstehe wirklich nicht, warum die "echte" Abfrage die Unterabfrage beeinflussen sollte.
Quano

Ich habe folgendes gefunden: xaprb.com/blog/2006/04/30/… . Ich denke, das könnte die Lösung sein. Ich werde es versuchen, wenn ich Zeit habe.
Quano

2

Erstens können Sie doppelte Zeilen finden und die Anzahl der Zeilen wird wie oft verwendet und nach dieser Nummer sortiert.

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
) 

Erstellen Sie anschließend eine Tabelle und fügen Sie das Ergebnis ein.

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
) 

Löschen Sie schließlich die Zeilen für die Veröffentlichung. Nein ist Start 0. Mit Ausnahme der ersten Nummer jeder Gruppe löschen Sie alle Zeilen für die Veröffentlichung.

delete from  CopyTable where No!= 0;


1

Manchmal, wenn die Daten größer werden, kann mysql WHERE IN aufgrund der Abfrageoptimierung ziemlich langsam sein. Versuchen Sie, mit STRAIGHT_JOIN mysql anzuweisen, die Abfrage unverändert auszuführen, z

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

Aber Vorsicht: In den meisten Fällen funktioniert der MySQL-Optimierer ziemlich gut. Ich würde daher empfehlen, ihn nur zu verwenden, wenn Sie solche Probleme haben


0

Dies ähnelt meinem Fall, in dem ich eine Tabelle mit dem Namen habe tabel_buku_besar. Was ich brauche sind

  1. Auf der Suche nach Aufzeichnungen, die account_code='101.100'in tabel_buku_besardenen haben companyarea='20000'und auch IDRals habencurrency

  2. Ich muss alle tabel_buku_besarDatensätze abrufen, deren Kontocode mit Schritt 1 identisch ist, aber transaction_numberin Schritt 1 das Ergebnis hat

Während der Verwendung select ... from...where....transaction_number in (select transaction_number from ....)läuft meine Abfrage extrem langsam und führt manchmal zu einem Zeitlimit für Anfragen oder dazu, dass meine Anwendung nicht reagiert ...

Ich versuche diese Kombination und das Ergebnis ... nicht schlecht ...

`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

Ich finde, dass dies am effizientesten ist, um festzustellen, ob ein Wert vorhanden ist. Die Logik kann leicht invertiert werden, um festzustellen, ob ein Wert nicht vorhanden ist (dh 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

* Ersetzen Sie das relevante_Feld durch den Namen des Werts, den Sie überprüfen möchten und der in Ihrer Tabelle vorhanden ist

* Ersetzen Sie den Primärschlüssel durch den Namen der Primärschlüsselspalte in der Vergleichstabelle.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.