แสดงกระทู้

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Messages - PNR

หน้า: 1 ... 21 22 23 [24] 25 26 27 ... 32
415
อ้างถึง
บ้านเลขที่ ให้เป็นตัวเลขและ มี / ได้
Private Sub txtAddress_KeyPress(KeyAscii As Integer)
If (KeyAscii > 46 And KeyAscii < 58)  Then
      KeyAscii = KeyAscii
      Else:
      KeyAscii = 0
   End If
End Sub

อ้างถึง
3. เบอร์ = ตัวเลข ใน tbl เป็น text

สมมุติ txtPhoneNumber

Private Sub txtPhoneNumber_KeyPress(KeyAscii As Integer)
If (KeyAscii > 47 And KeyAscii < 58) Or (KeyAscii = 8) Then
      KeyAscii = KeyAscii
      Else:
      KeyAscii = 0
   End If
End Sub

จะพิมพ์ได้เฉพาะตัวเลขครับ แต่สามารถใช้ Space Bar ได้ เผื่อว่าต้องการพิมพ์เบอร์ 000 0000000  แบบนี้
อ้างถึง
NameEN = เฉพาะอักษรภาษาอังกฤษ

Private Sub txtWordsEng_KeyPress(KeyAscii As Integer)
If (KeyAscii > 65 And KeyAscii < 90) Or (KeyAscii > 97 And KeyAscii < 122) Then
 KeyAscii = KeyAscii
      Else:
      KeyAscii = 0
End If
End Sub
อ้างถึง
ชื่อไทย = เฉพาะอักษรไทย
Private Sub txtWordsTH_KeyPress(KeyAscii As Integer)
If Not (KeyAscii > 0 And KeyAscii < 32) And Not (KeyAscii > 32 And KeyAscii < 127) Then
 KeyAscii = KeyAscii
      Else:
      KeyAscii = 0
End If
End Sub

สำหรับ ใส่ชื่อไทย กำหนดให้ยอมรับการกด Spacebar ได้เพื่อให้เว้นช่องว่างกรณี มีทั้งชื่อและนามสกุล
โพสต์นี้ได้รับคำขอบคุณจาก: thanapol.w, NattakornC

416
ห้อง MS Access / : Record Source
« เมื่อ: 26 ก.พ. 63 , 11:42:02 »
ก่อนอื่นถ้าน้องอยากเริ่มสร้างโปรแกรม สิ่งเริ่มต้นที่ควรศึกษาก่อนคือ
1.ER Diagram

2.ศึกษาความสัมพันธ์ของตาราง (RelationShip)

3.ศึกษาการทำ normalization เพื่อให้ได้ข้อมูลที่ถูกต้องไม่ซ้ำซ้อนกัน

รวม ทุกขั้นตอนจากลิ้งนี้ เครดิตอาจารย์ TTT
https://www.youtube.com/watch?v=HuztS7t9hZc

การสร้างโปรแกรม ต้องกำหนดเป้าหมาย และทำเป็นขั้นตอนครับเช่น
1.กำหนดวัตถุประสงค์ของฐานข้อมูลว่าเราจะสร้างเพื่ออะไร (ศึกษา ER Diagram)
2.ค้นหาและจัดระเบียบข้อมูล ที่คุณอาจต้องการบันทึกไว้ในฐานข้อมูล เช่น ชื่อผลิตภัณฑ์และหมายเลขคำสั่งซื้อ ชนิดข้อมูลเช่น เป็น อักษร วันที่ ตัวเลข อื่นๆ
3.แบ่งข้อมูลเป็นตาราง   
4.เปลี่ยนรายการข้อมูลเป็นคอลัมน์
5.ระบุคีย์หลัก  เลือกคีย์หลักของแต่ละตาราง คีย์หลักเป็นคอลัมน์ที่ใช้ระบุแถวแต่ละแถวแบบไม่ซ้ำกัน ตัวอย่างอาจเป็น ID ผลิตภัณฑ์หรือ ID คำสั่งซื้อ
6.ตั้งค่าความสัมพันธ์ตาราง   (ศึกษาความสัมพันธ์ของตาราง (RelationShip))
7.จำกัดการออกแบบของคุณ  สร้างตารางแล้วเพิ่มระเบียนข้อมูลตัวอย่างบางระเบียน ดูว่าคุณได้รับผลลัพธ์ที่คุณต้องการจากตารางของคุณหรือไม่ ปรับเปลี่ยนการออกแบบตามจำเป็น (การทำ normalization)


