ผู้เขียน หัวข้อ: [VB.NET] พื้นฐานการเขียนโปรแกรมติดต่อฐานข้อมูลแบบ One-To-One (เก็บข้อมูลลูกค้า)  (อ่าน 424 ครั้ง)

ออฟไลน์ ทองก้อน ทับทิมกรอบ

  • Administrator
  • *****
  • กระทู้: 245
  • เพศ: ชาย
  • Webmaster G2GNet
ทฤษฎีและปฏิบัติ:



คำแนะนำ: คุณต้องใช้ Debugger ที่มีอยู่ใน IDE ของ Visual Studio ให้เป็นด้วย ถึงจะมองเห็นการทำงานของโค้ดในทุกๆขั้นตอนได้

สำหรับคนที่ขี้เกียจอ่าน ดาวน์โหลดโค้ดต้นฉบับ VB.Net (2010) ได้ที่นี่

ความสัมพันธ์ของตารางข้อมูลแบบ One To One

Primary Key (PK) คือคีย์หลักที่กำหนดจากฟิลด์ในตาราง ที่มีค่าซ้ำกันไม่ได้ และเปลี่ยนแปลงค่าไม่ได้ ค่านี้โปรแกรมเมอร์ต้องใช้ในการอ้างอิงถึงในการเขียนโปรแกรม
- ควรเป็นตัวเลขชนิดจำนวนเต็มเท่านั้น เพราะง่ายต่อการคำนวณหา
- ไม่ควรใช้ AutoNumber เพราะเวลาสร้างข้อมูลทดสอบก่อนเขียนโปรแกรม จะทำได้ยากมาก (หลายคนมักออกแบบตารางข้อมูล แต่ไม่ยอมทดสอบความถูกต้องในการออกแบบซ่ะก่อน จริงอ่ะเปล่าครับ 5555+)

Identifier (ID) คือ รหัสชุดตัวเลขหรือตัวอักษรมาผสมกันก็ได้ ที่กำหนดจากฟิลด์ในตารางที่มี ค่าซ้ำกันไม่ได้ แต่สามารถเปลี่ยนแปลงค่าได้ ค่านี้จะเป็นตัวบ่งบอกวัตถุ คน หรือสิ่งของ เช่น รหัสบัตรประชาชน รหัสผู้เสียภาษี รหัสสินค้า หรือรหัสนักศึกษา ค่านี้ Users ต้องนำมาใช้งานในการอ้างอิงถึง

จะเห็นได้เลยว่า Primary Key ไม่ใช่ Identifier เพราะจุดประสงค์ในการใช้งานต่างกัน

ทฤษฏีตามตำรา กล่าวไว้ว่าบัตรประชาชน หรือ ID Card (หรือบัตรประกันสังคม) จะต้องมีค่าที่ซ้ำกันไม่ได้เลย ดังนั้นจึงนำค่านี้มาเป็น Primary Key (PK) สำหรับตาราง (Table) ได้อย่างแน่นอน  ... จริงหรือเปล่า???

ทางปฏิบัติ มันมีโอกาสเกิดความผิดพลาดในรับข้อมูล หรือการป้อนข้อมูลผิดของผู้ใช้ (Human Error) ได้เสมอ หรือแม้แต่ต้องการเปลี่ยนแปลงในภายหลัง เช่น รหัสสินค้า (มันก็คือรหัสบาร์โค้ดนั่นแหละ) ดังนั้นเราจึงต้องทำการแก้ไขข้อมูลได้ และ การแก้ไขนั้นจะต้องไม่ไปซ้ำกับของเดิมที่มีอยู่เดิม ... สรุปสั้นๆว่าจะนำเอา Identifier มาทำเป็น Primary Key ไม่ได้

ทำไม???


