MySQL - SELECT WHERE campo IN (subconsulta) - Extremamente lento por que?


133

Eu tenho algumas duplicatas em um banco de dados que quero inspecionar. Portanto, o que fiz para ver quais são duplicadas, fiz o seguinte:

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

Dessa forma, vou obter todas as linhas com relevantes_field ocorrendo mais de uma vez. Essa consulta leva milissegundos para executar.

Agora, eu queria inspecionar cada uma das duplicatas, então pensei em selecionar cada linha em alguma tabela com um campo relevante na consulta acima, então fiz o seguinte:

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

Isso acaba sendo extremamente lento por algum motivo (leva minutos). O que exatamente está acontecendo aqui para torná-lo tão lento? relevantes_campo é indexado.

Eventualmente, tentei criar uma visualização "temp_view" a partir da primeira consulta (SELECT relevant_field FROM some_table GROUP BY relevant_field HAVING COUNT(*) > 1)e, em seguida, fazer minha segunda consulta como esta:

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

E isso funciona muito bem. O MySQL faz isso em alguns milissegundos.

Algum especialista em SQL aqui que pode explicar o que está acontecendo?


o que você quer exatamente? deseja excluir entradas duplicadas, exceto uma? Sugestão: leia Self Join
diEcho

1
obviamente, é o grupo-by que é lento ...
ajreal

A primeira consulta é executada em milissegundos (o agrupamento e a filtragem com HAVING). É apenas em combinação com a outra consulta que torna tudo lento (leva minutos).
quano 26/05

@diEcho, quero encontrar duplicatas, inspecioná-las e excluir algumas manualmente.
quano 26/05

Respostas:


112

Reescreva a consulta neste

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

Eu acho que st2.relevant_fielddeve estar no select, porque caso contrário a havingcláusula dará um erro, mas não tenho 100% de certeza

Nunca use INcom uma subconsulta; isso é notoriamente lento.
Sempre use apenas INcom uma lista fixa de valores.

Mais dicas

  1. Se você deseja tornar as consultas mais rápidas, não SELECT *selecione apenas os campos que realmente precisa.
  2. Verifique se você possui um índice relevant_fieldpara acelerar a junção equitativa.
  3. Certifique-se de que está group byna chave primária.
  4. Se você estiver no InnoDB e selecionar apenas campos indexados (e as coisas não forem muito complexas), o MySQL resolverá sua consulta usando apenas os índices, acelerando as coisas.