อ้างถึง
ต้องการเชื่อมเพื่อที่จะให้สามารถรับข้อมูลได้จาก frm_car เป็นหลักไปเพิ่มข้อมูลที่ tb_car แล้วถึงจะไปเพิ่มข้อมูลให้ตารางอื่นๆค่ะ
จากที่ตอบมาคือ tb_car คือ ตารางหลัก มีรายการต่างๆ เป็นส่วนประกอบ ของตารางนี้ ซึ่งจริงๆแล้ว
อ้างถึง
ถึงจะไปเพิ่มข้อมูลให้ตารางอื่นๆค่ะ
ตารางอื่นๆนั้น ควรเพิ่มก่อนด้วยซ้ำไปเพราะเป็นรายละเอียด ที่จะมาใส่ในตารางหลักครับ

ซึ่งการกำหนด Gen , Size , offten และ Type นั้น เปรียบเสมือน การแยกส่วนต่างๆ มาเก็บไว้รวมกัน เท่านั้น ไว้เป็นตัวเลือก ในการระบุ รายละเอียดของรถ ในตาราง tbl_Car ครับ
โพสต์นี้ได้รับคำขอบคุณจาก: SakDa, S'Saifon SB B'Banjongjairak

417
ห้อง MS Access / : Record Source
« เมื่อ: 26 ก.พ. 63 , 09:50:10 »
คิวรี่ Car ต้องการสร้างขึ้นเพื่อทำอะไร

ถ้าอยากให้เชื่อมความสัมพันธ์กันเพื่อ แสดง ชื่อ ของแต่ละตารางให้ใส่แบบนี้

SELECT tb_car.no_car, tb_size.size, tb_type.typ, tb_gen.gener
FROM tb_type RIGHT JOIN (tb_size RIGHT JOIN (tb_gen RIGHT JOIN tb_car ON tb_gen.id_gen = tb_car.gen_car) ON tb_size.id_size = tb_car.size_car) ON tb_type.id_type = tb_car.type_car;


ที่คิวรี่ในมุมมอง SQL เอาโค้ดด้านบนไปวาง
โพสต์นี้ได้รับคำขอบคุณจาก: S'Saifon SB B'Banjongjairak

418
เป็นฟอร์มหลักแล้วเรียกตาราง tblAssign มาเพื่อทำการแก้ไขข้อมูลโดยตรง ในตารางมีรหัสงานอยู่เป็นจำนวนมาก อยากเช็คเวลาเรียน เพราะเวลาเรียนเช็คทุกวันและทุกคาบ ขี้เกียจเลื่อนเมาส์ไปหารหัสงานตัวนี้  เลยอยากจะให้มันแสดงข้อมูลชื่อฟิล์ด ่jobcode-รหัสงาน jobcode นี้มีค่าเท่ากับ class เวลาเราคลิกเรียกใช้ฟอร์มนี้ อยากให้มันกรองค่าและแสดงเฉพาะ ่jobcode = "class" ครับ
ทำได้หลายแบบมากครับ
แบบที่ 1 ใช้การกำหนด โดยใช้ FormLoad แบบที่คุณอยากทำ


เราใช้การกำหนด RecordSource แบบ Fix เงื่อนไข
ตรงข้อมูลแหล่งระเบียน เว้นว่างๆ ไว้นะครับ แล้วใส่โค้ดนี้เข้าไปแทน

Private Sub Form_Load()
Dim sql As String
sql = "SELECT tblAssign.* FROM tblAssign WHERE (((tblAssign.jobcode)='class'));"
Me.RecordSource = sql
End Sub

แบบที่ 2 กำหนด Criteria ไว้เลยว่าเอาเฉพาะ Class แบบนี้ไม่ต้องใช้ Event Form_Load



แบบที่ 3 ใช้ Combobox ในการเลือก Jobcode ที่ต้องการ อันนี้ผมคิดเผื่อไว้ในอนาคตถ้าอยาก แก้ไขตัวอื่นด้วย
โพสต์นี้ได้รับคำขอบคุณจาก: SakDa

