2013年5月22日

Oracle 分析統計函數 - OVER 累加、LAG 上一筆、LEAD 下一筆

Oracle 的資料庫在使用率之所以翌立不搖,就是因為穩定、效能高、維護容易,
再來就是提供 200 多種的相關 SQL 語法。
參考 Mastering Oracle SQL and SQL Plus 的書籍,就有提到進階的 Oracle SQL 指令。

分析統計的指令 OVER 說明如下:
SELECT 統計函數(欄位) OVER (window spec) FROM table
其實可以把 OVER 當作同一個條件下的子查詢,並且有 Current Row 的概念。
整個 table 的資料在某些條件下篩選的資料就是 window,也就是我們所下 where 條件出來的資料。

統計函數就不多說明了,一般就是 SUM、AVERAGE、MIN、MAX…

windows-spec 指令的語法就是: partition by 欄位 + order by 欄位 + range-spec
1. partiton by:就是區分成多個區段做分析運算
2. order by:要先跟 Database 說依什麼方式的順序來計算,所以就要在此區段來定義
3. range-spec:要計算的資料範圍,下面會再做說明

範例要累加料件庫存的數量:
依料件不同各別累加,累加的順序為 img01,img02,img03,img04
select img01,img02,img03,img04,img10,sum(img10) over (partition by img01 order by img01,img02,img03,img04) from img_file
where img10 > 0
order by img01,img02,img03,img04

range-spec 說明:
RANGE + BETWEEN 開始 AND 結束
RANGE + UNBOUNDED PRECEDING
ROW + BETWEEN 開始 AND 結束
ROW + UNBOUNDED PRECEDING

BETWEEN…AND…:開始或結束,可以用 CURRENT ROW(目前)、PRECEDING(往前)、FOLLOWING(往後)

上面的範例再加上資料的範圍,結果會是相同的,依料件不同各別累加,累加的順序為 img01,img02,img03,img04
select img01,img02,img03,img04,img10,sum(img10) over (partition by img01 order by img01,img02,img03,img04 range unbounded preceding) from img_file
where img10 > 0
order by img01,img02,img03,img04
或是
select img01,img02,img03,img04,img10,sum(img10) over (partition by img01 order by img01,img02,img03,img04 row between unbounded preceding and current row) from img_file
where img10 > 0
order by img01,img02,img03,img04

範例加總往前1筆到往後1筆的數量:
select img01,img02,img10,
sum(img10) over (partition by img01 order by img01,img02 rows between 1 preceding and 1 following) 
from img_file
where img10 > 0
order by img01,img02
要注意,當用 BETWEEN…AND…超過 partition by 的運算範圍的時候,partition by 就不會有作用。

當然也可以做到是統計數字是往下累加(遞增)的,還是往上累加(遞減)的方式。

統計函數還有提供 LAG 上一筆、LEAD 下一筆,想要比較上一筆或下一筆的資料就可以做資料的判斷。
範例為帶出上一筆的庫存數量:
select img01,img02,img10,lag(img10) over (partition by img01 order by img01,img02)
from img_file
where img10 > 0
order by img01,img02

如果在 SQL 想要能夠抓取上一筆的欄位或是下一筆的欄位資料,也是可以用 OVER 的方式來達到。

2013年5月17日

利用拖拉的功能,實現資料排序、複製或移除

在 Genero 有提供 Drag & Drop 功能,也就是使用滑鼠(或觸控螢幕)來進行資料的搬移。
這樣就可以調整資料的順序,或是將資料由 A 搬到 B ,或者可以把資料做移除的動作。

DEFINE l_dd ui.DragDrop 要先宣告螢幕拖拉的變數
必須要在 DISPLAY ARRAY 再加上 Drag 和 Drop 的區段,使用方法如下:
ON DRAG_START(l_dd) :當開始拖拉來源端 Object 的某一筆資料時
ON DRAG_FINISHED(l_dd):當來源端的 Object 拖拉完成時
ON DRAG_ENTER(l_dd):當拖拉到目的端 Object 時
ON DRAG_OVER(l_dd):當拖拉到目的端 Object 的某一個 Cursor 時
ON DROP(l_dd):當拖拉到目的端 Object 完成時

所以要在 DISPLAY ARRAY 可以進行拖的動作就要加上 DRAG ,要可以進行拉的動作就要加上 DROP ,不然滑鼠就會出現禁止的圖示而無法完成拖拉。

再來就是相關的指令說明:
1. setOperation:預設的拖拉動作
    CALL l_dd.setOperation(""):預設不能拖拉,其實你覺得拖拉很討厭的話,可以執行此指令
    CALL l_dd.setOperation("move"):預設為資料搬移
    CALL l_dd.setOperation("copy"):預設為資料複製

