DISTINCT ข้อมูลใน subquery เพื่อส่งให้กับ IN จำเป็นหรือไม่
มีข้อสงสัยจากโพสต์ว่า ถ้าเราต้องใช้คำสั่ง IN ร่วมกับ subquery เราควรกำหนด DISTINCT ให้กับผลลัพท์ของ subquery หรือไม่
ก่อนอื่น อธิบายคำสั่ง IN กันก่อน
คำสั่ง IN ถ้าแปลเป็นคำสั่งพื้นฐานจะได้ว่า
และคำสั่ง NOT IN
ทำนองเดียวกัน คำสั่ง NOT IN เมื่อแปลเป็นคำสั่งพื้นฐานจะได้ว่า
มีบางคนอาจจะเข้าใจว่ามันเป็นรูปแบบนี้
ซึ่งก็ทำงานได้ผลลัพท์เดียวกันนะครับ เพียงแต่แบบ OR จะทำงานเข้ากับรูปแบบของคำสั่งมากกว่า
ทีนี้มีคำถามว่า เราควรจำกัดผลลัพท์ของ subquery ให้เป็น DISTINCT (มีผลลัพท์ที่ไม่ซ้ำกันหรือไม่) ก่อนอื่น ผมจะอธิบายขั้นตอนการทำงานของ DISTINCT (รวมถึง GROUP BY) กันก่อนว่ามันทำอะไรถึงจะได้ผลลัพท์ที่ไม่ซ้ำกันออกมา
ทั้งสองคำสั่งด้านบน จะให้ผลลัพท์เป็นข้อมูลที่ไม่มี id ซ้ำกันเลยออกมา ซึ่งสามารถใช้กรองผลลัพท์ให้น้อยลงได้ โดยมีขั้นตอนการทำงานดังนี้
จะเห็นว่าในการใช้คำสั่ง DISTINCT กับข้อมูลใน IN ก่อให้เกิดงานเพิ่มซึ่งเป็นงานที่ใช้เวลามากที่สุดซะด้วย (คือการเรียงลำดับ) ในขณะที่หากไม่มีการใช้ จะมีข้อมูลที่อาจจะซ้ำกันจำนวนมากไปตรวจสอบใน IN ยกตัวอย่างเช่น IN (1, 1, 1, 1, 1, 5) คำถามก็คือ คุ้มมั้ยที่จะทำการคัดเลือกข้อมูลที่ซ้ำกันออกก่อน เนื่องจากมีเพื่อนสมาชิกแย้งว่า หากต้องการข้อมูล 5 ต้องผ่าน 1 ซ้ำๆกันมาก่อนหน้า
คำตอบผมไม่ขอฟันธงนะครับ เพราะแน่นอนว่าย่อมมีคนเห็นต่าง แล้วก็ไม่ได้มีคำตอบที่ถูกต้องชัดเจน 100% ด้วย ผมขอให้ความเห็นส่วนตัวนะครับว่า ผมไม่เคย DISTNIC และ GROUP BY กับ subquery เลย (สำหรับกรณีนี้) เนื่องจาก การเรียงลำดับข้อมูลเป็นเรื่องที่เสียเวลามาก รวมถึงการคัดเลือกข้อมูลที่ซ้ำกันออก ก็ต้องทำกับข้อมูลทุกแถวอยู่แล้ว หากเราไม่ต้องทำขั้นตอนนี้ แต่เลือกให้ IN กระทำกับข้อมูลจนถึงลำดับสุดท้ายแทน เวลารวมการทำงานก็ยังคงน้อยกว่า เพราะไม่ต้องผ่านการเรียงลำดับ
สำหรับคนขี้สงสัย อ่านเพิ่มเติมนะครับ DISTINCT และ GROUP BY ต่างกันอย่างไร
SELECT * FROM table_name WHERE id IN (SELECT id FROM table_name2)
ก่อนอื่น อธิบายคำสั่ง IN กันก่อน
id IN (1, 2, 3)
คำสั่ง IN ถ้าแปลเป็นคำสั่งพื้นฐานจะได้ว่า
id = 1 OR id = 2 OR id = 3
และคำสั่ง NOT IN
id NOT IN (1, 2, 3)
ทำนองเดียวกัน คำสั่ง NOT IN เมื่อแปลเป็นคำสั่งพื้นฐานจะได้ว่า
!(id = 1 OR id = 2 OR id = 3)
มีบางคนอาจจะเข้าใจว่ามันเป็นรูปแบบนี้
id != 1 AND id != 2 AND id != 3
ซึ่งก็ทำงานได้ผลลัพท์เดียวกันนะครับ เพียงแต่แบบ OR จะทำงานเข้ากับรูปแบบของคำสั่งมากกว่า
ทีนี้มีคำถามว่า เราควรจำกัดผลลัพท์ของ subquery ให้เป็น DISTINCT (มีผลลัพท์ที่ไม่ซ้ำกันหรือไม่) ก่อนอื่น ผมจะอธิบายขั้นตอนการทำงานของ DISTINCT (รวมถึง GROUP BY) กันก่อนว่ามันทำอะไรถึงจะได้ผลลัพท์ที่ไม่ซ้ำกันออกมา
SELECT DISTINCT id FROM table_name2;
SELECT id FROM table_name2 GROUP BY id;
ทั้งสองคำสั่งด้านบน จะให้ผลลัพท์เป็นข้อมูลที่ไม่มี id ซ้ำกันเลยออกมา ซึ่งสามารถใช้กรองผลลัพท์ให้น้อยลงได้ โดยมีขั้นตอนการทำงานดังนี้
- เมื่อ query ได้ผลลัพท์ออกมาแล้ว (หลังจากทำตามคำสั่ง WHERE หรือ JOIN แล้ว) จะมาทำการเรียงลำดับข้อมูล ตามคอลัมน์ที่ DISTINCT หรือ GROUP BY ไว้
- หลังจากเรียงเสร็จแล้วถึงจะทำการสำเนาข้อมูลไปยังตารางชั่วคราว โดยคัดเอาเฉพาะข้อมูลที่ไม่ซ้ากับรายการก่อนหน้าไป
// ข้อมูลต้นฉบับจากการ Query
$datas = array(..........);
// เรียงลำดับข้อมูลตาม id (ไม่ได้เขียนฟังก์ชั่นเรียงลำดับไว้นะครับ)
usort($datas, 'sortById');
// ตารางชั่วคราวเก็บผลลัพท์
$tmp = array();
// ตัวแปรสำหรับตรวจสอบข้อมูลก่อนหน้า
$old_id = null;
// วนลูปข้อมูลต้นฉบับทีละรายการ
foreach($datas as $item){
// เปรียบเทียบข้อมูลกับข้อมูลก่อนหน้า ถ้าไม่ตรงกันแสดงว่าเป็นข้อมูลใหม่ เข้าไปทำใน if ถ้าเหมือนกันข้ามไปลูปใหม่
if ($old_id != $item['id']) {
// บันทึกข้อมูลเก็บไว้ตรวจสอบกับรายการถัดไป
$old_id = $item['id'];
// สำเนาข้อมูลไปยังตารางชั่วคราว
$tmp[] = $item;
}
}
// คืนค่าผลลัพท์จากตารางชั่วคราว
return $tmp;
จะเห็นว่าในการใช้คำสั่ง DISTINCT กับข้อมูลใน IN ก่อให้เกิดงานเพิ่มซึ่งเป็นงานที่ใช้เวลามากที่สุดซะด้วย (คือการเรียงลำดับ) ในขณะที่หากไม่มีการใช้ จะมีข้อมูลที่อาจจะซ้ำกันจำนวนมากไปตรวจสอบใน IN ยกตัวอย่างเช่น IN (1, 1, 1, 1, 1, 5) คำถามก็คือ คุ้มมั้ยที่จะทำการคัดเลือกข้อมูลที่ซ้ำกันออกก่อน เนื่องจากมีเพื่อนสมาชิกแย้งว่า หากต้องการข้อมูล 5 ต้องผ่าน 1 ซ้ำๆกันมาก่อนหน้า
คำตอบผมไม่ขอฟันธงนะครับ เพราะแน่นอนว่าย่อมมีคนเห็นต่าง แล้วก็ไม่ได้มีคำตอบที่ถูกต้องชัดเจน 100% ด้วย ผมขอให้ความเห็นส่วนตัวนะครับว่า ผมไม่เคย DISTNIC และ GROUP BY กับ subquery เลย (สำหรับกรณีนี้) เนื่องจาก การเรียงลำดับข้อมูลเป็นเรื่องที่เสียเวลามาก รวมถึงการคัดเลือกข้อมูลที่ซ้ำกันออก ก็ต้องทำกับข้อมูลทุกแถวอยู่แล้ว หากเราไม่ต้องทำขั้นตอนนี้ แต่เลือกให้ IN กระทำกับข้อมูลจนถึงลำดับสุดท้ายแทน เวลารวมการทำงานก็ยังคงน้อยกว่า เพราะไม่ต้องผ่านการเรียงลำดับ
สำหรับคนขี้สงสัย อ่านเพิ่มเติมนะครับ DISTINCT และ GROUP BY ต่างกันอย่างไร