มีคนใกล้ตัวบางคน (ที่ผมบังคับให้เข้ามาอ่าน) บอกว่า ประโยคนี้ในหัวข้อก่อน อ่านแล้วไม่เข้าใจ
นอกจากนั้นข้อแตกต่างเล็ก ๆ น้อย ๆ ระหว่าง query 2 query จะเกิดขึ้นกับความเป็นไปได้ของข้อมูลบางชุดเท่านั้น เราต้องเข้าใจถึงลักษณะของข้อมูลที่เรามีอยู่ด้วย
นั่นสิ ผมอ่านที่ตัวเองเขียนอีกรอบ ก็เห็นด้วยกับเขาเหมือนกัน เพราะฉะนั้นคราวนี้ผมจะลองขยายความเพิ่มเติม พร้อมกับยกตัวอย่างที่หลายคนน่าจะเคยเจอกันมาบ้าง ให้ดูด้วย
จริง ๆ ครั้งที่แล้ว ผมตั้งใจจะบอกว่า ในวิธีการเขียนของ SQL ที่หลากหลายนั้น บาง syntax ให้ผลลัพธ์ที่คล้ายคลึงกันมาก จนบางครั้ง คนบางคนเข้าใจผิดคิดว่าผลลัพธ์ที่ได้เหมือนกันเปี๊ยบเลยก็มี ทั้งนี้เป็นเพราะความแตกต่างที่จะเกิดขึ้นนั้น จะเกิดในกรณีที่ข้อมูลมีลักษณะเฉพาะบางประการเท่านั้น ผลที่ตามมาคือ oracle เลือกใช้วิธีการที่แตกต่างกันอย่างสิ้นเชิงในการหาผลลัพธ์ออกมาให้กับเรา และมีผลกับความเร็วโดยตรง
อย่างไรก็ดี เราในฐานะที่เป็นเจ้าของข้อมูล เราอาจจะรู้และรับรองได้ว่าข้อมูลในลักษณะนั้น ๆ ไม่มีทางเกิดขึ้นได้ในข้อมูลจริง (พูดง่าย ๆ บางครั้ง oracle ก็คิดมากและฉลาดเกินไป) เราจึงต้องรับผิดชอบโดยการเลือกวิธีการเขียนที่เหมาะสมกับข้อมูลของเราจริง ๆ หรือบอกให้ oracle รับรู้ข้อเท็จจริงเดียวกับเราโดยผ่าน statistics หรือ integrity constraints (โดยเฉพาะเรื่อง constraints นี้ ผมคงใช้เขียนได้อีกหลายวันทีเดียว)
ผมขอยกตัวอย่างง่าย ๆ ให้พิจารณาสักตัวอย่างสองตัวอย่าง ตัวอย่างแรกเป็นการใช้ not in, not exists และ minus
select t1_id
from t1
where t1_id not in ( select t2_id from t2 ) ;
select t1_id
from t1
where not exists
( select null from t2 where t2_id = t1.t1_id ) ;
select t1_id
from t1
minus
select t2_id
from t2 ;
สาม statement นี้ ดูเผิน ๆ จะเหมือนว่าได้คำตอบเดียวกัน แต่จริง ๆ แล้วไม่ใช่ ผมสามารถแสดงให้ดูด้วยชุดข้อมูลดังนี้
insert into t1 (t1_id) values (1) ;
insert into t1 (t1_id) values (1) ;
insert into t1 (t1_id) values (2) ;
insert into t2 (t2_id) values (2) ;
insert into t2 (t2_id) values (null) ;
ข้อมูลบรรทัดที่สอง จะทำให้ minus ให้ผลลัพธ์ที่แตกต่าง เพราะ t1_id มีค่าซ้ำ ขณะที่ผลลัพธ์จาก set operators ทุกตัวยกเว้น union all จะถูกกำจัดค่าซ้ำก่อนเสมอ (select distinct … ประมาณนั้น) จึงได้ผลลัพธ์เพียง 1 แถว ส่วน not in กับ not exists ซ้ำก็ซ้ำ ไม่สนใจ แสดงทั้งหมด ส่วนข้อมูลบรรทัดที่ห้า จะทำให้ not in ให้ผลลัพธ์ที่แตกต่าง เพราะถ้าใช้ not in แล้ว ค่าที่ return จาก subquery ต้องไม่มีค่า null มิฉะนั้น ผลลัพธ์จะเป็น 0 แถวเสมอ ดังนั้นผลลัพธ์จากสาม query เมื่อกระทำกับข้อมูลชุดนี้แล้ว จะได้ไม่เท่ากันเลย
อย่างไรก็ดี ถ้าทั้งคอลัมน์ t1_id และ t2_id เป็น primary key ของตาราง t1 และ t2 ตามลำดับ จะเห็นได้ว่าข้อมูลชุดที่เรายกมาไม่มีทางเป็นจริงไปได้ เพราะ primary key จะต้องไม่ซ้ำและมีค่าเสมอ เป็น null ไม่ได้
นี่คือเหตุผลว่าทำไมเราจึงต้องทำความรู้จักกับข้อมูลของเราด้วย ในการ tuning query แบบนี้ผมไม่สามารถที่จะให้คำแนะนำที่เฉพาะเจาะจงได้ว่า query ทั้งสามนี้ ใช้แบบไหนดีกว่ากัน เพราะว่ามันให้คำตอบที่แตกต่างกันจึงไม่สามารถเปรียบเทียบกันได้ จะเลือกเขียนด้วยวิธีไหนขึ้นอยู่กับว่าข้อมูลของเรามีลักษณะอย่างไรต่างหาก
ตัวอย่างที่สอง หลายคนอาจจะเคยใช้อยู่บ่อย ๆ และสงสัยว่าแบบไหนดีกว่ากัน
select *
from t1
where :p_id is null or id = :p_id ;
select *
from t1
where id = nvl(:p_id, id) ;
สอง query มักถูกใช้เมื่อเราต้องการสร้างรายงาน ที่ผู้เรียกดูรายงานสามารถที่จะระบุ หรือไม่ระบุค่าพารามิเตอร์ก็ได้ ถ้าไม่ระบุก็ให้แสดงข้อมูลทั้งหมด คุ้น ๆ บ้างไหมครับ รู้ไหมครับว่า สอง query นี้แตกต่างกันตรงไหน
ค่า null อีกแล้วครับท่าน กติกาของ SQL ข้อหนึ่ง null ไม่เคยเท่ากับอะไรเลย แม้แต่ตัวมันเอง เงื่อนไข (null = null) จะให้ค่าเป็น null ไม่ใช่ true
ดังนั้นหากเราไม่ระบุค่าพารามิเตอร์ และในข้อมูลของเรา มีแถวที่ id มีค่าเป็น null รวมอยู่ด้วย query แรกจะแสดงผลทุกแถวจริง ๆ รวมทั้งแถวที่ id เป็น null ขณะที่ query ที่สองจะแสดงผลแค่แถวที่ id มีค่าเท่านั้น แถวที่ id เป็น null จะไม่แสดงออกมา
เป็นอย่างไรบ้างครับ ตัวอย่างที่ยกมาพอจะทำให้เห็นภาพไหมครับ ว่าเราต้องรู้อะไรบ้างก่อนจะทำการ tune query นอกจากนี้แล้วยังมีอีกหลายกรณี แต่วันนี้ผมยังนึกไม่ออก เอาไว้วันไหนนึกได้ แล้วจะกลับมาเล่าสู่กันฟังอีกรอบนะครับ
ด้วยความเคารพอย่างสูง