2013年4月22日

GDC 提供 Windows DDE 的支援

Dynamic Data Exchange (DDE) 就是二個不同的應用程式,能夠彼此相互共享記憶體的資料,
讓資料可以即時更新,不同應用程式資料保持一致。
MS Office 有提供 DDE 的功能,以一般常用的 Excel 來說明,
當 TIPTOP 資料進行修改的時候,Excel 資料也會跟著馬上進行同步的修改。

舉例 Excel DDE 語法如下說明:
CONSTANT file = "Sheet1"
CONSTANT prog = "EXCEL"
DEFINE val, rval STRING
DEFINE res INTEGER
CALL ui.Interface.frontCall("WINDDE","DDEConnect", [prog,file], [res] ) 開啟 Excel DDE
CALL ui.Interface.frontCall("WINDDE","DDEPoke", [prog,file,"R1C1",val], [res] ); 將 val 值傳送給 Excel 第1行第1欄
CALL ui.Interface.frontCall("WINDDE","DDEPeek", [prog,file,"R1C1"], [res,rval] ); 接收 Excel 第1 行第1欄的 rval 值
CALL ui.Interface.frontCall("WINDDE","DDEError",[],[mess]); # 顯示錯誤訊息
CALL ui.Interface.frontCall("WINDDE","DDEExecute", [prog,file,"[save]"], [res] ); 將 Excel 存檔
CALL ui.Interface.frontCall("WINDDE","DDEFinish", [prog,file], [res] ); 關閉此 DDE
CALL ui.Interface.frontCall("WINDDE","DDEFinishAll", [], [res] ); 關閉所有的 DDE

要注意,要啟動 Excel DDE 時,必須先開啟 Excel 且有 Sheet1 才可以使用。
可以使用 Windows Path 或是用絕對路徑方式,用 cl_open_prog("excel","/p c:\\tiptop") 來開啟。

範例,程式讀取 Excel 的料號,並將品名和規格回傳給 Excel 。
#宣告
CONSTANT file = "Sheet1"
CONSTANT prog = "EXCEL"
DEFINE val, rval,colrow STRING
DEFINE res INTEGER

#讀取 Excel
CALL ui.Interface.frontCall("WINDDE","DDEConnect", [prog,file], [res] )
WHILE TRUE
   LET l_ac = l_ac + 1
   LET colrow = "R",l_ac USING '<<<<<',"C1"
   CALL ui.Interface.frontCall("WINDDE","DDEPeek", [prog,file,colrow], [res,rval] );
   LET g_ima[l_ac].ima01 = rval
   LET colrow = "R",l_ac USING '<<<<<',"C2"
   CALL ui.Interface.frontCall("WINDDE","DDEPeek", [prog,file,colrow], [res,rval] );
   LET g_ima[l_ac].ima02 = rval
   LET colrow = "R",l_ac USING '<<<<<',"C3"
   CALL ui.Interface.frontCall("WINDDE","DDEPeek", [prog,file,colrow], [res,rval] );
   LET g_ima[l_ac].ima021 = rval
   IF cl_null(g_ima[l_ac].ima01) THEN
      EXIT WHILE
   END IF
END WHILE

# 回傳到 Excel
LET val = g_ima[l_ac].ima01
LET colrow = "R",l_ac USING '<<<<<',"C1"
CALL ui.Interface.frontCall("WINDDE","DDEPoke", [prog,file,colrow,val], [res] );
LET val = g_ima[l_ac].ima02
LET colrow = "R",l_ac USING '<<<<<',"C2"
CALL ui.Interface.frontCall("WINDDE","DDEPoke", [prog,file,colrow,val], [res] );
LET val = g_ima[l_ac].ima021
LET colrow = "R",l_ac USING '<<<<<',"C3"
CALL ui.Interface.frontCall("WINDDE","DDEPoke", [prog,file,colrow,val], [res] );

#存檔
CALL ui.Interface.frontCall("WINDDE","DDEExecute", [prog,file,"[save]"], [res] );

