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

สำหรับเนื้อหา ABAP ในฉบับนี้ จะเป็นตอนต่อเนื่องจากฉบับที่แล้วนะครับคือเรื่องเกี่ยวกับ Inner Join ในภาษา ABAP ผมหวังว่าทุกท่านคงจำวิธีการในการเขียน Open SQL ในส่วนของการใช้ Inner Join ของคำสั่ง Select ได้นะครับ ถ้าเราเข้าใจหลักการของการใช้ออปชัน Inner Join เรื่องยากๆ ในภาษา ABAP ก็จะกลายเป็นเรื่องง่ายไปทันที


เบื้องลึกเบื้องหลัง Inner Join

มีใครเคยสงสัยเหมือนผมบ้างไหมครับว่า ทำไมพอใช้ Inner Join ในคำสั่ง Select แล้วมันเหลือแค่ Endselect เดียวเท่านั้น ไม่ว่าจะมีการอ้างถึงตารางกี่สิบตารางในออปชัน Inner Join ก็ตาม แล้ว Select ... Endselect ของออปชัน Inner Join มันวนลูปข้อมูลมาจากไหน ส่วนต่อจากนี้ไป ผมจะเฉลยเบื้องหลังการทำงานของ Inner Join ในคำสั่ง Select

คำสั่ง Select ... Endselect ก็ยังคงทำหน้าที่เดิมของมัน คือไปอ่านข้อมูลจากตารางที่ฐานข้อมูล (ถ้าข้อมูลที่ต้องการไม่มีอยู่ใน Table Buffer ที่ Application Server) จากนั้นก็จะนำข้อมูลที่ได้มาเก็บไว้ที่ Result Set ที่ DB Interface ต่อจากนั้นคำสั่ง Select จึงทำหน้าที่ของมัน คืออ่านข้อมูลที่ Pointer ชี้อยู่ที่ Result Set จากนั้นจึงนำข้อมูลทั้งแถวที่อ่านได้จาก Result Set ไปให้ค่ากับสิ่งที่ระบุหลังออปชัน Into ในคำสั่ง Select ซึ่งเราได้สร้างไว้แล้วที่ Memory Space จากนั้นก็จะทำคำสั่งที่อยู่ในบล็อค Select ... Endselect เมื่อทำงานถึงคำสั่ง Endselect หน้าที่ของคำสั่งนี้ก็คือ เช็กข้อมูลที่ Result Set ว่ามีเรคอร์ดถัดไปที่จะต้องอ่านอีกหรือไม่ ถ้ามีก็จะเลื่อน Pointer ไปยังเรคอร์ดถัดไป จากนั้นจึงโอนการทำงานกลับไปที่คำสั่ง Select เพื่อทำหน้าที่ของมันต่อไป

ในส่วนของคำสั่ง Select ... Inner Join ... Endselect ก็เช่นเดียวกัน ระบบจะหาข้อมูลจากตารางที่สัมพันธ์กันตามเงื่อนไขในออปชัน On เพื่อสร้าง Single Result Table ที่ Result Set ใน DB Interface จากนั้นระบบก็จะโพรเซสคำสั่ง Select ... Endselect ต่อไป ดังนั้นจากโปรแกรมที่เราเขียนดังรูปที่ 1


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.

รูปที่ 1 โปรแกรมตัวอย่าง

รูปที่ 2 ผลที่เกิดจากโปรแกรมตัวอย่างที่
Result Set ของ DB Interface

ก่อนที่ระบบจะทำงานคำสั่ง Select ... Endselect ในระบบ SAP จริงๆ มันจะ Join ข้อมูลตามเงื่อนไขที่ระบุจากออปชัน Inner Join ... On ... จากคำสั่ง Select ในฐานข้อมูล จากนั้นผลลัพธ์ที่ได้จากการ Join จะถูกนำมาสร้าง Single Result Table ดังรูปที่ 2 ที่ Result Set ของ DB Interface

สำหรับคอลัมน์ต่างๆ ของ Single Result Table นั้น ได้มาจากคำสั่ง Select นั่นเอง ก็คือ Select zcustomer~name zsale~p_id zsale~qty
รูปที่ 3 ตารางข้อมูลที่จะนำมา Join กัน
ทั้งสามตาราง

Inner Join สำหรับตารางที่มากกว่า 2 ตารางขึ้นไป

ถ้ามีการ Join กันของตารางที่มากกว่า 2 ตารางขึ้นไป อย่างเช่น จากตารางตัวอย่างของระบบการขายดังรูปที่ 3

