MySQL - SELECT WHERE field IN (subquery) - ทำไมช้ามาก


138

ฉันมีข้อมูลซ้ำสองสามรายการในฐานข้อมูลที่ฉันต้องการตรวจสอบดังนั้นสิ่งที่ฉันทำเพื่อดูว่ารายการใดซ้ำกันฉันทำสิ่งนี้:

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

ด้วยวิธีนี้ฉันจะได้รับแถวทั้งหมดที่มี related_field เกิดขึ้นมากกว่าหนึ่งครั้ง แบบสอบถามนี้ใช้เวลาในการดำเนินการเป็นมิลลิวินาที

ตอนนี้ฉันต้องการตรวจสอบรายการที่ซ้ำกันแต่ละรายการดังนั้นฉันคิดว่าฉันสามารถเลือกแต่ละแถวใน some_table โดยมีฟิลด์ที่เกี่ยวข้องในแบบสอบถามด้านบนดังนั้นฉันจึงทำดังนี้

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

สิ่งนี้กลายเป็นว่าช้ามากด้วยเหตุผลบางประการ (ใช้เวลาไม่กี่นาที) เกิดอะไรขึ้นกันแน่ถึงทำให้มันช้าขนาดนั้น? related_field ถูกจัดทำดัชนี

ในที่สุดฉันก็ลองสร้างมุมมอง "temp_view" จากแบบสอบถามแรก(SELECT relevant_field FROM some_table GROUP BY relevant_field HAVING COUNT(*) > 1)จากนั้นสร้างแบบสอบถามที่สองของฉันเป็นแบบนี้แทน:

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

และใช้งานได้ดี MySQL ทำสิ่งนี้ในไม่กี่มิลลิวินาที

ผู้เชี่ยวชาญด้าน SQL คนใดที่สามารถอธิบายได้ว่าเกิดอะไรขึ้น


คุณต้องการอะไรกันแน่? ต้องการลบรายการที่ซ้ำกันยกเว้นรายการเดียว ?? ข้อเสนอแนะ: โปรดอ่านSelf Join
diEcho

1
เห็นได้ชัดว่าเป็นกลุ่มตามที่ช้า ...
ajreal

แบบสอบถามแรกดำเนินการเป็นมิลลิวินาที (หนึ่งการจัดกลุ่มและการกรองด้วย HAVING) ใช้ร่วมกับแบบสอบถามอื่นเท่านั้นซึ่งทำให้ทุกอย่างช้าลง (ใช้เวลาไม่กี่นาที)
quano

@diEcho ฉันต้องการค้นหารายการที่ซ้ำกันตรวจสอบและลบบางรายการด้วยตนเอง
quano

คำตอบ:


116

กำลังเรียกใช้แบบสอบถามย่อยสำหรับแต่ละแถวเนื่องจากเป็นแบบสอบถามที่สัมพันธ์กัน เราสามารถสร้างแบบสอบถามที่สัมพันธ์กันเป็นแบบสอบถามที่ไม่สัมพันธ์กันได้โดยการเลือกทุกอย่างจากแบบสอบถามย่อยดังนี้:

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

แบบสอบถามสุดท้ายจะมีลักษณะดังนี้:

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
สิ่งนี้ได้ผลดีอย่างน่าอัศจรรย์สำหรับฉัน ฉันมี IN (เคียวรีย่อย) อื่นภายใน IN (เคียวรีย่อย) และใช้เวลานานกว่า 10 นาทีนานมากจนฉัน googled ในขณะที่รอ การห่อแต่ละคำค้นหาย่อยใน SELECT * FROM () ตามที่คุณแนะนำจะลดลงเหลือ 2 วินาที!
Liam

ขอบคุณฉันพยายามหาวิธีที่ดีในการทำสิ่งนี้มาสองสามชั่วโมงแล้ว สิ่งนี้ทำงานได้อย่างสมบูรณ์แบบ หวังว่าฉันจะให้คะแนนโหวตมากขึ้น! นี่น่าจะเป็นคำตอบแน่นอน
thaspius

ทำงานได้อย่างสมบูรณ์ แบบสอบถามที่ใช้เวลา ~ 50 วินาทีในการเรียกใช้ขณะนี้เป็นแบบทันที หวังว่าฉันจะโหวตได้มากกว่านี้ บางครั้งคุณไม่สามารถใช้การรวมได้ดังนั้นนี่คือคำตอบที่ถูกต้อง
simon

ฉันสงสัยว่าทำไมเครื่องมือเพิ่มประสิทธิภาพจึงพิจารณาการค้นหากับสหภาพแรงงานที่สัมพันธ์กัน ... อย่างไรก็ตามเคล็ดลับนี้ทำงานได้เหมือนเวทมนตร์
Brian Leishman

