คุยกันก่อน ... ระบบงานฐานข้อมูล ไล่มาจาก DBase, FoxBase, FoxPro และ Visual Basic ผมต้องใช้เวลาในการเรียนรู้ด้วยตนเอง และ หาประสบการณ์มานานหลายปี ลองผิด ลองถูกมาก็เยอะ แต่จะไม่มีวันนี้ หากเมื่อครั้งที่ผมทำงานครั้งแรกในตำแหน่ง "ช่างเทคนิค" อยู่นั้น ถ้าผมบอกว่า "งานแบบนี้มันไม่ใช่หน้าที่ของผม" ... โค้ดที่เห็นอยู่ในบทความชิ้นนี้ แม้มันจะไม่ใช่โค้ดจริงที่ผมเขียนโปรแกรมขาย และ ยังต้องตัดทอนออกไปจากระดับงานสอนแบบ "เรียนรู้นอกระบบ รบนอกตำรา" ของผมอีก เพราะเวลาถ่ายทอดจริง เด็กๆเขาจะเห็นจากหน้าจอ พร้อมๆกับผมเลย หากสงสัยสิ่งใดก็สามารถสอบถามผมได้ทันที แต่ถึงอย่างไรก็ตาม แนวคิดต่างๆของการออกแบบ ก็ไม่ได้ต่างกันเลยครับ นอกจากนี้แล้ว ผมยืนยันได้เลยว่าสายภาษาโปรแกรมอื่นๆ ก็ยังสามารถที่จะนำหลักการนี้ไปใช้ได้เช่นเดียวกัน ... เนื่องจากช่วงนี้ผมติดงานอยู่หลายตัว จึงยังไม่มีเวลาเพียงพอที่จะมาอธิบายถึงขั้นตอนต่างๆได้ทั้งหมดครับ ทำได้ตอนนี้ คือ สรุปสาระสำคัญเอาไว้ให้ดูก่อน หากมีข้อสงสัยอื่นใด สามารถสอบถามได้ครับ เพราะนี่คืออีกหนึ่งแห่งความภูมิใจของ "คนไทย" คนหนึ่ง ที่มอบให้กับ "คนไทย" อีกนับหลายๆคน
ข้อแนะนำก่อนที่จะศึกษาโค้ดโปรแกรมระบบขายสินค้าของผม
- ลืมทฤษฎีที่ศึกษามาจากห้องเรียน หรือ ความรู้จากตำราที่มีอยู่ในอ้อมกอดออกไปให้หมด เพราะสิ่งที่จะเห็นต่อไปนี้ มันต่างกันอย่างสิ้นเชิง
- ต้องเข้าใจการเขียนโปรแกรมในลักษณะของ 1 : 1 มาก่อน เช่น การบันทึกรายการลูกค้า รายการสินค้า ซึ่งก็มีอยู่มากมายในเว็บไซต์ของผม
- ต้องเข้าใจการใช้งาน Control พื้นฐานต่างๆ และ MS FlexGrid ซึ่งผมเขียนอธิบายเอาไว้ก่อนหน้านี้ไม่นาน
- ต้องใจเย็นๆ ค่อยๆไล่แกะ และ คิดพิจารณาตามไปด้วย ... พยายามตั้งคำถามว่าทำไมให้บ่อยๆ
- ที่สำคัญ ... ต้องมีมานะ พยายาม ขยัน และ อดทนในการเรียนรู้ฝึกฝน ...
- วิธีการนี้มันคงไม่ใช่วิธีการที่ดีที่สุด ... แต่มันดีที่สุดสำหรับผม ณ ขณะเวลานี้ (และมานานหลายปีแล้ว)
คุณจะเข้าถึงความสุขนี้ได้ ก็ต่อเมื่อคุณรู้จักกับคำว่าให้
|
 |