419
ห้อง MS Access / : subform
« เมื่อ: 25 ก.พ. 63 , 17:04:37 »
แบบนี้ครับ
DoCmd.RunCommand acCmdSaveRecord
Forms!frm_Em![qrr_all subform].Form.Requery
โพสต์นี้ได้รับคำขอบคุณจาก: S'Saifon SB B'Banjongjairak

420
ห้อง MS Access / : subform
« เมื่อ: 25 ก.พ. 63 , 16:25:21 »
ได้ครับ
โพสต์นี้ได้รับคำขอบคุณจาก: S'Saifon SB B'Banjongjairak

421
อธิบายเพิ่มเติมอีกนิดครับ ผมยังงงคำถาม  :XD:
โพสต์นี้ได้รับคำขอบคุณจาก: SakDa

422
ห้อง MS Access / : subform
« เมื่อ: 25 ก.พ. 63 , 15:49:58 »
สั่งให้ subform Requery ครับ

เช่น
ที่ปุ่ม บันทึก(ดูจากมาโคร)

Private Sub Command25_Click()
DoCmd.GoToRecord , , acNewRec
Forms!frm_Em![qrr_all subform].Form.Requery
End Sub

ทีนี้ ปุ่มไหน เมื่อต้องการให้กดแล้ว ให้แสดงค่า Subform ใหม่ก็เอา
Forms!frm_Em![qrr_all subform].Form.Requery
ใส่เข้าไปด้วยครับ
โพสต์นี้ได้รับคำขอบคุณจาก: S'Saifon SB B'Banjongjairak

423
เช่น  65.00 อ่านว่า "sixty five bath"  เป็นต้น จนถึงหลักล้าน

สร้าง Module ใส่โค้ดนี้เข้าไป


โค๊ด: [Select]
Public Function wsiSpellNumber(ByVal MyNumber)
    Dim Bath, Stang, Temp
    Dim DecimalPlace, Count
    ReDim Place(9) As String
    Place(2) = " Thousand "
    Place(3) = " Million "
    Place(4) = " Billion "
    Place(5) = " Trillion "
    ' String representation of amount.
    MyNumber = Trim(Str(MyNumber))
    ' Position of decimal place 0 if none.
    DecimalPlace = InStr(MyNumber, ".")
    ' Convert Stang and set MyNumber to dollar amount.
    If DecimalPlace > 0 Then
        Stang = GetTens(Left(Mid(MyNumber, DecimalPlace + 1) & _
                  "00", 2))
        MyNumber = Trim(Left(MyNumber, DecimalPlace - 1))
    End If
    Count = 1
    Do While MyNumber <> ""
        Temp = GetHundreds(Right(MyNumber, 3))
        If Temp <> "" Then Bath = Temp & Place(Count) & Bath
        If Len(MyNumber) > 3 Then
            MyNumber = Left(MyNumber, Len(MyNumber) - 3)
        Else
            MyNumber = ""
        End If
        Count = Count + 1
    Loop
    Select Case Bath
        Case ""
           Bath = "No Bath"
        Case "One"
            Bath = "One Bath"
         Case Else
            Bath = Bath & " Bath"
    End Select
    Select Case Stang
        Case ""
            Stang = " and No Stang"
        Case "One"
            Stang = " and One Stang"
              Case Else
           Stang = " and " & Stang & " Stang"
    End Select
    wsiSpellNumber = Bath & Stang
End Function
     
' Converts a number from 100-999 into text
Function GetHundreds(ByVal MyNumber)
    Dim result As String
    If Val(MyNumber) = 0 Then Exit Function
    MyNumber = Right("000" & MyNumber, 3)
    ' Convert the hundreds place.
    If Mid(MyNumber, 1, 1) <> "0" Then
        result = GetDigit(Mid(MyNumber, 1, 1)) & " Hundred "
    End If
    ' Convert the tens and ones place.
    If Mid(MyNumber, 2, 1) <> "0" Then
        result = result & GetTens(Mid(MyNumber, 2))
    Else
        result = result & GetDigit(Mid(MyNumber, 3))
    End If
    GetHundreds = result
End Function
     