2
คุณช่วยอธิบายได้ไหมว่าอะไรคือสิ่งที่ทำให้แบบสอบถามย่อยที่สัมพันธ์กัน ความเข้าใจของฉันว่าเคียวรีย่อยมีความสัมพันธ์กันเมื่อใช้ค่าที่ขึ้นอยู่กับคิวรีภายนอก แต่ในตัวอย่างนี้ฉันไม่เห็นการพึ่งพาซึ่งกันและกัน มันจะให้ผลลัพธ์เดียวกันสำหรับแต่ละแถวที่ส่งคืนโดยแบบสอบถามภายนอก ฉันมีตัวอย่างที่คล้ายกันที่นำไปใช้กับ MariaDB และฉันไม่เห็นประสิทธิภาพที่ยอดเยี่ยม (จนถึงตอนนี้) ดังนั้นฉันต้องการเห็นอย่างชัดเจนเมื่อSELECT *จำเป็นต้องมีการตัดนี้
sbnc.eu

114

เขียนข้อความค้นหาใหม่ในสิ่งนี้

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

ฉันคิดว่าst2.relevant_fieldต้องอยู่ในการเลือกเพราะมิฉะนั้นhavingประโยคจะให้ข้อผิดพลาด แต่ฉันไม่แน่ใจ 100%

ห้ามใช้เด็ดขาด INกับแบบสอบถามย่อย มันช้ามาก
ใช้INกับรายการค่าคงที่เท่านั้น

เคล็ดลับเพิ่มเติม

  1. หากคุณต้องการสร้างแบบสอบถามให้เร็วขึ้นอย่าทำ SELECT *เลือกเฉพาะช่องที่คุณต้องการจริงๆ
  2. ตรวจสอบให้แน่ใจว่าคุณมีดัชนีอยู่ relevant_fieldเพื่อเพิ่มความเร็วในการเข้าร่วม Equi-join
  3. ตรวจสอบให้แน่ใจว่า group byใช้คีย์หลัก
  4. หากคุณใช้ InnoDB และคุณเลือกเฉพาะฟิลด์ที่จัดทำดัชนี(และสิ่งต่างๆไม่ซับซ้อนเกินไป)กว่าที่ MySQL จะแก้ไขแบบสอบถามของคุณโดยใช้เฉพาะดัชนีทำให้สิ่งต่างๆเร็วขึ้น

วิธีแก้ปัญหาทั่วไปสำหรับ 90% ของไฟล์ IN (select คำถาม

ใช้รหัสนี้

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
คุณยังสามารถเขียนด้วยHAVING COUNT(*) > 1. โดยปกติแล้วจะเร็วกว่าใน MySQL
ypercubeᵀᴹ

@ypercube สำหรับข้อความค้นหาด้านล่างฉันคิดว่าสำหรับข้อความค้นหาด้านบนมันจะเปลี่ยนผลลัพธ์
โยฮัน

@ โจฮัน: เนื่องจากst2.relevant_fieldไม่ใช่NULL(มันรวมอยู่ในONข้อแล้ว) มันจะไม่เปลี่ยนแปลงผลลัพธ์
ypercubeᵀᴹ

@ypercube เพื่อให้คุณสามารถเปลี่ยน count (afield) เป็น count (*) ได้หากคุณแน่ใจว่าafieldจะไม่เป็นnullเช่นนั้น ขอบคุณ
โยฮัน

1
@quano ใช่มันแสดงรายการทุกรายการที่ซ้ำกันเพราะgroup byอยู่ในที่ไม่เกี่ยวกับst1.id st1.relevant_field
โยฮัน

6

ฉันสงสัยว่ามีการเรียกใช้แบบสอบถามย่อยสำหรับแต่ละแถว
quano

MySQL บางเวอร์ชันไม่ได้ใช้ดัชนีใน IN ฉันได้เพิ่มลิงค์อื่นแล้ว
edze

1
MySQL 6 ยังไม่เสถียรฉันไม่แนะนำให้ใช้ในการผลิต!
โยฮัน

1
ฉันไม่อยากจะแนะนำมัน แต่นี่คือคำอธิบายว่ามันทำงานอย่างไรภายใน (4.1 / 5.x -> 6) สิ่งนี้แสดงให้เห็นถึงข้อผิดพลาดบางประการของเวอร์ชันปัจจุบัน
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;

ฉันได้ลองใช้การสืบค้นของคุณในฐานข้อมูลหนึ่งของฉันแล้วและยังได้ลองเขียนใหม่เป็นการรวมคิวรีย่อยด้วย

วิธีนี้ทำงานได้เร็วขึ้นมากลองดูสิ!


ใช่นี่อาจเป็นการสร้างตารางชั่วคราวพร้อมผลลัพธ์กลุ่มดังนั้นจึงจะมีความเร็วเท่ากับเวอร์ชันมุมมอง แต่แผนการสืบค้นควรบอกความจริง
ypercubeᵀᴹ

4

ฉันฟอร์แมตแบบสอบถาม sql ช้าของคุณด้วย www.prettysql.net

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

เมื่อใช้ตารางทั้งในแบบสอบถามและแบบสอบถามย่อยคุณควรใช้นามแฝงทั้งสองแบบนี้เสมอ

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

ที่ช่วย?


1
มันไม่ช่วยอะไรได้อย่างน่าเสียดาย มันดำเนินการช้าพอ ๆ
quano

ฉันได้อัปเดตคำตอบแล้วคุณลองอีกครั้งได้ไหม แม้ว่าการจัดกลุ่มจะช้า แต่ก็ควรดำเนินการเพียงครั้งเดียว ...
plang

เมื่อครั้งที่แล้วฉันได้ฆ่าเซิร์ฟเวอร์ mysql ที่ใช้งานอยู่โดยไม่ได้ตั้งใจดังนั้นฉันจึงกลัวว่าจะลองไม่ได้ในตอนนี้ ฉันจะต้องตั้งค่าฐานข้อมูลทดสอบในภายหลัง แต่ฉันไม่เข้าใจว่าเหตุใดจึงควรส่งผลต่อข้อความค้นหา คำสั่ง HAVING ควรใช้กับข้อความค้นหาที่อยู่ภายในเท่านั้นใช่หรือไม่ ฉันไม่เข้าใจจริงๆว่าเหตุใดข้อความค้นหา "จริง" จึงควรส่งผลต่อการสืบค้นย่อย
quano

ฉันพบสิ่งนี้: xaprb.com/blog/2006/04/30/… . ฉันคิดว่านี่อาจเป็นทางออก จะพยายามเมื่อฉันมีเวลา
quano

3

ลองทำตามนี้

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

ประการแรกคุณสามารถค้นหาแถวที่ซ้ำกันและค้นหาจำนวนแถวที่ใช้กี่ครั้งและเรียงลำดับตามหมายเลขเช่นนี้

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
) 

