Month: January 2016

  • ฟังก์ชัน WMSYS.WM_CONCAT และการเปลี่ยนแปลงเมื่ออัพเกรดไปใช้ Oracle 12c

    ฟังก์ชัน WMSYS.WM_CONCAT

    นักพัฒนาบางท่านที่พัฒนาระบบบนฐานข้อมูล Oracle 10g หรือ 11g อาจจะผ่านตาหรือเคยใช้งานฟังก์ชัน  WMSYS.WM_CONCAT โดยฟังก์ชันนี้เป็นฟังก์ชันที่ใช้ในการนำข้อมูลในฟีลด์เดียวกัน แต่อยู่ต่างเร็คคอร์ดมาเชื่อมต่อกันเป็นข้อมูลเร็คคอร์ดเดียว

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

    stationery

     

    ทดลองใช้คำสั่ง SELECT แบบปกติ ด้วยคำสั่ง

    SELECT WMSYS.WM_CONCAT(COLOR) COLOR_LIST

    FROM STATIONERY;

    ผลลัพธ์ที่ได้

    list04

     

    แต่ในเง่การใช้งานส่วนใหญ่ มักจะต้องการแสดงข้อมูลสรุปเป็นกลุ่ม เช่น จากตัวอย่างนี้ ถ้าต้องการแยกข้อมูลสรุปเป็นกลุ่มตามชนิดเครื่องเขียน ว่าเครื่องเขียนแต่ละชนิดมีสีอะไรบ้าง ซึ่งก็สามารถทำได้โดยการเพิ่มการ GROUP BY ตามฟีลด์ STATIONERY เข้าไป ดังนี้

    SELECT STATIONERY, WMSYS.WM_CONCAT(COLOR) COLOR_LIST

    FROM STATIONERY

    GROUP BY STATIONERY;

    ผลลัพธ์ที่ได้

    list05

     

    หรือหากต้องการข้อมูลสรุปตามสีของเครื่องเขียน ว่าแต่ละสีมีเครื่องเขียนชนิดใดอยู่บ้าง คำสั่งที่ใช้งานก็จะเป็น

    SELECT COLOR, WMSYS.WM_CONCAT(STATIONERY) STATIONERY_LIST

    FROM STATIONERY

    GROUP BY COLOR;

    ผลลัพธ์ที่ได้

    list06

     

    ที่กล่าวไปข้างต้นคือการใช้งานฟังก์ชัน WMSYS.WM_CONCAT บนฐานข้อมูล Oracle 10g หรือ 11g แต่ถ้านักพัฒนาท่านใดวางแผนที่จะอัพเกรตฐานข้อมูลไปเป็น Oracle 12c  ท่านก็จะเจอกับปัญหาเมื่อมีการเรียกใช้งานฟังก์ชันนี้ โดยจะมีข้อความ error แจ้งกลับมาว่า 

    ORA-00904: "WMSYS"."WM_CONCAT": invalid identifier 

    นั่นเป็นเพราะใน Oracle 12c จะไม่มีฟังก์ชันนี้ให้เรียกใช้งานอีกต่อไปแล้ว

    ดังนั้นในบทความนี้จะขอแนะนำฟังก์ชันอีกฟังก์ชันหนึ่งซึ่งทำงานคล้ายคลึงกัน และสามารถให้ผลลัพธ์แบบเดียวกันกับฟังก์ชัน WMSYS.WM_CONCAT ซึ่งฟังก์ชันที่ว่านี้คือ LISTAGG

     

    ฟังก์ชัน LISTAGG

    ฟังก์ชัน LISTAGG เป็นฟังก์ชันที่เริ่มมีให้ใช้งานใน Oracle 11g R2 ใช้งานในลักษณะเดียวกันกับ ฟังก์ชัน WMSYS.WM_CONCAT แต่ในส่วนของรายละเอียดนั้นจะมีบางจุดที่แตกต่างกันออกไป

    จากตัวอย่างข้อมูลที่นำเสนอไปข้างต้น จากที่ใช้งานกับฟังก์ชัน WMSYS.WM_CONCAT ลองเปลี่ยนมาใช้ฟังก์ชัน LISTAGG ได้ดังนี้

     

    ตัวอย่างแรกเป็นการทดลอง SELECT แบบปกติ

    SELECT LISTAGG(COLOR,’,’)

    WITHIN GROUP (ORDER BY COLOR) COLOR_LIST

    FROM STATIONERY;

    ผลลัพธ์ที่ได้

    list07

     

    อธิบายการใช้งานคำสั่ง

    • LISTAGG(COLOR,’,’)  ภายในวงเล็บเป็นฟีลด์ข้อมูลจากต่างเร็คคอร์ดกันแต่ต้องการให้แสดงเรียงต่อกัน ซึ่งในตัวอย่างนี้ก็คือฟีลด์ COLOR ส่วน ‘,’ ก็คือการระบุตัวคั่นระหว่างข้อมูล ซึ่งในทีนี้ใช้เป็นจุลภาคนั่นเอง
    • WITHIN GROUP (ORDER BY COLOR) เป็นการระบุรูปแบบการเรียงข้อมูล ซึ่งในที่นี้จะเรียงตามฟีลด์ COLOR

    จากตัวอย่างจะเห็นว่าสิ่งที่ฟังก์ชัน LISTAGG ทำได้แตกต่างจาก WMSYS.WM_CONCAT คือ การระบุตัวคั่นระหว่างข้อมูล และการระบุการเรียงลำดับของข้อมูลที่มาต่อกันนั่นเอง

     

    ตัวอย่างต่อมา จะให้แสดงผลลัพธ์แยกสรุปเป็นกลุ่มตามชนิดเครื่องเขียน ว่าเครื่องเขียนแต่ละชนิดมีสีอะไรบ้าง คำสั่งที่ใช้ก็จะใช้การ GROUP BY ด้วยฟีลด์ STATIONERY เช่นเดิม คือ

    SELECT STATIONERY, LISTAGG(COLOR,’,’)

    WITHIN GROUP (ORDER BY STATIONERY) COLOR_LIST

    FROM STATIONERY GROUP BY STATIONERY;

    ผลลัพธ์ที่ได้

    list01

     

    ส่วนตัวอย่างสุดท้าย ก็จะเป็นการให้แสดงผลลัพธ์สรุปตามสีของเครื่องเขียน ว่าแต่ละสีมีเครื่องเขียนชนิดใดอยู่บ้าง คำสั่งที่ใช้งานก็จะเป็น

    SELECT COLOR, LISTAGG(STATIONERY,’,’)

    WITHIN GROUP (ORDER BY STATIONERY) LIST_STATIONERY

    FROM STATIONERY GROUP BY COLOR;

    ผลลัพธ์ที่ได้

    list02

     

    ข้อมูลอ้างอิง :

    https://oracle-base.com/articles/misc/string-aggregation-techniques

    http://docs.oracle.com/cd/E11882_01/server.112/e41084/functions089.htm#SQLRF30030

  • สร้างเอกสาร PDF ด้วย iTextSharp

    ที่มา

    บ่อยครั้งที่ในชีวิตของโปรแกรมเมอร์จะต้องพบกับความต้องการของลูกค้าที่อยากได้รายงานหรือเอกสารที่สามารถสร้างได้จากระบบ แน่นอนว่าประเภทเอกสารที่ต้องการย่อมมี PDF บรรจุไว้แน่นอนเพราะเป็นเอกสารที่นิยมใช้กันอย่างแพร่หลาย ทั้งนี้ เครื่องมือสำหรับการสร้างเอกสารประเภทดังกล่าวมีอยู่มากมาย แต่จุดสำคัญนั้นอยู่ที่การเลือกใช้งานซึ่งย่อมแตกต่างกันไปตามปัจจัยต่าง ๆ เช่นในบทความนี้ ลูกค้าต้องการเอกสารเพื่อพิมพ์เป็น hard copy ไว้ที่หน่วยงาน ยังไม่ถึงขั้นรายงานนะครับ แค่เอกสารบันทึกข้อความ ดังนั้นผู้เขียนจึงไม่เลือกใช้เครื่องมือที่เก่งกาจเช่น Crystal report หรือ Reporting service และทำการค้นหาเครื่องมือที่มีน้ำหนักเบา (เวลาใช้ไม่กินทรัพยากรเยอะ) แต่ตอบสนองความต้องการได้ในขณะนั้น รวมไปถึงการมี documentation ที่ดี เข้าใจง่าย ปฏิบัติตามได้ไม่ยาก ซึ่งสุดท้ายก็มาเจอกับเครื่องมือที่ชื่อ iTextSharp

    คุณสมบัติของเครื่องมือ

    iTextSharp เป็นผลงานของ iText ซึ่งทำมาเพื่อการสร้างเอกสาร PDF บน C# platform โดยเฉพาะ ในขณะเดียวกันก็มีเครื่องมือแบบเดียวกันสำหรับ platform อื่น ๆ ด้วย เราจึงจะได้เห็นตัวอย่างใน documentation ของเขาเป็นภาษา Java

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

    การใช้งานเครื่องมือนี้จะต้องใช้การเขียนโปรแกรม 100% ครับหรือเรียกเป็นภาษาอังกฤษว่า Programmatically ซึ่งยุ่งยากพอควรทีเดียวโดยเฉพาะการจัดวางตำแหน่งของแต่ละส่วน พูดได้ว่าจะต้องจินตนาการหรือร่างแบบลงบนกระดาษเลยทีเดียว ทั้งนี้ทั้งนั้นนี่ก็เป็นข้อดีข้อหนึ่งสำหรับคนชอบเขียนโปรแกรมเพราะเห็นกระบวนการชัดเจน (ไม่ค่อยสะดวกแต่สนุกดี)

    ทดลองใช้

    ติดตั้งโปรแกรม

    ก่อนอื่นเลยก็ต้อง download library มาก่อนครับ (Link)การติดตั้งนั้นไม่ยาก แค่ reference ไปหา dll ที่เค้าให้มาก็พอ ไฟล์ที่ได้มามีทั้งหมด 3 ชุดครับคือ

    1. itextsharp-dll-core
    2. itextsharp-dll-pdfa
    3. itextsharp-dll-xtra

    ทั้งหมดนี้ทำหน้าที่แตกต่างกันครับ สำหรับการสร้าง PDF เราใช้แค่ตัว itextsharp-dll-core ก็พอ

    องค์ประกอบพื้นฐาน

    องค์ประกอบพื้นฐานของ itextsharp นั้นมีหลายตัว แต่ตัวที่เพียงพอสำหรับความต้องการของผู้เขียนในตอนนี้มีดังนี้ครับ

    1. Chunk เป็นองค์ประกอบสำหรับ “คำ”
    2. Phrase คือ “ประโยค” ซึ่งประกอบด้วยหลาย Chunk
    3. Paragraph คือ “ย่อหน้า” ซึ่งประกอบด้วยหลาย phrase และ chunk
    4. PdfPCell คือ cell ในตาราง
    5. PdfPTable คือ ตารางประกอบด้วยหลาย PdfPCell
    6. iTextSharp.text.Image คือรูปภาพ

    การกำหนด font และการเพิ่ม font

    iTextSharp นั้นสามารถเพิ่ม font ได้ครับ โดยการทำตามขั้นตอนดังนี้:

    BaseFont bf_bold = BaseFont.CreateFont(HttpContext.Current.Server.MapPath(“~/Regist/Theme/fonts/THSarabunNewBold.ttf”), BaseFont.IDENTITY_H, BaseFont.EMBEDDED);

    เท่านี้เราก็จะมี font แบบที่เราอยากได้ไว้ใช้งานในเอกสารครับ หลังจากนั้น เราก็สร้างตัวอักษรเพื่อใช้งาน ตามตัวอย่างนี้ครับ

    // Bold
    BaseFont bf_bold = BaseFont.CreateFont(HttpContext.Current.Server.MapPath(“~/Regist/Theme/fonts/THSarabunNewBold.ttf”), BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
    h1 = new Font(bf_bold, 18);
    bold = new Font(bf_bold, 16);
    smallBold = new Font(bf_bold, 14);

    // Normal
    BaseFont bf_normal = BaseFont.CreateFont(HttpContext.Current.Server.MapPath(“~/Regist/Theme/fonts/THSarabunNew.ttf”), BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
    normal = new Font(bf_normal, 16);
    smallNormal = new Font(bf_normal, 14);

    เริ่มต้นสร้างเอกสาร PDF

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

     

    Document pdfDoc = new Document(PageSize.A4, 30, 30, 20, 20);
    PdfWriter pdfWriter = PdfWriter.GetInstance(pdfDoc, Response.OutputStream);
    pdfDoc.Open();

     

    โดยตัวเลข 4 ตัวหลัง layout ของกระดาษคือระยะขอบครับ

    เริ่มใส่องค์ประกอบลงในเอกสาร

    ตัวเอกสารของผมจะเริ่มจากส่วนที่เป็นหัวของเอกสารก่อน ซึ่งจะประกอบด้วยรูปภาพ 1 รูปและข้อความ 1 ข้อความ แต่ปัญหาอยู่การจัดการ layout เพราะเมื่อเราใส่องค์ประกอบเข้าไปมันจะชิดทางด้านซ้ายให้โดยอัตโนมัติ ในกรณีนี้จึงต้องใช้ PdfPTable มาช่วยจัดส่วนหัวเอกสารก่อนครับ ทำการกำหนดขนาดของตารางแล้วจัดองค์ประกอบเข้าไปอยู่ในแต่ละ cell

    private PdfPTable GetHeader() {

    PdfPTable headerTable = new PdfPTable(2);
    headerTable.TotalWidth = 530f;
    headerTable.HorizontalAlignment = 0;
    headerTable.SpacingAfter = 20;
    //headerTable.DefaultCell.Border = Rectangle.NO_BORDER;

    float[] headerTableColWidth = new float[2];
    headerTableColWidth[0] = 220f;
    headerTableColWidth[1] = 310f;

    headerTable.SetWidths(headerTableColWidth);
    headerTable.LockedWidth = true;

    iTextSharp.text.Image png = iTextSharp.text.Image.GetInstance(imagePath + “/thai_gov.png”);
    png.ScaleAbsolute(40, 40);

    PdfPCell headerTableCell_0 = new PdfPCell(png);
    headerTableCell_0.HorizontalAlignment = Element.ALIGN_LEFT;
    headerTableCell_0.Border = Rectangle.NO_BORDER;
    headerTable.AddCell(headerTableCell_0);

    PdfPCell headerTableCell_1 = new PdfPCell(new Phrase(“บันทึกข้อความ”, h1));
    headerTableCell_1.HorizontalAlignment = Element.ALIGN_LEFT;
    headerTableCell_1.VerticalAlignment = Element.ALIGN_BOTTOM;
    headerTableCell_1.Border = Rectangle.NO_BORDER;
    headerTable.AddCell(headerTableCell_1);

    return headerTable;
    }

    เมื่อทำการเพิ่มเข้าไปเอกสารแล้วจะได้ลักษณะตามภาพ

    pdf_header

    หลังจากนั้นผมก็เริ่มใส่องค์ประกอบที่เป็นรายละเอียดของส่วนหัวของเอกสารดังนี้

    private PdfPTable GetHeaderDetail() {
    PdfPTable table = new PdfPTable(2);
    table.TotalWidth = 530f;
    table.HorizontalAlignment = 0;
    table.SpacingAfter = 10;

    float[] tableWidths = new float[2];
    tableWidths[0] = 400f;
    tableWidths[1] = 130f;

    table.SetWidths(tableWidths);
    table.LockedWidth = true;

    Chunk blank = new Chunk(” “, normal);

    Phrase p = new Phrase();

    p.Add(new Chunk(“ส่วนราชการ”, bold));
    p.Add(new Chunk(blank));
    p.Add(new Chunk(“วิเทศสัมพันธ์”, normal));

    PdfPCell cell0 = new PdfPCell(p);
    cell0.Border = Rectangle.NO_BORDER;

    table.AddCell(cell0);

    p = new Phrase();
    p.Add(new Chunk(“โทร”, bold));
    p.Add(new Chunk(blank));
    p.Add(new Chunk(“2012”, normal));

    PdfPCell cell1 = new PdfPCell(p);
    cell1.Border = Rectangle.NO_BORDER;

    table.AddCell(cell1);

    p = new Phrase();
    p.Add(new Chunk(“ที่ มอ”, bold));
    p.Add(new Chunk(blank));
    p.Add(new Chunk(master.ApplicationNo, normal));

    cell0 = new PdfPCell(p);
    cell0.Border = Rectangle.NO_BORDER;

    table.AddCell(cell0);

    p = new Phrase();
    p.Add(new Chunk(“วันที่”, bold));
    p.Add(new Chunk(blank));
    p.Add(new Chunk(master.CreatedDate, normal));

    cell1 = new PdfPCell(p);
    cell1.Border = Rectangle.NO_BORDER;

    table.AddCell(cell1);

    p = new Phrase();
    p.Add(new Chunk(“เรื่อง”, bold));
    p.Add(new Chunk(blank));
    p.Add(new Chunk(“ขออนุมัติเดินทางไปต่างประเทศ”, normal));

    cell0 = new PdfPCell(p);
    cell0.Border = Rectangle.NO_BORDER;
    cell0.Colspan = 2;

    table.AddCell(cell0);

    return table;
    }

    เช่นเคยครับ เราจะต้องใช้ Table มากำหนด layout ของเอกสาร เมื่อทำการเพิ่มแล้วก็จะได้เอกสารหน้าตาดังนี้

    pdf_header_detail

    หลังจากนั้นก็ใส่องค์ประกอบต่าง ๆ เพิ่มเข้าไปทีละชิ้น ๆ ตาม source code ด้านล่าง

     

    private Paragraph GetBodyHeader()
    {
    Phrase p = new Phrase();

    p.Add(new Chunk(“เรียน “, normal));
    p.Add(new Chunk(“รองอธิการบดีฝ่ายองค์กรสัมพันธ์และสารสนเทศ”, normal));

    Paragraph para = new Paragraph(p);
    para.SpacingBefore = 20;
    para.SpacingAfter = 20;

    return para;
    }

    private Paragraph GetBody() {
    Paragraph para = new Paragraph();

    para.FirstLineIndent = 38.1f;

    para.Add(new Phrase(“ด้วย”, normal));
    para.Add(new Phrase(“งานวิเทศสัมพันธ์”, normal));
    para.Add(new Phrase(“ขออนุมัติให้นักศึกษาจำนวน ” + master.StudentCount + ” คน เดินทางไปราชการต่างประเทศระหว่างวันที่ ” + master.StartDate + ” ถึงวันที่ ” + master.EndDate + ” รวม ” + master.PeriodDay + ” วัน เพื่อดำเนินกิจกรรมดังต่อไปนี้”, normal));

    return para;
    }

    private PdfPTable GetActivities()
    {
    PdfPTable table = new PdfPTable(3);

    table.TotalWidth = 530f;
    table.HorizontalAlignment = 0;
    table.SpacingBefore = 20;
    table.SpacingAfter = 20;

    // ชื่อกิจกรรม
    // สถาบัน
    // ประเทศ

    float[] columnWidths = new float[3];
    columnWidths[0] = 200f;
    columnWidths[1] = 200f;
    columnWidths[2] = 130f;

    table.SetWidths(columnWidths);
    table.LockedWidth = true;

    PdfPCell cell0 = new PdfPCell(new Phrase(“กิจกรรม”, bold));
    cell0.HorizontalAlignment = Element.ALIGN_LEFT;
    cell0.Border = Rectangle.NO_BORDER;
    table.AddCell(cell0);

    PdfPCell cell1 = new PdfPCell(new Phrase(“สภานที่”, bold));
    cell1.HorizontalAlignment = Element.ALIGN_LEFT;
    cell1.Border = Rectangle.NO_BORDER;
    table.AddCell(cell1);

    PdfPCell cell2 = new PdfPCell(new Phrase(“ประเทศ”, bold));
    cell2.HorizontalAlignment = Element.ALIGN_LEFT;
    cell2.Border = Rectangle.NO_BORDER;
    table.AddCell(cell2);

    List<MasterActivity> activity = master.GetActivities();

    foreach (MasterActivity a in activity)
    {
    cell0 = new PdfPCell(new Phrase(a.ActivityNameThai, normal));
    cell0.HorizontalAlignment = Element.ALIGN_LEFT;
    cell0.Border = Rectangle.NO_BORDER;
    table.AddCell(cell0);

    cell1 = new PdfPCell(new Phrase(a.HostName, normal));
    cell1.HorizontalAlignment = Element.ALIGN_LEFT;
    cell1.Border = Rectangle.NO_BORDER;
    table.AddCell(cell1);

    Institution host = Institution.GetById(a.HostId);

    cell2 = new PdfPCell(new Phrase(host.CountryName, normal));
    cell2.HorizontalAlignment = Element.ALIGN_LEFT;
    cell2.Border = Rectangle.NO_BORDER;
    table.AddCell(cell2);
    }

    return table;
    }

    private Paragraph GetBodyFooter()
    {
    Paragraph para = new Paragraph(new Phrase(“จึงเรียนมาเพื่อโปรดพิจารณาอนุมัติด้วย จักเป็นพระคุณยิ่ง”, normal));
    para.FirstLineIndent = 38.1f;
    para.SpacingAfter = 25;
    return para;
    }

    private void GetSignature(Document pdfDoc) {

    Paragraph para;
    Phrase p;
    Chunk dotLine = new Chunk(“……………………………………………”, normal);

    //if (master.LevelId.Equals(“D”))
    //{
    // p = new Phrase(dotLine);
    // p.Add(new Chunk(“หัวหน้าภาควิชา”, normal));
    // para = new Paragraph(p);
    // pdfDoc.Add(para);
    //}

    p = new Phrase(dotLine);
    p.Add(new Chunk(“หัวหน้าภาควิชา”, normal));
    para = new Paragraph(p);
    para.SpacingAfter = 15;
    pdfDoc.Add(para);

    p = new Phrase(dotLine);
    p.Add(new Chunk(“คณบดี”, normal));
    para = new Paragraph(p);
    para.SpacingAfter = 15;
    pdfDoc.Add(para);
    }

     

    private PdfPTable GetStudentList() {

    Phrase p;

    PdfPTable table = new PdfPTable(8);
    table.TotalWidth = 530f;
    table.HorizontalAlignment = 0;
    //table.SpacingAfter = 20;
    //headerTable.DefaultCell.Border = Rectangle.NO_BORDER;

    float[] colWidths = new float[8];
    colWidths[0] = 30f;
    colWidths[1] = 70f;
    colWidths[2] = 70f;
    colWidths[3] = 70f;
    colWidths[4] = 70f;
    colWidths[5] = 70f;
    colWidths[6] = 70f;
    colWidths[7] = 70f;

    table.SetWidths(colWidths);
    table.LockedWidth = true;

    PdfPCell cell;

    p = new Phrase(“รายชื่อผู้เดินทางจาก ” + master.StartDate + ” ถึง ” + master.EndDate, normal);

    cell = new PdfPCell(p);
    cell.Colspan = 8;
    cell.HorizontalAlignment = Element.ALIGN_CENTER;
    cell.Border = Rectangle.NO_BORDER;
    cell.PaddingBottom = 10;
    table.AddCell(cell);

    #region Header

    cell = new PdfPCell(new Phrase(“ที่”, smallBold));
    cell.HorizontalAlignment = Element.ALIGN_CENTER;
    table.AddCell(cell);

    cell = new PdfPCell(new Phrase(“รหัสนักศึกษา”, smallBold));
    cell.HorizontalAlignment = Element.ALIGN_CENTER;
    table.AddCell(cell);

    cell = new PdfPCell(new Phrase(“คำนำหน้า”, smallBold));
    cell.HorizontalAlignment = Element.ALIGN_CENTER;
    table.AddCell(cell);

    cell = new PdfPCell(new Phrase(“ชื่อ”, smallBold));
    cell.HorizontalAlignment = Element.ALIGN_CENTER;
    table.AddCell(cell);

    cell = new PdfPCell(new Phrase(“สกุล”, smallBold));
    cell.HorizontalAlignment = Element.ALIGN_CENTER;
    table.AddCell(cell);

    cell = new PdfPCell(new Phrase(“คณะ”, smallBold));
    cell.HorizontalAlignment = Element.ALIGN_CENTER;
    table.AddCell(cell);

    cell = new PdfPCell(new Phrase(“วันที่เริ่ม”, smallBold));
    cell.HorizontalAlignment = Element.ALIGN_CENTER;
    table.AddCell(cell);

    cell = new PdfPCell(new Phrase(“วันที่สิ้นสุด”, smallBold));
    cell.HorizontalAlignment = Element.ALIGN_CENTER;
    table.AddCell(cell);

    #endregion

    #region Data

    List<OutboundApplication> apps = master.GetApplications();

    int i = 0;

    foreach (OutboundApplication app in apps)
    {
    cell = new PdfPCell(new Phrase((i + 1).ToString(), smallNormal));
    cell.HorizontalAlignment = Element.ALIGN_RIGHT;
    table.AddCell(cell);

    cell = new PdfPCell(new Phrase(app.StudentId, smallNormal));
    cell.HorizontalAlignment = Element.ALIGN_CENTER;
    table.AddCell(cell);

    cell = new PdfPCell(new Phrase(app.TitleName, smallNormal));
    cell.HorizontalAlignment = Element.ALIGN_LEFT;
    table.AddCell(cell);

    cell = new PdfPCell(new Phrase(app.Firstname, smallNormal));
    cell.HorizontalAlignment = Element.ALIGN_LEFT;
    table.AddCell(cell);

    cell = new PdfPCell(new Phrase(app.Lastname, smallNormal));
    cell.HorizontalAlignment = Element.ALIGN_LEFT;
    table.AddCell(cell);

    cell = new PdfPCell(new Phrase(app.FacultyNameThai, smallNormal));
    cell.HorizontalAlignment = Element.ALIGN_LEFT;
    table.AddCell(cell);

    cell = new PdfPCell(new Phrase(app.StartDate, smallNormal));
    cell.HorizontalAlignment = Element.ALIGN_CENTER;
    table.AddCell(cell);

    cell = new PdfPCell(new Phrase(app.EndDate, smallNormal));
    cell.HorizontalAlignment = Element.ALIGN_CENTER;
    table.AddCell(cell);

    i += 1;
    }

    #endregion

    return table;
    }

    และส่วนของ Main program ก็จะเป็นแบบนี้ครับ

    protected void Page_Load(object sender, EventArgs e)
    {
    InitElements();

    try
    {
    // Create PDF document
    Document pdfDoc = new Document(PageSize.A4, 30, 30, 20, 20);
    PdfWriter pdfWriter = PdfWriter.GetInstance(pdfDoc, Response.OutputStream);
    pdfDoc.Open();

    pdfDoc.Add(GetHeader());
    pdfDoc.Add(GetHeaderDetail());

    LineSeparator line = new LineSeparator();

    pdfDoc.Add(line);

    pdfDoc.Add(GetBodyHeader());
    pdfDoc.Add(GetBody());
    pdfDoc.Add(GetActivities());
    pdfDoc.Add(GetBodyFooter());
    GetSignature(pdfDoc);
    pdfDoc.NewPage();
    pdfDoc.Add(GetStudentList());

    pdfWriter.CloseStream = false;
    pdfDoc.Close();
    Response.Buffer = true;
    Response.ContentType = “application/pdf”;
    Response.AddHeader(“content-disposition”, “attachment;filename=Example.pdf”);
    Response.Cache.SetCacheability(HttpCacheability.NoCache);
    Response.Write(pdfDoc);
    Response.End();
    }
    catch (Exception ex)
    {
    Response.Write(ex.Message);
    }
    }

    สุดท้ายแล้วเราก็จะได้เอกสาร PDF หน้าตาแบบนี้ครับ Example

    บทสรุป

    จะเห็นได้ว่าการสร้างเอกสาร PDF ด้วยเครื่องมือนี้จะกินแรงพอควรนะครับ แต่ข้อดีของ iTextSharp คือน้ำหนักเบาและด้วยการเขียนโค้ดของเรา เราสามารถจะนำอะไรก็ได้ (ตามที่มันมีให้) ไปใส่ในเอกสารได้อย่างอิสระ สำหรับตอนนี้ iTextSharp ยังคงตอบสนองได้ตามความต้องการ แต่เมื่อความต้องการของลูกค้าเปลี่ยนแปลง เราจะได้เห็นกันว่าเครื่องมือตัวนี้จะทรงพลังพอหรือไม่

  • การอัปโหลดไฟล์หลาย ๆ ไฟล์พร้อมกันด้วย Dojo

    ที่มา

    การ upload แฟ้มข้อมูล (File) เป็นกิจกรรมที่อยู่คู่กับ Web application มาเนิ่นนาน ยิ่งเมื่อการพัฒนา Web application เข้าสู่ยุคสมัยของ Web 2.0 และเป็น Web 3.0 ในยุคปัจจุบันก็ยิ่งเกิดเครื่องมือต่าง ๆ เพื่ออำนวยความสะดวกในการ upload ไฟล์มากขึ้นรวมไปถึงความสามารถในการอัปโหลดหลาย ๆ ไฟล์พร้อมกัน (Multi-File Uploading)

    ณ เวลานี้มีเครื่องมือมากมายสำหรับนักพัฒนา Web application  ผู้อ่านอาจมีโอกาสได้ใช้งานเครื่องมือจากบางค่ายบ้างแล้ว แต่ในบทความนี้ผู้เขียนจะแนะนำวิธีการใช้งานเครื่องมือในการทำให้ Web application ของเราสามารถรองรับความต้องการในการอัปโหลดไฟล์หลาย ๆ ไฟล์ได้พร้อมกันตัวหนึ่งชื่อ HTML5 Multi-File Uploader ของ Dojo Toolkit ซึ่งทำงานได้ตามความต้องการของผู้เขียนดังนี้

    ความต้องการ

    1. สามารถ upload file ได้หลาย ๆ ไฟล์พร้อมกัน
    2. upload file ด้วย AJAX
    3. ต้องไม่มีการ Post Back หรือทำให้เกิดการ Load หน้า Web ใหม่
    4. สามารถปรับแต่งได้ตามความต้องการ

    ที่จริงแล้วเครื่องมือของ Dojo Toolkit เป็นเครื่องมือที่ใช้ยากพอสมควรสำหรับคนที่ไม่ค่อยถนัดในการใช้งาน JavaScript แต่เนื่องจาก API ที่เปิดโอกาสให้ผู้ใช้ได้เข้าถึงทุกส่วนของ Widget ทำให้เราสามารถปรับแต่งการใช้งานเพื่อให้เหมาะกับความต้องการได้โดยอิสระ

    ศึกษาคุณสมบัติของเครื่องมือ

    HTML5 Multi-File Uploader เป็นส่วนหนึ่งใน package ชื่อ dojox (dojox/form/Uploader) ซึ่งในความเป็นจริงแล้ว เครื่องมือตัวนี้ไม่ได้เจาะจงเฉพาะการใช้งาน HTML5 เท่านั้นแต่ยังคงใช้งานกับ Flash หรือ iframe ได้ด้วยตามแต่ผู้ใช้จะปรับแต่งเนื่องจาก Web browser แต่ละยี่ห้อก็มีข้อจำกัดแตกต่างกันไปตาม Browser engine และ Rendering engine

    ตามชื่อของเครื่องมือ Uploader ใช้ input ของ HTML5 เป็นองค์ประกอบหลัก จึงต้องกำหนดใน Form ให้รองรับการอัปโหลดไฟล์ด้วยการกำหนด attribute ชื่อ method และ enctype ดังนี้

     

    <form method=”post” enctype=”multipart/form-data”> 

     

    การสร้าง Widget ของ Uploader ทำได้ 2 รูปแบบดังนี้

    Programmatically

    require([

    “dojox/form/Uploader”

    ], function (Uploader) {

    myUploader = new dojox.form.Uploader();

    }

     

    Markup

    <input name=”uploadedFile” multiple=”true” type=”file” data-dojo-type=”dojox/form/Uploader” data-dojo-props=”label: ‘Select Some Files’,……..” />

    <div id=”files” data-dojo-type=”dojox/form/uploader/FileList” data-dojo-props=”uploaderId: ‘uploader’”></div>

    ลองใช้งาน

    เนื่องจากผู้เขียนพัฒนา Web application ด้วย Visual C# จึงจะยกตัวอย่างการประยุกต์การใช้งาน Uploader ร่วมกับ ASP.NET ซึ่งมีขั้นตอนดังนี้

    1. นำ Widget มาวางในส่วนที่เราต้องการ

    <input name=”uploadedfile” type=”file” id=”uploader” data-dojo-id=”fileUploader” data-dojo-type=”dojox/form/Uploader” data-dojo-props=”label: ‘Select files’, url: ‘../Test/TestUpload.ashx’, multiple: true” />
    <input type=”hidden” name=”hdnMasterId” id=”hdnMasterId” value=”test” />
    <input type=”hidden” name=”hdnFileDesc” id=”hdnFileDesc” value=”test” />
    <div id=”files” data-dojo-type=”dojox/form/uploader/FileList” data-dojo-props=”uploaderId: ‘uploader’”></div>

     

    เมื่อสังเกตุแล้วจะเห็นได้ว่าจะไม่มีปุ่ม Submit เนื่องจากผู้เขียนต้องการ upload file แบบ AJAX จึงไม่ต้องการให้ผู้ใช้กด ENTER แล้วส่งฟอร์มทั้งหมดไปทันที อีกจุดหนึ่งคือผู้เขียนต้องการส่งข้อมูลบางอย่างไปในคราวเดียวกันด้วยคือ  hdnMasterId และ hdnFileDesc

     

    ลอง Run ดูน่าจะได้น่าตาประมาณนี้

    ex1

    เมื่อเราลองเลือก file แล้ว ชื่อ file ที่เราเลือกจะแสดงอยู่ตำแหน่งด้านล่าง

    ex2

    2. กำหนดการทำงานในฝั่ง client

    ในขั้นตอนนี้เราจะต้องเขียน JavaScript เพื่อควบคุมการทำงานของ widget โดยผู้เขียนจะกำหนดให้ทำการ upload file แบบ AJAX เมื่อกดปุ่มใดปุ่มหนึ่งดังนี้

    fileUploader..set(“onComplete”, function(){

    //อะไรก็ตามที่ต้องให้ทำเมื่อทำการ upload เสร็จแล้ว

    });

    fileUploader.submit();

     

    3. กำหนดการทำงานในฝั่ง server

    ในส่วนของฝั่ง server เราก็ต้องมีการกำหนดการทำงานเช่นกัน เช่น การจัดเก็บไฟล์ การดึงข้อมูลจากการส่งข้อมูลแบบ AJAX เนื่องจากเรามีการส่งข้อมูลอื่น ๆ ไปด้วย ทั้งนี้เราไม่จะเป็นจะต้องใช้ web form มารองรับในการ upload เราสามารถใช้ ashx มาทำงานแทนที่ได้เลย การเขียนโปรแกรมฝั่ง server เป็นดังตัวอย่างด้านล่าง

    public void ProcessRequest(HttpContext context)
    {
    context.Response.ContentType = “text/plain”;
    JavaScriptSerializer js = new JavaScriptSerializer();
    string result = String.Empty;

    IDictionary<string, string> postData = new Dictionary<string, string>();

    foreach (string name in context.Request.Form.Keys)
    {
    //context.Response.Write(“<br>KEYS: ” + name);
    postData[name] = context.Request.Form[name];
    }

    //result = js.Serialize(new Response(false, postData[“hdnTest”], String.Empty));
    //context.Response.Write(result);

    string masterId = postData[“hdnMasterId”];
    string description = postData[“hdnFileDesc”];

    try
    {
    int len = context.Request.Files.Count;

    for (int i = 0; i < len; i++)
    {
    HttpPostedFile postedFile = context.Request.Files.Get(i) as HttpPostedFile;

    string[] fileNames = postedFile.FileName.Split(‘\\’);
    string fileName = String.Empty;

    if (fileNames.Length > 1)
    {
    fileName = fileNames[fileNames.Length – 1];
    }
    else
    {
    fileName = fileNames[0];
    }

    string[] fileType = postedFile.FileName.Split(‘.’);

    string uploadPath = context.Server.MapPath(Settings.Default.UploadFilePath);
    string newFileName = “[” + masterId + “]_” + fileName;
    string saveFileName = uploadPath + @”\” + newFileName;
    postedFile.SaveAs(saveFileName);

    OutboundMaster app = OutboundMaster.GetById(masterId);
    app.AddAttachedFile(newFileName, description);

    result = js.Serialize(new BizResponse(true));
    }

    context.Response.Write(result);
    }
    catch (Exception ex)
    {
    result = js.Serialize(new BizResponse(false, ex.Message, ex.StackTrace));
    context.Response.Write(result);
    }
    }

    จะเห็นได้ว่าตัวแปรที่มาจากการ post จะอยู่ในรูปแบบของ array ดังนั้นเราจะต้องทำการหาตัวแปรด้วย key ก่อน จากนั้นก็จะเข้าสู่ขั้นตอนการบันทึกไฟล์ตามที่เราออกแบบ

    ทั้งนี้ตัว Uploader มีข้อกำหนดข้อหนึ่งคือจะต้องมีการตอบสนองจากฝั่ง server หากไม่มีจะทำให้ Uploader แสดง error ถึงแม้ว่าการ upload file นั้นเสร็จสิ้นไปแล้วก็ตาม ดังนั้นที่ไฟล์ ashx จึงต้องมีการส่งข้อมูลกลับมาดังตัวอย่างด้านล่างนี้

    context.Response.Write(……);

     

    บทสรุป

    การใช้งาน Uploader อาจจะซับซ้อนมากน้อยขึ้นอยู่กับคนที่นำไปใช้ และถึงแม้ว่าเครื่องมือตัวนี้จะไม่ค่อย compatibility กับ Visual Studio เนื่องจากแนวคิดในการทำงานนั้นแตกต่างกัน แต่ยังคงสามารถนำมาประยุกต์ใช้งานเพื่อให้ได้ผลลัพธ์ตามที่ต้องการได้ และเนื่องจาก Dojo Toolkit นั้นเปิดเผย source code ทำให้เราสามารถทำความเข้าใจและจุดประกายแนวคิดใหม่ ๆ ได้อีกด้วย

  • ข้อแตกต่างระหว่างการลงนามเอกสารด้วย Electronic Signature กับ Digital Signature

    ที่ผ่านมาได้มีโอกาสเดินทางไปดูงานระบบเอกสารอิเล็กทรอนิกส์ของหน่วยงานภาครัฐในภาคตะวันออกเฉียงเหนือ และภาคกลางด้วยความอยากรู้ว่าหน่วยงานอื่นๆ เขาพัฒนาระบบนี้กันไปถึงไหนแล้ว เพื่อจะได้นำมาเป็นแนวทางในการพัฒนาปรับปรุงระบบของม.อ.เราต่อไป จากการดูงานที่ผ่านมาพบว่า ระบบที่มีการใช้งานกันส่วนใหญ่ยังไม่ได้รับการพัฒนาให้รองรับงานด้านสารบรรณอย่างเต็มรูปแบบสักเท่าไหร่ บางที่ยังเป็นแค่ระบบที่ใช้ในการเก็บเอกสารแต่ยังไม่สามารถติดตามการดำเนินการต่างๆ ที่เกิดขึ้นกับเอกสารเรื่องนั้นๆ ได้ ซึ่งต่างจากของม.อ. เราที่สามารถติดตามเส้นทางการดำเนินการต่างๆ ที่เกิดขึ้นได้ทั้งหมด

    และเป็นที่ทราบกันดีว่าวัตถุประสงค์หลักของระบบเอกสารอิเล็กทรอนิกส์คือ การดำเนินการต่างๆ ด้านงานสารบรรณทั้งหมดในรูปแบบไฟล์อิเล็กทรอนิกส์แทนการใช้งานกระดาษ นั่นหมายความว่าเป้าหมายที่สำคัญของระบบนี้คือการดำเนินการทั้งหมดจะต้องดำเนินการผ่านระบบคอมพิวเตอร์ได้ 100% โดยไม่มีการใช้กระดาษเลย แต่ในปัจจุบันระบบยังไม่สามารถทำแบบนั้นได้ โดยเฉพาะในเรื่องของการลงนามเอกสาร เนื่องจากผู้ใช้ยังไม่มั่นใจรูปแบบการลงนามเอกสารในรูปแบบอิเล็กทรอนิกส์เท่าที่ควร จึงเลือกที่จะพิมพ์เอกสารเป็นกระดาษแล้วลงนามกันด้วยปากกาเช่นเดิม อย่างไรก็ตามเทคโนโลยีนี้กำลังจะได้รับการยอมรับมากยิ่งขึ้น เนื่องจากมีกฎหมายออกมารองรับ และสามารถใช้เป็นหลักฐานในชั้นศาลได้แล้ว

    จากการเดินทางไปศึกษาดูงานพบว่ามีระบบของสถาบันแห่งหนึ่งในภาคกลาง มีความสามารถในการลงนามเอกสาร โดยผู้ใช้จะต้องใช้รหัสผ่านที่ 2 เพื่อยืนยันตัวตนอีกครั้งหนึ่งก่อนการลงนาม จากนั้นระบบจะดึงรูปภาพลายเซ็นของผู้ลงนามมาแปะลงในเอกสารตามตำแหน่งที่กำหนดไว้ ข้อดีของวิธีการนี้คือในเอกสารจะมีรูปภาพลายเซ็นแปะอยู่ ทำให้ผู้อ่านคนอื่นๆ รับทราบได้ทันทีว่าใครเป็นคนลงนาม แต่ข้อเสียคือ รูปภาพลายเซ็นดังกล่าวสามารถคัดลอกแล้วนำไปแปะในเอกสารอื่นๆ ได้ง่าย จึงทำให้การลงนามแบบนี้ยังขาดคุณสมบัติด้านความปลอดภัย

    จากการศึกษาข้อมูลจากแหล่งต่างๆ พบว่าวิธีการในการลงนามเอกสารแบบอิเล็กทรอนิกส์นั้นมี 2 วิธีหลักๆ คือ  Electronic signature และ Digital Signature ซึ่งมีความหมายและข้อแตกต่างกันดังนี้

    Electronic Signature

    เป็นการทำสัญลักษณ์ หรือลายเซ็นที่อยู่ในรูปแบบอิเล็กทรอนิกส์โดยบุคคล เพื่อเป็นการยืนยันหรือลงนามในเอกสาร สัญลักษณ์ที่นิยมใช้กันได้แก่ รูปภาพลายเซ็นที่เซ็นด้วยหมึกปากกาลงในกระดาษแล้วสแกนเข้าสู่ระบบคอมพิวเตอร์ การใช้เมาส์ หรือนิ้วมือ หรือ stylus วาดรูปลายเซ็นบนหน้าจอคอมพิวเตอร์ ลายเซ็นที่แนบท้ายอีเมล์ การพิมพ์ชื่อด้วยคีย์บอร์ด รูปภาพลายนิ้วมือ การคลิก “I Agree” ใน Electronic form ต่างๆ เป็นต้น

    [ที่มา : https://www.signinghub.com/electronic-signatures]
    [ที่มา : https://www.signinghub.com/electronic-signatures]

    Digital Signature

    Digital Signature เป็น Subset ของ Electronic Signature เนื่องจากเป็นลายเซ็นที่อยู่ในรูปแบบของอิเล็กทรอนิกส์เหมือนกัน แต่ได้เพิ่มเติมคุณสมบัติด้านความปลอดภัยเข้าไป เพื่อให้มีความน่าเชื่อถือมากยิ่งขึ้น คุณสมบัติดังกล่าวประกอบด้วย

    1. Signer Authentication เป็นความสามารถในการพิสูจน์ว่าใครเป็นคนเซ็นเอกสาร ตัวลายเซ็นจะสามารถใช้ในการเชื่อมโยงไปยังบุคคลที่เซ็นเอกสารได้
    2. Data Integrity เป็นความสามารถในการตรวจสอบ หรือพิสูจน์ได้ว่ามีการแก้ไขเอกสารหลังจากที่ได้มีการเซ็นไปแล้วหรือไม่
    3. Non-repudiation การไม่สามารถปฏิเสธความรับผิดชอบได้ เนื่องจากลายเซ็นที่สร้างขึ้นมีเอกลักษณ์ สามารถพิสูจน์ในชั้นศาลได้ว่าใครเป็นผู้เซ็นเอกสาร

    [ที่มา : https://www.signinghub.com/electronic-signatures]
    สรุปข้อดีข้อเสียของ Electronic Signatures และ Digital Signature

    ข้อดี ข้อเสีย
    Electronic Signature – ใช้สัญลักษณ์ หรือรูปภาพลายเซ็นที่ผู้ใช้สามารถเห็นได้ง่าย ทำให้ทราบได้ว่าใครเป็นเจ้าของลายเซ็น (ทราบได้จากการดูรูปลายเซ็น) – ถูกคัดลอกจากเอกสารฉบับหนึ่งไปยังอีกฉบับหนึ่งได้ง่าย

    – ไม่สามารถตรวจจับการแก้ไขเนื้อหา หรือข้อความในเอกสารที่เกิดขึ้นหลังจากการเซ็นได้

    – วิธีการนี้ยังขาดความชัดเจน ไม่สามารถพิสูจน์ได้ว่าเจ้าของลายเซ็นเป็นผู้ลงนามจริงหรือไม่ จึงอาจโดนปฏิเสธความรับผิดชอบในชั้นศาลได้

    Digital Signature – เอกสารที่ผ่านการเซ็นแล้วจะไม่สามารถแก้ไขได้ หากมีการแก้ไขก็จะสามารถตรวจสอบได้ และส่งผลให้ลายเซ็นหมดสภาพไป

    – ผู้เซ็นเอกสารสามารถกำหนดระดับความน่าเชื่อถือได้

    – ผู้เซ็นเอกสารจะไม่สามารถปฏิเสธความรับผิดชอบได้

    – สามารถใช้เป็นหลักฐานในชั้นศาลได้เทียบเท่ากับการเซ็นเอกสารในกระดาษด้วยหมึกปากกา

    – ขึ้นอยู่กับการเข้ารหัสลับ และมีความยุ่งยากในการเชื่อมโยงกับลายเซ็นบนกระดาษ

     

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

     

    แหล่งข้อมูลอ้างอิง : https://www.signinghub.com/electronic-signatures/

  • Resource Governor แนวคิดการจัดการทรัพยากรใน SQL Server 2014

          Resource Governor เป็นแนวคิดใน SQL Server ซึ่งมีมาให้ใช้ตั้งแต่ SQL Server 2008 โดยมีความสามารถในการจัดการ CPU และ Memory ให้พอเหมาะกับการใช้งานของแต่ละฐานข้อมูลได้ แต่ใน SQL Server 2014 ได้มีเพิ่มเติมการจัดการ I/O เพิ่มเข้ามาทำให้ผู้ดูแลสามารถปรับเปลี่ยนการใช้งานได้

          หากจะว่าไปแล้วในการ Tuning ฐานข้อมูลนั้นส่วนใหญ่จะเน้นการทำงานไปที่ CPU และ Memory เป็นส่วนใหญ่ แต่อีกสิ่งหนึ่งที่สามารถปรับแต่งให้ประสิทธิภาพในการทำงานของฐานข้อมูลดีขึ้นได้ ก็คือ I/O นี่เอง ซึ่งทำให้ SQL Server 2014 มีคุณสมบัติในการจัดการทรัพยากรที่หลากหลายและครบถ้วนมากขึ้น

    ในการจัดการ Resource Governor นั้น มีสิ่งสำคัญที่เราควรรู้ 3 สิ่ง ดังนี้

    • Resource Pool หลังจากมีการติดตั้ง SQL Server เสร็จสิ้น ระบบจะสร้าง Resource Pool ตั้งต้นขึ้น 2 ตัว คือ internal และ default ไว้คอยสนับสนุนทรัพยากรที่ผู้ใช้กำหนด ซึ่งผู้ดูแลสามารถสร้าง Resource Pool ขึ้นมาใหม่ได้
    • Workload Group จะมีความสัมพันธ์กับ Resource pool กลุ่มภาระงานแต่ละกลุ่มจะถูกกำหนด Resource Pool ให้ใช้งานได้
    • Classification คือ การจำแนกประเภทการร้องขอที่เข้ามาในระบบให้อยู่ใน Workload Group ที่ได้กำหนดไว้ และเหมาะสมตามการใช้งาน

    โดยสรุปการทำงานตามแผนผัง ดังนี้

    IC122094

          จากแผนผังจะเห็นว่ามีการจำแนกการติดต่อที่ส่งเข้ามาโดยผ่านเงื่อนไขที่ถูกสร้างไว้ภายใน Classification Function ให้เข้าไปยัง Workload Group ที่กำหนดไว้ ซึ่งในแต่ละ Workload Group ก็จะมี Pool ที่จำกัดทรัพยากรไว้เรียบร้อยแล้ว หากเรารู้ว่าการทำงานของกลุ่มไหนมีความต้องการมากกว่าก็สามารถแบ่งทรัพยากรไปให้ใช้งานได้มากกว่า โดยไม่ต้องเสียทรัพยากรไปกับกลุ่มการทำงานที่มีความสำคัญน้อยกว่า ทำให้ประสิทธิภาพในการทำงานเพิ่มมากขึ้น

     

    ตัวอย่างการจัดการทรัพยากรใน Resource Pool

    USE master; 
    GO
    CREATE RESOURCE POOL TestFixIOPool WITH
    (
           MAX_IOPS_PER_VOLUME = 30,
           MIN_IOPS_PER_VOLUME = 1
    	-- MIN_CPU_PERCENT=0,
    	-- MAX_CPU_PERCENT=30,
    	-- MIN_MEMORY_PERCENT=0,
    	-- MAX_MEMORY_PERCENT=30
    );
    GO
    

     

    อ้างอิง : https://technet.microsoft.com/en-us/library/bb934084%28v=sql.105%29.aspx

  • Convert Solution Visual Studio 2005 to 2013

    ในบทความนี้ ผู้เขียนจะขอกล่าวถึงเครื่องมือในการพัฒนาตัวหนึ่งที่ชื่อว่า Visual Studio โดยจะนำเสนอวิธีการ Convert Solution ASP.NET จากเวอร์ชั่นเก่าไปยังเวอร์ชั่นใหม่โดยไม่ต้องสร้างโปรเจคขึ้นมาใหม่ ตัวอย่างในวันนี้จะแสดงการ Convert Project Solution ที่พัฒนาด้วย Visual Studio 2005 ไปพัฒนาบน Visual Studio 2013 ซึ่งแน่นอนครับว่าจะต้องมีการ config ค่าเพิ่มเติมต่างๆ ผู้เขียนจะกล่าวไว้ช่วงท้ายนะครับ เราเริ่มขั้นตอนการ Convert กันเลยดีกว่าครับ

    ขั้นตอนแรก : เตรียมข้อมูลให้พร้อม

    1. เตรียม solution ตัวเก่าของเราให้พร้อม (ในที่นี้เราใช้ solution ของ Visual Studio 2005 ชื่อ GSMISII )

    Capture

    รูปที่ 1 Folder Project Solution Visual Studio 2005

    Capture2

    รูปที่ 2 ไฟล์ Project Solution Visual Studio 2005

         2. เตรียม Visual Studio 2013 ให้พร้อม

    Capture3

    รูปที่ 3 Visual Studio 2013

    ขั้นตอนที่สอง : เริ่ม Convert

          1. เราจะทำการ Convert Solution โดยคลิกขวาไฟล์ Solution ( xxx.sln ) ตามรูปที่ 2 เลือก Open with… ก็จะปรากฎดังรูป

    Capture4

    รูปที่ 4 Keep using Microsoft Visual Studio Version Selector

     

    2. เลือก Keep using Microsoft Visual Studio Version Selector

    Capture5

    รูปที่ 5 Review Project And Solution Changes

         3. Visual Studio จะตรวจสอบว่ามีโปรเจคหรือ Solution ใดบ้าง ตามตัวอย่างมีแค่ 1 Solution กด OK

    Capture10

    รูปที่ 6 Loading solution projects…

          กรณีมีการใช้ Crystal Report จะมีการให้ Backup ก่อนที่จะ Convert (ซึ่งหากต้องการใช้ Crystal Report จะต้องลง SAP Version ที่รองรับกับ Visual Studio 2013 ก่อน)Capture11

    รูปที่ 7 Loading solution projects…

         กรณีที่มีการใช้ Source Control จะมีให้เลือกว่าจะ remove ออกหรือไม่

    Capture9

    รูปที่ 8 Source Control remove

         เสร็จเรียบร้อยแล้ว… จริงหรือ??

    Capture12 

    รูปที่ 9 Solution in Visual Studio 2013

         เรามาลอง Build Solution กันดีกว่า…

    Capture13

    รูปที่ 10 Error Build Solution 

        Oops! Error!!  ไม่ต้องตกใจกันนะครับ เพราะ error พวกนี้ส่วนใหญ่จะเป็นค่า config ที่อยู่ใน file web.config ที่ไม่รองรับกับ control เวอร์ชั่นใหม่อยู่แล้ว ซึ่งรวมไปถึง crystal report ด้วย ตามแก้ให้เรียบร้อย (ใช้ความอดทนเล็กน้อย ให้สังเกตุว่า error assembly ตัวใด สังเกตุเวอร์ชั่นด้วย) แล้วลอง Build ใหม่กันอีกครั้ง

         ปล. สำหรับ Crystal Report สำหรับ Visual Studio 2013 สามารถ Download ได้ที่
    http://scn.sap.com/docs/DOC-7824

  • การใช้ LINQ ในการจัดการข้อมูลอย่างง่าย สำหรับมือใหม่(Ep.2)

              ความเดิมตอนที่แล้ว… ผู้เขียนได้ทิ้งท้ายไว้เกี่ยวกับเรื่องการใช้งาน LINQ ในการจัดการข้อมูลในเบื้องต้น ได้แก่ วิธีการดึงข้อมูลโดยทั่วไป(Select) การดึงข้อมูลแบบมีเงื่อนไข(Where) และการเรียงลำดับ(OrderBy) เป็นต้น หากใครที่ยังไม่เคยอ่านบทความที่แล้ว และต้องการศึกษาในส่วนดังกล่าวสามารถหาอ่านได้จากลิงค์ “การใช้ LINQ ในการจัดการข้อมูลอย่างง่าย สำหรับมือใหม่(Ep.1)” เพื่อเพิ่มความเข้าใจพื้นฐานในการใช้งานเบื้องต้น LINQ เพิ่มเติม และสำหรับในบทความนี้ ผู้เขียนจะขอพูดถึงการใช้งาน LINQ ในส่วนอื่นๆที่นอกเหนือจากการทำงานทั่วไป ที่คิดว่าน่าจะเป็นประโยชน์กับผู้พัฒนาที่มีความสนใจในการใช้งาน LINQ จัดการข้อมูล ดังนี้

    • การคำนวณค่าร่วม/นับจำนวน

    ตัวอย่างที่ 1 : การคำนวณค่าผลรวมของฟิลด์ที่ดึงข้อมูลโดยใช้เมธอด Sum

    decimal sumLineTotal = (from od in orderdetailscollection
    select od.LineTotal).Sum();
    

    หรือ

    decimal sumLineTotal = orderdetailscollection.Sum(od => od.LineTotal);
    

    คำอธิบาย : จากตัวอย่างข้างต้น เป็นการดึงข้อมูลโดยมีการคำนวณค่าผลรวมที่ได้จากการดึงข้อมูลทั้งหมดในฟิลด์ LineTotal ผ่านเมธอด Sum โดยที่ไม่ต้องมาวนค่าเพื่อหาผลรวมของแต่ละฟิลด์ที่ดึงมาอีกครั้งในภายหลัง ซึ่งถือว่าเป็นการอำนวยความสะดวกและประหยัดเวลาในการพัฒนาให้กับผู้พัฒนาที่ต้องการทำงานในกรณีดังกล่าวได้

    ตัวอย่างที่ 2 : เป็นการคำนวณค่าเฉลี่ยตามรหัส

    double RatingAverage = ctx.Rates.Where(r => r.Id == Id).Average(r => r.Rating);
    

    หรือ

    var RatingAverage = (from a in ctx.Rates where a. Id.Equals(id)
    select a.Rating).Average();
    

    คำอธิบาย : จากตัวอย่างข้างต้น เป็นการคำนวณหาค่าเฉลี่ยของฟิลด์ Rating โดยใช้เมธอด Average ภายใต้เงื่อนไขรหัส Id ในการดึงข้อมูล

    ตัวอย่างที่ 3 : เป็นตัวอย่างการคำนวณผลรวม และการนับจำนวนแถวของการอ่านข้อมูลโดยมีการจัดกลุ่มข้อมูลร่วมด้วย

    var ListByOwner = list.GroupBy(l => l.Owner)
    .Select(lg => new {
    Owner = lg.Key,
    Boxes = lg.Count(),
    TotalWeight = lg.Sum(w => w.Weight),
    AverageVolume = lg.Average(w => w.Volume)
    });
    

    คำอธิบาย : จากตัวอย่างข้างต้น จะเห็นได้ว่าเป็นการจัดกลุ่มของข้อมูลตาม Owner โดยใช้เมธอด GroupBy และมีการนับจำนวนแถวเก็บไว้ในฟิลด์ Boxes คำนวณผลรวมของคอลัมน์ Weight และหาค่าเฉลี่ยของคอลัมน์ Volume ใส่ในฟิลด์ TotalWeight และ AverageVolume นั่นเอง

    ตัวอย่างที่ 4 : เป็นการนับจำนวนข้อมูลตามเงื่อนไขที่กำหนดโดยใช้เมธอด Count

    int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
    
    int oddNumbers = numbers.Count(n => n % 2 == 1);
    

    คำอธิบาย : จากตัวอย่างข้างต้น จะเห็นว่า เป็นการดึงค่าจากตัวแปรอาร์เรย์ที่ชื่อว่า numbers และนับจำนวนตัวเลขเฉพาะที่เป็นเลขคี่(หารด้วยสองและมีค่าเศษ 1)เท่านั้นด้วยเมธอด Count ซึ่งในส่วนของเงื่อนไขดังกล่าว ผู้อ่านสามารถนำไปประยุกต์ใช้กับงานของตนในการนับจำนวนข้อมูลได้

    ผลลัพธ์ : จากการอ่านข้อมูลจากตัวแปร numbers ส่งผลให้ oddNumbers มีค่าเท่ากับ 5 นั่นคือในการดึงข้อมูลพบเลขที่มีค่าเป็นเลขคี่ดังนี้ 5,1,3,9,7

    • การค่าที่น้อยที่สุดและมากที่สุดโดยใช้เมธอด Min/Max

    ตัวอย่างที่ 1 : การหาค่าที่น้อยที่สุดโดยใช้เมธอด Min

    string[] words = { "cherry", "apple", "blueberry" };
    
    int shortestWord = words.Min(w => w.Length);
    

    คำอธิบาย : จากตัวอย่างข้างต้น จะเห็นว่า เป็นการอ่านค่าจากตัวแปรอาร์เรย์ที่ชื่อว่า words และหาค่าที่น้อยที่สุดของจำนวนความยาวตัวอักษรจากค่าที่อ่านได้ด้วยเมธอด Min

    ผลลัพธ์shortestWord มีค่าเท่ากับ 5 ซึ่งก็คือค่าความยาวตัวอักษรของคำว่า “apple” ที่มีค่าเท่ากับ 5 ซึ่งเป็นค่าที่น้อยที่สุดนั่นเอง

    ตัวอย่างที่ 2 : เป็นการหาราคาที่ถูกที่สุดจากรายการสินค้าที่อ่านได้แต่ละประเภท(จัดกลุ่มตามประเภทสินค้า) โดยใช้เมธอด Min

    List<Product> products = GetProductList();
    var categories =
    from p in products
    group p by p.Category into g
    select new { Category = g.Key, CheapestPrice = g.Min(p => p.UnitPrice) };
    

    คำอธิบาย : จากตัวอย่างข้างต้น เป็นการหาค่าที่น้อยที่สุดของราคาในคอลัมน์ UnitPrice จาก list ของคลาส Product ที่ดึงมาได้ และมีการจัดกลุ่มตามประเภทของสินค้า นั่นหมายถึง ค่าที่ได้จากการอ่านข้อมูลนี้จะเป็นค่าของราคาสินค้าต่อหน่วยที่น้อยที่สุดในแต่ละประเภทสินค้านั่นเอง

    หมายเหตุ : การใช้งานเมธอด Max จะมีลักษณะการทำงานเช่นเดียวกับเมธอด Min แต่เป็นการหาค่าที่มากที่สุดจากการอ่านข้อมูล ซึ่งผู้อ่านสามารถนำไปใช้แทนกันได้ตามตัวอย่างการใช้งานเมธอด Min ข้างต้นได้

    • การใช้งาน set operator
      • การกำจัดข้อมูลที่ซ้ำกัน

      ตัวอย่างที่ 1 : เป็นการจำกัดข้อมูลที่ซ้ำกันด้วยเมธอด Distinct/DistinctBy

      List<Product> products = GetProductList();
      var categoryNames = (
      from p in products
      select p.Category).Distinct();
      

      หรือ 

      List<Product> products = GetProductList();
      var categoryNames = products.DistinctBy(x=> x.Category);
      

      คำอธิบาย : เป็นการดึงข้อมูลประเภทสินค้าที่ไม่ซ้ำกันไว้ในตัวแปร categoryNames โดยใช้เมธอด Distinct และ DistinctBy (ที่เรียกใช้จาก MoreLinq Library)
      หมายเหตุ : หากต้องการกำจัดข้อมูลที่ซ้ำกันมากกว่า 1 คอลัมน์ สามารถทำได้ ดังนี้

      List<Product> products = GetProductList();
      var categoryNames = products.DistinctBy(a => new { a.Category, a.Code });
      

      ตัวอย่างที่ 2 :

      List<Product> products = GetProductList();
      var categoryNames = products
       .GroupBy(a => a.Category )
       .Select(g => g.First());
      

      คำอธิบาย : เป็นการดึงข้อมูลประเภทสินค้าที่ไม่ซ้ำกันไว้ โดยใช้เมธอดการจัดกลุ่มข้อมูลตามประเภทสินค้าด้วยเมธอด GroupBy และดึงค่าแรกสุดของแต่ละกลุ่มมาแสดงด้วยเมธอด First ตามลำดับ

      ตัวอย่างที่ 3 :

      var total = items.Select(item => item.Value).Distinct().Count();
      

      คำอธิบาย : เป็นการนับจำนวนของข้อมูลที่ไม่ซ้ำกันโดยใช้เมธอด Distinct และเมธอด Count

      • การผสานข้อมูลโดยใช้ Union

      ตัวอย่างที่ 1 :

      int[] numbersA = { 0, 2, 4, 5, 6, 8, 9 }; 
      int[] numbersB = { 1, 3, 5, 7, 8 }; 
      var uniqueNumbers = numbersA.Union(numbersB); 
      

      คำอธิบาย : เป็นการรวมข้อมูลจาก 2 แหล่ง ด้วยเมธอด Union
      ผลลัพธ์ : uniqueNumbers = {0, 2, 4, 5, 6, 8, 9,1, 3, 7, 8}

      • การเลือกเฉพาะข้อมูลที่ซ้ำกันจากข้อมูล 2 แหล่งมาแสดงด้วยเมธอด Intersect

      ตัวอย่างที่ 1 :

      var infoQuery =
       (from cust in db.Customers
       select cust.Country)
       .Intersect
       (from emp in db.Employees
       select emp.Country);
      

      คำอธิบาย : เป็นการอ่านข้อมูลจาก 2 ส่วน คือ ในตาราง Customers และ Employees เพื่อเปรียบเทียบค่าคอลัมน์ Country จาก 2 แหล่ง หากมีซ้ำกันทั้ง 2 ที่จะดึงมาเก็บไว้ในตัวแปร infoQuery

      ตัวอย่างที่ 2 :

       int[] numbersA = { 0, 2, 4, 5, 6, 8, 9 }; 
       int[] numbersB = { 1, 3, 5, 7, 8 }; 
       
       var commonNumbers = numbersA.Intersect(numbersB); 
      

      คำอธิบาย : เป็นการอ่านข้อมูลจาก 2 ส่วน คือ ในตัวแปรอาร์เรย์ numbersA และ numbersB เพื่อเปรียบเทียบค่า หากมีซ้ำกันทั้ง 2 ที่จะดึงมาเก็บไว้ในตัวแปร commonNumbers
      ผลลัพธ์ : commonNumbers = { 5, 8}

      • การยกเว้นการอ่านข้อมูลโดยใช้ Except

      ตัวอย่างที่ 1 :

       List<Product> products = GetProductList(); 
       List<Customer> customers = GetCustomerList(); 
       
       var productFirstChars = 
       from p in products 
       select p.ProductName[0]; ///ดึงอักษรตัวแรกของชื่อสินค้า
       var customerFirstChars = 
       from c in customers 
       select c.CompanyName[0]; ///ดึงอักษรตัวแรกของชื่อบริษัท
       
       var productOnlyFirstChars = productFirstChars.Except(customerFirstChars); 
      

      คำอธิบาย : เป็นการอ่านข้อมูลจาก 2 ส่วน คือ ในส่วนของลิสต์ products และ customers โดยเอาเฉพาะอักษรตัวแรกของข้อมูลมาเพื่อเปรียบเทียบค่าโดยเทียบจาก productFirstChars หากพบว่าซ้ำกับในตัวอักษรแรกของข้อมูลชื่อบริษัทที่เก็บไว้ในตัวแปร customerFirstChars จะข้ามไป ไม่ดึงมาเก็บไว้ในตัวแปร productOnlyFirstChars แต่หากไม่ซ้ำ จะนำมาเก็บไว้ ผลลัพธ์สุดท้ายที่ได้จึงจะมีเฉพาะที่มีใน productFirstChars และไม่ซ้ำกับในข้อมูล customerFirstChars เท่านั้น
      ตัวอย่างที่ 2 :

       int[] numbersA = { 0, 2, 4, 5, 6, 8, 9 }; 
       int[] numbersB = { 1, 3, 5, 7, 8 }; 
       
       IEnumerable<int> aOnlyNumbers = numbersA.Except(numbersB); 
      

      คำอธิบาย : เป็นการอ่านข้อมูลจาก 2 ส่วน คือ ในตัวแปรอาร์เรย์ numbersA และ numbersB เพื่อเปรียบเทียบค่าโดยเทียบจาก numbersA เทียบกับ numbersB จะเอาเฉพาะที่มีใน numbersB แต่ไม่มีใน numbersB มาเก็บไว้ในตัวแปร aOnlyNumbers
      ผลลัพธ์ : aOnlyNumbers = { 0, 2, 4, 6, 9}

      • การอ่านข้อมูลจาก 2 แหล่งข้อมูลด้วยวิธีการ join
        • การ join แบบ innerjoin

        ตัวอย่างที่ 1 : เป็นการเชื่อมตาราง 2 ตารางโดยจะเอาเฉพาะข้อมูลแถวที่มี key ร่วมกันจากทั้ง 2 แหล่ง

        var result = from p in Person.BuiltPersons()
         join a in Address.BuiltAddresses() ///ระบุตารางที่นำมาเชื่อมกัน
         on p.IdAddress equals a.IdAddress ///กำหนด key ร่วมจาก 2 ตาราง
         select new 
         { ///เลือกคอลัมน์ที่ต้องการนำมาใช้
         Name = a.MyPerson.Name,
         Age = a.MyPerson.Age,
         PersonIdAddress = a.MyPerson.IdAddress,
         AddressIdAddress = a.MyAddress.IdAddress,
         Street = a.MyAddress.Street
         }; 
        

        คำอธิบาย : จากตัวอย่างข้างต้น จะเห็นเป็นการเชื่อมตาราง 2 ตารางเข้าด้วยกัน และเลือกเฉพาะข้อมูลที่มีรหัส IdAddress ตรงกันมาแสดงเท่านั้น

        หรือ 

        var resultJoint = Person.BuiltPersons().Join( /// Source Collection
         Address.BuiltAddresses(), /// Inner Collection
         p => p.IdAddress, /// กำหนด key จากตาราง Person.BuiltPersons()
         a => a.IdAddress, /// กำหนด key จากตาราง Address.BuiltAddresses()
         (p, a) => new { MyPerson = p, MyAddress = a }) /// Result Collection
         .Select(a => new
         {
         Name = a.MyPerson.Name,
         Age = a.MyPerson.Age,
         PersonIdAddress = a.MyPerson.IdAddress,
         AddressIdAddress = a.MyAddress.IdAddress,
         Street = a.MyAddress.Street
         });
        • การ join แบบ leftjoin

        ตัวอย่างที่ 1 :

        string[] categories = new string[]{ 
         "Beverages", 
         "Condiments", 
         "Vegetables", 
         "Dairy Products", 
         "Seafood" }; 
          List<Product> products = GetProductList(); 
          
         var q = 
         from c in categories 
         join p in products on c equals p.Category into ps 
         from p in ps.DefaultIfEmpty() 
        ///ถ้าไม่มีข้อมูลจะแสดงข้อความ (No products)
         select new { Category = c, ProductName = p == null ? "(No products)" : p.ProductName }; 
        

        คำอธิบาย : จากตัวอย่างข้างต้น จะเห็นได้ว่าเป็นการเชื่อมข้อมูล 2 แหล่ง โดยยึดตามแหล่งข้อมูลแรก ในที่นี้คือข้อมูล categories และเทียบกับข้อมูลใน products หากข้อมูลที่เปรียบเทียบมีใน categories แต่ไม่มีใน products ก็จะนำมาแสดง โดยให้ค่าของ ProductName เป็น (No products) แทน(ตามหลักการ join แบบ leftjoin ) หรืออธิบายง่ายๆว่า จะแสดงทุกรายการที่มีใน categories นั่นเอง

        • การ join แบบ rightjoin

        ตัวอย่างที่ 1 :

        var rightOuterJoin = from last in lastNames
         join first in firstNames
         on last.ID equals first.ID
         into temp
         from first in temp.DefaultIfEmpty(new { last.ID, Name = default(string) })
         select new
         {
         last.ID,
         FirstName = first.Name,
         LastName = last.Name,
         };
        

        คำอธิบาย : จากตัวอย่างข้างต้น จะเห็นได้ว่าเป็นการเชื่อมข้อมูล 2 แหล่ง โดยยึดตามแหล่งข้อมูลแรก ในที่นี้คือข้อมูล lastNames และเทียบกับข้อมูลใน firstNames โดยยึดข้อมูลจาก firstNames หากข้อมูลที่เปรียบเทียบมีใน firstNames แต่ไม่มีใน lastNames ก็จะนำมาแสดง โดยให้ค่าของ default(string) แทน(ตามหลักการ join แบบ rightjoin ) หรืออธิบายง่ายๆว่า จะแสดงทุกรายการที่มีใน firstNames นั่นเอง

                จะเห็นได้ว่าจากบทความการใช้งาน LINQ ในการจัดการข้อมูลอย่างง่าย สำหรับมือใหม่ ทั้งใน Ep.1 และ Ep.2 นี้ ผู้เขียนพยายามรวบรวมวิธีที่เป็นการใช้งานพื้นฐานและยกตัวอย่างในกรณีต่างๆ เพื่อให้ผู้อ่านเห็นภาพ และทำความเข้าใจได้โดยง่ายขึ้น สามารถนำไปประยุกต์ใช้กับงานพัฒนาที่กำลังพัฒนาอยู่ได้ แต่ความสามารถของ LINQ ไม่ได้จบเพียงเท่านี้ ยังคงมีเมธอดและวิธีการใช้งานอื่นๆ รวมทั้งการประยุกต์ใช้งานอีกมากมายรอให้ผู้อ่านได้ทำการศึกษาเพิ่มเติมและหยิบมาใช้งานกัน เพื่อเพิ่มประสิทธิภาพและความคล่องตัวในการพัฒนาโปรแกรมกันต่อไป ผู้เขียนเชื่อว่าหากผู้อ่านได้มีการศึกษาและฝึกลองทำหลายๆกรณีก็จะทำให้สามารถใช้งาน LINQ ได้เกิดประโยชน์สูงสุด สุดท้ายนี้ผู้เขียนหวังเป็นอย่างยิ่งว่าสิ่งที่ผู้เขียนได้หยิบยกมาให้ผู้อ่านได้อ่านกันนี้จะเป็นประโยชน์ไม่มากก็น้อย หากผิดพลาดประการใดผู้เขียนขออภัยไว้ ณ ที่นี้ด้วย และขอบคุณที่ติดตามอ่านบทความนี้จนจบนะคะ ขอบคุณค่ะ

      แหล่งข้อมูลอ้างอิง :
      https://code.msdn.microsoft.com/LINQ-Aggregate-Operators-c51b3869

      http://stackoverflow.com/questions/61870/sum-of-items-in-a-collection

      http://stackoverflow.com/questions/17983024/finding-average-of-value-based-on-count-of-rows-in-linq?rq=1

      https://code.msdn.microsoft.com/LINQ-Set-Operators-374f34fe

      http://www.codeproject.com/Articles/535374/DistinctBy-in-Linq-Find-Distinct-object-by-Propert
      https://code.msdn.microsoft.com/LINQ-Join-Operators-dabef4e9

      http://www.codeproject.com/Articles/488643/LinQ-Extended-Joins

  • จับตา Bootstrap 4 Beta

    เมื่อช่วงเดือนสิงหาคม 2015 ผู้พัฒนา Bootstrap ได้เปิดตัว Bootstrap 4 Beta ออกมาให้ผู้พัฒนาเว็บไซต์ได้ทดลองใช้งานกัน โดยได้มีการแก้ไข bug ที่เจอในเวอร์ชั่น 3 และเพิ่มเติมความสามารถต่างๆ เข้าไป ทำให้การพัฒนาเว็บไซต์สามารถทำได้ง่าย และเป็นที่น่าสนใจได้มากขึ้น โดยในเวอร์ชั่นนี้มีการปรับปรุงจากเวอร์ชั่น 3 ค่อนข้างเยอะ แต่ที่เด่นๆ ได้แก่

    bootstrap 4

     

    • ใช้ Sass แทน Less ทำให้ compile ได้เร็วขึ้น ข้อดีของการใช้ Sass คือการมี community ขนาดใหญ่เป็นตัวช่วยสำหรับนักพัฒนาในการค้นคว้าหาความรู้เพิ่มเติม หรือขอความช่วยเหลือต่างๆ จาก community ได้
    • ปรับปรุง Grid System ให้รองรับกับขนาดหน้าจอของอุปกรณ์ต่างๆ ให้มากยิ่งขึ้น
    • สนับสนุนการใช้งาน flexbox grid system และ component
    • ใช้ Card component แทนการใช้ wells, thumbnails และ panel
    • รวม HTML Reset ทั้งหมดไว้ใน Module เดียวกันชื่อ Reboot
    • เพิ่ม option ในการ customize ก่อนการดาวน์โหลดมาใช้งาน เช่นการกำหนด gradients, transitions, shadows ให้กับ component ต่างๆ โดยเก็บไว้ในตัวแปร Sass แล้ว recompile เป็น CSS มาใช้งาน
    • ยกเลิกการสนับสนุน IE8 แล้ว ส่งผลทำให้เราสามารถใช้ประโยชน์จาก Style sheet ได้อย่างเต็มประสิทธิภาพ และในเวอร์ชั่นนี้ก็ได้เปลี่ยนการใช้งานหน่วย pixel มาเป็น rems และ ems แทน เนื่องจากหน่วยทั้ง 2 แบบนี้ จะช่วยให้การพัฒนาเว็บไซต์ในรูปแบบ Responsive ทำได้ง่ายขึ้น ทั้งในส่วนของ Typography และ component sizing แต่ถ้าหากยังอยากให้เว็บไซต์รองรับ IE8 ด้วย ทางผู้พัฒนา Bootstrap แนะนำว่าให้ใช้ Bootstrap Version 3 ตามเดิม
    • เขียน Javascript plugin ใหม่ทั้งหมด
    • ปรับปรุง auto-placement หรือการจัดวางตำแหน่งของ tooltips และ popovers
    • ปรับปรุงเอกสาร Documentation ให้อ่าน และค้นหาได้ง่ายขึ้น
    • และอื่นๆ เช่น Custom form control, มีคลาสสำหรับ margin และ padding ให้ใช้งานได้ง่ายขึ้น และ Utility Class สำหรับอำนวยความสะดวกในการพัฒนาเว็บไซต์ เป็นต้น

     

    จะเห็นว่าจากรายการที่เปลี่ยนแปลงต่างๆข้างต้นนั้นมีความน่าสนใจมาก ทางผู้พัฒนาจึงได้สร้างเว็บไซต์ตัวอย่างไว้ให้ดูโดยสามารถดูได้จาก http://expo.getbootstrap.com/

  • ข้อจำกัดและข้อควรระวังในการใช้เงื่อนไข IN ในคำสั่ง SELECT บนฐานข้อมูล Oracle

    สำหรับบทความนี้ จะนำเสนอข้อจำกัดและข้อควรระวังในการใช้งานคำสั่ง SELECT บนฐานข้อมูล Oracle ซึ่งประสบมาจากการใช้งานจริงสองเรื่องด้วยกัน

     

    เรื่องแรกจะเป็นข้อจำกัดในการใช้เงื่อนไข IN (value1,value2,value3,…) ในคำสั่ง SELECT  ส่วนอีกเรื่องจะเป็นเรื่องของข้อควรระวังในการใช้ IN ร่วมกับเงื่อนไขที่เป็น subquery ในคำสั่ง SELECT  เช่นกัน

     

    การใช้คำสั่ง SELECT และเงื่อนไข IN นั้น เป็นรูปแบบคำสั่งพื้นฐานแบบหนึ่งที่นักพัฒนาที่ทำงานคลุกคลีกับฐานข้อมูลส่วนใหญ่จะคุ้นเคยกันเป็นอย่างดี  โดยรูปแบบที่เรามักจะใช้งานกันบ่อย คือ

    รูปแบบที่ 1 รูปแบบ SELECT * FROM TABLE1 WHERE FIELD1 IN  (value1,value2,value3,…)  โดยผลลัพธ์จะเป็นรายการข้อมูลในตาราง TABLE1 ที่ค่าของข้อมูลใน FIELD1 มีอยู่ใน value1,value2,value3 ,…

    รูปแบบที่ 2 คล้ายกับรูปแบบที่ 1  นั่นเอง แต่จะเป็นการใช้ subquery แทนที่ (value1,value2,value3,…)   โดยมีรูปแบบ SELECT * FROM TABLE1 WHERE FIELD1 IN  (SELECT FIELD2 FROM TABLE2) สำหรับผลลัพธ์จะเป็นรายการข้อมูลในตาราง TABLE1 ที่ค่าของข้อมูลใน FIELD1 มีใน FIELD2 ซึ่งเป็นผลลัพธ์จากการ SELECT ข้อมูลจากตาราง TABLE2

     

    ข้อจำกัดในการใช้เงื่อนไข IN (value1,value2,value3,…)

    สำหรับใน Oracle นั้น list รายการที่อยู่ภายในเครื่องหมายวงเล็บ สามารถมีได้มากสุดไม่เกิน 1000 ค่า  ซึ่งบางท่านอาจจะสงสัยว่าในการ SELECT ข้อมูลตามปกตินั้น มีโอกาสน้อยมากที่เราจะพิมพ์ค่าของข้อมูลใน list จนถึง 1000  ค่า แต่ก็มีโอกาสที่จะพบได้คือ เมื่อมีการใช้งานคำสั่งนี้ผ่านโปรแกรมที่เขียนขึ้นนั่นเอง ตัวอย่างเช่น หน้าจอการทำงานของโปรแกรมที่มีลักษณะเป็นชุดของรายการข้อมูลที่ให้ผู้ใช้สามารถเลือกเองได้ จากนั้นรายการที่ถูกเลือกจะถูกส่งไปแปลงเป็นเงื่อนไขในคำสั่ง SELECT  อีกครั้ง ทำให้มีโอกาสที่จะเกิดกรณีที่มี list เกิน 1000 ค่า ได้นั่นเอง

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

    Capture

    ถือว่าเป็นจุดหนึ่งที่ผู้พัฒนาควรระมัดระวังในการเขียนโปรแกรมที่มีการใช้เงื่อนไข IN ลักษณะนี้ในคำสั่ง SELECT

     

    ข้อควรระวังในการใช้ IN ร่วมกับเงื่อนไขที่เป็น subquery

    ในที่นี้ขอยกตัวอย่างข้อมูลเพื่อให้เห็นภาพชัดเจน  โดยมีข้อมูลจากตารางสองตาราง คือ TABLE01 และ TABLE02

    สำหรับ TABLE01 เป็นข้อมูลที่ต้องการ SELECT เพื่อให้ได้ผลลัพธ์ออกมา ส่วนตาราง TABLE02 จะเป็นเงื่อนไขที่จะใช้ใน subquery  ข้อมูลในตารางทั้งสองจะเป็นดังนี้

     

    ข้อมูลใน  TABLE01

    t01

    ข้อมูลใน  TABLE02

    t02

     

    จะยกตัวอย่างกรณีการ SELECT ออกเป็น 2 กรณีดังนี้

    1. ต้องการข้อมูลใน TABLE01 ที่ข้อมูลในฟีลด์ F02 ของตารางนี้มีในฟีลด์ F02 ของ TABLE02 ด้วย

    คำสั่งที่ใช้คือ SELECT * FROM TABLE01 WHERE F02 IN (SELECT F02 FROM TABLE02);

    ผลลัพธ์ที่ได้คือ

    result1

    ซึ่งถูกต้อง

     

    1. ต้องการข้อมูลใน TABLE01 ที่ข้อมูลในฟีลด์ F02 ของตารางนี้ไม่มีในฟีลด์ F02 ของ TABLE02 โดยปรับคำสั่งจาก IN เป็น NOT IN

    คำสั่งที่ใช้คือ SELECT * FROM TABLE01 WHERE F02 NOT IN (SELECT F02 FROM TABLE02);

    ผลลัพธ์ที่ได้คือ

    result2

    ซึ่งถูกต้อง

     

    จะเห็นว่าทั้งสองกรณีทั้งการใช้ IN หรือ NOT IN ผลลัพธ์ที่ได้ก็ออกมาถูกต้อง

     

    ทดลองต่อไปโดยการเพิ่มข้อมูลในตาราง TABLE02 ดังนี้

    t02_2

    โดยข้อมูลที่เพิ่มจะมี 1 รายการที่ข้อมูลใน F02 มีค่าเป็น null

     

    จากนั้นลองทำการ SELECT แบบเดิมดังนี้

    1. ต้องการข้อมูลใน TABLE01 ที่ข้อมูลในฟีลด์ F02 ของตารางนี้มีในฟีลด์ F02 ของ TABLE02 ด้วย

    คำสั่งที่ใช้คือ SELECT * FROM TABLE01 WHERE F02 IN (SELECT F02 FROM TABLE02);

    ผลลัพธ์ที่ได้คือ

    result1

    จะเห็นว่าได้ผลลัพธ์แบบเดิม ซึ่งถูกต้อง

     

    1. ต้องการข้อมูลใน TABLE01 ที่ข้อมูลในฟีลด์ F02 ของตารางนี้ไม่มีในฟีลด์ F02 ของ TABLE02 โดยปรับคำสั่งจาก IN เป็น NOT IN

    คำสั่งที่ใช้คือ SELECT * FROM TABLE01 WHERE F02 NOT IN (SELECT F02 FROM TABLE02);

    ผลลัพธ์ที่ได้คือ

    result3

    ซึ่งไม่มีผลลัพธ์ใด ๆ ออกมาเลย

    ลองเพิ่มเงื่อนไขใน subquery โดยเอารายการที่ F02 มีค่าเป็น null ออกไป

    คำสั่งที่ใช้คือ SELECT * FROM TABLE01 WHERE F02 NOT IN (SELECT F02 FROM TABLE02 WHERE F02 IS NOT NULL);

    ผลลัพธ์ที่ได้คือ

    result2

    ซึ่งเป็นไปตามที่เราต้องการ

     

    สรุปได้ว่า กรณีที่เราต้องการใช้งานคำสั่ง SELECT ที่ใช้ร่วมกับเงื่อนไข NOT IN ตามด้วย subquery  ต้องระมัดระวังในเรื่องของผลลัพธ์ที่ได้จาก subquery มีค่าข้อมูลที่เป็น null อยู่ด้วย ซึ่งจะทำให้ได้ผลลัพธ์ไม่ตรงตามที่เราต้องการ

     

    ข้อมูลอ้างอิง 

    https://docs.oracle.com/cd/B19306_01/server.102/b14200/conditions013.htm

    https://docs.oracle.com/cd/B19306_01/server.102/b14200/expressions014.htm#i1033664