ถ้าเราต้องการแสดงข้อมูลรายการขายดังนี้คือ zcustomer-name zproduct-p_name zsale-qty หรือต้องการแสดงข้อมูลว่า มีลูกค้าชื่ออะไร ซื้อสินค้าชื่ออะไร ไปปริมาณเท่าไร เราสามารถเขียนโปรแกรมได้ดังรูปที่ 4


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

รูปที่ 4


หลักการของการเขียน Inner Join ของคำสั่ง Select ก็ยังคงเหมือนเดิม ส่วนที่เพิ่มเติมเข้ามาในกรณีของ Inner Join ที่มากกว่าสองตารางขึ้นไปก็คือ หลังจาก From ... inner join ... on ... แล้ว ถ้ายังมีตารางใดๆ มาขอ Join ด้วย ก็เขียน Inner Join ต่อไปได้เลย เช่น inner join zproduct เป็นต้น จากนั้นก็ต้องระบุว่าที่จะมาขอ Join ด้วยนั้น มันสัมพันธ์กับฟิลด์ของตารางอะไร ด้วยออปชัน on นั่นเอง โดยที่ From ในคำสั่ง Select จะต้องมีที่เดียวเท่านั้น และโดยหลักการแล้วในการ Join กันของตารางที่มากกว่าสองตารางขึ้นไปนั้น เราจะต้องจับคู่ความสัมพันธ์ของตารางให้ได้หนึ่งคู่ ก่อนที่ From จากตัวอย่างข้างต้นคือ From zcustomer inner join zsale On zcustomer~id = zsale~cust_id จากนั้นใครจะมาขอ Join ด้วย ก็ง่ายแล้ว เพียงแค่ระบุออปชัน inner join ต่อไป ด้วยเงื่อนไขที่สัมพันธ์กับตารางใดจากออปชัน On ... เพียงแค่นี้ ไม่ว่าจะมีกี่สิบตารางที่จะมา Join กัน ก็กลายเป็นเรื่องง่าย แต่จากตัวอย่างข้างต้นนั้น เราจะใช้ตาราง zcustomer กับ zproduct มา Join กันเป็นคู่แรกจาก From ไม่ได้ เพราะสองตารางนี้ไม่สัมพันธ์กันเลย เห็นไหมครับว่า Inner Join ของคำสั่ง Select ถ้าเราเข้าใจหลักการของมันแล้ว การ Join กันในภาษา ABAP ก็จะกลายเป็นเรื่องง่ายๆ ของคนเขียนโปรแกรม ABAP ไปในทันที

การใช้ชื่อเล่นหรือ Alias Name ของตารางในคำสั่ง Select

บางครั้งการระบุชื่อเต็มๆ ของตาราง อาจจะทำให้เราเขียนโปรแกรมยาวจนน่าเบื่อ ดังนั้นเราสามารถใช้ Alias Name ของตารางในคำสั่ง Select ได้ เช่นจากตัวอย่างเดิมของคำสั่ง Select ดังรูปที่ 5


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

รูปที่ 5


เราสามารถใช้ Alias Name จากออปชัน as ในคำสั่ง Select ได้ดังรูปที่ 6


Tables: zcustomer, zproduct, zsale.
Select a~name b~p_name c~qty
Into (zcustomer-name, zproduct-p_name, zsale-qty)
From zcustomer as a inner join zsale as c
On a~id = c~cust_id
Inner join zproduct as b
On b~p_id = c~p_id.
Write: / zcustomer-name, zproduct-p_name, zsale-qty.
Endselect.

รูปที่ 6


จากตัวอย่างข้างต้น เราจะใช้ชื่อ a แทนชื่อตาราง zcustomer และ b แทนชื่อตาราง zproduct ส่วน c จะแทนชื่อตาราง zsale แต่ในการใช้ Alias Name นั้น เราจะใช้ในออปชัน Into ไม่ได้ เพราะ Into จะต้องตามด้วยสิ่งที่อยู่ใน Memory Space นั่นเอง

การใช้ Where Clause ของ Inner Join

ถ้าเราต้องการที่จะระบุเงื่อนไข Where Clause ในคำสั่ง Select ของ Inner Join แล้ว เราจะต้องระบุ Where Clause ไว้ท้ายสุดเลยหลังจากที่ Join ทุกตารางครบเรียบร้อยแล้ว เช่น ถ้าเราต้องการแสดงข้อมูลว่า มีลูกค้าชื่ออะไร ซื้อสินค้าชื่ออะไร ปริมาณเท่าไร โดยพิจารณาเฉพาะลูกค้าที่ซื้อสินค้าในวันนี้เท่านั้น เราสามารถเขียนโปรแกรมได้ดังรูปที่ 7