2. addPossibleOperation:額外增加拖拉的動作
    CALL l_dd.addPossibleOperation("copy"):通常都是預設設定為搬移,然後拖拉時加上 Ctrl 鍵,就可以 copy 的動作

3. setMimeType:設定 MIME 的格式,當要拉到外部的程式時,必須要可以支援拖拉的程式才行(記事本就沒這個功能了)
    CALL l_dd.setMimeType("text/plain")
    CALL l_dd.setMimeType("text/uri-list")
    CALL l_dd.setMimeType("text/x-vcard")
                         :
    (自定義的 MIME 格式)

4. setBuffer:設定拖資料時暫存的資料
    CALL l_dd.setBuffer(g_sfa[arr_curr()].sfa03):舉例要把 sfa03 的欄位內容拖拉的方式複製到 Excel 來貼上

5. setFeedback:設定拖拉的模式為取代或是新增
    CALL l_dd.setFeedback("all") :可取代也可以新增
    CALL l_dd.setFeedback("insert"):只能新增
    CALL l_dd.setFeedback("select"):只能取代

6. getLocationRow() :回傳目前要拖拉的 cursor 行號
    LET l_ac = l_dd.getLocationRow():舉例目前要拖拉的 cursor 行號指派給 l_ac 變數

7. getLocationParent():回傳 Tree View 的 Node,用法和 getLocationRow() 相同

8. getOperation():回傳拖拉動作是搬移還是複製
    CASE l_dd.getOperation()
          WHEN "move"
                 MESSAGE "MOVE"
          WHEN "copy"
                 MESSAGE "COPY"
    END CASE

9. selectMimeType():所拖拉的資料格式
    CASE
            WHEN dnd.selectMimeType("text/plain") 
            WHEN dnd.selectMimeType("text/uri-list") 
    END CASE

範例為要把單身的資料用拖拉的方式進行資料的排序
DEFINE   l_dd      ui.DragDrop,
              l_n_old   LIKE type_file.num5,
              l_n_new   LIKE type_file.num5
DEFINE   l_i       LIKE type_file.num5
DISPLAY ARRAY g_oeb TO s_oeb.* ATTRIBUTE(COUNT=g_rec_b) 
         ON DRAG_START(l_dd)
            LET l_n_old = arr_curr()
            CALL l_dd.setOperation("move")
         ON DRAG_FINISHED(l_dd)
            IF l_n_old <> l_n_new THEN
               IF l_n_old < l_n_new THEN
                  FOR l_i = l_n_old+1 TO l_n_new 
                      LET g_oeb[l_i].oeb03n= l_i-1
                  END FOR 
                  LET g_oeb[l_n_old].oeb03n= l_n_new
               ELSE 
                  FOR l_i = l_n_new TO l_n_old-1
                      LET g_oeb[l_i].oeb03n= l_i+1
                  END FOR 
                  LET g_oeb[l_n_old].oeb03n= l_n_new
               END IF 
               FOR l_i = 1 TO g_tc_sfb1.getlength()
                   UPDATE oeb_file SET oeb03= g_oeb[l_i].oeb03n
                    WHERE oeb01 = g_oea.oea01
                      AND oeb03 = g_oeb[l_i].oeb03
               END FOR 
            END IF
            CALL DIALOG.setCurrentRow("s_oeb",l_n_new)     # 將 cursor 移到新的位置
            CALL t400_b_fill()                                                     # 更新資料
            CONTINUE DISPLAY 
         ON DRAG_OVER(l_dd)
            LET l_n_new = l_dd.getLocationRow()
         ON DROP(l_dd)
END DISPLAY
此範例只提供參考用,要注意欄位是否為 INDEX 有重複的情況,會造成 update 失敗。

結論:拖拉方式的操作在某些情況可以帶來使用者更方便的使用,試試看吧~~

2013年5月16日

Linux 檔案同步,AP主機程式保持一致

當有二台 TIPTOP 主機提供 AP 服務時,希望程式修改後也能夠定期同步到另一台主機,
這時候就可以用 rsync 的指令,快速的把不相同的檔案複製到另一台主機上,權限和檔案屬性也不會改變。

rsync 的指令就不加以敘述,請參考其他的網站的說明。

同步指令如下:
rsync -av -e ssh root@192.168.1.100:/u1/topprod /u1
命令的說明是用 ssh 以 root 帳號登入到 192.168.1.100 主機,然後將 /u1/topprod / 目錄的所有檔案同步到此主機的 /u1 目錄。
記得目的端就不要再加上 topprod 不然會建立新的資料夾。