# 關閉 DDE
CALL ui.Interface.frontCall("WINDDE","DDEFinish", [prog,file], [res] );

# 關閉所有 DDE
CALL ui.Interface.frontCall("WINDDE","DDEFinishAll", [], [res] );

因 Genero 的畫面不像 Excel 隨時偵測記憶體並更新顯示資料,TIPTOP 畫面只能下達 DISPLAY 指令手動更新資料,
所以用 TIPTOP 程式來更新 Excel 的資料對此 DDE 的功能使用上或許會比較適合,
或是將 TIOTOP 資料匯出 Excel 的功能並寄送 e-mail 等功能會實用一點。

利用 Client 端 GDC 發送 e-mail

現今 e-mail 已是公司通知不可獲缺的工具,
在 GDC 有提供寄送 e-mail 的功能,直接使用者的電腦寄送 e-mail 。
當使用者執行某一個動作時,就可以發送 e-mail 給相關的人員,可做為溝通連絡的用途。
舉例公司就是當修改料件製程時,有工單生產此料件還未結案時,就發通知給相關人員做檢查。

GDC 是屬於 Client 端的應用程式,所以 e-mail 是由 Client 端來發出,
可以透過 MAPI (如 Outlook) 或是 SMTP (郵件伺服器) 方式寄送。

語法說明:
需要注意 l_buf1、l_buf 變數必需定義為 STRING ,不能為 varchar 的型態,l_result、l_id 為 INTEGER 型態。

CALL ui.Interface.frontCall("WinMail", "Init", [], [l_id])     宣告。
CALL ui.interface.frontCall("WinMail", "SetSubject", [l_id, l_buf], [l_result])    設定 e-mail 主旨
CALL ui.interface.frontCall("WinMail", "SetBody", [l_id, l_buf], [l_result])       設定 e-mail 的內容
CALL ui.Interface.frontCall("WinMail", "AddTo", [l_id, l_buf1, l_buf], [l_result])    設定多個收件者名稱、e-mail

CALL ui.Interface.frontCall("WinMail", "AddCC", [l_id, l_buf1, l_buf], [l_result])   設定多個複本收件者名稱、e-mail

CALL ui.Interface.frontCall("WinMail", "AddBCC", [l_id, l_buf1, l_buf], [l_result])   設定多個密件複本收件者名稱、e-mail

CALL ui.Interface.frontCall("WinMail", "AddAttachment", [l_id, "c:\\mydocs\report.doc"], [result])  設定多個附件


GDC 有二個方式寄送 e-mail ,透過 SMTP 直送發送 e-mail 或是開啟 Client 郵件軟體(如 Outlook)
A. MAPI 透過使用者端的郵件軟體來發送。
CALL ui.Interface.frontCall("WinMail","SendMailMAPI", [l_id], [l_result] )    

B. SMTP 直接傳送郵件到郵件主機。
CALL ui.Interface.frontCall("WinMail", "SetFrom", [l_id, l_buf1, l_buf], [l_result])    設定 e-mail 的寄件者名稱、e-mail
CALL ui.Interface.frontCall("WinMail", "SetSmtp", [l_id, "192.168.1.100"], [l_result])    設定 SMTP Server
CALL ui.Interface.frontCall("WinMail", "SendMailSMTP", [l_id], [l_result])    利用 SMTP 發送 e-mail

CALL ui.Interface.frontCall("WinMail", "GetError", [l_id], [l_str])     # 錯誤訊息
CALL ui.Interface.frontCall("WinMail", "Close", [l_id], [l_result])    # 關閉

雖然提供 Client 端的 e-mail 功能,不過使用上就顯的很陽春,只能用純文字發送 e-mail ,不能改為 HTML 樣式,也不能修改 e-mail 的編碼,所以中文字就不能顯示,再來就是 SMTP 沒有提供帳號驗証的功能,必須公司內部郵件主機是不需身份驗証的,所以使用此功能要特別注意。

在 TIPTOP 設定 e-mail 的收件者,儘可能在 aooi998 來設定,日後比較方便維護和紀錄。

2013年4月9日

