MySQL - SELECCIONA DONDE campo EN (subconsulta) - Extremadamente lento ¿por qué?

133

Tengo un par de duplicados en una base de datos que quiero inspeccionar, así que lo que hice para ver cuáles son duplicados, hice esto:

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

De esta manera, obtendré todas las filas con relevantes_campos que ocurran más de una vez. Esta consulta tarda milisegundos en ejecutarse.

Ahora, quería inspeccionar cada uno de los duplicados, por lo que pensé que podía SELECCIONAR cada fila en alguna_tabla con un campo relevante en la consulta anterior, así que me gustó esto:

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

Esto resulta ser extremadamente lento por alguna razón (lleva minutos). ¿Qué está pasando exactamente aquí para que sea tan lento? relevante_campo está indexado.

Eventualmente intenté crear una vista "temp_view" desde la primera consulta (SELECT relevant_field FROM some_table GROUP BY relevant_field HAVING COUNT(*) > 1), y luego hice mi segunda consulta como esta:

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

Y eso funciona bien. MySQL hace esto en algunos milisegundos.

¿Algún experto en SQL aquí que pueda explicar lo que está pasando?

quano
fuente
¿Qué quieres exactamente? desea eliminar entradas duplicadas excepto una? Sugerencia: lea Self Join
diEcho
1
obviamente es el grupo que es lento ...
ajreal
La primera consulta se ejecuta en milisegundos (la que agrupa y filtra con HAVING). Es solo en combinación con la otra consulta que hace que todo sea lento (lleva minutos).
Quano
@diEcho, quiero encontrar duplicados, inspeccionarlos y eliminar algunos manualmente.
Quano

Respuestas:

112

Reescribe la consulta en este

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

Creo que st2.relevant_fielddebe estar en la selección, porque de lo contrario la havingcláusula dará un error, pero no estoy 100% seguro

Nunca lo use INcon una subconsulta; Esto es notoriamente lento.
Solo use INcon una lista fija de valores.

Mas consejos

  1. Si desea realizar consultas más rápido, no SELECT *seleccione solo los campos que realmente necesita.
  2. Asegúrese de tener un índice relevant_fieldpara acelerar la equi-join.
  3. Asegúrese de hacerlo group byen la clave principal.
  4. Si está en InnoDB y solo selecciona campos indexados (y las cosas no son demasiado complejas), MySQL resolverá su consulta utilizando solo los índices, acelerando las cosas.

Solución general para el 90% de sus IN (select consultas.

Usa este código

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) 
Johan
fuente
1
También puedes escribir eso con HAVING COUNT(*) > 1. Suele ser más rápido en MySQL.
ypercubeᵀᴹ
@ypercube, hecho para la consulta inferior, creo que para la consulta superior alterará el resultado.
Johan
@Johan: Como st2.relevant_fieldno lo está NULL(ya está incluido en la ONcláusula), no alterará el resultado.
ypercubeᵀᴹ
@ypercube, para que pueda cambiar el conteo (campo) en conteo (*) si está seguro de afieldque nunca lo será null, lo tengo. Gracias
Johan
1
@quano, sí que enumera todos los duplicados porque el group byestá en st1.id, no en st1.relevant_field.
Johan
110

La subconsulta se ejecuta para cada fila porque es una consulta correlacionada. Se puede hacer una consulta correlacionada en una consulta no correlacionada seleccionando todo desde la subconsulta, de esta manera:

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

La consulta final se vería así:

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
)
quano
fuente
3
Esto funcionó increíblemente bien para mí. Tuve otra IN (subconsulta) dentro de una IN (subconsulta), y me tomó más de 10 minutos, tanto tiempo que busqué en Google mientras esperaba. ¡Ajustar cada subconsulta en SELECT * FROM () como sugirió lo redujo a 2 segundos!
Liam
GRACIAS, he estado tratando de encontrar una buena manera de hacer esto durante un par de horas. Esto funcionó perfectamente. ¡Ojalá pudiera darte más votos a favor! Esta definitivamente debería ser la respuesta.
thaspius
Funciona perfectamente. Una consulta que tardó ~ 50 segundos en ejecutarse ahora es instantánea. Ojalá pudiera votar más. A veces no puedes usar combinaciones, así que esta es la respuesta correcta.
Simon
Me pregunto por qué el optimizador considera que las consultas con los sindicatos están correlacionadas ... De todos modos, este truco funcionó como magia
Brian Leishman
2
¿Podría explicar qué hace que sea una subconsulta correlacionada? Entiendo que la subconsulta se correlaciona, cuando usa un valor que depende de la consulta externa. Pero en este ejemplo no puedo ver ninguna interdependencia. Daría el mismo resultado para cada fila devuelta por la consulta externa. Tengo un ejemplo similar que se está implementando en MariaDB y no puedo ver ningún impacto en el rendimiento (hasta ahora), por lo que me gustaría ver claramente, cuando SELECT *se necesita este ajuste.
sbnc.eu
6

