MySQL - SELECT WHERE field IN (subquery) - Sangat lambat kenapa?


133

Saya punya beberapa duplikat di database yang ingin saya periksa, jadi apa yang saya lakukan untuk melihat duplikat yang mana, saya lakukan ini:

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

Dengan cara ini, saya akan mendapatkan semua baris dengan relevan_field terjadi lebih dari satu kali. Permintaan ini membutuhkan milidetik untuk dieksekusi.

Sekarang, saya ingin memeriksa setiap duplikat, jadi saya pikir saya bisa PILIH setiap baris di some_table dengan relevan_field dalam permintaan di atas, jadi saya suka ini:

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

Ini ternyata sangat lambat untuk beberapa alasan (butuh beberapa menit). Apa sebenarnya yang terjadi di sini untuk membuatnya sepelan itu? relevan_field diindeks.

Akhirnya saya mencoba membuat tampilan "temp_view" dari kueri pertama (SELECT relevant_field FROM some_table GROUP BY relevant_field HAVING COUNT(*) > 1), dan kemudian membuat kueri kedua seperti ini:

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

Dan itu bekerja dengan baik. MySQL melakukan ini dalam beberapa milidetik.

Adakah ahli SQL di sini yang dapat menjelaskan apa yang terjadi?


apa yang sebenarnya kamu inginkan? ingin menghapus duplikat entri kecuali satu ?? Saran: silakan baca Self Join
diEcho

1
jelas kelompok yang lambat ...
ajreal

Kueri pertama dijalankan dalam milidetik (pengelompokan dan penyaringan dengan HAVING). Ini hanya dalam kombinasi dengan permintaan lain yang membuat semuanya lambat (butuh beberapa menit).
quano

@diEcho, saya ingin mencari duplikat, memeriksanya, dan menghapusnya secara manual.
quano

Jawaban:


112

Tulis ulang permintaan menjadi ini

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

Saya pikir st2.relevant_fieldharus di pilih, karena kalau tidak havingklausa akan memberikan kesalahan, tapi saya tidak 100% yakin

Jangan pernah gunakan INdengan subquery; ini sangat lambat.
Hanya digunakan INdengan daftar nilai yang pasti.

Lebih banyak tips

  1. Jika Anda ingin membuat kueri lebih cepat, jangan SELECT *hanya pilih bidang yang benar-benar Anda butuhkan.
  2. Pastikan Anda memiliki indeks relevant_fielduntuk mempercepat equi-gabung.
  3. Pastikan untuk group bymenggunakan kunci utama.
  4. Jika Anda menggunakan InnoDB dan Anda hanya memilih bidang yang diindeks (dan hal-hal yang tidak terlalu rumit) daripada MySQL akan menyelesaikan permintaan Anda hanya dengan menggunakan indeks, mempercepat segalanya.