Tables: zcustomer, zproduct, zsale.
Select a~name b~p_name c~qty
Into (zcustomer-name, zproduct-p_name, zsale-qty)
From zcustomer as a inner join zsale as c
On a~id = c~cust_id
Inner join zproduct as b
On b~p_id = c~p_id
Where b~sale_date = sy-datum.
Write: / zcustomer-name, zproduct-p_name, zsale-qty.
Endselect.

รูปที่ 7


โดยที่ใน Where Clause เราจะใช้ฟิลด์ของตารางใดๆ ที่มีการ Join ก็ได้ทั้งนั้น โดยต้องระบุชื่อ ~ ตามปกติ

Outer Join ในคำสั่ง Select

ใน Open SQL ของภาษา ABAP นอกจากจะมี Inner Join ให้เราใช้งานในคำสั่ง Select แล้ว ระบบยังได้เตรียมออปชัน Outer Join ให้เราใช้ ในกรณีที่ต้องการแสดงข้อมูลของตารางที่มากกว่าสองตารางในการ Join กัน โดยที่ Outer Join จะรวมถึงข้อมูลที่ไม่สัมพันธ์กันด้วย ลองมาดูตัวอย่างต่อไปนี้ จากตารางต่างๆ ในระบบขาย ถ้าเราต้องการแสดงข้อมูลของลูกค้าทั้งหมดว่า มีลูกค้าชื่ออะไร ที่ซื้อสินค้าไปหรือไม่ก็ตาม ถ้าซื้อ ซื้ออะไรไปปริมาณเท่าไร เราสามารถเขียนโปรแกรมได้ดังรูปที่ 8


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

รูปที่ 8


จากโจทย์ที่ต้องการแสดงข้อมูลทั้งหมดของลูกค้าที่ซื้อสินค้ากับไม่ซื้อ สินค้า ถ้าเราใช้ Inner Join เราจะได้เฉพาะข้อมูลของลูกค้าที่ซื้อสินค้าเท่านั้น เพราะ Inner Join จะแสดงเฉพาะข้อมูลที่สัมพันธ์กันเท่านั้น ถ้าเราต้องการแสดงข้อมูลที่ไม่สัมพันธ์กันด้วยจากตารางทั้งสองตาราง เราจะต้องใช้ออปชัน Outer Join แทน Inner Join เพราะ Outer Join จะแสดงผลทั้งข้อมูลที่สัมพันธ์กับไม่สัมพันธ์กันออกมา และสำหรับ Outer Join เราจะต้องระบุออปชัน left ด้วยเสมอ เพราะเป็นตัวบอกระบบว่า ให้พิจารณาตารางทางซ้ายของ Outer Join เป็นหลักในการเปรียบเทียบข้อมูล ดังนั้นการสลับตารางทางซ้ายกับทางขวาของออปชัน Outer Join จะมีผลต่อข้อมูลที่แสดงออกมา

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


Tables: zcustomer, zsale.
Select a~name b~p_id
Into (zcustomer-name, zsale-p_id)
From zcustomer as a left outer join zsale as b
On a~id = b~cust_id.
If zsale-p_id = space.
Write: / zcustomer-name.
Endif.
Endselect.

รูปที่ 9

รูปที่ 10

จากโปรแกรมข้างต้น ที่ส่วนของ Single Result Table ใน Result Set จะมีข้อมูลของลูกค้าทั้งหมดที่ซื้อสินค้าและไม่ได้ซื้อสินค้าดังรูปที่ 10

จากรูปข้างต้นจะเห็นได้ว่า ลูกค้าที่ไม่ได้ซื้อสินค้าจะมีข้อมูลที่ฟิลด์ zsale~p_id เป็นค่า space ดังนั้นในแต่ละรอบการวนลูปของคำสั่ง Select ... Endselect เราจึงสามารถเช็กได้ว่าลูกค้าคนใดบ้างที่ไม่ได้ซื้อสินค้าจากเรา โดยการเช็กจากฟิลด์ zsale~p_id นั่นเอง แต่อย่าลืมนะครับว่า การสลับตารางทางซ้ายกับทางขวาของออปชัน Left Outer join จะมีผลต่อข้อมูลที่ได้ใน Result Set นะครับ
รูปที่ 11

