ABAP - 5 ตอน รู้จักภาษา เปิดเบื้องหลังของ SAP R/3

ภาคต่อของการเขียนโปรแกรมภาษา ABAP เจาะลึกคำสั่ง Open SQL

ต่อเนื่องจากฉบับที่แล้วนะครับ สำหรับคำสั่ง Select ในกรณีที่ไม่มีข้อมูลที่ต้องการ จากการที่ DB Interface ส่งคำสั่ง Native SQL ไปให้ Database Server ประมวลผล ดังนั้นก็จะไม่มีข้อมูลส่งกลับมาที่ Result Set ระบบก็จะไม่เรียกคำสั่งภายในบล็อกของ Select ... Endselect ขึ้นมาทำงานแต่อย่างใด แต่จะไปต่อที่คำสั่งถัดจาก Endselect โดยทันที ดังนั้นโดยปกติแล้ว หลังคำสั่ง Endselect จึงควรมีการเช็กว่ามีข้อมูลใน Result Set ที่จะอ่านเข้ามาหรือไม่ โดยให้เราเช็กจากตัวแปรระบบที่ชื่อ sy-subrc ซึ่งเป็นตัวแปรระบบที่เก็บค่าการทำงานของคำสั่ง ABAP ล่าสุด ซึ่งถ้าการทำงานสำเร็จก็จะได้ค่า 0 แต่ถ้าไม่สำเร็จก็จะได้ค่าที่มากกว่า 0 เช่น

Tables customers.
Select * from customers.
Write: / customers-id, customers-name.
Endselect.
If sy-subrc <> 0.
Write: / 'No data found'.
Endif.


โดยที่ถ้ามีข้อมูลในตาราง customers ที่ฐานข้อมูล เราก็จะได้ข้อมูลของตาราง customers ทั้งหมดที่หน้าจอ แต่ถ้าไม่มีข้อมูลในตาราง customers ที่ฐานข้อมูล เราก็จะได้ข้อความ No data found ออกมาแทน นอกจากนี้เรายังสามารถใช้ตัวแปรระบบ sy-dbcnt ซึ่งเป็นตัวแปรระบบที่ให้ค่ารอบในการวนลูปของคำสั่ง Select ... Endselect (เหมือนกับตัวแปรระบบ sy-index ในบล็อกของคำสั่ง Do ... Enddo) ดังนั้นถ้าเราต้องการทราบว่ามีกี่เรคอร์ดที่คำสั่ง Select ... Endselect อ่านขึ้นมาได้นั้น เราก็สามารถใช้ตัวแปรระบบ sy-dbcnt หลังคำสั่ง Endselect ได้ดังนี้


Tables customers.
Select * from customers.
Write: / customers-id, customers-name.
Endselect.
If sy-subrc <> 0.
Write: / 'No data found'.
Else.
Write: / sy-dbcnt, 'Record(s) read'.
Endif.


จะเห็นได้ว่าคำสั่ง Select ... Endselect นี้ใช้ในการวนลูป เพื่ออ่านข้อมูลในตารางของฐานข้อมูล ในลักษณะของ Multiple Record แต่ถ้าเราต้องการอ่านข้อมูลเพียงเรคอร์ดเดียว หรือที่เรียกว่า Single Record เราจะใช้ออปชัน single ในคำสั่ง Select เช่น


Tables customers.
Select single * from customers where id = 1.
If sy-subrc = 0.
Write: / customers-id, customers-name.
Else.
Write: / 'No data found'.
Endif.


โดยที่คำสั่ง Select single ... จะไม่ปิดด้วย Endselect เพราะเป็นการอ่านข้อมูลเพียงแค่เรคอร์ดเดียวนั่นเอง ส่วนการทำงานของคำสั่ง Select single นั้นยังคงเป็นเช่นเดิม ก็คือ DB Interface จะแปลงคำสั่ง Open SQL นี้ให้เป็น Native SQL เพื่อส่งไป Database Server เพื่ออ่านข้อมูลที่ฐานข้อมูล จากนั้นผลลัพธ์ที่ได้จะส่งมาที่ Result Set เพียงเรคอร์ดเดียว พร้อมกับมี Pointer ชี้อยู่ที่เรคอร์ดนั้น

จากนั้นคำสั่ง Select single ... ก็จะอ่านข้อมูลจาก Result Set นี้ไปไว้ที่ Table Structure ที่ชื่อ customers ที่ Memory Space ต่อไป โดยที่ถ้ามีข้อมูลที่อ่านได้จาก Result Set ระบบก็จะให้ค่า 0 ให้กับตัวแปรระบบ sy-subrc แต่ถ้าไม่มีข้อมูล ค่า sy-subrc ก็จะได้ค่าที่มากกว่า 0