Solução geral para 90% de suas IN (select consultas

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

1
Você também pode escrever isso com HAVING COUNT(*) > 1. Geralmente é mais rápido no MySQL.
ypercubeᵀᴹ

@ypercube, feito para a consulta inferior, acho que para a consulta superior ele alterará o resultado.
26411 Johan

@ Johan: Como st2.relevant_fieldnão está NULL(já está incluído na ONcláusula), não altera o resultado.
ypercubeᵀᴹ

@ypercube, para que você possa alterar a contagem (fora) em contagem (*) se tiver certeza de afieldque nunca será null, entendeu. Obrigado
Johan

1
@quano, sim ele lista todas as duplicatas porque o group byé em st1.id, e não sobre st1.relevant_field.
26411 Johan

110

A subconsulta está sendo executada para cada linha porque é uma consulta correlacionada. Pode-se transformar uma consulta correlacionada em uma consulta não correlacionada selecionando tudo da subconsulta, da seguinte forma:

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

A consulta final ficaria assim:

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
Isso funcionou incrivelmente bem para mim. Eu tinha outra IN (subconsulta) dentro de uma subconsulta (IN) e demorava mais de 10 minutos, tanto tempo que pesquisei no Google enquanto esperava. O agrupamento de cada subconsulta em SELECT * FROM () como você sugeriu reduziu para 2 segundos!
Liam

OBRIGADO, estou tentando descobrir uma boa maneira de fazer isso há algumas horas. Isso funcionou perfeitamente. Gostaria de poder lhe dar mais votos! Definitivamente, essa deve ser a resposta.
thaspius 27/01

Funciona perfeitamente. Uma consulta que levou ~ 50 segundos para ser executada agora é instantânea. Gostaria de poder votar mais. Às vezes você não pode usar junções, então esta é a resposta certa.
simon

Eu me pergunto por que o otimizador considera consultas com sindicatos correlacionados ... De qualquer forma, este truque funcionou como mágica
Brian Leishman

2
Você poderia explicar o que faz disso uma subconsulta correlacionada? Meu entendimento de que a subconsulta se correlaciona quando utiliza um valor que depende da consulta externa. Mas neste exemplo, não consigo ver nenhuma interdependência. Daria o mesmo resultado para cada linha retornada pela consulta externa. Eu tenho um exemplo semelhante sendo implementado no MariaDB e não vejo impacto no desempenho (até agora), então gostaria de ver claramente quando esse SELECT *empacotamento é necessário.
sbnc.eu

6

Suspeitei algo assim, que a subconsulta está sendo executada para cada linha.
quano 26/05

Algumas versões do MySQL ainda não usam um índice em IN. Eu adicionei outro link.
edze

1
O MySQL 6 ainda não é estável, eu não recomendaria isso para produção!
26411 Johan

1
Eu não recomendaria. Mas aqui é explicado como ele é executado internamente (4.1 / 5.x -> 6). Isso demonstra algumas armadilhas das versões atuais.
edze 26/05

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;

Tentei sua consulta em um dos meus bancos de dados e também a reescrevi como uma junção a uma subconsulta.

Isso funcionou muito mais rápido, tente!


Sim, isso provavelmente criará uma tabela temporária com os resultados do grupo, portanto terá a mesma velocidade que a versão da visualização. Mas os planos de consulta devem dizer a verdade.
ypercubeᵀᴹ

3

Tente isto

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

Reformatei sua consulta sql lenta com www.prettysql.net

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

Ao usar uma tabela na consulta e na subconsulta, você sempre deve usar o alias de ambos, assim:

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

Isso ajuda?


1
Infelizmente, isso não ajuda. Ele executa tão devagar.
quano 26/05

Atualizei minha resposta, você pode tentar novamente? Mesmo se o grupo por é lento, ele deve ser executado apenas uma vez ...
plang

Matei acidentalmente um servidor mysql ao vivo da última vez, por isso tenho medo de não poder tentar isso agora. Terei que configurar um banco de dados de teste mais tarde. Mas não entendo por que isso deve afetar a consulta. A instrução HAVING deve ser aplicada apenas à consulta em que está, não deveria? Realmente não entendo por que a consulta "real" deve afetar a subconsulta.
quano 26/05

Encontrei o seguinte: xaprb.com/blog/2006/04/30/… . Eu acho que essa pode ser a solução. Vou tentar quando eu tiver tempo.
quano 26/05

2

Primeiramente, você pode encontrar linhas duplicadas e a contagem de linhas é usada quantas vezes e ordenada por 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
) 

depois disso, crie uma tabela e insira o resultado nela.

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
) 

Por fim, exclua as linhas públicas. Não é o início 0. Exceto o primeiro número de cada grupo, exclua todas as linhas públicas.

delete from  CopyTable where No!= 0;


1

Às vezes, quando os dados aumentam, o mysql WHERE IN pode ser bem lento devido à otimização da consulta. Tente usar STRAIGHT_JOIN para dizer ao mysql para executar a consulta como está, por exemplo

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

mas cuidado: na maioria dos casos, o otimizador mysql funciona muito bem, então eu recomendaria usá-lo somente quando você tiver esse tipo de problema


0

Isso é semelhante ao meu caso, onde eu tenho uma tabela chamada tabel_buku_besar. O que eu preciso é

  1. Procurando registro que tenha account_code='101.100'em tabel_buku_besarque tenha companyarea='20000'e também tenha IDRcomocurrency

  2. Preciso obter todos os registros dos tabel_buku_besarquais possuem account_code igual ao passo 1, mas transaction_numberno resultado do passo 1

durante o uso select ... from...where....transaction_number in (select transaction_number from ....), minha consulta é extremamente lenta e às vezes causa o tempo limite da solicitação ou faz com que meu aplicativo não responda ...

Eu tento essa combinação e o resultado ... não é ruim ...

`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

Eu acho que isso é o mais eficiente para descobrir se existe um valor, a lógica pode ser facilmente invertida para descobrir se um valor não existe (isto é, 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

* Substitua relevantes_campo pelo nome do valor que você deseja verificar existe na sua tabela

* Substitua primaryKey pelo nome da coluna da chave primária na tabela de comparação.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.