Aggregate Function ในคำสั่ง Select

ในคำสั่ง Open SQL ของภาษา ABAP นั้น เราสามารถที่จะใช้ Aggregate Function ในคำสั่ง Select ได้อย่างใน Standard SQL ตามปกติทุกประการ เช่น ฟังก์ชัน Count, Max, Min และ Avg เป็นต้น สมมติว่าเรามีตารางสองตารางคือ spfli ซึ่งเป็นตารางที่เก็บข้อมูลรายละเอียดของสายการบิน และตาราง sflight ซึ่งเป็นตารางที่เก็บข้อมูลเกี่ยวกับตารางเที่ยวบินของสายการบินต่างๆ ดังรูปที่ 11

ถ้าเราต้องการแสดงข้อมูลระยะทางสูงสุด ระยะทางต่ำสุด และจำนวนเรคอร์ดทั้งหมดของตาราง spfli เราสามารถเขียนโปรแกรมได้ดังรูปที่ 12


Tables spfli.
Data: maxdistance like spfli-distance,
mindistance like spfli-distance,
counter type I.
Select count( * ) max( distance ) min( distance )
Into (counter, maxdistance, mindistance)
From spfli.
Write: / 'Total record =',counter,
/ 'Max distance = ', maxdistance,
/ 'Min distance = ', mindistance.

รูปที่ 12

โดยที่ฟังก์ชัน Max จะให้ค่าสูงสุดของฟิลด์ที่ระบุ (ในวงเล็บจะต้องเว้น 1 space ด้วย) ซึ่งฟิลด์ที่จะใช้กับฟังก์ชันนี้ จะต้องเป็นฟิลด์ที่เก็บค่าตัวเลขเท่านั้น ส่วนฟังก์ชัน Min จะให้ค่าต่ำสุดของข้อมูลในตาราง และฟังก์ชัน Count ถ้าไม่ระบุชื่อฟิลด์ เราจะใช้ * ก็จะเป็นการนับจำนวนเรคอร์ดในตาราง ซึ่งฟังก์ชัน Max, Min และ Avg (การหาค่าเฉลี่ยของข้อมูล) จะต้องตามด้วยฟิลด์ที่เป็นตัวเลขเท่านั้น และที่สำคัญคือ เมื่อมีการใช้ Aggregate Function ในคำสั่ง Select แล้ว เราไม่จำเป็นจะต้องปิดด้วยคำสั่ง Endselect แต่อย่างใด เพราะไม่ใช่การวนลูปเพื่ออ่านข้อมูลนั่นเอง

นอกจากนี้ เรายังสามารถใช้ออปชัน Distinct ในฟังก์ชัน Count ในการนับจำนวนเรคอร์ดเฉพาะข้อมูลที่แตกต่างเท่านั้น เช่นถ้าเราต้องการแสดงข้อมูลจำนวนสายการบินที่มีเที่ยวบินไป New York ของตาราง spfli เราสามารถเขียนโปรแกรมได้ดังรูปที่ 13



Tables spfli.
Data counter type i.
Select count( Distinct carrid)
Into counter
From spfli
Where cityto = 'New York'.
Write: / counter.

รูปที่ 13


จากตัวอย่างโปรแกรมข้างต้น เราจะได้จำนวนสายการบินที่บินไป New York อยู่ 2 สายการบิน แต่ถ้าเราไม่ใช้ออปชัน Distinct เราก็จะได้ 3 สายการบิน เพราะสายการบิน LH มีเที่ยวบินที่บินไป New York อยู่สองเที่ยวบินนั่นเอง

Select ... Group by ...

ในการใช้ Aggregate Function ของคำสั่ง Select นั้น เราสามารถใช้ออปชัน Group by ในการหาค่าฟังก์ชันของกลุ่มข้อมูลตามที่ต้องการ เช่น ถ้าเราต้องการแสดงข้อมูลราคาตั๋วเครื่องบินสูงสุดและต่ำสุดของสายการบิน ต่างๆ โดยแบ่งข้อมูลออกเป็นกลุ่มตามสายการบินในตาราง sflight เราสามารถเขียนโปรแกรมได้ดังรูปที่ 14


Tables sflight.
Data: carrier like sflight-carrid,
minprice like sflight-price,
maxprice like sflight-price.
Select carrid min( price ) max( price )
Into (carrier, minprice, maxprice)
From sflight
Group by carrid.
Write: / carrier,minprice,maxprice.
Endselect.

รูปที่ 14