Solusi umum untuk 90% dari IN (select pertanyaan Anda

Gunakan kode ini

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
Anda juga dapat menulis itu dengan HAVING COUNT(*) > 1. Biasanya lebih cepat di MySQL.
ypercubeᵀᴹ

@perperempat, dilakukan untuk kueri bawah, saya pikir untuk kueri atas itu akan mengubah hasilnya.
Johan

@ Johnny: Karena st2.relevant_fieldtidak NULL(sudah termasuk dalam ONklausa), itu tidak akan mengubah hasilnya
ypercubeᵀᴹ

@ ypercube, jadi Anda bisa mengubah count (afield) menjadi count (*) jika Anda yakin afieldtidak akan pernah null, mengerti. Terima kasih
Johan

1
@ Quano, ya itu daftar semua duplikat karena group byaktif st1.id, bukan aktif st1.relevant_field.
Johan

110

Subquery dijalankan untuk setiap baris karena merupakan kueri yang dikorelasikan. Seseorang dapat membuat kueri terkorelasi menjadi kueri yang tidak berkorelasi dengan memilih segala sesuatu dari subquery, seperti:

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

Kueri akhir akan terlihat seperti ini:

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
Ini bekerja sangat baik untuk saya. Saya memiliki IN (subquery) lain dalam IN (subquery), dan butuh lebih dari 10 menit, sangat lama sehingga saya mencari di Google saat saya sedang menunggu. Membungkus setiap subquery dalam SELECT * FROM () seperti yang Anda sarankan dikurangi menjadi 2 detik!
Liam

Terima kasih, saya sudah mencoba mencari cara yang baik untuk melakukan ini selama beberapa jam sekarang. Ini bekerja dengan sempurna. Seandainya saya bisa memberi Anda lebih banyak upvotes! Ini pasti jawabannya.
thaspius

Bekerja dengan sempurna. Kueri yang membutuhkan ~ 50sec untuk dijalankan sekarang menjadi instan. Seandainya aku bisa lebih banyak memilih. Terkadang Anda tidak dapat menggunakan gabungan sehingga ini adalah jawaban yang tepat.
simon

Saya ingin tahu mengapa pengoptimal menganggap kueri dengan serikat terkait ... Pokoknya, trik ini berfungsi seperti sulap
Brian Leishman

2
Bisakah Anda jelaskan apa yang membuat subquery yang berkorelasi? Pemahaman saya bahwa subquery menjadi berkorelasi, ketika menggunakan nilai yang tergantung pada permintaan luar. Tetapi dalam contoh ini saya tidak dapat melihat saling ketergantungan. Itu akan memberikan hasil yang sama untuk setiap baris yang dikembalikan oleh permintaan luar. Saya memiliki contoh serupa yang sedang diimplementasikan pada MariaDB dan saya tidak dapat melihat kinerja yang memukul (sejauh ini), jadi saya ingin melihat dengan jelas, ketika SELECT *pembungkus ini diperlukan.
sbnc.eu

6

Saya curiga sesuatu seperti ini, bahwa subquery dijalankan untuk setiap baris.
quano

Beberapa Versi MySQL bahkan tidak menggunakan Indeks dalam IN. Saya telah menambahkan tautan lain.
edze

1
MySQL 6 belum stabil, saya tidak akan merekomendasikan itu untuk produksi!
Johan

1
Saya tidak akan merekomendasikan itu. Namun di sini dijelaskan cara kerjanya secara internal (4.1 / 5.x -> 6). Ini menunjukkan beberapa jebakan dari versi saat ini.
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;

Saya telah mencoba kueri Anda di salah satu basis data saya, dan juga mencobanya ditulis ulang sebagai bergabung ke sub-kueri.

Ini bekerja jauh lebih cepat, coba!


Ya, ini mungkin akan membuat tabel tempa dengan hasil grup, sehingga kecepatannya akan sama dengan versi tampilan. Tetapi rencana kueri harus mengatakan yang sebenarnya.
ypercubeᵀᴹ

3

Coba ini

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

Saya telah memformat ulang permintaan sql Anda yang lambat dengan www.prettysql.net

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

Saat menggunakan tabel di kueri dan subquery, Anda harus selalu alias keduanya, seperti ini:

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

Apakah itu membantu?


1
Sayangnya itu tidak membantu. Menjalankan sama lambat.
quano

Saya telah memperbarui jawaban saya, dapatkah Anda mencoba lagi? Bahkan jika grup lambat, itu harus dieksekusi hanya sekali ...
plang

Saya tidak sengaja membunuh server mysql hidup terakhir kali, jadi saya khawatir saya tidak dapat mencoba ini sekarang. Saya harus menyiapkan database pengujian nanti. Tapi saya tidak mengerti mengapa ini mempengaruhi permintaan. Pernyataan HAVING seharusnya hanya berlaku untuk kueri yang ada di dalamnya, bukan? Saya benar-benar tidak mengerti mengapa permintaan "nyata" harus memengaruhi subquery.
quano

Saya menemukan ini: xaprb.com/blog/2006/04/30/… . Saya pikir ini mungkin solusinya. Akan mencoba ketika saya punya waktu.
quano

2

Pertama, Anda dapat menemukan baris duplikat dan menemukan jumlah baris digunakan berapa kali dan memesannya dengan nomor seperti ini;

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
) 

setelah itu buat tabel dan masukkan hasilnya.

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
) 

Akhirnya, hapus baris dublicate. Tidak ada mulai 0. Kecuali nomor pertama dari setiap grup hapus semua baris dublicate.

delete from  CopyTable where No!= 0;


1

kadang-kadang ketika data tumbuh lebih besar mysql WHERE IN bisa sangat lambat karena optimasi kueri. Coba gunakan STRAIGHT_JOIN untuk memberi tahu mysql untuk mengeksekusi kueri apa adanya, misalnya

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

tetapi waspadalah: dalam kebanyakan kasus pengoptimal mysql bekerja dengan sangat baik, jadi saya akan merekomendasikan untuk menggunakannya hanya ketika Anda memiliki masalah seperti ini


0

Ini mirip dengan kasus saya, di mana saya memiliki tabel bernama tabel_buku_besar. Yang saya butuhkan adalah

  1. Mencari record yang memiliki account_code='101.100'di tabel_buku_besarmana telah companyarea='20000'dan juga memiliki IDRsebagaicurrency

  2. Saya perlu mendapatkan semua catatan tabel_buku_besaryang memiliki kode akun sama dengan langkah 1 tetapi ada transaction_numberdalam langkah 1 hasil

saat menggunakan select ... from...where....transaction_number in (select transaction_number from ....), permintaan saya berjalan sangat lambat dan kadang-kadang menyebabkan waktu permintaan habis atau membuat aplikasi saya tidak menanggapi ...

Saya mencoba kombinasi ini dan hasilnya ... tidak buruk ...

`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

Saya menemukan ini menjadi yang paling efisien untuk menemukan jika ada nilai, logika dapat dengan mudah dibalik untuk menemukan jika nilai tidak ada (yaitu 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

* Ganti relevan_field dengan nama nilai yang ingin Anda periksa ada di tabel Anda

* Ganti primaryKey dengan nama kolom kunci utama pada tabel perbandingan.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.