Subconsultas vs uniones

http://www.scribd.com/doc/2546837/New-Subquery-Optimizations-In-MySQL-6

edze
fuente
Sospeché algo como esto, que la subconsulta se está ejecutando para cada fila.
Quano
Algunas versiones de MySQL incluso no usan un índice en IN. He agregado otro enlace.
edze
1
MySQL 6 aún no es estable, ¡no lo recomendaría para la producción!
Johan
1
Yo no lo recomendaría. Pero aquí se explica cómo funciona internamente (4.1 / 5.x -> 6). Esto demuestra algunas trampas de las versiones actuales.
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;

He intentado su consulta en una de mis bases de datos y también he intentado reescribirla como una combinación para una subconsulta.

Esto funcionó mucho más rápido, ¡pruébalo!

ceteras
fuente
Sí, esto probablemente creará una tabla temporal con los resultados del grupo, por lo que será la misma velocidad que la versión de vista. Pero los planes de consulta deberían decir la verdad.
ypercubeᵀᴹ
3

Prueba esto

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;
usuario2244323
fuente
2

He formateado su consulta sql lenta con www.prettysql.net

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

Al usar una tabla tanto en la consulta como en la subconsulta, siempre debe tener un alias para ambos, así:

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

¿Eso ayuda?

plang
fuente
1
Desafortunadamente no ayuda. Se ejecuta igual de lento.
Quano
He actualizado mi respuesta, ¿puedes intentarlo de nuevo? Incluso si el grupo es lento, debe ejecutarse solo una vez ...
plang
Accidentalmente maté un servidor mysql en vivo la última vez, así que me temo que no puedo intentarlo ahora. Tendré que configurar una base de datos de prueba más tarde. Pero no entiendo por qué esto debería afectar la consulta. La instrucción HAVING solo debería aplicarse a la consulta dentro de la cual se encuentra, ¿no? Realmente no entiendo por qué la consulta "real" debería afectar a la subconsulta.
quano
Encontré esto: xaprb.com/blog/2006/04/30/… . Creo que esta podría ser la solución. Lo intentaré cuando tenga tiempo.
Quano
2

En primer lugar, puede encontrar filas duplicadas y encontrar el recuento de filas que se utiliza cuántas veces y ordenarlo por un número como este;

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
) 

después de eso, cree una tabla e inserte el resultado.

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
) 

Finalmente, elimine las filas duplicadas. No es el inicio 0. Excepto el primer número de cada grupo, elimine todas las filas duplicadas.

delete from  CopyTable where No!= 0;

harun ugur
fuente
1

a veces, cuando los datos crecen, mysql WHERE IN podría ser bastante lento debido a la optimización de la consulta. Intente usar STRAIGHT_JOIN para decirle a mysql que ejecute la consulta tal como está, por ej.

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

pero cuidado: en la mayoría de los casos, mysql optimizer funciona bastante bien, por lo que recomendaría usarlo solo cuando tenga este tipo de problema

Andrey Posudevsky
fuente
0

Esto es similar a mi caso, donde tengo una tabla llamada tabel_buku_besar. Lo que necesito son

  1. Buscando registro que tienen account_code='101.100'en tabel_buku_besarque se companyarea='20000'y también tienen IDRcomocurrency

  2. Necesito obtener todos los registros de los tabel_buku_besarque tienen account_code igual que el paso 1 pero tienen el resultado transaction_numberen el paso 1

durante el uso select ... from...where....transaction_number in (select transaction_number from ....), mi consulta se ejecuta extremadamente lenta y, a veces, causa un tiempo de espera de la solicitud o hace que mi aplicación no responda ...

Intento esta combinación y el resultado ... no está 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`
Hilarius L. Doren
fuente
0

Creo que este es el más eficiente para encontrar si existe un valor, la lógica se puede invertir fácilmente para encontrar si un valor no existe (es decir, ES NULO);

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

* Reemplace relevante_campo con el nombre del valor que desea verificar existe en su tabla

* Reemplace primaryKey con el nombre de la columna de clave principal en la tabla de comparación.

Mate
fuente