ในการใช้ Aggregate Function แบบแบ่งกลุ่มจากออปชัน Group by นี้ เราจะต้องปิดคำสั่ง Select ด้วย Endselect เสมอ เพราะระบบจะคำนวณข้อมูลเป็นกลุ่ม จากนั้นก็จะได้ผลของการคำนวณเก็บไว้ที่ Result Set เพื่อวนลูปอ่านข้อมูลจากคำสั่ง Select ... Endselect ต่อไป โดยหลักการของการใช้ออปชัน Group by นั้น สังเกตง่ายๆ คือฟิลด์ที่เราใช้ในการแบ่งกลุ่มจากออปชัน Group by ควรจะต้องเป็นฟิลด์ที่อยู่ใน List ของคำสั่ง Select ด้วย ซึ่งในที่นี้คือ carrid จากนั้นส่วนที่เหลือใน List ของคำสั่ง Select ก็จะเป็น Aggregate Function ที่จะทำงานกับกลุ่มข้อมูลที่มีการแบ่งกลุ่มจากออปชัน Group by และที่สำคัญข้อมูลในตารางจะต้องเรียงลำดับตามฟิลด์ใน Group by ด้วยเสมอ

สำหรับเงื่อนไขของการแบ่งกลุ่มจากออปชัน Group by เราจะใช้ออปชัน Having แทน Where ในกรณีที่เงื่อนไขนั้นๆ เป็นเงื่อนไขของข้อมูลที่ได้จากการแบ่งกลุ่มข้อมูลแล้ว เช่น ถ้าเราต้องการแสดงข้อมูลราคาตั๋วเครื่องบินสูงสุด และราคาต่ำสุดของสายการบินต่างๆ ในตาราง sflight โดยให้แสดงเฉพาะข้อมูลสายการบินที่มีราคาตั๋วต่ำสุดไม่น้อยกว่า 100 เราสามารถเขียนโปรแกรมได้ดังรูปที่ 15


Tables sflight.
Data: carrier like sflight-carrid,
minprice like sflight-price,
maxprice like sflight-price.
Select carrid min( price ) max( price )
Into (carrier, minprice, maxprice)
From sflight
Group by carrid
Having min( price ) > 100.
Write: / carrier,minprice,maxprice.
Endselect.

รูปที่ 15


แต่ถ้าเงื่อนไขที่ต้องการไม่ใช่เงื่อนไขของข้อมูลที่มีการแบ่งกลุ่ม หรือเป็นเงื่อนไขที่เราต้องการที่จะกรองข้อมูลก่อนการแบ่งกลุ่ม เราจะใช้ Where Clause ตามปกติ ซึ่ง Where จะต้องตามหลัง From และต้องอยู่ก่อน Group by เสมอ ส่วน Having จะต้องตามหลัง Group by นอกจากนี้เราสามารถใช้ Where ผสมกับ Having ในคำสั่ง Select ... Group by ก็ได้ ยกตัวอย่างเช่น ถ้าเราต้องการแสดงข้อมูลราคาตั๋วเครื่องบินสูงสุดและต่ำสุดของสายการบิน ต่างๆ ในตาราง sflight เฉพาะสายการบินที่มีราคาตั๋วต่ำสุดไม่น้อยกว่า 3000 โดยไม่รวมข้อมูลของสายการบิน UA เราสามารถเขียนโปรแกรมได้ดังรูปที่ 16


Tables sflight.
Data: carrier like sflight-carrid,
minprice like sflight-price,
maxprice like sflight-price.
Select carrid min( price ) max( price )
Into (carrier, minprice, maxprice)
From sflight
Where carrid <> 'UA'
Group by carrid
Having min( price ) > 3000.
Write: / carrier,minprice,maxprice.
Endselect.

รูปที่ 16


สำหรับคำสั่ง Open SQL ของภาษา ABAP ความสามารถของมันไม่ได้ด้อยไปกว่าภาษาอื่นแต่อย่างใด ใครที่เคยเขียนภาษา Standard SQL มาแล้วคงจะเข้าใจได้ไม่ยาก อย่าลืมนะครับถ้าต้องการเขียนโปรแกรมในลักษณะ Standard ของ SAP เราก็จะต้องใช้คำสั่ง Open SQL เท่านั้น ไม่ว่า Database Server จะเป็นของค่ายไหน การเขียนโปรแกรมที่มีการใช้ Open SQL ก็จะไม่แตกต่างกันแต่อย่างใด
ที่มา : http://www.arip.co.th/articles.php?id=406115

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

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