ภาพนี้แอดมินคิดว่าน่าจะอธิบายได้อย่างชัดเจนในตัวของมันเอง จะเห็นได้ชัดเจนเลยว่า เราไม่ได้นำเอา Identifier-ID มาโยงใยความสัมพันธ์ใดๆในตารางสักตัว แต่ใช้ Primary Key- PK แทนหมด ... โดยให้สังเกตจากหมายเลข 1 (และหมายเลข 2) เราใช้ คีย์หลัก CustomerPK จากตารางลูกค้า (tblCustomer) เชื่อมโยงมายังตารางการขายสินค้า (tblInvoice) กับคีย์ CustomerFK (FK = Foreign Key) ซึ่ง 2 คีย์นี้มันก็เป็นตัวเดียวกันนั่นแหละครับ แต่อยู่คนละตาราง คือสื่อสารให้รู้ว่ามันมีความสัมพันธ์ที่เชื่อมโยงเข้ามาหากัน (ปกติที่นิยมใช้ก็คือให้คำลงท้ายว่า ID เหมือนกัน)

บทสรุป : เมื่อเราให้ความหมายแยก PK กับ ID ออกจากกันได้แล้ว (จากภาพด้านบน) กรณีที่เราต้องการเปลี่ยนแปลงค่า ID ในตารางลูกค้า (tblCustomer) ไม่ว่าจะกี่ล้านตลบ ก็จะไม่ส่งผลกระทบกับตารางอื่นๆที่เกี่ยวข้อง ... แต่ลองหลับตานึกภาพหากคุณใช้ ID มาเป็น PK แล้วเกิดไปเปลี่ยนแปลงค่านี้ คุณก็ต้องตามไปแก้ไขในตารางการขายสินค้า (tblInvoice) ในทุกๆครั้งที่มีการขายสินค้าให้กับลูกค้ารายนี้เสมอ คิดว่าสนุกมั้ยล่ะครับ 5555+

*** ขาด ID ได้ แต่จะขาด PK ไม่ได้ ***

ตารางย่อย (Detail) ที่จะต้องเชื่อมความสัมพันธ์ไปยังตารางหลัก (Master) บางตัวก็ไม่จำเป็นต้องมี ID แต่บางตัวก็ต้องมี ขึ้นอยู่กับการนำไปใช้งานครับผม

วกกลับมาเข้าเรื่อง ทำอย่างไรไม่ให้ค่า Identifier (ID) มีค่าที่ซ้ำกันได้เลย
ทีนี้ปัญหาจะเกิดขึ้นเมื่อ ... หากต้องการเพิ่ม หรือแก้ไขข้อมูลจะทำอย่างไรถึงจะไม่ให้ ID ไปซ้ำกับค่าเดิมได้ (เริ่มต้นกระบวนการแนวคิดการแก้ปัญหา)


กรณีของ Visual Basic เรานำเอาคุณสมบัติ Tag ของ TextBox มาใช้งาน (หากเป็นภาษาอื่นใช้ตัวแปรแทนก็ได้)

หลักการแนวคิด อภิมหาหมูน้อย ...  ในการโหลดข้อมูลเข้าสู่หน้าจอทุกๆครั้ง เราต้องเก็บค่าเดิมของ ID เอาไว้เสมอ ซึ่งค่านี้จะนำมาจากคุณสมบัติ Text จากนั้นก็คัดลอกค่านี้ไปเก็บไว้ในคุณสมบัติ Tag แทน ดังนี้