ดังนั้นหลังจากอ่านข้อมูลจากคำสั่ง Select single ... แล้ว เราควรจะเช็กค่าตัวแปร sy-subrc ด้วยเสมอ มิฉะนั้นเราอาจเอาข้อมูลที่ค้างอยู่ที่ Table Structure ใน Memory Space ไปทำงานอย่างผิดๆ

สำหรับรูปแบบของ Where Clause ในคำสั่ง Select นั้น จะมีรูปแบบเป็นชื่อฟิลด์ตามด้วย Operator ต่างๆ เช่น


Select * from customers where id = 1. หรือ
Select * from customers where id > 1 and city = 'Bangkok'.


นอกจากนี้เรายังสามารถใช้ออปชัน Between หรือ in ใน Where Clause ได้เช่น


Select * from customers where id between 1 and 9. หรือ
Select * from customers where city in ('Bangkok', 'Rayong').


ก็คือต้องการอ่านข้อมูลของตาราง customers โดยมีเงื่อนไขคือ ค่าฟิลด์ id อยู่ระหว่าง 1 ถึง 9 และค่าของฟิลด์ city มีค่าเป็น 'Bangkok' หรือ 'Rayong' ตามลำดับ

อ่านข้อมูลเฉพาะบางฟิลด์

การอ่านข้อมูลทุกฟิลด์หรือทุกคอลัมน์จากคำสั่ง Select * ... หรือ Select single * ... นั้น ถ้าพิจารณาในด้านของประสิทธิภาพแล้วดูจะไม่ค่อยดีสักเท่าไร เพราะมีการอ่านข้อมูลทุกฟิลด์จาก Database Server มาไว้ที่ Result Set ถ้าเรานับจำนวนข้อมูลเป็นไบต์ที่มีการโอนย้ายจาก Database Server ไปที่ Application Server ก็มากพอสมควร ยิ่งถ้ามีข้อมูลหลายเรคอร์ด ปริมาณการโอนย้ายคงมหาศาลเลยทีเดียว และที่สำคัญเรามักจะใช้ข้อมูลที่อ่านขึ้นมาได้เพียงแค่ไม่กี่ฟิลด์เท่านั้น เอง ดังนั้นถ้าอยากเขียนโปรแกรมให้มีประสิทธิภาพ เราก็ควรหลีกเลี่ยงการอ่านข้อมูลทุกฟิลด์จากคำสั่ง Select * ... หรือ Select single * ... โดยให้ทำการอ่านข้อมูลเฉพาะฟิลด์ที่เราต้องการเท่านั้น เช่น สมมติว่าเราต้องการแสดงข้อมูล id และ name ของตาราง customers เราสามารถเขียนโปรแกรมได้ดังนี้


Data: v_id like customers-id,
V_name like customers-name.
Select id name
Into (v_id, v_name)
From customers.
Write: / v_id, v_name.
Endselect.


โดยที่ DB Interface จะแปลงคำสั่ง Select id name ... ให้เป็น Select id, name from customers; เพื่อส่งไปให้ Database Server (ORACLE RDBMS) จากนั้น Database Server ก็จะส่งข้อมูลกลับไปที่ Result Set เฉพาะฟิลด์ที่ต้องการเท่านั้น ดังรูปที่ 1
รูปที่ 1 ผลจากการรันคำสั่ง Select id name into (v_id, v-name)

จากนั้นคำสั่ง Select id name into (v_id, v_name) ... ก็จะถูกนำมารัน ทำให้อ่านข้อมูลที่ Pointer ชี้อยู่ไปไว้ที่ตัวแปร v_id และ v_name ที่ Memory Space ตามลำดับ ส่วนการทำงานของคำสั่ง Write และ Endselect นั้นยังคงเป็นเช่นเดิม ซึ่งจะเห็นได้ว่า เมื่อใดก็ตามที่เราระบุออปชัน into ในคำสั่ง Select เราก็ไม่จำเป็นที่จะต้องสร้าง Table Structure ขึ้นมาที่ Memory Space แต่อย่างใด เพราะ Table Structure ที่สร้างขึ้นมาจากคำสั่ง Tables นั้นรองรับการทำงานของคำสั่ง Select * ... หรือ Select single * ... ที่ไม่มีการระบุออปชัน into เท่านั้น

เรียงลำดับของข้อมูลที่อ่านได้