' Converts a number from 10 to 99 into text.
Function GetTens(TensText)
    Dim result As String
    result = ""           ' Null out the temporary function value.
    If Val(Left(TensText, 1)) = 1 Then   ' If value between 10-19...
        Select Case Val(TensText)
            Case 10: result = "Ten"
            Case 11: result = "Eleven"
            Case 12: result = "Twelve"
            Case 13: result = "Thirteen"
            Case 14: result = "Fourteen"
            Case 15: result = "Fifteen"
            Case 16: result = "Sixteen"
            Case 17: result = "Seventeen"
            Case 18: result = "Eighteen"
            Case 19: result = "Nineteen"
            Case Else
        End Select
    Else                                 ' If value between 20-99...
        Select Case Val(Left(TensText, 1))
            Case 2: result = "Twenty "
            Case 3: result = "Thirty "
            Case 4: result = "Forty "
            Case 5: result = "Fifty "
            Case 6: result = "Sixty "
            Case 7: result = "Seventy "
            Case 8: result = "Eighty "
            Case 9: result = "Ninety "
            Case Else
        End Select
        result = result & GetDigit _
            (Right(TensText, 1))  ' Retrieve ones place.
    End If
    GetTens = result
End Function
     
' Converts a number from 1 to 9 into text.
Function GetDigit(Digit)
    Select Case Val(Digit)
        Case 1: GetDigit = "One"
        Case 2: GetDigit = "Two"
        Case 3: GetDigit = "Three"
        Case 4: GetDigit = "Four"
        Case 5: GetDigit = "Five"
        Case 6: GetDigit = "Six"
        Case 7: GetDigit = "Seven"
        Case 8: GetDigit = "Eight"
        Case 9: GetDigit = "Nine"
        Case Else: GetDigit = ""
    End Select
End Function

เวลาเรียกใช้ก็ใส่ในคิวรี่ ก็ใส่ EngText:wsiSpellNumber([ชื่อฟิลล์]) หรือ ตัวแปรที่จำให้แสดง

Credit : www.microsoftaccessexpert.com
โพสต์นี้ได้รับคำขอบคุณจาก: Krathok-man, SakDa

424
ผมสมมุติว่า รายการ เราใส่ในตารางแบบนี้


เราสามารถสร้างปุ่มไว้ก่อนแล้วแสดงรายการจาก combobox หลังจากกดcombobox  ประมาณนี้ครับ เช่น
สมมุติ combobox ชื่อ Combo1 เราอยากแสดงไอดีไหนบ้าง สมมุติอยากให้ แสดงรายการที่ 1 2 3  ก็กำหนด ตรง where clause เอาครับ

Private Sub Command0_Click()
Me.Combo1.SetFocus
Me.Combo1.RowSourceType = "Table/Query"
Me.Combo1.RowSource = "SELECT Table1.ID, Table1.List FROM Table1 WHERE (((Table1.ID)=1 Or (Table1.ID)=2 Or  (Table1.ID)=3));"
Me.Combo1.Dropdown
End Sub

combobox ก็จะแสดง รายการ ที่มีไอดี 1 2 3 คือ แปรงสีฟัน / สบู่ / ยาสระผม เป็นต้น
โพสต์นี้ได้รับคำขอบคุณจาก: Eakaluk Sansila

425
ห้อง MS Access / : ลบค่า Parameter Value ใน Forms ค่ะ
« เมื่อ: 25 ก.พ. 63 , 12:56:54 »
ตัวรายงาน อยากให้มีการ กำหนด Parameter Value ข้อมูลที่ต้องการแสดงในรายงานใช่ไหมครับ

ตัวรายงาน ไม่ต้องใช้ คิวรี่ตัวเดียวกับ Form ก็ได้



วิธีทำ ที่ RecordSource ของรายงาน ลบคิวรี่ออกไปแล้ว กดตรง จุด 3 จุด สังเกตุว่ามันสามารถกำหนด ข้อมูลที่อยากนำมาแสดง เหมือนตอนเราสร้างคิวรี่
เรามากำหนด PARAMETER ตรงนี้ได้เลย (วิธีนี้เราจะไม่ได้สร้างคิวรี่ไว้แต่จะกำหนดในรายงานเลยครับ)