Oracle 發送 e-mail 的功能

有些時候能夠定期由 Oracle 來寄送一些資料庫的狀況和資訊或是建立 Alert 機制,
對於 DBA 來說工作就可以輕鬆不少,也可以避免一些預期上的問題。
或是有一些報表或是執行的結果也可以透過這個方式,將  SQL 結果寄送到相關人員的 e-mail 信箱。

Oracle 提供 e-mail 傳送的功能 UTL_SMTP 來發送郵件,
只要放到 Oracle 排程裡就會定期發送 e-mail 給相關的人員,
使用此功能必須要以 sysdba 的角色來登入才能使用 (用 sys 的帳號) 。

指令看了就知道作用了,就不詳細介紹。

範例:發送 Oracle Tablespace 可用容量的 e-mail 。

declare
 --宣告
  l_mail_conn   UTL_SMTP.connection;
  l_boundary    VARCHAR2(50) := '----=*#abc1234321cba#*=';
BEGIN
 --定義郵件主機,不能用 IP 只能用 host name,可以在 /etc/hosts 增加
  l_mail_conn := UTL_SMTP.open_connection('mailserver', '25');
  UTL_SMTP.helo(l_mail_conn, 'mailserver');

  --寄件者
  UTL_SMTP.mail(l_mail_conn, '4shiun@gmail.com');

  --多個收件者
  UTL_SMTP.rcpt(l_mail_conn, '4shiun@gmail.com');

  UTL_SMTP.open_data(l_mail_conn);

  --以 HTML 方式來傳送,定義收件者和寄件者名稱
  UTL_SMTP.write_data(l_mail_conn, 'Date: ' || TO_CHAR(SYSDATE, 'DD-MON-YYYY HH24:MI:SS') || UTL_TCP.crlf);
  UTL_SMTP.write_data(l_mail_conn, 'To: ' || 'Adam' || UTL_TCP.crlf);
  UTL_SMTP.write_data(l_mail_conn, 'From: ' || 'Adam' || UTL_TCP.crlf);
  UTL_SMTP.write_data(l_mail_conn, 'Subject: ' || 'Tablespace Information' || UTL_TCP.crlf);
  UTL_SMTP.write_data(l_mail_conn, 'Reply-To: ' || 'Adam' || UTL_TCP.crlf);
  UTL_SMTP.write_data(l_mail_conn, 'MIME-Version: 1.0' || UTL_TCP.crlf);
  UTL_SMTP.write_data(l_mail_conn, 'Content-Type: multipart/alternative; boundary="' || l_boundary || '"' || UTL_TCP.crlf || UTL_TCP.crlf);

  UTL_SMTP.write_data(l_mail_conn, '--' || l_boundary || UTL_TCP.crlf);
  UTL_SMTP.write_data(l_mail_conn, 'Content-Type: text/html; charset="iso-8859-1"' || UTL_TCP.crlf || UTL_TCP.crlf);

  --把 Tablespace 的資料用 Table 來顯示
  UTL_SMTP.write_data(l_mail_conn, '<table border=1 width=800px>');
  UTL_SMTP.write_data(l_mail_conn, '<TR align=center><TD>tablespace_name</TD><TD>free</TD><TD>used</TD><TD>total</TD><TD>used_percent</TD><TD>free_percent</TD></TR>');
  for i in(select a.tablespace_name,to_char(b.free,'fm999,999,999,999') free,to_char(a.total-b.free,'fm999,999,999,999') used,to_char(a.total,'fm999,999,999,999') total,to_char(((a.total - b.free)/a.total)*100,'999.99')||'%' used_percent,to_char((b.free/a.total)*100,'999.99')||'%' free_percent from (select tablespace_name,sum(bytes) total from dba_data_files group  by tablespace_name)a,(select tablespace_name,sum(bytes) free from dba_free_space group by tablespace_name) b where a.tablespace_name= b.tablespace_name order by 1)
      loop
      UTL_SMTP.write_data(l_mail_conn, '<TR align=right><TD align=left>'||i.tablespace_name||'</TD><TD>'||i.free||'</TD><TD>'||i.used||'</TD><TD>'||i.total||'</TD><TD>'||i.used_percent||'</TD><TD>'||i.free_percent||'</TD></TR>');
      end loop;
  UTL_SMTP.write_data(l_mail_conn, '</table>');
  UTL_SMTP.write_data(l_mail_conn, UTL_TCP.crlf || UTL_TCP.crlf);

  UTL_SMTP.write_data(l_mail_conn, '--' || l_boundary || '--' || UTL_TCP.crlf);
  UTL_SMTP.close_data(l_mail_conn);

  UTL_SMTP.quit(l_mail_conn);