โดยปกติแล้วข้อมูลที่อ่านได้ที่ Result Set จะเรียงลำดับตาม Primary Key แต่ถ้าเราต้องการอ่านข้อมูล โดยให้มีการเรียงลำดับของข้อมูลตามฟิลด์อื่นๆ ที่ไม่ใช่ Primary Key ให้ใช้ออปชัน Order by ในคำสั่ง Select เช่น สมมุติว่าเราต้องการอ่านข้อมูลของตาราง customers ทั้งหมด โดยให้เรียงลำดับตามชื่อของลูกค้า เราสามารถเขียนโปรแกรมได้ดังนี้


Tables customers.
Select * from customers order by name.
Write: / customers-name, customers-city.
Endselect.


ซึ่งโดยปกติของการเรียงลำดับของข้อมูลจะเรียงจากน้อยไปหามาก ถ้าเราต้องการเรียงลำดับจากมากไปหาน้อยก็ให้เราใช้ออปชัน descending เช่น


Tables customers.
Select * from customers order by name descending.
Write: / customers-name,customers-city.
Endselect.


แต่ในทางปฏิบัติแล้ว การเรียงลำดับของข้อมูลจากออปชัน Order by นั้นไม่ค่อยจะดีสักเท่าไร ถ้าคำนึงถึงเรื่องของประสิทธิภาพ เพราะเป็นการเรียงลำดับของข้อมูลที่ Database Server ทางที่ดีกว่าก็คือการเรียงลำดับที่ Memory Space โดยใช้ Internal Table ซึ่งผมจะได้กล่าวถึงในตอนต่อไป

Table Join

ในการอ่านข้อมูลจากฐานข้อมูลที่ Database Server นั้น ถ้าเป็นการอ่านข้อมูลจากตารางเพียงตารางเดียว โลกของคนเขียนโปรแกรม ABAP ก็คงเป็นสีชมพูเป็นแน่แท้ แต่ในโลกแห่งความเป็นจริง เรามักจะพบว่าข้อมูลที่ปรากฏอยู่ในรายงาน มักจะมาจากหลายตาราง ไม่ใช่เพียงตารางเดียวอย่างที่ทุกคนฝันกัน ลองมาดูตัวอย่างของระบบงานสักตัวอย่าง สมมติว่าในระบบขายมีตารางที่สัมพันธ์กันในฐานข้อมูลดังนี้
ตาราง zcustomer
ตาราง zsale
ตาราง zproduct


โดยที่ตาราง zcustomer สัมพันธ์กับตาราง zsale ด้วยฟิลด์ id กับ cust_id ตามลำดับ และตาราง zsale กับตาราง zproduct สัมพันธ์กันด้วยฟิลด์ p_id ในการอ่านข้อมูลจากตารางในฐานข้อมูลที่มากกว่า 1 ตารางนั้น ในระบบ SAP สามารถทำได้ 4 วิธีด้วยกันคือ
    1. Nested Select 2. Internal Table 3. View 4. Inner Join Select

สำหรับ Nested Select (หรือการ Select ซ้อน) ซึ่งเป็นวิธีการดั้งเดิมสมัย SAP Release 3.0 ผมคงไม่กล่าวถึงนะครับ เพราะเป็นวิธีที่ทำงานช้าที่สุดในการอ่านข้อมูลที่ฐานข้อมูล ดังนั้นถ้าทำใจลืมได้ก็กรุณาลืมไปเสียเถอะครับ ส่วน Internal Table นั้นเอาไว้ค่อยไปเรียนรู้กันในตอนที่ผมพูดถึง Internal Table ก็แล้วกัน ส่วน View นั้นก็เป็นการสร้าง View ที่ Data Dictionary แต่วิธีที่ผมจะกล่าวถึงในส่วนนี้ก็คือ Inner Join Select นั่นเอง ผมแนะนำให้ใช้วิธีนี้ในการอ่านข้อมูลของตารางที่มากกว่า 2 ตารางขึ้นไป เพราะเป็นวิธีที่มีประสิทธิภาพค่อนข้างดีมาก และมีความคล่องตัวในการเขียนโปรแกรมมากกว่าวิธีอื่น สำหรับรูปแบบซินแท็กซ์ของ Inner Join ในคำสั่ง Select จะเป็นดังนี้


Select &lttable>~&ltfield> ...
Into &lttable&gt-&ltfield> ...
From &lttable1> inner join &lttable 2>
On &lttable 1>~&ltfield> = &lttable 2>~&ltfield> and ...
Where &lttable>~&ltfield> ... &ltcondition> ...