ส่วนตัวคิวรี่ของฟอร์มเอา parameter ออกไปได้เลยครับ
โพสต์นี้ได้รับคำขอบคุณจาก: oraaoi

426
เรากำลังทำโปรแกรม เข้า ออกงาน
เช่น วันที่ 1 เข้าก็ยิงบาร์โค้ดจากบัตร พนักงาน แต่มันยิงซ้ำได้ ทำให้การบันทึกข้อมูลซ้ำ อยากไห้ภายใน 1 วัน ยิงได้แค่ ครั้งเดียวครับ

เช็คจาก บาร์โค้ด และ วันที่ปัจจุบันบน textbox วันที่ Date()ก็ได้ครับ

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

โดย เช่น If not isnull(dlookup("Barcode","tblMain","Barcode = " & me.txtBarcode & " AND [Current_Date] = #" & Forms!frmMain!txtDate & "#")) then
Msgbox "มีการลงทะเบียนแล้ว"
Me.txtBarcode.setfocus
Me.txtbarcode = null
else
........... ทำเงื่อนไขเดิมที่มี
end if

โพสต์นี้ได้รับคำขอบคุณจาก: tanuki

427
ไม่รู้แบบนี้ไหมนะครับ ผมสมมุติ table1 คือชื่อตารางนะ

โค๊ด: [Select]
SELECT Table1.EBELN, Table1.EBELP, Table1.BELNR
FROM Table1
WHERE ((Not (Table1.VGABE)=2 And Not (Table1.VGABE)=[EBELP]) AND ((Table1.BWART)="101"));
โพสต์นี้ได้รับคำขอบคุณจาก: taeclubz

428
ได้แล้วครับอาจารย์ ปัญหาเกิดจาก ผมอ้างอิง Rs!Num  ใส่ชื่อฟิลล์จริงผิดไปครับลิมสังเกตุ

แก้เป็น
โค๊ด: [Select]
Public Sub ExplodeTable()
   Dim DB  As DAO.Database
    Dim RS  As DAO.Recordset
    Dim LastID  As Variant
    Dim I   As Integer
   
    Set DB = CurrentDb
    Set RS = DB.OpenRecordset("SELECT MakeQryOT.MemID, MakeQryOT.sOT  FROM MakeQryOT ORDER BY MakeQryOT.MemID;")
    Do Until RS.EOF
        If RS!MemID <> LastID Then
            I = 1
            DB.Execute "insert into tblOT_Report (MemID, OT1) values (" & CStr(RS!MemID) & ", " & CStr(RS!sOT) & ")", dbFailOnError
        Else
            I = I + 1
            DB.Execute "update tblOT_Report set OT" & CStr(I) & " = '" & CStr(RS!sOT) & "' where MemID = '" & CStr(RS!MemID) & "'", dbFailOnError
        End If
        LastID = RS!MemID
        RS.MoveNext
    Loop
    RS.Close: Set RS = Nothing
End Sub

ตอนนี้ได้แล้วครับ ขอบคุณครับ
โพสต์นี้ได้รับคำขอบคุณจาก: apirak

429
ใช้คำสั่ง SQL ล้วนๆคงไม่ได้ ต้องใช้ VBA ด้วย โค้ดนี้ต้องปรับแก้ให้ใส่ ' ' คร่อม ถ้า data type ของ ID, Num, Fieldxx เป็น Text ด้วยนะครับ

Public Sub xxx()
    Dim DB  As DAO.Database
    Dim RS  As DAO.Recordset
    Dim LastID  As Variant
    Dim I   As Integer
   
    Set DB = CurrentDb
    Set RS = DB.OpenRecordset("select * from Table1 (order by ..., ถ้ามี)")
    Do Until RS.EOF
        If RS!ID <> LastID Then
            I = 1
            DB.Execute "insert into Table2 (ID, Field1) values (" & CStr(RS!ID) & ", " & CStr(RS!Num) & ")", dbFailOnError
        Else
            I = I + 1
            DB.Execute "update Table2 set Field" & CStr(I) & " = " & CStr(RS!Num) & " where ID = " & CStr(RS!ID), dbFailOnError
        End If
        LastID = RS!ID
           
        RS.MoveNext
    Loop
    RS.Close: Set RS = Nothing
End Sub