因為不同主機 IP 位置不同,所以必須排除 tiptop_env 檔案,所以再加上 --exclude 後面加上相對的路徑名稱。
不能加上絕對的路徑,會沒辦法排除。
rsync -av -e ssh --exclude tiptop/bin/tiptop_env root@192.168.1.100:/u1/topprod /u1
再來就輸入來源主機的 root 的密碼就開始進行檔案同步。

如果檔案很多的話,需建立 list 檔來排除。
建立 vi /root/sync2.list 檔,排除以下的檔案。
tiptop/bin/tiptop_env
tiptop/lib/4gl/cl_user.4gl
tiptop/lib/42m/lib_cl_user.42m
tiptop/lib/42m/lib_cl_user.4gl
tiptop/lib/42m/lib.42x

然後再加上 --exclude-from 參數來排除指定的 list 檔案,
rsync -av -e ssh --exclude-from=/root/sync2.list root@192.168.1.100:/u1/topprod /u1

如果希望能夠排程的同步檔案,必須要先讓 ssh 不需要密碼就可以登入。
在目的主機 /root 目錄建立 .ssh 的資料夾, mkdir /root/.ssh 。
到 .ssh 目錄執行 ssh-keygen -d ,會產生二個檔案 id_dsa 和 id_dsa.pub 。
然後再到來源主機,一樣建立相同的資料夾 /root/.ssh 。
將 id_dsa.pub 檔案傳送到來源主機,並更名為 authorized_keys2 。
執行:scp id_dsa.pub 192.168.1.100:/root/.ssh/authorized_keys2

測試看看是否不需輸入密碼:ssh root@192.168.1.100
如果還是會跳出密碼的驗証,請修改 authorized_keys2 權限改為 640 (chmod 640 authorized_keys2 )

再來就是建立一個檔名為 sync2 批次檔。
vi  /root/sync2
rsync -av -e ssh --exclude-from=/root/sync2.list root@192.168.1.100:/u1/topprod /u1
檔案權限改為 700  (chmod 700 sync2),可執行檔。

執行看看是否成功:./sync2

再來就是加到 cron job 每 5 分鐘同步一次。
執行 crontab -e 編輯排程檔:
加上:*/5 * * * * /root/sync2

這樣每 5 分鐘就會將來源主機和目的主機有差異的檔案進行複製的動作。
這樣AP主機的程式就會保持一致。

2013年5月9日

鼎新所提供的 Java Mail 的功能

鼎新提供 TIPTOP 可以寄送 Mail 的功能,
也有許多程式也已經有把程式碼都加進去裡面。
所以備忘一下,日後比較方式使用。

先維護 xml 檔:/u1/topprod/tiptop/ds4gl2/bin/javamail/genxml
維護 mail server 主機、SMTP Port、驗証使用者名稱、密碼…等。

再來就是 4GL 的部份:
g_xml.subject:郵件主旨

g_xml.body:郵件內容的檔案路徑,所以要先把內容存成一個 Temp File 的方式
將郵件的內文存到 Temp File:
LET l_buf1 = FGL_GETENV("TEMPDIR")
LET l_buf1 = l_buf1,"/report_context_" || FGL_GETPID() || ".txt"
LET l_buf = "echo '" || l_buf || "' > " || l_buf1
RUN l_buf WITHOUT WAITING
LET g_xml.body = l_buf1

g_xml.sender:寄件者 Mail Address:寄件者名稱 (中間用冒號 : 區隔)

g_xml.recipient:收件者 Mail Address:收件者名稱 (中間用冒號 : 區隔),多個用收件件用分號 ; 來區別
g_xml.ccrecipient:複本 Mail Address:收件者名稱 (中間用冒號 : 區隔),多個用收件件用分號 ; 來區別
g_xml.bccrecipient:密件複本 Mail Address:收件者名稱 (中間用冒號 : 區隔),多個用收件件用分號 ; 來區別

g_xml.attach:附件的檔案位置,多個附件用分號 ; 來區別

CALL cl_jmail():發送 e-mail

就可以把 e-mail 透過鼎新所提供的 Java Mail 寄送出去,
收件者建議在 aooi998 進行維護,寄件者可以抓 gen_file 資料。

郵件的內容為 HTML 的格式,所以如果是資料的話可以用 TABLE 的方式來呈現。
<TABLE BORDER=1 WIDTH=400px STYLE=border-collapse:collapse; BORDERCOLOR=black>
<TR align=center><TD>訂單+項次</TD><TD>原交期</TD><TD>新交期</TD></TR>
<TR><TD>g_oea.oea01</TD><TD>g_oeb.oeb15</TD><TD>g_oeb.oeb16</TD></TR>
</TABLE>