เช่น ถ้าเราต้องการแสดงข้อมูลดังต่อไปนี้จากตารางในระบบขาย

zcustomer-name zsale-p_id zsale-qty

หรือต้องการแสดงข้อมูลว่ามีลูกค้าชื่ออะไร ได้ซื้อสินค้าอะไร เป็นปริมาณเท่าไร เราสามารถเขียนโปรแกรมได้ดังนี้


Tables: zcustomer, zsale.
Select zcustomer~name zsale~p_id zsale~qty
Into (zcustomer-name, zsale-p_id, zsale-qty)
From zcustomer inner join zsale
On zcustomer~id = zsale~cust_id.
Write: / zcustomer-name, zsale-p_id, zsale-qty.
Endselect.


โปรดสังเกตว่า ในการระบุชื่อตารางกับฟิลด์ใน Inner Join ของคำสั่ง Select นั้น เราจะใช้เครื่องหมาย "~" เสมอ ยกเว้นที่ส่วน into เท่านั้นที่เราจะต้องระบุเป็น Structure เพราะสิ่งที่อยู่หลัง into ในคำสั่ง Select นั้น จะเป็นสิ่งที่อยู่ใน Memory Space นั่นเอง และในการใช้ Inner Join นั้น สิ่งแรกที่เราจะต้องทราบก่อนก็คือ ตารางที่เกี่ยวข้องทั้งหมดนั้นสัมพันธ์กันด้วยฟิลด์อะไรบ้าง