ขึ้น พารามิเตอร์น้อยเกินไปต้องการ 1 ครับ แถวล่าง Else
DB.Execute "update tblOT_Report set OT" & CStr(I) & " = '" & CStr(RS!OT) & "' where ID = '" & CStr(RS!MemName) & "'", dbFailOnError

Field OT จะมี OT1 , OT2 OT3 ไปเรื่อยๆ
โพสต์นี้ได้รับคำขอบคุณจาก: apirak

430
 :question: มีข้อมูลจากตารางชื่อ Table1  จะ Add ข้อมูลไปยังตาราง Table 2 โดยให้แยกจากแถวไปใส่ในคอลัมน์แทนจะทำได้ไหม
แบบในรูปเลยครับ  :sweat: :sweat:

โพสต์นี้ได้รับคำขอบคุณจาก: apirak

431
เรามีชุดข้อมูล
100      100
90         90
80         80
70         70
60         60
50         50
40         40
30         30
20        20
10         10

นายA    นายฺB
จะมีขั้นให้เลื่อน ได้ 1 - 3 ขั้น
เริ่มจากรอบแรก ประเมินนาย A ถ้าได้ 1 ขั้น
นายA ก็จะได้ 1
รอบสอง นายA ได้ 3 ขั้น ก็จะ+เพิ่มของเก่าที่มีอยุ่ 10 ไปอีก 3ขั้น จะเป็น 40
ทำแบบนี้ไปเลื่อยๆครับ

การเลื่อนขั้นแต่ละครั้งทำบนฟอร์มไหม

ใช้ Dmax Field เลขของขั้น แยกตาม IDของคน พอได้มาแล้ว ก็นำมา + กับจำนวนขั้นที่ได้เพิ่ม ก็จะได้จำนวนขั้นปัจจุบันแล้วครับ
ผมไม่แน่ใจว่าเวลาคุณเก็บ จำนวนขั้น เก็บแบบ 1 2 3 4 5 6 7 8 9 10 หรือเปล่า จะเก็บแบบ 10 20 30 40 50 60 70 80 90
คุณได้มีการเก็บวันที่จะการได้รับการเลื่อนขั้นด้วยหรือไม่ ?

ถ้าแบบแรกสร้างฟอร์ม
มี textbox       txtNewStep ระบุขั้นปัจจุบัน
   combobox   CmbID        เก็บ ID และ ชื่อของผู้ได้รับการเลื่อนขั้น
   textbox       txttotalStep  เก็บจำนวนขั้นล่าสุด
ถ้าแบบง่ายๆ เลยสาย DIY

Dim A as integer
A = Dmax("Field ขั้น","จากตารางไหน","ID = " & Me.CmbID & "") *10  สมมุติว่ามีอยู่ 2 ขั้น A จะ เท่ากับ 20
ต่อไป A = A + (Me.txtNewStep * 10)   ถ้า txtNewStep ระบุว่า 3 ก็จะเป็น 20 + 30 = 50 เป็นต้น
txttotalStep = A

ผมยังไม่เข้าใจโจทย์เท่าไรครับ ว่าคุณต้องการทำแบบตัวอย่างข้างบนหรือแบบ  Running Total กันแน่

ถ้าแบบ Running Total ข้อมูลจะประมาณนี้
https://theaccessbuddy.wordpress.com/2014/05/04/using-ms-access-to-create-a-running-total-or-a-cumulative-sum-part-1-of-2/
โพสต์นี้ได้รับคำขอบคุณจาก: tanuki

432
อ้างถึง
ทำคิวรี่เพื่อเปรียบเทียบค่าถ้า 0 หรือ ค่าว่างให้แสดงเป็นค่า 1 ลองใช้ iif แล้วทำคิวรี่อีกอันมารวม

ใช้ Sum ในคิวรี่นี้เลยก็ได้นะครับ  เช่น โดยการ group by
กำหนดตรง groupby เป็น expression
Expr1:Sum(IIf(IsNull([score]),1,0)

ตัวอย่างในคิวรี่ในงานเก่าๆ ที่เคยทำไว้
โพสต์นี้ได้รับคำขอบคุณจาก: SakDa

หน้า: 1 ... 21 22 23 [24] 25 26 27 ... 32