เริ่มต้นการสร้างความสัมพันธ์ระหว่างตารางการขายสินค้า กับ ตารางลูกค้า ตอนนี้ขอให้มองมาที่ ใบขายสินค้า 1 ใบ (ตาราง tblInvoice จะเป็น Master หรือ ตารางหลัก) จะขายสินค้าให้กับลูกค้าได้เพียง 1 รายเท่านั้น (ตาราง tblCustomer จะเป็น Detail หรือ ตารางย่อย) ดังนั้นมันคือความสัมพันธ์กันแบบ 1 : 1 (เช่นเดียวกับใบเช่าหนังสือ 1 ใบ ก็จะให้ลูกค้า/สมาชิก เช่าได้เพียง 1 รายเท่านั้นเช่นกัน) ... มองแบบนี้จะทำให้ง่ายต่อการวิเคราะห์และออกแบบ จะปรากฏดังภาพด้านล่างนี้
 มองไปที่ใบขายสินค้า (tblInvoice) เป็นหลักก่อน (ผมถึงเรียกมันว่าตารางหลัก หรือ Master ยังไงล่ะครับ) เวลาเรียกใช้งานจริงเพื่อทำการแสดงผล เราไม่จำเป็นต้องเอา CustomerPK/CustomerFK มาก็ได้ เพราะขณะนี้มันเกิดความสัมพันธ์ขึ้นระหว่างทั้ง 2 ตารางแล้ว
ตารางข้อมูลการขายสินค้าหลัก tblInvoice (Master)
 |