มาดูจากโค้ดในส่วนของโปรแกรมย่อย RecordToScreen (frmCustomerDetail.vb)
    ' / --------------------------------------------------------------------------------
    ' / โปรแกรมย่อยในการนำข้อมูลจาก DataBase มาแสดงผล
    ' / --------------------------------------------------------------------------------

    Sub RecordToScreen()
        strSQL = _
            " SELECT tblCustomer.*, tblProvince.ProvinceName " & _
            " FROM tblCustomer INNER JOIN tblProvince ON tblCustomer.ProvinceFK = tblProvince.ProvincePK " & _
            " WHERE CustomerPK = " & PK & _
            " ORDER BY CustomerPK "
        Try
            If ConnDB.State = ConnectionState.Closed Then ConnDB.Open()
            DAP1 = New OleDb.OleDbDataAdapter(strSQL, ConnDB)
            Dim DT As New DataTable
            DAP1.Fill(DT)
            txtCustomerID.Text = DT(0).Item("CustomerID").ToString()
            '/ ------------------------------------------------------------------
            '/ VERY IMPORTANT - ใจกลางความรู้สึกดีดี 5555+
            '/ นำค่าเดิมจาก CustomerID.Text ไปเก็บไว้ที่ CustomerID.Tag
            '/ โดยเราจะทำการเปรียบเทียบ 2 ค่าในเหตุการณ์ btnSave_Click

            txtCustomerID.Tag = txtCustomerID.Text
            '/ ------------------------------------------------------------------
            With DT(0)
                txtCustomerName.Text = .Item("CustomerName").ToString()
                txtAddress.Text = .Item("Address").ToString
                txtAmphur.Text = .Item("Amphur").ToString
                txtPostCode.Text = .Item("PostCode").ToString
                txtTelephone.Text = .Item("Telephone").ToString
                txtFacsimile.Text = .Item("Facsimile").ToString
                txtContactPerson.Text = .Item("ContactPerson").ToString
                txtEmail.Text = .Item("Email").ToString
                txtLineID.Text = .Item("LineID").ToString
                txtFacebook.Text = .Item("Facebook").ToString
                ' คือเอาค่าชื่อจังหวัดในตารางข้อมูล มาเปรียบเทียบกับค่าที่มีอยู่ใน ComboBox
                cmbProvince.Text = .Item("ProvinceName").ToString
            End With
            DAP1.Dispose()
        Catch ex As Exception
            MsgBox(ex.ToString())
        End Try
    End Sub

คราวนี้มาคิดวิเคราะห์กันต่อในการตรวจสอบการซ้ำของ ID มันมีโอกาสเป็นได้ 2 กรณี คือ

(1) เพิ่มข้อมูลใหม่
- ทำให้ txtCustomerID.Text (เป็นการป้อนค่าใหม่) จะไม่ตรงกันกับ txtCustomerID.Tag (ค่า Tag นี้จะต้องเป็นค่าว่างก่อนเสมอ)
ต้องนำค่าไปตรวจสอบว่ามีค่า txtCustomerID.Text (ที่เปลี่ยนไป) ไปซ้ำกับค่าเดิมในฐานข้อมูลหรือไม่ ก่อนการบันทึกข้อมูล

(2) แก้ไขข้อมูล - มีโอกาสได้ 2 ทาง คือ
- ไม่มีการแก้ไขค่าใน txtCustomerID.Text จะทำให้
txtCustomerID.Text = txtCustomerID.Tag
*** ดังนั้นหากนำไปทำการเปรียบเทียบค่าเดิมในฐานข้อมูล มันก็เจอค่ามันเองน่ะซิครับ เราต้องข้ามไปทำการบันทึกข้อมูลทันที

- มีการแก้ไขค่าใน txtCustomerID.Text ดังนั้น
txtCustomerID.Text <> txtCustomerID.Tag
ต้องนำค่าไปตรวจสอบว่ามีค่า txtCustomerID.Text (ที่เปลี่ยนไป) ไปซ้ำกับค่าเดิมในฐานข้อมูลหรือไม่ ก่อนการบันทึกข้อมูล

จากวิธีคิดและการออกแบบ นำมาสู่การเขียนโค้ดโปรแกรมจริง