進階的 Dialog 說明-可多重選取的模式

先前有討論過更便利的 DIALOG - 同時單頭單身查詢/輸入的 Multiple Dialogs 功能,
可以同時進行多個 Display、Input、Display Array、Input Array 的操作。
Genero 不斷的加強 Dialog 的功能,提供更方便的操作使用。
像是 Input 和 Display Array 在同一個 Dialog 時,就可以輸入資料,然後同步 Display Array 一直 Insert 或是 Append 資料,
在 Display Array 選擇時,也同步顯示資料在 Input ,可以快速進行資料的修改,
就不用局限一定要輸入或維護完一筆資料後就異動一次資料庫了,全部資料維護完後再一次更新到資料庫。
另外還提供多重選擇的功能,搭配之前在畫面加上資料清單,從此不必再下一筆查了的方式,
就可以讓使用者選擇多筆資料後,按下過帳或是確認的按鈕,就可以一次完成所有的動作。

Dialog 的函數說明:
CLASS Method:
1. getCurrent():回傳目前是在那一個 Object 的操作。
2. setDefaultUnbuffered(boolean):設定取消 Buffer ,才可以修改資料。
Object Method:
1. accept():確認所有的欄位是否完整。
2. insertNode(Object Name, 第幾個 Node):新增 Tree View 的 Node
3. appendNode(Object Name, 第幾個 Node):增加 Tree Viw 的 Node
4. deleteNode(Object Name,第幾個 Node):刪除 Tree Viw 的 Node
5. insertRow(Object Name,第幾行):新增 Object 畫面上的一行紀錄
6. appendRow(Object Name):增加 Object 畫面上的一行紀錄
7. deleteRow(Object Name, 第幾行 ):刪除 Object 畫面上的一行紀錄
8. deleteAllRows(Object Name):刪除 Object 畫面上的所有紀錄
9. getArrayLength(Object Name):回傳 Object 的筆數
10. getCurrentItem( ):回傳目前 Object 的在那一個欄位、List、Action
11. getCurrentRow(Object Name):回傳目前在那一個 Object 上的第幾行
12. getFieldBuffer(Field Name):回傳 Construct 或是 Input Buffered Mode 的 Buffer 值
13. getFieldTouched(Field List):回傳欄位有動到的清單
14. setFieldTouched(Field List,boolean):設定或取消欄位的 Touch Flag 的值來判斷是否欄位有動過
15. setArrayLength(Object Name,數量):設定總共 Object 的行數
16. getForm():回傳目前的 Form 名稱
17. nextField(Field Name):將游標跳到下一個欄位
18. setActionActive(Object,bollean):設定 Object 的 Action 是否要開啟或關閉
19. setActionHidden(Object Name,bollean):設定 Object 的 Action 是否要顯示或是隱藏
20. setCurrentRow(Object Name,第幾行):跳到所指定的第 n 行
21. setFieldActive(Field List,bollean):設定欄位是否可以編輯
22. setCellAttributes(Object Array):設定欄位的屬性(單一個 Dialog)
23. setArrayAttributes(Object Name,Object Array):設定欄位的屬性(多個 Dialog)
24. validate(Field List):驗証欄位是否為必須輸入、Not Null 或是 Validation Rules 的規則
25. isRowSelected(Object Name,第幾行):判斷第幾行是否有選取反白
26. selectionToString(Object Name):回傳所選擇的資料行的所有欄位內容
27. setSelectionMode(Object Name,bollean):開啟或關閉是否可以多筆的功能,0 為單選,1為多選
28. setSelectionRange(Object Name,開始行,結束行,選或不選):設定開始行到結束行要 1 為選取或是 0 為取消選取,結束行是 -1 表示為最後一行

範例多重選擇後然後將所選取的全部確認:
DISPLAY ARRAY g_oea_l TO s_oea_l.* ATTRIBUTE(COUNT=g_row_count)
   BEFORE DISPLAY
       CALL DIALOG.setSelectionMode( "s_oea_l", 1 )
   ON ACTION confirm
       LET g_action_choice="confirm"
       IF cl_chk_act_auth() THEN
          IF cl_confirm('axm-351') THEN
             FOR l_i=1 TO g_row_count
                IF DIALOG.isRowSelected( "s_oea_l", l_i) THEN
                     LET g_oea.oea01 = g_oea_l[l_i].oea01_l
                     CALL i101_y()
               END IF
            END FOR
         END IF
      END IF
      CONTINUE DIALOG
END DISPLAY