หลังจากนั้นสร้างตารางและแทรกผลลัพธ์ลงไป

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
) 

สุดท้ายลบแถว dublicate ไม่มีเริ่มต้น 0 ยกเว้นจำนวนกำปั้นของแต่ละกลุ่มลบแถว dublicate ทั้งหมด

delete from  CopyTable where No!= 0;


1

บางครั้งเมื่อข้อมูลขยาย mysql ใหญ่ขึ้น WHERE IN อาจค่อนข้างช้าเนื่องจากการเพิ่มประสิทธิภาพการสืบค้น ลองใช้ STRAIGHT_JOIN เพื่อบอกให้ mysql ดำเนินการสืบค้นตามที่เป็นอยู่เช่น

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

แต่ระวัง: ในกรณีส่วนใหญ่ mysql optimizer ทำงานได้ดีดังนั้นฉันขอแนะนำให้ใช้เฉพาะเมื่อคุณมีปัญหาประเภทนี้


0

tabel_buku_besarนี้จะคล้ายกับกรณีของฉันที่ฉันมีตารางชื่อ สิ่งที่ฉันต้องการคือ

  1. กำลังมองหาบันทึกที่มีaccount_code='101.100'ในtabel_buku_besarที่ได้companyarea='20000'และยังมีIDRเป็นcurrency

  2. ฉันต้องการรับบันทึกทั้งหมดtabel_buku_besarที่มี account_code เหมือนกับขั้นตอนที่ 1 แต่มีtransaction_numberผลลัพธ์ในขั้นตอนที่ 1

ขณะใช้งาน select ... from...where....transaction_number in (select transaction_number from ....)งานข้อความค้นหาของฉันทำงานช้ามากและบางครั้งทำให้คำขอหมดเวลาหรือทำให้แอปพลิเคชันของฉันไม่ตอบสนอง ...

ลองชุดนี้แล้วผลลัพธ์ ... ไม่เลว ...

`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

ฉันพบว่าสิ่งนี้มีประสิทธิภาพมากที่สุดในการค้นหาว่ามีค่าอยู่หรือไม่ตรรกะสามารถกลับด้านได้อย่างง่ายดายเพื่อค้นหาว่าไม่มีค่า (เช่น 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

* แทนที่ related_field ด้วยชื่อของค่าที่คุณต้องการตรวจสอบที่มีอยู่ในตารางของคุณ

* แทนที่ primaryKey ด้วยชื่อของคอลัมน์คีย์หลักในตารางเปรียบเทียบ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.