Category: Database

  • Auto remove schema in EDMX on build

    Entity Framework (EF)  คือ data access technology ที่เริ่มเปิดตัวครั้งแรกเป็นส่วนหนึ่งของ .NET Framework 3.5 SP1 โดยตัว EF จะทำหน้าที่เป็น object-relational mapper ที่ทำให้ผู้พัฒนาไม่จำเป็นต้องเขียน code ในส่วน data access ก็สามารถใช้ข้อมูลจาก relational database โดยผ่าน object model

    การพัฒนาโปรแกรมโดยใช้ EF นั้นจำเป็นต้องมี Entity Data Model เป็น model ที่กำหนดรายละเอียดเกี่ยวกับ entity และ relationship ระหว่าง entity นั้นๆ การสร้าง Entity Data Model สามารถแยกออกเป็น 2 แนวทางคือ “Code First” เป็นการกำหนดรูปร่างของ model โดยการสร้าง class (เขียน code) จะมี database หรือไม่มีอยู่ก่อนก็ได้  และ “Database First” ที่จะทำการสร้าง model ( reverse engineer) จาก database ที่มีอยู่โดย EF Designer ซึ่ง model ที่ได้จะเก็บอยู่ใน EDMX file (.edmx) สามารถเปิดหรือแก้ไขเพิ่มเติมได้ด้วย EF Designer สำหรับ class ที่ใช้ในโปรแกรมจะถูกสร้างโดยอัตโนมัติจาก EDMX file

    ข้อมูล Entity Data Model ใน EDMX file อยู่ในรูปแบบ xml สามารถแบ่งออกเป็น 3 ส่วนคือ Storage model, Conceptual model และ Mapping ซึ่งในส่วนของ Storage model จะเป็นข้อมูลรายละเอียดของ entity จาก database เช่น

    ข้อมูล EntityType ที่ให้รายละเอียดของชื่อของ entity (table ใน database), ชื่อและประเภทของ property (column ของ table ใน database)

     <EntityType Name="VF_CONFIG_REPORT">
      <Key>
        <PropertyRef Name="ID" />
      </Key>
        <Property Name="ID" Type="number" Precision="38" Scale="0" Nullable="false" />
        <Property Name="REPORT_NAME" Type="varchar2" MaxLength="512" />
        <Property Name="REPORT_PATH" Type="varchar2" MaxLength="512" />
        <Property Name="GROUP_TYPE" Type="number" Precision="38" Scale="0" />
        <Property Name="SIGN_NUM" Type="number" Precision="38" Scale="0" />
        <Property Name="SIGNS" Type="varchar2" MaxLength="128" />
     </EntityType>

    ข้อมูล EntitySet ที่ประกอบด้วย ชื่อ,ประเภทของ entity, schema และ query ที่ใช้ดึงข้อมูล

    <EntitySet Name="VF_CONFIG_REPORT" EntityType="Self.VF_CONFIG_REPORT" store:Type="Views" store:Schema="FINANCE">
       <DefiningQuery>
          SELECT 
           "VF_CONFIG_REPORT"."ID" AS "ID",
           "VF_CONFIG_REPORT"."REPORT_NAME" AS "REPORT_NAME", 
           "VF_CONFIG_REPORT"."REPORT_PATH" AS "REPORT_PATH", 
           "VF_CONFIG_REPORT"."GROUP_TYPE" AS "GROUP_TYPE", 
           "VF_CONFIG_REPORT"."SIGN_NUM" AS "SIGN_NUM", 
           "VF_CONFIG_REPORT"."SIGNS" AS "SIGNS"
         FROM "FINANCE"."VF_CONFIG_REPORT" "VF_CONFIG_REPORT"   
       </DefiningQuery>
    </EntitySet>

    เมื่อมีการระบุ schema ของ entityใน EDMX file  นั่นทำให้การ deploy ระบบ(โปรแกรมและ database) จำเป็นต้องมี database ที่มี schema ชื่อเดียวกับที่กำหนดใน EDMX file เท่านั้น(schema ได้มาจากการ generate ของ EF Designer ในขั้นตอนการพัฒนาระบบ) ถ้าต้องการให้ EF ทำงานกับ database schema อื่นจะต้องแก้ไข schema ใน EDMX file ให้ตรงกัน หรือไม่ระบุ schema โดยลบส่วนที่ระบุ schema ออก ซึ่งการแก้ไขจะต้องทำการแก้ไขโดยตรงไปที่ EDMX file แล้วทำการ build ใหม่ (ในกรณีที่ไม่ได้เลือก build EDMX file เป็นแบบ embeded resource สามารถแก้ไขที่ .ssdl file ได้โดยไม่ต้อง build ใหม่)

    ในการเปลี่ยน schema ใน EDMX file นั้นจะต้องแก้ทุก EntitySet ที่มี ทำให้มีความเสี่ยงที่จะเกิดความผิดพลาดในการแก้ไข ทำให้ระบบไม่สามารถทำงานได้ และถ้ามีความจำเป็นต้องปรับ Entity Data Model เพื่อเพิ่ม, แก้ไข หรือลบ entity ใดๆ EF Designer จะทำการ update .EDMX file ใหม่ ทำให้ schema ที่แก้ไขไปแล้วกลับมาเหมือนเดิม ต้องเปลี่ยน schema ใหม่อีกครั้ง ก็ยิ่งจะเพิ่มความเสี่ยงที่จะเกิดความผิดพลาด และยุ่งยากในการบริการจัดการ source code

    เราสามารถทำให้กระบวนการแก้ไขหรือลบ schema ใน EDMX file เป็นไปโดยอัตโนมัติ โดยการแก้ใข .csproj เพิ่มกระบวนการแก้ไขหรือลบ schema เข้าไปในขั้นตอนการ build ของ MsBuild หลังจากกระบวนการ “EntityDeployEmbededResource” ของ EF ดังนี้

     

    <Target Name="RemoveSchemaEntityDeployEmbeddedResources" AfterTargets="EntityDeployEmbeddedResources" Condition="'@(EntityDeployEmbeddingItems)' != ''">
      <PropertyGroup>
        <RemoveSchemaEmbeddedResources>"Libs\EFRemoveSchema" $(EntityDeployIntermediateResourcePath)%(EntityDeployEmbeddedResources.EntityDeployRelativeDir)</RemoveSchemaEmbeddedResources>
      </PropertyGroup>
      <Exec WorkingDirectory="$(MSBuildProjectDirectory)" Command="$(RemoveSchemaEmbeddedResources)" />
    </Target>

    “Libs\EFRomoveSchema” เป็นโปรแกรมเล็กๆที่พัฒนาเพื่อลบ schema ใน Entity Data Model ที่อยู่ใน folder   $(EntityDeployIntermediateResourcePath)%(EntityDeployEmbeddedResources.EntityDeployRelativeDir) โดยใช้ เทคนิคการค้นหา attribute ของ node ที่ต้องการใน XML file (EDMX file) เพื่อลบ และบันทึกกลับลงไปที่ XML file นั้นๆ

     

    อ้างอิง : https://msdn.microsoft.com/en-us/data/ee712907

  • การเขียนโปรแกรม JSP เชื่อมต่อ ORACLE

         JSP หรือชื่อเต็มว่า Java Server Page เป็นภาษาที่ใช้ในการพัฒนา Application ที่ทำงานบนเว็บไซต์ โดยรูปแบบการทำงานจะทำงานคล้ายกับภาษา  ASP ,PHP และ .Net รูปแบบการทำงานจะแตกต่างกันตรงที่ JSP เป็น Subset ของภาษา Java โดยรูปแบบการเขียนนั้นจะใช้รูปแบบคำสั่งและชุด SDK ของ Java และใน JSP จะมีนามสกุลของไฟล์เป็น .jsp โดยการทำงานจะทำงานในรูปแบบของ Server และ Client แสดงผลและโต้ตอบกับ User Interface ผ่าน Web Browser เช่น  IE ,Chrome ,Firefox และอื่นๆ โดยจะสามารถทำงานร่วมกับ Client Tags เช่นพวก HTML / JavaScript / CSS และพวก jQuery ให้ได้ผลลัพธ์ตามที่ต้องการ ที่สำคัญ JSP สามารถใช้งานได้ฟรี และสามารถรองรับได้ทุก Platform ไม่ว่าจะเป็น Windows ,Linux และ iOS รวมทั้ง Software อื่น ๆ

         Oracle เป็น Database ชนิดหนึ่งซึ่งเป็นโปรแกรมที่ใช้ในการจัดการฐานข้อมูล โดยจะทำหน้าที่เป็นตัวกลางคอยติดต่อประสานระหว่างผู้ใช้และฐานข้อมูล ทำให้ผู้ใช้งานสามารถใช้งานฐานข้อมูลได้สะดวกขึ้น เช่น การค้นหาข้มูลต่างๆ ภายในฐานข้อมูลที่ง่ายและสะดวก โดยผู้ใช้ไม่จำเป็นต้องทราบถึงโครงสร้างภายในของฐานข้อมูลก็สามารถเข้าใช้ฐานข้อมูลนั้นได้

         เริ่มต้นในการเขียนโปรแกรมที่นิยมการ connect  oracle ด้วย jsp จะใช้การ connect  ด้วย  JDBC โดยในส่วน ภาษา jsp จะสามารถใช้ packet ของ java ที่จะสามารถ import class ของ JDBC มาใช้งานใน  jsp ได้ทันที โดยไม่จำเป็นต้องเขียน โปรแรกม เพิ่มเติมให้ยุ่งยาก

     

    ขั้นตอนที่ 1 สร้างไฟล์ jsp โดยข้อมูลภายในจะมีsyntax เหมือนกับ HTML แต่ต่างกันที่นามสกุลไฟล์ จะเป็นนามสกุล .jsp

    <HTML>
    <HEAD>
    <TITLE>Simple JSP to Oracle connection Example</TITLE>
    </HEAD>
    <BODY>
    </BODY>
    <HTML>
    

    ขั้นตอนที่ 2 ทำการ import library ที่ใช้ในการเชื่อมต่อกับ database oracle ด้วย java.sql.*

    <%@ page import="java.sql.*" %>
    
    <HTML>
    <HEAD>
    <TITLE>Simple JSP to Oracle connection Example</TITLE>
    </HEAD>
    <BODY>
    </BODY>
    <HTML>
    

    ขั้นต้อนที่ 3 สร้างตัวแปลที่ใช้ในการเขื่อมต่อ

    String strdrive ="oracle.jdbc.OracleDriver";   //driver ของ oracle
    String url = "jdbc:oracle:thin:@localhost:1521:xe";  //server database
    String usr = "username";  //user name
    String pwd = "pwd";     //password 

    ขั้นต้อนที่ 4 สร้าง connection การเชื่อมต่อไปยัง database

    Connection conn = null;

    ขั้นตอนที่ 5 ทำการเชื่อมต่อกับ database โดยใช้ ตัวแปลจากข้างต้น

    <%
        Connection conn = null;
        try
        {
            Class.forName(strdrive );
            conn = DriverManager.getConnection(url, usr, pwd);
            out.println("connected....!!");
    
        }
    
        catch(Exception e)
        {
            out.println("Exception : " + e.getMessage() + "");
        }
    
    
    %>

    ขั้นตอนที่6 ต้องสร้างตัวแปลเพื่อใช้ในการประมวลผล

    Statement st; // ใช้ในการรับคำสัง sql
    ResultSet rs;  //เก็บข้อมูลที่ได้ขาการประมวลผล
    

    ขั้นตอนที่ 7 ทดสอบการทำงาน

    String SQL="select T1 from table";
    rs=st.executeQuery(SQL);
           while(rs.next())
           {
              out.println("number is: "+rs.getString(1));%>          
           } 

     

  • Oracle Database 12CR1 monitoring with MRTG

    • OS: Oracle Enterprise Linux  7.2  (CentOS 7.2)
    • วิธีติดตั้ง MRTG สามารถติดตั้งได้โดยสามารถดูคู่มือที่ ติดตั้ง mrtg บน ubuntu อาจไม่เหมือนกันแต่สามารถทำได้ทำนองเดียวกัน
    • กราฟสำหรับ Idle CPU and Load average, CPU Time spent waiting for IO, Traffic Analysis for eth0, TCP Current Establish สามารถใช้ script เดียวกับลิงค์ในข้อ ๒ ได้เลย
    • สร้างแฟ้ม /etc/mrtg/get-memory.sh มีข้อความว่า
      #!/bin/bash
      FREE=$(free |grep "Mem:"|awk '{print $7}')
      SWAP=$(free |grep "Swap:"|awk '{print $3}')
      TIME=$(uptime)
      echo "${FREE}"
      echo "${SWAP}"
      echo "$TIME"
      hostname 

      สร้างแฟ้ม /etc/mrtg/myhost-memory.cfg มีข้อความว่า
      WorkDir: /var/www/mrtg/myhost
      Target[myhost-mem]:`/etc/mrtg/get-memory.sh`
      MaxBytes[myhost-mem]: 20000000000
      Title[myhost-mem]: Free Memory and Swap Used
      PageTop[myhost-mem]: <H1>Free Memory and Swap Used</H1>
      ShortLegend[myhost-mem]: bytes
      YLegend[myhost-mem]: bytes
      LegendI[myhost-mem]:  Free Memory:
      LegendO[myhost-mem]: Swap Used:
      Legend1[myhost-mem]: Free memory, in bytes
      Legend2[myhost-mem]: Swap Used, in bytes
      Options[myhost-mem]: gauge, nopercent, growrightทดสอบสร้างภาพต้นแบบด้วยคำสั่ง

      env LANG=C /usr/bin/mrtg/myhost-memory.cfgปรับปรุงแฟ้ม index.html ด้วยคำสั่ง
      indexmaker --column=2 --output=/var/www/mrtg/myhost/index.html /etc/mrtg/myhost-cpu.cfg /etc/mrtg/myhost-cpu-io.cfg /etc/mrtg/myhost-speed-eth0.cfg /etc/mrtg/myhost-tcpestab.cfg /etc/mrtg/myhost-memory.cfg

    • โฟลเดอร์ที่ต้องเฝ้าระวังได้แก่ /u02/app/oracle/adump, /u02/app/oracle/diag/rdbms/regist/regist/alert, /u02/app/oracle/rdbms_trace ซึ่งเป็นโฟลเดอร์สำหรับเก็บ Log ไฟล์ต่างๆ ซึ่งอาจมีขนาดเพิ่มขึ้นจนระบบไม่สามารถให้บริการได้ และโฟลเดอร์ /u03 เป็นโฟลเดอร์ที่ใช้เก็บ archive log (ในกรณีที่ฐานข้อมูลเปิด archive log mode)
      • สร้างแฟ้ม /etc/mrtg/get-diskfree-misc1.sh มีข้อความว่า
        #!/bin/bash
        adump=$(du -sm /u02/app/oracle/adump|awk '{ print $1 }')
        free=$(df -m /u02|grep u02|awk '{ print $4 }')
        TEMP=$(uptime|grep -o "load average.*"|awk '{print $3}'|cut -d',' -f 1)
        LOAD=$(echo "${TEMP:-0} * 100"|bc|cut -d'.' -f 1)
        TIME=$(uptime)
        echo "${adump}"
        echo "${free}"
        echo "$TIME"
        hostname
      • สร้างแฟ้ม /etc/mrtg/myhost-diskfree-misc1.cfg มีข้อความว่า
        WorkDir: /var/www/mrtg/myhost
        Target[myhost-misc1]:`/etc/mrtg/get-diskfree-misc1.sh`
        MaxBytes[myhost-misc1]: 20000000000
        Title[myhost-misc1]: Free disk space and disk Used of /u02/app/oracle/adump
        PageTop[myhost-misc1]: Free disk space and disk Used of /u02/app/oracle/adump
        ShortLegend[myhost-misc1]: bytes
        kMG[myhost-misc1]: M,G,T
        kilo[myhost-misc1]: 1024
        YLegend[myhost-misc1]: bytes
        LegendI[myhost-misc1]: Disk Used:
        LegendO[myhost-misc1]: Free Disk:
        Legend1[myhost-misc1]: Disk usage, in Bytes
        Legend2[myhost-misc1]: Free Disk Space, in Bytes
        Options[myhost-misc1]: gauge, nopercent, growright
        Timezone[myhost-misc1]: Bangkok

        ทดสอบสร้างภาพต้นแบบด้วยคำสั่ง
        env LANG=C /usr/bin/mrtg /etc/mrtg/myhost-diskfree-misc1.cfg
        ปรับปรุงแฟ้ม index.html ด้วยคำสั่ง
        indexmaker --column=2 --output=/var/www/mrtg/myhost/index.html /etc/mrtg/myhost-cpu.cfg /etc/mrtg/myhost-cpu-io.cfg /etc/mrtg/myhost-speed-eth0.cfg /etc/mrtg/myhost-tcpestab.cfg /etc/mrtg/myhost-memory.cfg /etc/mrtg/myhost-diskfree-misc1.cfg
      • สร้างแฟ้มเลียนแบบข้อ 1 และ 2 สำหรับโฟลเดอร์ที่เหลือ
      • แก้ไขแฟ้ม /etc/mrtg/mymrtg.sh เพิ่มข้อความ  env LANG=C /usr/bin/mrtg /etc/mrtg/myhost-diskfree-misc1.cfg ต่อท้ายไฟล์และเพิ่มทุกไฟล์ของทุกโฟลเดอร์
      • สำหรับโฟลเดอร์ /u03 ให้สร้างแฟ้ม /etc/mrtg/get-diskfree-u03.sh มีข้อความว่า
        #!/bin/bash
        used=$(df -m /u03|grep u03|awk '{ print $3 }')
        free=$(df -m /u03|grep u03|awk '{ print $4 }')
        TEMP=$(uptime|grep -o "load average.*"|awk '{print $3}'|cut -d',' -f 1)
        LOAD=$(echo "${TEMP:-0} * 100"|bc|cut -d'.' -f 1)
        TIME=$(uptime)
        echo "${used}"
        echo "${free}"
        echo "$TIME"
        hostname
        nof=$(ls -d1 /u03/app/oracle/fast_recovery_area/REGIST/archivelog/* |wc -l)
        max=3if [ "${nof}" == "${max}" ]
        then
                nod=$(expr ${max} - 1)
                f2d=$(ls -d1 /u03/app/oracle/fast_recovery_area/REGIST/archivelog/*|head -${nod})
                rm -rf ${f2d}
                su - oracle -c "/bin/sh /home/oracle/reclaim.sh"
        fi
        สร้างแฟ้ม /home/oracle/reclaim.sh มีข้อความว่า
        rman target / <<EOF
        crosscheck archivelog all;
        delete noprompt expired archivelog all;
        quit
        EOF
        สร้างแฟ้ม /etc/mrtg/myhost-diskfree-u03.cfg มีข้อความว่า
        WorkDir: /var/www/mrtg/myhost
        Target[myhost-u03]:`/etc/mrtg/get-diskfree-u03.sh`
        MaxBytes[myhost-u03]: 20000000000
        Title[myhost-u03]: Free disk space and disk Used of /u03
        PageTop[myhost-u03]: Free disk space and disk Used of /u03
        ShortLegend[myhost-u03]: bytes
        kMG[myhost-u03]: M,G
        kilo[myhost-u03]: 1024
        YLegend[myhost-u03]: bytes
        LegendI[myhost-u03]: Disk Used:
        LegendO[myhost-u03]: Free Disk:
        Legend1[myhost-u03]: Disk usage, in Bytes
        Legend2[myhost-u03]: Free Disk Space, in Bytes
        Options[myhost-u03]: gauge, nopercent, growrightทดสอบสร้างภาพต้นแบบด้วยคำสั่ง
        env LANG=C /usr/bin/mrtg /etc/mrtg/myhost-diskfree-u03.cfg
        อย่าลืมปรับปรุงแฟ้ม index.html ด้วย
    • เฝ้าระวังขนาดของ Tablespace SYSTEM และ USERS
      • สร้างแฟ้ม /etc/mrtg/get-tablespace-system.sh มีข้อความว่า
        #!/bin/bash
        used=$(su - oracle -c "sh /home/oracle/monitor/tablespacesize.sh"|grep SYSTEM|awk '{ print $2 }'|sed -e 's/,//g')
        used=$(expr ${used} \* 1024)
        free=$(su - oracle -c "sh /home/oracle/monitor/tablespacesize.sh"|grep SYSTEM|awk '{ print $3 }'|sed -e 's/,//g')
        free=$(expr ${free} \* 1024)
        TIME=$(uptime)
        echo "${used}"
        echo "${free}"
        echo "$TIME"
        hostname

        สร้างแฟ้ม /home/oracle/monitor/tablespacesize.sh มีข้อความว่า
        #!/bin/bash
        sqlplus / as sysdba << EOF
        col "Tablespace" for a22
        col "Used MB" for 99,999,999
        col "Free MB" for 99,999,999
        col "Total MB" for 99,999,999
        select df.tablespace_name "Tablespace",
        totalusedspace "Used MB",
        (df.totalspace - tu.totalusedspace) "Free MB",
        df.totalspace "Total MB",
        round(100 * ( (df.totalspace - tu.totalusedspace)/ df.totalspace))
        "Pct. Free"
        from
        (select tablespace_name,
        round(sum(bytes) / 1048576) TotalSpace
        from dba_data_files
        group by tablespace_name) df,
        (select round(sum(bytes)/(1024*1024)) totalusedspace, tablespace_name
        from dba_segments
        group by tablespace_name) tu
        where df.tablespace_name = tu.tablespace_name ;
        quit
        EOF

        สร้างแฟ้ม /etc/mrtg/myhost-tablespace-system.cfg
        WorkDir: /var/www/mrtg/myhost
        Target[myhost-system]:`/etc/mrtg/get-tablespace-system.sh`
        MaxBytes[myhost-system]: 20000000000
        Title[myhost-system]: Tablespace SYSTEM disk usage
        PageTop[myhost-system]: Tablespace SYSTEM Disk Usage
        ShortLegend[myhost-system]: bytes
        kMG[myhost-system]: k,M,G
        kilo[myhost-system]: 1024
        YLegend[myhost-system]: bytes
        LegendI[myhost-system]: Disk Used:
        LegendO[myhost-system]: Free Disk:
        Legend1[myhost-system]: Disk usage, in Bytes
        Legend2[myhost-system]: Free Disk Space, in Bytes
        Options[myhost-system]: gauge, nopercent, growrightทดสอบสร้างภาพต้นแบบด้วยคำสั่ง
        env LANG=C /usr/bin/mrtg /etc/mrtg/myhost-tablespace-system.cfgอย่าลืมปรับปรุง index.html ด้วย
      • ทำแบบเดียวกันกับ tablespace users
    • จบขอให้สนุก
  • ฟังก์ชัน 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

  • ข้อจำกัดและข้อควรระวังในการใช้เงื่อนไข 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

     

     

     

     

     

     

  • เกร็ดความรู้ประกอบการกู้หรือย้ายฐานข้อมูล SQL Server

    Count record แต่ละ tables ใน database

    SELECT T.name AS [TABLE NAME], I.row_count AS [ROWCOUNT]
    FROM sys.tables AS T
    INNER JOIN
    sys.dm_db_partition_stats AS I
    ON T.object_id = I.object_id AND I.index_id < 2
    ORDER BY I.row_count DESC

    หา Trigger ทั้งหมดในทุกๆ tables ใน Database

    SELECT [so].[name] AS [trigger_name], USER_NAME([so].[uid]) AS [trigger_owner],
    USER_NAME([so2].[uid]) AS [table_schema], OBJECT_NAME([so].[parent_obj]) AS [table_name],
    OBJECTPROPERTY( [so].[id], ‘ExecIsUpdateTrigger’) AS [isupdate],
    OBJECTPROPERTY( [so].[id], ‘ExecIsDeleteTrigger’) AS [isdelete],
    OBJECTPROPERTY( [so].[id], ‘ExecIsInsertTrigger’) AS [isinsert],
    OBJECTPROPERTY( [so].[id], ‘ExecIsAfterTrigger’) AS [isafter],
    OBJECTPROPERTY( [so].[id], ‘ExecIsInsteadOfTrigger’) AS [isinsteadof],
    OBJECTPROPERTY([so].[id], ‘ExecIsTriggerDisabled’) AS [disabled]
    FROM sysobjects AS [so]
    INNER JOIN sysobjects AS so2 ON so.parent_obj = so2.Id WHERE [so].[type] = ‘TR’

    ค้นหาข้อความในทุกๆ Stored Procedure ใน Database

    SELECT DISTINCT obj.name AS Object_Name,obj.type_desc
    FROM sys.sql_modules sm INNER JOIN sys.objects obj ON
    sm.object_id=obj.object_id
    WHERE sm.definition Like ‘%xxx%’

    วิธีการ Rebuild Full-text Catalogs of Database

    Use Management Studio
    1. In Object Explorer, expand the server, expand Databases, and then expand the
    database that contains the full-text catalogs that you want to rebuild.
    2. Expand Storage, and then right-click Full Text Catalogs.
    3. Select Rebuild All.
    4. To the question, Do you want to delete all full-text catalogs and rebuild them?,
    click OK.
    5. In the Rebuild All Full-Text Catalogs dialog box, click Close.

    rebuild all index in Database

    use DatabaseName;
    DECLARE @TableName varchar(255)
    DECLARE TableCursor CURSOR FOR
    SELECT table_name FROM information_schema.tables
    WHERE table_type = ‘base table’
    OPEN TableCursor
    FETCH NEXT FROM TableCursor INTO @TableName
    WHILE @@FETCH_STATUS = 0
    BEGIN
    DBCC DBREINDEX(@TableName,’ ‘,90)
    FETCH NEXT FROM TableCursor INTO @TableName
    END
    CLOSE TableCursor
    DEALLOCATE TableCursor

    ค้นหา tables ทั้งหมดใน Database ที่มี Identity Column

    select o.name,’set identity_insert [‘+s.name+’].[‘+o.name+’] ON;’ as ION,’set identity_insert
    [‘+s.name+’].[‘+o.name+’] OFF;’ as IOff
    from sys.objects o
    inner join sys.schemas s on s.schema_id=o.schema_id
    where o.[type]=’U‘ and
    exists(select 1 from sys.columns where object_id=o.object_id and is_identity=1)
    order by o.name

    ตัอย่างการ insert table ที่มี Identity Column

    set identity_insert [dbo].[AGroup] ON;
    insert into [xDB].[dbo].[AGroup] ([AGroupID],[AGroupName])
    select [AGroupID],[AGroupName]
    from [aDB].[dbo].[AGroup]
    order by AGroupID;
    set identity_insert [dbo].[AGroup] OFF;

    Compare two table data

    แบบที่ 1
    select * from zlog1
    except
    select * from zlog
    แบบที่ 2
    select * from
    ( select checksum(*) as chk, id as k from zlog1) as t1
    left join
    ( select checksum(*) as chk, id as k from zlog) as t2 on t1.k = t2.k
    where t1.chk <> t2.chk

    ## เสนอแนะ ถ้ามีการกู้หรือย้ายฐานข้อมูลโดยที่ตัวเดิมยังเปิดใช้งานอยู่ สิ่งที่ควรทำคือให้สร้าง User ขึ้นมาใหม่ที่มีสิทธิใช้งานเฉพาะ Database ตัวใหม่เท่านั้น เพื่อป้องกันการเรียกใช้งานฐานข้อมูลทั้งสองที่โดยที่เราอาจจะไม่รู้ซึ่งจะทำให้ข้อมูลในฐานข้อมูลมั่วมากจนอาจจะเกินเยียวยา โดยมากจะเกิดกับ การเขียน code Stored Procedure ที่มีการระบุชื่อ Database ไว้ใน Stored Procedure

  • การกู้ Suspect Database ของ SQL Server

    Recovery SQL Server Suspect Database

    สาเหตุของ Suspect Mode

    การที่ฐานข้อมูล SQL Server เข้าสู่ Mode Suspect นั้นมีได้จากหลายสาเหตุ ดังนี้

    • Hardware เกิดความเสียหาย
    • มีการปิด (Shutdown) ฐานข้อมูลที่ไม่เหมาะสม คือปิดโดยที่ยังมีกระบวนการทำงานยังไม่เสร็จสมบูรณ์ หรือมีบาง Transaction ค้างอยู่
    • เกิดความเสียหายกับ Database Files (*.mdf,*.log)
    • SQL Server ไม่พบ Device ที่เก็บ Files
    • SQL Server ไม่พบ Database Files
    • Database Resource ถูกใช้งานอยู่โดย Operation System
    • ไม่มีพื้นที่มากพอใน Page space ที่มีการเพิ่ม (Insert) ข้อมูลเข้าไป

    วิธีแก้ปัญหา

    แต่ต้องอยู่บนพื้นฐานที่ไม่มีอะไรผิดปกติที่เกิดจาก Devices หรือ ตัว Database Files จึงจะ สามารถ Recovery โดยวิธีดังนี้

    EXEC sp_resetstatus [YourDatabase];
    ALTER DATABASE [YourDatabase] SET EMERGENCY
    DBCC checkdb([YourDatabase])
    ALTER DATABASE [YourDatabase] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
    DBCC CheckDB ([YourDatabase], REPAIR_ALLOW_DATA_LOSS)
    ALTER DATABASE [YourDatabase] SET MULTI_USER

    อธิบาย

    • EXEC sp_resetstatus [YourDatabase];

    เป็นคำสั่งทำการปิด suspect mode เมื่อทำแล้วก็ต้อง Stop และ Restart SQL Server ด้วย

    • ALTER DATABASE [YourDatabase] SET EMERGENCY

    เป็นคำสั่งให้ Database เข้าสู่ READ_ONLY MODE และจำกัดการเข้าถึงให้เข้าได้เฉพาะ SysAdmin Account เท่านั้น

    • DBCC checkdb([YourDatabase])

    CheckDB จะตรวจสอบการจัดสรรทรัพยากรสำหรับทุกๆ Object และตรวจสอบความสมบูรณ์ของโครงสร้างฐานข้อมูลทั้งหมด

    • ALTER DATABASE [YourDatabase] SET SINGLE_USER WITH ROLLBACK IMMEDIATE

    กำหนดให้สามารถเข้าใช้งานฐานข้อมูลได้เพียง User เดียวเท่านั้น

    • DBCC CheckDB ([YourDatabase], REPAIR_ALLOW_DATA_LOSS)

    จะมีการทำงานหลายขั้นตอนซึ่งแรกสุดจะเป็นการตรวจสอบทรัพยากรต่างๆและมีการตรวจสอบโครงสร้างของฐานข้อมูลจะมีการ Run คำสั่ง DBCC CheckAlloc, DBCC CheckTable, DBCC CheckCatalog ทั้งฐานข้อมูล มีการตรวจสอบข้อมูลของแต่ละ Indexed View ตรวจสอบ Link-Level Consistency ระหว่าง table metadata และ file system directories ตรวจสอบ Service Broker Data ในฐานข้อมูลทั้งหมด

    ส่วน Option REPAIR_ALLOW_DATA_LOSS นั้นจะมีการพยายามซ้อมแซ่มฐานข้อมูลในส่วนที่เสียไปตามรายการที่ได้จากการ CheckDB และเป็นการอนุญาตให้ข้อมูลสามารถศูนย์หายได้บาง

    • ALTERDATABASE [YourDatabase] SET MULTI_USER

    เป็นการ set database ให้กลับมาใช้งานตามปกติ

    หลังจากขั้นตอนเหล่านี้แล้ว ผมจะทำการ Shutdown Server แล้วเปิดขึ้นมาใหม่แล้วลองใช้คำสั่ง DBCC CheckDB() ตรวจสอบอีกครั้งเพื่อความแน่ใจ

  • T-SQL นับจำนวน record ที่เกิดจากการสั่ง execute Dynamic Query

    ตัวอย่างไม่ต้องอธิบายมาก

    DECLARE @str_SQL NVARCHAR(100), @rowcount INT –กำหนดตัวแปร
    SET @str_SQL = ‘select * from sysobjects’ — set ค่าให้ตัวแปร
    EXEC sp_executesql @str_SQL — สั่ง execute dynamic query
    SELECT @rowcount = @@ROWCOUNT — get ค่าที่จำนวน record ที่เกิดผลกระทบจากการ execute dynamic query
    PRINT ‘@rowcount = ‘ + CAST(@rowcount AS VARCHAR(4)) — แสดงผลออกหน้าจอ

     

  • วิธีการใช้ Google Sheets เป็นระบบเฝ้าระวังเว็บไซต์ (Website Monitoring) จากภายนอกองค์กร

    จาก “วิธีการใช้ Google Sheets เป็นฐานข้อมูล” ซึ่งได้กล่าวถึงพื้นฐานการพัฒนา Google Apps Script เพื่อใช้ต่อยอดความสามารถของ Google Sheets สามารถนำมาประยุกต์ใช้เพื่อเฝ้าระวังบริการเว็บไซต์จากภายนอกระบบเครือข่ายขององค์กรได้ (จาก Google Cloud Infrastructure เลยทีเดียว)

    ขั้นตอนในการทำ

    1. ในโปรเจค ProjectMyDB สร้างไฟล์ monitoring.gs ดังภาพ
    2. ประกอบด้วย 3 ฟังก์ชัน คือ
      function check_website(url) {  
        var response = UrlFetchApp.fetch(url, {muteHttpExceptions: true});
        return response.getResponseCode();
      }
      
      function doLog(timestamp, responseCode, timeDiff) {
        var ss = SpreadsheetApp.openByUrl('https://docs.google.com/a/psu.ac.th/spreadsheets/d/1HJmyqiBYC_AEATmdUWakLgHFyYGqSqeqSA8xEw-8o-c/edit');
        SpreadsheetApp.setActiveSpreadsheet(ss);
        SpreadsheetApp.setActiveSheet(ss.getSheetByName("Log"));
        var activeSheet=ss.getActiveSheet();
        activeSheet.appendRow([timestamp, responseCode, timeDiff]);
      }
      
      function getTime() {
        var startTime = new Date() ;
        var responseCode=check_website("http://www.psu.ac.th");
        var endTime = new Date() ;
        var timeDiff = endTime-startTime;  
        doLog(Utilities.formatDate(new Date(), "GMT+7", "yyyyMMdd-HHmmss") , responseCode , timeDiff);
      }
    • check_website ใช้ UrlFetchApp เพื่อ url ของเว็บไซต์ แล้วรีเทิร์นผล Response Code ของ HTTP Protocol กลับไป
    • doLog ใช้สำหรับเพิ่มค่า timestamp, responseCode และ timeDiff (เวลาในการตอบสนอง) ลงใน Sheet “Log” ใน Google Sheets ที่กำหนดไว้
    • getTime ใช้คำนวนเวลาตั้งแต่เริ่มต้น แล้วเรียกใช้ฟังก์ชั่น check_website และ จับเวลาที่สิ้นสุด จากนั้นคำนวนเป็นเวลาในการตอบสนอง (timeDiff) แล้ว เรียกฟังก์ขัน doLog เพื่อเขียนข้อมูลต่อไป
    1. สร้าง Trigger ด้วยเมนู Resources > Current project’s triggers
    2. เลือกฟังก์ชัน getTime กำหนดเป็น Time-driven ทำงานในหน่วยนาที (Minute timmer) และ ทำงานทุกๆ 5 นาที แล้วกดปุ่ม Save
    3. ผลการทำงาน และการสร้าง Chart ประกอบทำให้สามารถเห็นแนวโน้มได้

    จากตัวอย่างข้างต้น ทำให้เห็นว่า การใช้ Google Apps Script ร่วมกับ Google Sheet สามารถสร้างระบบเฝ้าระวังเว็บไซต์จากภายนอกองค์กรได้อย่างง่ายๆ และไม่มีค่าใช้จ่าย ทำให้เห็นภาพการใช้งานจากภายนอกได้เป็นอย่างดี