กระบวนการตรวจสอบจะเกิดขึ้นที่เหตุการณ์ btnSave_Click
    ' / --------------------------------------------------------------------------------
    Private Sub btnSave_Click(sender As System.Object, e As System.EventArgs) Handles btnSave.Click
        '/ ดักเช็คข้อมูลสำคัญๆที่ต้องการเอาไว้ก่อน
        If Trim(txtCustomerID.Text) = "" Then
            MessageBox.Show("กรุณาป้อนรหัสลูกค้าให้เรียบร้อยก่อนด้วย.")
            txtCustomerID.Focus()
            '/ ออกจากโปรแกรมย่อยไปโลด
            Exit Sub
        ElseIf Trim(txtCustomerName.Text) = "" Then
            MessageBox.Show("กรุณาป้อนรายชื่อลูกค้าให้เรียบร้อยก่อนด้วย.")
            txtCustomerName.Focus()
            Exit Sub
        End If
        '/ ตรวจสอบการซ้ำกันของค่า CustomerID
        If txtCustomerID.Text <> txtCustomerID.Tag Then
            ' หากมีค่าซ้ำ ฟังค์ชั่นจะคืนค่ากลับมากกว่า 0
            If CheckDuplicateID() > 0 Then
                MsgBox("มีรหัสลูกค้า: " & Trim(txtCustomerID.Text) & " เรียบร้อยแล้ว กรุณาแก้ไขใหม่ด้วย.", _
                                    vbOKOnly + vbExclamation, "รายงานสถานะ")
                txtCustomerID.Focus()
                Exit Sub
            End If
        End If

        ' / --------------------------------------------------------------------------------
        ' / เริ่มต้นกระบวนการบันทึกข้อมูลได้เลย

        Call SaveData()
        ' / --------------------------------------------------------------------------------
    End Sub

    ' / --------------------------------------------------------------------------------
    ' / ฟังค์ชั่นตรวจสอบการซ้ำกันของรหัสลูกค้า (หรืออื่นๆ) กรณีข้อมูลเป็น Text
    ' / จากนั้นส่งค่ากลับ หากเป็น 0 แสดงว่าไปไม่เกิดการซ้ำกันของข้อมูล
    ' / ค่าส่งกลับมากกว่า 0 เกิดการซ้ำกัน จะต้องบังคับไม่สามารถเพิ่ม หรือ แก้ไขข้อมูลได้

    Function CheckDuplicateID() As Integer
        strSQL = _
            " SELECT Count(tblCustomer.CustomerID) AS CountID FROM tblCustomer " & _
            " WHERE CustomerID = " & "'" & txtCustomerID.Text & "'"
        If ConnDB.State = ConnectionState.Closed Then ConnDB.Open()
        Comm = New OleDb.OleDbCommand(strSQL, ConnDB)
        ' Return count records
        CheckDuplicateID = Comm.ExecuteScalar
    End Function

เพิ่มเติม ... ส่วนของการสร้าง Primary Key (PK) ไม่ให้ซ้ำกัน

    ' / --------------------------------------------------------------------------------
    ' / ฟังค์ชั่นในการหาค่า Primary Key ตัวใหม่ไม่ให้ซ้ำกัน

    Function SetupNewData() As Long
        strSQL = _
            " SELECT MAX(tblCustomer.CustomerPK) AS MaxPK FROM tblCustomer "
        If ConnDB.State = ConnectionState.Closed Then ConnDB.Open()
        Comm = New OleDb.OleDbCommand(strSQL, ConnDB)
        '/ ตรวจสอบว่ามีข้อมูลอยู่หรือไม่ และคืนค่ากลับ
        If IsDBNull(Comm.ExecuteScalar) Then
            SetupNewData = 1
        Else
            SetupNewData = Comm.ExecuteScalar + 1
        End If
    End Function

ดาวน์โหลดโค้ดต้นฉบับ VB.Net (2010) ได้ที่นี่

บันทึกการเข้า
สิ่งที่ดีกว่าการให้ คือการให้แบบไม่มีที่สิ้นสุด

ออฟไลน์ Mr.Den

  • Jr. Member
  • **
  • กระทู้: 73
  • เพศ: ชาย
ขอบพระคุณอย่างสูงครับอาจารย์ :'(

บันทึกการเข้า

ออฟไลน์ ทองก้อน ทับทิมกรอบ

  • Administrator
  • *****
  • กระทู้: 245
  • เพศ: ชาย
  • Webmaster G2GNet
ขอบพระคุณอย่างสูงครับอาจารย์ :'(

อย่าได้แปลกใจ ลอกแนวคิดมาจาก VB6 ทั้งหมดล่ะครับ 5555+

บันทึกการเข้า
สิ่งที่ดีกว่าการให้ คือการให้แบบไม่มีที่สิ้นสุด

ออฟไลน์ hot2

  • Newbie
  • *
  • กระทู้: 16
ขอบคุณมากครับ

บันทึกการเข้า

ออฟไลน์ kai54

  • Newbie
  • *
  • กระทู้: 10
ขอบพระคุณมากครับ ท่านพี่
ผมจะเดินตามหลัง(ผู้ใหญ่)ท่านพี่ อย่างติดๆเลยครับ....
 ;D ;D ;D ;D

บันทึกการเข้า