ชื่อฟิลด์
|
ชนิดข้อมูล
|
คำอธิบาย
|
InvoicePK
|
Long Integer
|
จะเก็บค่า Primary Key ของ InvoicePK เอาไว้ ค่านี้จะซ้ำไม่ได้ ... และที่สำคัญอย่างมาก ขอให้สังเกตว่าผมจะไม่ใช้ Primary Key ที่เป็น Autonumber เลย แต่จะมีวิธีการหาค่านี้ได้แบบไม่ซ้ำ ทั้งแบบ Stand Alone หรือ ต่อให้ทำงานบน LAN ... เพราะ Autonumber มันยากต่อการสร้างข้อมูล (ตุ๊กตา) ขึ้นมาทดสอบ ก่อนการลงโค้ดโปรแกรม และมีข้อเสียอื่นๆอีกแยะ
|
InvoiceCode
|
Text
|
เลขที่ใบการขาย เช่น INV-53000001 เพื่อใช้อ้างอิงในเอกสารเท่านั้น และผมขอร้องอย่าเอามันมาเป็น Primary Key เลยครับ ... เชื่อผมเหอะ หากไม่อยากมีปัญหาต้องปวดหัวตามมาในภายหลัง 55555+ เห็นหรือยังครับว่า แค่นี้ผมก็ต่างไปจากชาวบ้านชาวเมืองเขาแล้ว (Thongkorn's Theory)
|
CustomerFK
|
Long Integer
|
เป็นคีย์รอง หรือ Foreign Key เชื่อมความสัมพันธ์ไปยังตารางลูกค้า (เพราะคีย์หลัก หรือ Primary Key ของตารางนี้ คือ InvoicePK) ดังนั้นแล้วการออกแบบของผม มันจะไม่มีหรอกครับที่มีรูปกุญแจ (Primary Key) มากกว่า 1 ตัว
|
TotalAmount
|
Currency
|
รวมเงินค่าสินค้าทุกรายการทั้งหมด ในใบขายใบนี้ใบเดียว
|
TotalDiscount
|
Currency
|
รวมเงินค่าส่วนลดสินค้าทุกรายการทั้งหมด ในใบขายใบนี้ใบเดียว
|
นั่นก็คือ ใบขาย 1 ใบ เช่น InvoicePK = 1 จะขายให้กับลูกค้าทั่วไป CustomerPK = 0 เพียงคนเดียวเท่านั้น เราถึงเรียกมันว่าเป็นความสัมพันธ์กันแบบ 1 : 1 ต่อไปพิจารณาว่าเราจะเอารายการสินค้าเข้าไปได้อย่างไร หากเราเอา ProductFK ยัดใส่เข้าไปในตารางหลัก tblInvoice เพื่อเชื่อมความสัมพันธ์เข้าสู่ตารางสินค้า (tblProduct) ผลที่ได้จะทำให้ความสัมพันธ์เดิมคลาดเคลื่อนไป
 ตัวอย่างคือการขายสินค้า 2 รายการให้กับลูกค้า แทนที่ความสัมพันธ์มันจะเป็นแบบ 1 : 1 กลับกลายเป็นแบบ 1 : Many ไปซ่ะงั้น (มันมี 2 แถวรายการ) นอกจากนี้แล้วยังมีการเก็บข้อมูลซ้ำซ้อนกันเกิดขึ้นอีก เช่น InvoiceCode มันซ้ำๆตลอดทุกรายการ การรวมราคาสินค้าในการขายแต่ละใบขาย ก็ทำไม่ได้อีก ... ทางแก้คือ ต้องหาตารางข้อมูลอีก 1 ตารางมารับรายการขายสินค้าแต่ละรายการแทน โดยการเชื่อมความสัมพันธ์ด้วยใบขายสินค้าหลัก (InvoicePK) (เหมือนกับเชื่อมไปหาตารางลูกค้านั่นเอง) ... โอกาสหน้าจะอธิบายวิธีการแยกแยะให้ละเอียดมากกว่านี้ครับ ... เรื่องนี้มันก็เข้าสูตรเดิมของผม คือ ฟิลด์อันไหนที่ซ้ำกันให้จับแยกออกไปอยู่อีกตาราง ฟิลด์ที่แยกออกไปนั้น นำมันกลับมาเชื่อมความสัมพันธ์กันใหม่อีกครั้ง ( ตามตำราเขาเรียกมันว่า Normalization ครับพี่น้อง ... 55555+)
การออกแบบตารางข้อมูลความสัมพันธ์แบบ One To Many
ตารางข้อมูลการขายสินค้าย่อย tblInvoiceDetail (Detail)
ชื่อฟิลด์
|
ชนิดข้อมูล
|
คำอธิบาย
|
InvoicePK
|
Long Integer
|
จะเก็บค่า Primary Key ของ InvoicePK เอาไว้ ค่านี้จะซ้ำได้ (Duplicate) เพราะใบขาย 1 ใบ สามารถขายสินค้าออกไปได้มากกว่า 1 รายการ ดังนั้นตาราง tblInvoiceDetail ตัวนี้จะไม่มี และ ไม่จำเป็นต้องมี Primary Key เลย (ก็มันเป็นสูตรของผมเองน่ะครับ ... อย่างง เดี๋ยวไปดูโค้ดก็จะเข้าใจเอง)
|
ProductFK
|
Long Integer
|
เชื่อมความสัมพันธ์ไปยังข้อมูลสินค้า (tblProduct) ซึ่งมันก็เป็นไปตามธรรมชาติเหมือนตารางลูกค้ากับใบขายสินค้า โดยเรามองว่าใบขายสินค้า 1 ใบ จะต้องขายสินค้ารายการนั้นๆได้เพียงรายการเดียวเท่านั้น (จำนวนไม่เกี่ยว) แบบนี้ก็เรียกความสัมพันธ์ว่า 1 : 1 อีกเช่นเดียวกัน
|
PriceUnit
|
Currency
|
ราคาของสินค้า สาเหตุที่ต้องเก็บค่านี้ไว้ เพราะว่าการขายสินค้าแต่ละครั้งมันมีค่าไม่เท่ากันหรอกครับ
|
Quantity
|
Integer
|
จำนวนสินค้าที่ขายไป
|
TotalAmount
|
Currency
|
รวมเงินค่าสินค้ารายการนี้ทั้งหมด (จำนวน คูณ ราคาขาย)
|
TotalDiscount
|
Currency
|
รวมเงินส่วนลดราคาสินค้า
|
 |
 พอเราเชื่อมความสัมพันธ์กันแล้วอย่างถูกต้อง ... นี่คือใบขายสินค้า 1 ใบ ที่มี ค่า InvoicePK = 1 สามารถขายสินค้าได้มากกว่า 1 รายการ (ในตัวอย่างมีสินค้า 2 รายการ) เราถึงเรียกมันว่าเป็นความสัมพันธ์แบบ 1 : Many ยังไงล่ะครับพี่น้อง ... และการเชื่อมความสัมพันธ์ในลักษณะนี้ เราจะเอาไปใส่ไว้ในตารางกริดเพื่อทำรายการขายสินค้า ที่สามารถเข้าไปแก้ไข เพิ่มเติมการขายสินค้าในแต่ละครั้งต่อไปได้ครับพี่น้อง
ในขั้นตอนของการออกแบบตารางข้อมูล เราจะต้องสมมุติข้อมูลตัวอย่างขึ้นมาก่อน เพื่อทำการทดสอบความถูกต้อง (คือขณะนี้ผมยังไม่ได้เขียนเป็นโปรแกรมขึ้นมาเลยน่ะครับ) ดังนั้นหากเราเข้าใจในเรื่องความสัมพันธ์ของตารางข้อมูลแล้ว ก็ไม่ยาก แต่จะยากก็เพราะเราไม่เข้าใจ (พยายามลืมๆทฤษฎีตามหนังสือ หรือ ตามห้องเรียนก่อนด้วยครับ)
 หน้าจอการแสดงผลในส่วนของใบขายสินค้า (tblInvoice) และ ตารางลูกค้า (tblCustomer)
จำกัดเงื่อนไขเรื่องเวลา ... ผมขอนำเสนอเฉพาะบางอย่างไว้หน้าเว็บไซต์ก่อนน่ะครับ
เทคนิคที่สำคัญของการทำรายการขายสินค้า อยู่ที่ frmInvoiceDetail.frm และ โปรแกรมย่อย SaveData ขั้นตอนกระบวนการที่สำคัญ จะมีอยู่ทั้งหมด 5 ขั้นตอน (หรือ 4 ขั้นตอน ... ให้ไปคิดเป็นการบ้าน) ขอให้ทุกท่านค่อยๆพยายามทำความเข้าใจไปน่ะครับ ... และขอให้สังเกตว่าตัวแปร PK (Primary Key) ตัวนี้ เป็นตัวสำคัญที่สามารถใช้งานได้ทั้งตาราง tblInvoice และ ตาราง tblInvoiceDetail
- ขั้นตอนที่ 1
เราจะสนใจพิจารณา คือ ตารางการขายหลัก (tblInvoice) เท่านั้น อย่างอื่นไม่ได้มาเกี่ยวข้องน่ะครับ
- กรณีของการเพิ่มข้อมูล เราต้องทำการค้นหาค่า Primary Key (InvoicePK) ตัวใหม่
- กรณีของการแก้ไขข้อมูล อาศัยค่า Primary Key ที่ส่งออกมาจากฟอร์มหลัก (frmInvoiceMain)
(ดูที่ Form_Load ของ frmInvoiceDetail)
' #########################################################
' หากเป็นข้อมูลใหม่ NewData = True
If NewData Then
' #########################################################
' ค้นหาค่า Primary Key ของ InvoicePK ใหม่ ซึ่งเราจะต้องมาคำนวณหาในตอนจะ Save ข้อมูลเท่านั้น
' นี่คือปัญหาที่หลายคนเจอ ทำไมเวลาใช้งานจริง ค่า Primary Key หรือ InvoiceCode มันถึงซ้ำกันได้
' ก็เพราะจัดเรียงเหตุการณ์ หรือ ลำดับการทำงาน (Flow Control) ไม่ถูกต้องนั่นเองครับ ... พี่น้อง
'
Call SetupNewInvoice
'
' จะเกิดการส่งค่า Primary Key ของ InvoicePK มาตัวแปร PK ตัวแปรตัวนี้ใช้ได้ทั้งตอนเพิ่ม และ แก้ไข
' #########################################################
Statement = "SELECT * FROM tblInvoice ORDER BY InvoicePK"
RS.Open Statement, ConnDB, adOpenKeyset, adLockOptimistic, adCmdText
RS.AddNew
' ค่า Primary Key ที่คำนวณหาได้จากโปรแกรมย่อย SetupNewInvoice
RS("InvoicePK") = PK
RS("InvoiceCode") = Trim(txtInvoiceCode.Text)
RS("DateAdded") = FormatDateTime(Now(), vbGeneralDate)
RS("DateModified") = FormatDateTime(Now, vbGeneralDate)
' บันทึกผู้ใช้งาน เผื่อไว้กรณีที่ใช้งานแบบ Multi User
RS("AddedByFK") = 0
RS("LastUserFK") = 0
'======================== แก้ไขข้อมูล ========================
Else
' ก็เทียบค่าจาก Primary Key ที่ส่งมาจากฟอร์มหลัก (frmInvoiceMain)
Statement = "SELECT * FROM tblInvoice WHERE InvoicePK = " & PK
RS.Open Statement, ConnDB, adOpenKeyset, adLockOptimistic, adCmdText
End If
' #########################################################
'
' เริ่มต้นการบันทึกข้อมูลส่วนที่เหลือลงในตาราง tblInvoice เท่านั้น
'
' #########################################################
RS("InvoiceCode") = "" & Trim(txtInvoiceCode.Text)
' #########################################################
' รายการลูกค้า ที่เราซ่อน หรือ ฝาก Primary Key เอาไว้ใน txtCustomerCode.Tag
' และเราจะไม่ไปเชื่อมกับตารางลูกค้า tblCustomer เลย เพราะนี่แหละครับที่เรียกว่าการสร้างความสัมพันธ์
'
RS("CustomerFK") = CLng(txtCustomerCode.Tag)
'
' #########################################################
' #########################################################
' รวมจำนวนเงินของสินค้าทั้งหมดทุกๆรายการ
Dim TotalAmount As Currency
TotalAmount = 0
' คำนวณหาราคาสินค้าทั้งหมดทุกชิ้นตามจำนวนแถวในตารางกริด ถามว่าทำไมต้องจัดเก็บลงตาราง tblInvoice
' ก็เพราะจะได้สะดวกต่อการนำไปแสดงผลในฟอร์มหลัก frmInvoiceMain ยังไงล่ะครับ
' จะได้ไม่ต้องมาคำนวณผลใหม่ทุกรอบ ทุกครั้งไป และ สามารถทำรายงานได้ง่ายขึ้นอีก
For Rec = 1 To fgData.Rows - 1
RS("TotalAmount") = Format(TotalAmount + CDbl(fgData.TextMatrix(Rec, 6)), "#,##0.00")
TotalAmount = RS("TotalAmount")
Next
' #########################################################
' บันทึกเวลาของการแก้ไขไว้ด้วย
RS("DateModified") = FormatDateTime(Now(), vbGeneralDate)
' บันทึกผู้ใช้งาน เผื่อไว้กรณีที่ใช้งานแบบ Multi User (เมื่อ 0 คือ Administrator)
RS("LastUserFK") = 0
RS.Update
RS.Close: Set RS = Nothing
' =================================================================
' หมดแล้วสำหรับข้อมูลในตาราง tblInvoice หากมีภาษี หรือ อื่นๆเราก็จะกระทำที่นี่ทั้งหมด
' =================================================================
- ขั้นตอนที่ 2
จะเกิดเฉพาะตอนที่ทำการแก้ไขข้อมูลเท่านั้น ... นั่นคือเิกิดบันทึกรายการขายสินค้าไปแล้ว แต่ลูกค้าเกิดคืนสินค้ากลับมา เราจึงต้องนำ จำนวนสินค้า ที่ขายออกไปเดิม ใน tblInvoiceDetail คืนกลับเข้าในรายการสินค้า (tblProduct) เสียก่อน หากคุณไม่ตัดสต็อค ก็ข้ามส่วนนี้ไปได้เลย
 การทำรายการขายเดิม
 การทำรายการขายใหม่ หรือ การแก้ไขข้อมูล จะเห็นได้ว่าในรายการที่ 2 เดิมมันหายไป
 แต่ในตารางข้อมูล tblInvoiceDetail มันยังคงมีรายการขายเดิมอยู่ ดังนั้นเราต้องทำการคืนจำนวนสินค้านี้กลับเข้าสต็อคไปก่อน ... ทำไมเหรอครับ เพราะว่าต่อไปในใบขายสินค้าใบนี้ (InvoicePK = 1) ของตาราง tblInvoiceDetail เราจะลบรายการขายสินค้าเดิมในตารางข้อมูล tblInvoiceDetail ทิ้งออกไปให้หมดก่อน (อยู่ในขั้นตอนที่ 3) จากนั้นก็ค่อยเอาข้อมูลปัจจุบันที่อยู่ในตารางกริด มาทำการบันทึกเข้าไปแทนยังไงล่ะครับ (อยู่ในขั้นตอนที่ 4)
' #########################################################
' ทดสอบว่ามีข้อมูลสินค้าอยู่ในตาราง tblInvoiceDetail หรือไม่
Dim blnData As Boolean
' รับค่า Primary Key ของสินค้านั้นๆไว้ก่อน
Dim ProdPK As Long
' #########################################################
' ขั้นตอนนี้ คือ กรณีของการเข้าไปแก้ไขข้อมูล แล้วเกิดการลบข้อมูลสินค้าออกไปจากตารางกริด
' ดังนั้นต้องเอาข้อมูลที่อยู่ในแถวของตารางกริด ไปเปรียบเทียบกับข้อมูลเดิมตาม InvoicePK ใน tblInvoiceDetail
' หากพบข้อมูลในตาราง tblInvoiceDetail แต่ไม่เจอในตารางกริด แสดงว่ามีการลบข้อมูลสินค้าเดิมออกไป
' เราจะต้องทำการนำจำนวนสินค้าที่อยู่ในตาราง tblInvoiceDetail ส่งคืนกลับไปให้ตารางสินค้า tblProduct
' เพื่อปรับจำนวนสต็อคให้ตรงกับความเป็นจริง (ลูกค้าอาจคืนสินค้ากลับมาไงล่ะครับ)
' จะเกิดเหตุการณ์ตรวจสอบข้อมูลในส่วนนี้เฉพาะการแก้ไขข้อมูลเท่านั้น
' #########################################################
' ดังนั้น Not True ก็คือ ไม่จริง หรือ เท็จ นั่นเอง หรือจะเขียน If NewData = False ก็ได้
If Not NewData Then
'
' #########################################################
' เพราะเราจะทำการลบข้อมูลที่มีรหัส InvoicePK ตัวปัจจุบันนี้ทิ้งไปทั้งชุด ใน tblInvoiceDetail
' แล้วค่อยทำการบันทึกข้อมูลจากตารางกริดเข้าไปใหม่ (ในขั้นตอนที่ 4)
' #########################################################
Set RS = New Recordset
Statement = "SELECT * FROM tblInvoiceDetail WHERE InvoicePK = " & PK
RS.CursorLocation = adUseClient
RS.Open Statement, ConnDB, adOpenForwardOnly, adLockReadOnly, adCmdText
blnData = False
' #########################################################
' เฉพาะการเข้ามาแก้ไขรายการขายสินค้าเท่านั้น วนรอบตามจำนวนค่า Primary Key ของใบขายสินค้า
' #########################################################
Do Until RS.EOF
' วนรอบตามจำนวนแถวของตารางกริด
For Rec = 1 To fgData.Rows - 1
' เปรียบเทียบจากตารางข้อมูล (tblInvoiceDetail) หากมีค่าตรงกันกับ Primary Key ของสินค้า
' ให้ออกจาก Loop For ไปเลย เพราะแสดงว่าเป็นข้อมูลสินค้าตัวเดิมอยู่แล้ว
' หลัก 0 ที่ซ่อนเอาไว้ของตารางกริด คือ ค่า Primary Key ของสินค้านั้นๆ
If RS("ProductFK") = CLng(fgData.TextMatrix(Rec, 0)) Then
blnData = True
Exit For
' กรณีที่ที่เจอข้อมูลในตาราง tblInvoiceDetail แต่ไม่พบในตารางกริด แสดงว่า User ลบข้อมูลขายสินค้าเดิมออกไป
' ต้องลงไปปรับจำนวนสต็อคสินค้าใหม่ ทางด้านล่าง
Else
' เก็บค่า Primary Key ของสินค้าเพื่อนำจำนวนกลับไปคืนสต็อค
ProdPK = RS("ProductFK")
blnData = False
End If
Next
' เมื่ออกจากวงรอบ FOR หากไม่พบรายการสินค้า blnData = False
' ทำการคืนกลับสินค้าเข้าสต็อคตรงนี้
If Not blnData Then
Set DS = New Recordset
SQLStmt = "SELECT ProductPK, Stocked FROM tblProduct " & _
" WHERE [ProductPK] = " & ProdPK & _
" ORDER BY ProductPK"
DS.CursorLocation = adUseClient
DS.Open SQLStmt, ConnDB, adOpenKeyset, adLockOptimistic, adCmdText
If DS.RecordCount > 0 Then
' #####################################################
' สังเกตด้วยน่ะครับ RS("Quantity") เป็นค่าจำนวนสินค้าจากตาราง tblInvoiceDetail
' ส่วน DS("Stocked") เป็นค่าที่อยู่ในตารางสินค้า tblProduct
' ผมเคยเรียนให้ทราบแล้วว่า ผมประกาศใช้ RecordSet มาใช้งานไม่เกิน 3 ตัวหรอกครับ
' เราอาศัยการเวียนนำมาใช้ ... หากประกาศพร่ำเพรื่อก็ต้องกิน Memory สูง และ โอกาสผิดพลาดก็มีสูงด้วย
'
DS("Stocked") = DS("Stocked") + RS("Quantity")
'
' #####################################################
DS.Update
End If
DS.Close: Set DS = Nothing
End If
' เลื่อนรายการจากตารางข้อมูล
RS.MoveNext
Loop
RS.Close: Set RS = Nothing
End If
' #########################################################
- ขั้นตอนที่ 3 (กรณีเพิ่มข้อมูลใหม่ให้ข้ามไปได้เลย เพราะว่ายังไม่มีข้อมูลเดิม ... แต่คิดเอาเองครับจะทำยังไง)
ทำการลบข้อมูลตาม Primary Key ของใบขาย (InvoicePK) ออกไปจากตารางข้อมูล tblInvoiceDetail
' #########################################################
' 3. คราวนี้ต้องลบข้อมูลในตารางย่อย tblInvoiceDetail ตามค่า Primary Key ออกไปก่อนให้หมด
' จากนั้นค่อยเอาข้อมูลจากตารางกริดที่กำลังทำรายการปัจจุบัน มาบันทึกใหม่เข้าไปแทนที่ของเดิม
' #########################################################
SQLStmt = "DELETE tblInvoiceDetail.* FROM tblInvoiceDetail " & _
" WHERE ([InvoicePK] = " & PK & ")"
' สั่งลบข้อมูลออกไปทันทีเลย แม้ว่าจะไม่เจอ Primary Key ของ InvoicePK เลย
Set DS = ConnDB.Execute(SQLStmt)
' #########################################################
- ขั้นตอนที่ 4
เป็นการนำข้อมูลรายการขายสินค้าที่อยู่ใน MS FlexGrid เข้าไปจัดเก็บไว้ในตาราง tblInvoiceDetail
' #########################################################
For Rec = 1 To fgData.Rows - 1
' =========== เฉพาะตาราง tblInvoiceDetail ===========
' สำหรับตารางกริด
' หลัก 0 = ProductFK (หลัก 0 ที่ซ่อนไว้) เป็นคีย์รอง (Foreign Key) ที่เชื่อมโยงไปหาตาราง tblProduct ได้นั่นเอง
' หลัก 1 = รหัสสินค้า เช่น 0001 เราไม่เก็บ เพราะมี Primary Key (ProductPK) ของ tblProduct ชี้ไปหา
' Foreign Key (ProductFK) ใน tblInvoiceDetail อยู่แล้ว (นี่แหละคือความสัมพันธ์ระหว่างตาราง)
' หลัก 2 = ชื่อสินค้า (ไม่จัดเก็บ)
' หลัก 3 = หน่วยนับ (ไม่จัดเก็บ)
' หลัก 4 = เก็บราคาขายปัจจุบัน
' หลัก 5 = เก็บจำนวนการขาย
' หลัก 6 = คำนวณจำนวนเงินทั้งหมดเฉพาะสินค้าตัวนี้
' หลัก 7 = จำนวนสินค้าเดิมที่เราซ่อนเอาไว้ เพื่อนำไปตัดสต็อค
Set RS = New Recordset
Statement = "SELECT * FROM tblInvoiceDetail ORDER BY InvoicePK "
RS.Open Statement, ConnDB, adOpenKeyset, adLockOptimistic, adCmdText
With fgData
RS.AddNew
RS("InvoicePK") = PK
RS("ProductFK") = CLng(.TextMatrix(Rec, 0))
RS("PriceUnit") = Format(CDbl(.TextMatrix(Rec, 4)), "#,##0.00")
RS("Quantity") = Format(CLng(.TextMatrix(Rec, 5)), "0")
RS("TotalAmount") = Format(CDbl(.TextMatrix(Rec, 6)), "#,##0.00")
RS.Update
' ====================================================
End With
RS.Close: Set RS = Nothing
Next
' #########################################################
- ขั้นตอนที่ 5
ปรับจำนวนสต็อคสินค้าที่ขายไปให้ถูกต้อง (อันที่จริงทำพร้อมๆกับขั้นตอนที่ 4 ก็ได้ แต่ผมขอแยกออกมาเพื่อให้ดูได้ชัดเจน และ ง่ายขึ้นครับ)
' #########################################################
' 5. ปรับสต็อคสินค้าใหม่ทั้งหมด ............................................................ ขั้นตอนสุดท้ายแล้ว
' โดยนำค่าจำนวนสินค้าเดิมที่เราซ่อนในตารางกริด (หลักที่ 7) ไปบวกกลับคืนเข้าในสต็อคก่อน
' แล้วลบออกด้วยจำนวนสินค้าใหม่ในหลักที่ 5 แทน ... เทคนิคง่ายๆเท่านั้นเองครับ ไม่ยากเลย
' ตอนนี้จะทำงานเฉพาะตารางข้อมูลสินค้าเท่านั้น (tblProduct)
' #########################################################
For Rec = 1 To fgData.Rows - 1
' ===== ตาราง tblProduct ===========
Set RS = New Recordset
' เอาค่า Primary Key ในหลัก 0 ที่ซ่อนไว้ออกมาใช้ เพราะนั่นมันคือ Primary Key ของสินค้า
Statement = "SELECT * FROM tblProduct WHERE [ProductPK] = " & _
CLng(fgData.TextMatrix(Rec, 0)) & " ORDER BY ProductPK "
RS.Open Statement, ConnDB, adOpenKeyset, adLockOptimistic, adCmdText
With fgData
' เอาจำนวนในสต็อคเดิม RS("Stocked") มาบวกกับจำนวนเดิม (หลักที่ 7)
' แล้วลบออกจากจำนวนใหม่ (หลักที่ 5)
RS("Stocked") = Format((RS("Stocked") + CLng(.TextMatrix(Rec, 7))) - _
CLng(.TextMatrix(Rec, 5)), "0")
RS.Update
' ====================================================
End With
RS.Close: Set RS = Nothing
Next
' #########################################################
' จบกระบวนการขั้นตอนของการทำงานแบบ 1 To Many
' ขั้นตอนที่ 4 และ 5 สามารถทำได้พร้อมๆกันน่ะครับ ... ลองไปคิดเองเป็นการบ้านด้วยล่ะกันครับ
' #########################################################
Conclusion: กระบวนการบันทึกข้อมูลนี้ มันกระทำได้รวดเร็วมากน่ะครับ และจะเห็นได้อย่างชัดเจนเลยว่า มันไม่ได้มีกลไกอะไรที่สลับซับซ้อนมากมายแต่อย่างใดเลย มันเป็นวิธีการที่คิดง่ายๆแบบคณิตศาสตร์พื้นฐาน มองดูเหมือนตรรกะศาสตร์ขั้นสูง ทั้งๆที่มันก็มีเพียงแค่จริง กับ เท็จเท่านั้นเอง ... การที่จะสร้างนักพัฒนาซอฟท์แวร์รุ่นใหม่ๆ ตั้งแต่อยู่ในรั้วสถาบันการศึกษานั้น หลักที่สำคัญ คือ ต้องสร้างพื้นฐานทางปฏิบัติให้แน่น และ ต้องหาตัวอย่างจริง ของจริง มาสอน มาถ่ายทอด ให้กับเด็กๆเหล่านี้ ได้เห็น ได้สัมผัส หากทำได้แบบนี้ เราก็จะมีโอกาสสูงในการผลิตบุคลากรที่พร้อมจะทำงานจริงได้เลย หลังจากจบการศึกษาออกมาแล้วโดยทันที ... นี่คือสิ่งที่ผม "คิด" และ "ทำ" อย่างไรให้เด็กรุ่นใหม่ๆ ได้รับข้อมูล ความรู้ ที่ถูกต้อง เป็นจริง สามารถนำไปต่อยอดได้ ...
เพิ่มเติม
|