โดยหลักการง่ายๆ ของการใช้ Inner Join ที่ผมใช้ในการเขียนโปรแกรมจะมีดังนี้


  • ให้พิจารณาสิ่งที่เราต้องการในรายงาน ว่าต้องการข้อมูลในฟิลด์ของตารางใด จากตัวอย่างคือ zcustomer-name, zsale-p_id และ zsale-qty จะเห็นได้ว่ามีตารางที่เกี่ยวข้องอยู่ 2 ตารางด้วยกันคือ zcustomer และ zsale จากนั้นให้สร้าง Table Structure จากคำสั่ง Tables: zcustomer, zsale เพื่อรอรับการทำงานของคำสั่ง Select โดยที่จริงๆ แล้วในส่วนนี้ เราอาจสร้างตัวแปรมารอรับข้อมูลที่ได้จาก select ก็ได้

  • จากนั้นจากโจทย์ที่ต้องการ เราก็เลือกข้อมูลจากคำสั่ง Select ดังนี้ Select zcustomer~name zsale~p_id zsale~qty

  • เมื่ออ่านข้อมูลขึ้นมาได้แล้วจะเก็บไว้ที่ไหน? จากออปชัน into ก็คือที่ Table Structure ที่เราสร้างรอไว้นั่นเอง Into (zcustomer-name, zsale-p_id, zsale-qty)

  • ต่อมาเป็นการอ่านข้อมูลจากตารางที่สัมพันธ์กันคือ

    From zcustomer inner join zsale

    ที่ส่วนของ From ... inner join ... นี้ เราสามารถสลับตารางทางซ้ายกับตารางทางขวาของ inner join อย่างไรก็ได้ เพราะ inner join คือการอ่านข้อมูลที่สัมพันธ์กันเท่านั้น

  • จากนั้นเราจะระบุว่าทั้ง 2 ตารางสัมพันธ์กันด้วยฟิลด์อะไรจากออปชัน on ดังนี้

    On zcustomer~id = zsale~cust_id.

  • สุดท้ายก็แสดงข้อมูลที่ต้องการออกมาจากคำสั่ง Write ซึ่งเป็นข้อมูลที่ได้จากออปชัน into ของคำสั่ง Select นั่นเอง และจะต้องปิดด้วย Endselect อย่างเดียวเท่านั้น

    เชื่อผมเถอะครับ ใช้หลักการง่ายๆ อันนี้ จะช่วยให้เขียนโปรแกรมสำหรับอ่านข้อมูลจากตารางที่มากกว่า 1 ตารางได้อย่างง่ายดายในพริบตา ท่องให้ขึ้นใจนะครับสำหรับ inner join ว่า "อยากได้อะไรก็ให้ Select มันมา จากนั้นอ่านขึ้นมาได้จะ into ในไหน และอ่านจาก From ตารางอะไร inner join กับตารางอะไร ด้วยเงื่อนไข on ที่ตารางทั้งสองสัมพันธ์กันด้วยฟิลด์อะไรบ้าง" เพียงแค่นี้ก็จะทำให้คุณลืม Nested Select ไปได้ในทันที


  • ทิปเทคนิค


  • โดยปกติแล้ว ถ้าใน Where Clause ของคำสั่ง Select Single... นั้น เราไม่มีการอ้างถึงฟิลด์ที่เป็น Primary Key แล้ว เมื่อเช็กซินแท็กซ์ของโปรแกรม ระบบจะแสดงข้อความเตือนว่า ไม่มีการใช้ Primary Key ใน Where Clause แต่เป็นแค่เพียงเตือนเฉยๆ นะครับ เรายังสั่งเอ็กซิคิวต์โปรแกรมของเราได้ตามปกติทุกอย่าง การที่ระบบเตือนในกรณีที่ไม่มีการระบุ Primary Key ใน Where Clause ของคำสั่ง Select Single ก็เพราะการเข้าถึงข้อมูลที่ฐานข้อมูลอาจจะไม่มีประสิทธิภาพ คืออาจใช้ Index ไม่เหมาะสมหรือใช้วิธี Full Table Scan ก็เป็นได้ เช่นถ้าเราใช้คำสั่งต่อไปนี้

    Select single * from customers where name = 'John'.

    ถ้าไม่อยากให้มีข้อความเตือน (Warning Message) เมื่อเช็กซินแท็กซ์ของโปรแกรม ให้เราแก้ไขคำสั่ง Select Single * ... โดยใช้ออปชัน up to 1 rows ในคำสั่ง Select ... Endselect แทนดังนี้

    Select * from customers up to 1 rows where name = 'John'.
    ...
    Endselect.


    ผลลัพธ์ที่ได้จะเหมือนกับใช้คำสั่ง Select single * ... ทุกประการ เพียงแต่ว่าจะไม่มีข้อความ Warning Message ขึ้นมากวนใจเวลาเช็กซินแท็กซ์ของโปรแกรมนั่นเอง ซึ่งการทำงานจริงๆ ของคำสั่ง Select * from ... up to 1 rows ... Endselect ก็คือ ระบบจะค้นหาข้อมูลตามเงื่อนไขใน Where Clause ที่ฐานข้อมูล โดยถ้าพบข้อมูลตามเงื่อนไขแล้วก็จะหยุดการอ่านข้อมูลเรคอร์ดต่อไปทันที เพราะเรากำหนดให้อ่านข้อมูล ตามเงื่อนไขแค่ 1 เรคอร์ดเท่านั้นนั่นเอง แต่ถ้าในคำสั่ง Select * ... เราไม่ระบุออปชัน up to 1 rows ระบบก็จะอ่านข้อมูลจากฐานข้อมูลไปจนหมดทุกเรคอร์ดในตารางตามเงื่อนไข ถึงแม้ว่าจะพบเรคอร์ดที่ต้องการแล้วก็ตาม เพราะคำสั่ง Select ... Endselect เป็นคำสั่งที่ใช้ในการวนลูป เพื่ออ่านข้อมูลหลายเรคอร์ดในตารางที่ฐานข้อมูลนั่นเอง

  • เราสามารถใช้ Wildcard ในเงื่อนไขของคำสั่ง Select ได้ โดยที่เราจะต้องใช้ออปชัน Like แทน = ใน Where Clause ของคำสั่ง Select เช่น สมมุติว่าต้องการแสดงข้อมูลลูกค้าจากตาราง customers เฉพาะลูกค้าที่ชื่อขึ้นต้นด้วย P เราสามารถเขียนคำสั่งได้ดังนี้

    Select * from customers where name like 'P%'.

    โดยที่ค่า % แทนอะไรก็ได้ แต่ถ้าต้องการแสดงข้อมูลลูกค้าที่ชื่อตัวอักษรตัวที่สองเป็นตัว r เราสามารถเขียนคำสั่งได้ดังนี้

    Select * from customers where name like '_r%'.

    โดยที่ค่า _ จะแทนตัวอะไรก็ได้ 1 ตัวอักษร และถ้าต้องการแสดงข้อมูลลูกค้าที่ชื่อมีคำว่า SAP อยู่ด้วย เราก็ จะเขียนคำสั่งได้ดังนี้

    Select * from customers where name like '%SAP%'.

    ก็คือค่า % แทนตัวอะไรก็ได้กี่ตัวก็ได้ ส่วนค่า _ แทนตัวอักษรอะไรก็ได้ 1 ตัวแต่อย่าลืมใช้ออปชัน like ด้วย นะครับ 


  • ที่มา : http://www.arip.co.th/articles.php?id=406089

    ไม่มีความคิดเห็น:

    แสดงความคิดเห็น