END;

發送 e-mail 當然也可以把 Blob 欄位的資料當成附件的方式來傳送。
要注意的是,如果 mail server 有認證才能發送 e-mail 的話,就需要再加上帳號和密碼,
發送的 e-mail 如果是中文的話,HTML 的文字編碼也需要修改。
建議是把此 SQL 指令寫成 PROCEDURE 的方式方便執行。

2013年4月2日

SQLCA.SQLCODE 和 SQLCA.SQLERRD 和 STATUS 錯誤碼查詢

要判斷程式在執行的時候,SQL 指令回傳結果正確與否,
在 4GL 就提供以下三個變數來使用:

SQLCA.SQLCODE:
執行 SQL 指令後,回傳 Informix 4GL 所表示的 Error Code。

SQLCA.SQLERRD:
SQLERRD[1]~SQLERRD[6],SQLERRD[2] 表示資料庫的錯誤訊息,SQLERRD[3] 表示執行的 row 數,其他特別功能使用就不參考。

STATUS:
通常會等於 SQLCA.SQLCODE ,但是多了系統的錯誤碼,所以執行 SQL 指令後系統 I/O 錯誤時就會顯示另一個錯誤碼。

程式執行時會回傳 SQLCA.SQLCODE 訊息碼,有以下三種情況:
0 表示執行成功。
100 表示成功但無資料,所以當 Update 沒有符合條件的資料可以用。
<0 負數表示有錯誤。

當訊息碼為負數時,表示為錯誤訊息,但是我們要怎麼查是什麼錯誤,
只要在命令環境下,執行 finderr 錯誤碼 就會出現錯誤的說明了。

例:執行 finderr -201 會出現以下訊息,英文應該大概上就知道什麼涵義:
-201    A syntax error has occurred.

This is the general error code for all types of mistakes in the form of an SQL statement. Look for missing or extra punctuation (for example, missing or extra commas, omission of parentheses around a subquery, etc.), keywords misspelled (for example VALEUS for VALUES), keywords misused (for example, SET in an INSERT statement, INTO in a subquery), keywords out of sequence (for example a condition of "value IS NOT" instead of "NOT value IS"), or the use of a reserved word as an identifier.
Note: Database servers that support "full NIST compliance" do not reserve any words; queries that work with these database servers may fail with error -201 when used with earlier implementations.

不過因為 SQLCA.SQLCODE 是 Informix 在 4GL 所規範出來的錯誤訊息,所以比較籠統的方式來呈現,舉例 -201 表示語法錯誤,但是沒辦法詳細說明錯誤在那,因此在 Debug 時可以用 SQLERRD[2] 就可以知道 Oracle 的 ORA 的錯誤碼,比較快速的知道語法是那裡的錯誤。

要得知 SQL 執行的筆數 SQLCA.SQLERRD[3] 就可以知道 SQL 指令所處理的筆數,在程式上也可以多加利用。

另外 Genero 還有提供二個指令,要注意不是變數型態的 SQL Error Code 而是指令,所以 Debug 不能顯示內容,
要先 LET 到變數或是 DISPLAY 才看的到,也是可以參考使用,功能和 SQLERRD[2] 相同。

SQLSTATE:
回傳 ANSI SQLSTATE 錯誤碼,現在應該所有商用資料庫都有支援 ANSI SQL。

SQLERRMESSAGE:
回傳資料庫的錯誤碼和錯誤的說明。