2013年3月26日

建立 Oracle 觸發 Trigger 來 Debug

有時候系統會到遇到靈異事件,
經常發生還容易查,用 r.d2+ 就大概可以 debug 出來問題在那,
偶發情況然後又只要重新再做一次又正常了,不知道到底是時間差、還是資料的問題,或是程式的 bug。
系統又沒有記錄的話,使用者刪除或修改資料是不會承認的,都是朝系統的問題來處理。

只好建立 Trigger 來記錄資料的異常歷史記錄了,看到底是誰搞的鬼。
在 TIPTOP 有提供 log_file 和 aooq040 提供 Trigger log 的查詢,但是 Trigger 要自已在 Oracle 建立。

Trigger 的語法:
CREATE OR REPLACE TRIGGER 觸發名稱
BEFORE INSERT OR UPDATE OR DELETE OF 觸發的欄位 ON 觸發的表
AFTER INSERT OR UPDATE OR DELETE OF 觸發的欄位 ON 觸發的表
FOR EACH ROW
記錄異動的資料
END;

bmd_file 異動資料的範例,把異動的欄位記錄在 log_file:
create or replace trigger bmd_file_log
--只記錄4 個欄位的異動
after insert or update or delete of bmd01,bmd02,bmd03,bmd04,bmd08 on bmd_file
for each row
declare l_col varchar2(3);
        l_user varchar2(6);
        l_type varchar2(20);
        l_cnt  number;
begin
l_col :='';
--先找有沒有資料,當執行失敗時 trigger 就會中止,所以要先找是否有資料,SID 是唯一值,SESSION有可能重複。
select count(*) into l_cnt from v$session,gbq_file where process=gbq01 and sid = userenv('sid') and process not like '%:%';
if l_cnt > 0 then
--把TIPTOP使用者和程式代號找出來
   select gbq03,gbq04 into l_user,l_type from v$session,gbq_file where process=gbq01 and audsid = userenv('sessionid') and process not like '%:%';
else
--把非TIPTOP的使用者和程式找出來
   l_user:=sys_context('userenv','os_user'); l_type:=substr(sys_context('userenv','module'),1,10);
end if ;
--新增
if inserting then
   l_col :='ins';
   insert into log_file values('bmd_file',:new.bmd01,:new.bmd02,:new.bmd03,:new.bmd04,:new.bmd08,l_col,sysdate,l_user,l_type,'','');
end if;
--修改就記錄修改前和修改後
if updating then
   l_col :='upd';
   insert into log_file values('bmd_file',:old.bmd01,:old.bmd02,:old.bmd03,:old.bmd04,:old.bmd08,l_col||'-old',sysdate,l_user,l_type,'','');
   insert into log_file values('bmd_file',:new.bmd01,:new.bmd02,:new.bmd03,:new.bmd04,:new.bmd08,l_col||'-new',sysdate,l_user,l_type,'','');
end if;
--刪除
if deleting then
   l_col :='del';
   insert into log_file values('bmd_file',:old.bmd01,:old.bmd02,:old.bmd03,:old.bmd04,:old.bmd08,l_col,sysdate,l_user,l_type,'','');
end if;
end;

要刪除 TRIGGER ,就用 drop trigger 名稱,就可以刪除了。

2013年3月25日

更便利的 DIALOG - 同時單頭單身查詢/輸入

剛開始使用 TIPTOP 很多人都會問為什麼查詢要按二次確定,
需要單頭按確認後再到單身再按確認,才會執行查詢的動作,
用 DIALOG 就可以同時做單頭、單身的查詢。

再來操作上不方便的就是,像工單發料單 asfi510 或多主件工程變異單 abmi710 ,
像這種多單身的樣式,就不能同時將二個單身做 scroll bar 的捲動和資料不能複製到剪貼簿,
一般都會再做一個按鈕查詢另一個單身,然後操作完後要再跳回去原本的單身,實在是很不方便。
就是可以用 DIALOG 來達到比較人性化的介面。

在 Genero 官方文件說明:
The DIALOG block is an interactive instruction that executes multiple kinds of sub-controllers simultaneously to drive different parts of a form.

只要將 CONSTRUCT 和 CONSTRUCT ARRAY 都包在 DIALOG 裡面,就可以做到單頭和單身同時查詢。
將二個不同的 DISPLAY ARRAY 也包在 DIALOG 裡面,也一樣可以同時操作這二個單身的 Action 和 Scroll Bar。

DIALOG ATTRIBUTES(UNBUFFERED)
     DISPLAY ARRAY g_bmz to s_bmz.* ATTRIBUTE(COUNT=g_rec_b)
     END DISPLAY
     DISPLAY ARRAY g_bmy to s_bmy.* ATTRIBUTE(COUNT=g_rec_d)
     END DISPLAY
END DIALOG

DIALOG 提供 INPUT、CONSTRUCT、DISPLAY ARRAY、INPUT ARRAY 的多重控制功能,
利用這個功能就可以做到單頭、單身輸入完資料後,才會產生單號和同時寫入資料到 TABLE,
作廢的單子或許就可以少了一些。

要判斷目前 DIALOG 目前控制在那一個的話,提供一個函式 DIALOG.getCurrentItem() 回傳就是畫面的變數值。
這樣多單身就匯出 Excel 就可以判斷要匯出是那一個單身的資料。

官方網站提供其中一個例子做動態的資料查詢,單頭輸入條件後單身就馬上顯示出資料,
原本是寫在 AFTER FEILD 後加上 DISPLAY ARRAY 也是可以做到,但是缺點就是資料的捲軸沒辦法動。
用 DIALOG 就可以做到像 Windows 應用程式類似的作業,盡可能用 DIALOG 來開發程式吧!!

2013年3月15日

回復 Oracle 刪除或是已更新的資料

我們都知道還原資料最多就是只能還原到最近一前的備份點,
但是備份不可能是隨時隨地都在備份,
Oracle 有一個 undo 的機制,會將資料進行異動之前就進行 Data Image,
原本的用意為 A 進行 Query 之後 B 又進行 Update 時,A 的 Query 結果會是當時下達指令時點的資料,
就不會被 B 在 Update 時所影響,也不會造成資料不一致。
因此我們就可以利用此功能回復到你所想要的某一時間的資料。
undo 區分為 ACTIVE、UNEXPIRED、EXPIRED 三種,
當資料 commit 或 rollback 後就會改變為非活動中的狀態。

查看目前 undo 的狀態:
select * from DBA_UNDO_EXTENTS

利用 Oracle 的 undo log 來查到底是誰把資料做刪除或變更的,
使用 LogMiner 來開啟 undo log 或 archived log 檔案,就可以查出 SQL_TEXT 和 Session ,
就知道到底是誰幹的好事。

先來看 Oracle 的參數:
sqlplus: show parameter undo;

select * from v$parameter where name like '%undo%'

NAME                   VALUE
--------------------------
undo_management  AUTO
undo_tablespace     UNDOTBS1
undo_retention        900

undo_retention 保存時間(秒) 900 秒,
設定 undo_retention 越大就會佔用更多的 undo tablespace 的空間。
要是 undo 空間不足時,才會把 undo_retention 以前的資料蓋掉,
當 undo tablespace 設定 RETENTION GUARANTEE 時,
還是不夠 undo 空間就會自動擴展,增加 undo tablespace 檔案容量,
必須要保証磁碟空間是足夠的,當有大量資料異動時,會造成 undo 檔案過大。
設定為 RETENTION NOGUARANTEE , undo tablespace 不會擴增,
當容量不足時就會一直把非活動的空間蓋掉,所以 undo_retention 不保証一定會保存完整資料。

將 undo_retention 改為 1 小時,指令如下:
alter system set undo_retention = 3600

修改 undo tablespace 的屬性,開啟 retention 自動擴展:
ALTER TABLESPACE undotbs1 RETENTION GUARANTEE

關閉 retention 自動擴展:
ALTER TABLESPACE undotbs1 RETENTION NOGUARANTEE

預設 undo_management 都是 auto 管理,
undo_retention 的值會由 Oracle 自動進行最佳的調整,
所以超過 undo_retention 的時間,也有可能狀態還是在 UNEXPIRED 。
因為 Oracle 每 10 分鐘就會依 undo tablespace 的空間和 Oracle 操作狀況來設定最佳的 undo_retention 值。

查看 Oracle 所設定的 undo_retention (秒)
SELECT * FROM v$undostat;

要如何將資料還原,只要加上 timestamp 就可以查到當時的時間點上資料內容。
select * from ds.zx_file as of timestamp to_timestamp('2013-03-15 15:55:29', 'yyyy-mm-dd hh24:mi:ss');

查出來結果應該會很開心,好險資料還在。
再來就是把資料還原就可以了,一般都是用 create table 的方式,再比對資料後做更新。
create table ds.zx_temp as
select * from ds.zx_file as of timestamp to_timestamp('2013-03-15 15:55:29', 'yyyy-mm-dd hh24:mi:ss');

scn (System Change Number) 當資料異動時,就會將 scn + 1 保存在 undo log 。
比較建議還是用 scn 會比較避免同一秒的資料異動時,資料還原的完整性。
再來就是 scn 和 timestamp 在 9i 會有 5 分鐘的資料同步,
所以 9i 用 timestamp 進行 flashback query 時會查不到 5 分鐘內異動的資料。
在 10g 以後的版本就修正此情況, scn 和 timestamp 會在 LogMiner 取得。

改用 scn 的方式來查詢:
select * from ds.zx_file as of scn 2267300000

查詢最近一次資料異動的 scn 和 timestamp :
select scn_to_timestamp(dbms_flashback.get_system_change_number) ,dbms_flashback.get_system_change_number from dual;

雖然 Oracle 這個機制可以讓我們放心資料異動的問題,但還是有限制:
1. Table Schema 變更就不能回復,因 flashback query 是用目前的資料字典。
2. 不能 flashback 到 5 天前的資料。
3. 不能保証 undo_retention 所保存資料是否完整,undo tablespace 過小會蓋掉舊的。
4. drop、truncate 不需 commit ,所以就不能回復,所以要把 truncate 和 drop 指令鎖住。

一般的帳號也可以使用 flashback 的功能,設定權限:
grant execute on dbms_flashback to USER ;

想要看某筆資料所有異動的紀錄,在 Oracle 有 versions 的指令來查看每個版本的資料和起始、結束時間
欄位 versions_starttime 表示版本開始時間,欄位 versions_endtime  表示版本結束時間
使用方式只要在 table 後面加上 versions between timestamp minvalue and maxvalue 就可以了。
範例:select zx_file.*,versions_starttime,versions_endtime from ds.zx_file versions between timestamp minvalue and maxvalue

2013年2月22日

設定報表排程自動發送 Mail 給相關人員

應該是從 TIPTOP 5.2 版之後就有 CR 發送 mail 的功能,
這樣就可以讓系統將異常檢查報表每天都寄給相關人員,
IT 人員也就輕鬆很多了。

系統的運作是:
TIPTOP 主機執行背景程式 → 呼叫 CR 主機報表 → 執行結果存在 CR 主機的 temp 目錄 →
CR 主機將 temp 目錄的檔案發送 mail 。

執行報表後會有約 5 分鐘延遲時間。
執行過報表也都會存在 CR 主機的 temp 目錄,看要不要固定時間清理一下。
因 CR 報表 IIS 為匿名的驗証方式,所以必需將 temp 的目錄改為 everyone 讀取。

首先要先設定 aooi998 Mail 收件人維護作業,
也可以選擇要寄送的是 PDF、Word、Excel …等。

再來就是在 p_cron 建立排程,
通常報表程式的參數五 g_prtway 表示報表的執行結果的顯示方式,
參數四表示是背景作業。
只要參數五設定為 A ,然後背景作業設定為 Y ,
這樣就會自動寄報表的到相關人員的 E-mail 了。

或是只要加上三個變數也可以有寄送 Mail 的功能。
CALL FGL_SETENV("MAIL_TO",g_receiver)
CALL FGL_SETENV("MAIL_CC",g_cc)
CALL FGL_SETENV("MAIL_BCC",g_bcc)
變數的格式為:
收件者名稱:收件者 E-Mail:格式(1.PDF,2.RPT,3.DOC,4.XLS,5.XLS,6.XLS)

2013年2月6日

畫面能夠依資料內容來變更文字顏色、樣式

TIPTOP的畫面設計比較偏向 TEXT-Mode ,所以會以比較單調樣式來呈現,
現在應該沒有人在用這種方式了吧,都是用 GUI 的方式來使用。
這樣就可以添加一些色彩、底線、粗體,來增加使用者操作上資料的辦識能力。

依 4js 的說明,在 DISPLAY 可以加上屬性,如下:
1. BLACK, BLUE, CYAN, GREEN, MAGENTA, RED, WHITE, YELLOW – 顯示顏色
2. BOLD, DIM, NORMAL – 顯示字體樣式
3. REVERSE, BLINK, UNDERLINE – 顯示文字的屬性

屬性的字面上的意思就能知道用途了,多重屬性時只要加上逗號來區隔,就不多加說明了。

例如我們想要 sfb01 這個欄位在某些情況下顯示藍色時,只要加判斷再加上 ATTRIBUTE,
例:
IF g_sfb01.sfb04 = '8' THEN
    DISPLAY BY NAME g_sfb01.sfb01 ATTRIBUTE(BLUE)
ELSE
    DISPLAY BY NAME g_sfb01.sfb01 ATTRIBUTE(BLACK)
END IF

鼎新也有提供 lib 的函式來改變顏色:
CALL cl_set_comp_font_color("sfb01,sfb02","RED")

但是如果是單身的話會用 DISPLAY ARRAY 就只能所有單身全部的顏色都變一樣,
要改用 DIALOG.setCellAttributes 的方式來顯示欄位的屬性。
例:
要先定義單身有那些是要動態變更欄位屬性的陣列變數:
DEFINE g_gen_attr DYNAMIC ARRAY OF RECORD
        gen01       STRING,
        gen02       STRING,
        gen03       STRING
END RECORD

記得要先做 g_gen_attr.clear()。

在 fill() 函數時 FOREACH 的時候判斷:
IF g_gen[g_cnt].gen03 IS NULL TEHN
    LET g_gen_attr[g_cnt].gen01 = 'MAGENTA REVERSE'
ELSE
    LET g_gen_attr[g_cnt].gen01 = ''
END IF

再來就是 DISPLAY ARRAY 顯示的時候依定義好的屬性來顯示:
BEFORE DISPLAY
         CALL cl_navigator_setting( g_curs_index, g_row_count )
         CALL DIALOG.setCellAttributes(g_gen_attr)    #加上這一句

這樣單身也可以依資料來變化顏色了。

2013年1月30日

增加是否有相關文件的圖示

查詢資料的時候,都看不出來那些是有附上相關文件的檔案,
需使用者點擊相關文件 action ,再看有沒有資料才知道有沒有附上去。


我們可以參照 E-Mail 的樣式,只要在程式上面加上一個迴紋針的圖示,
就知道那些單子有沒有相關文件了。

首先要把圖先上傳,一般是上傳到 /u1/topprod/tiptop/doc/pic 目錄,
TIPTOP 的圖示是透過 web 的方式,將網址傳給 GDC ,
然後 GDC 再依 HTTP 網址抓取圖片來顯示。

程式的部份,比照 cl_set_pic1 函式的寫法就差不多了,
因 cl_set_pic1 的畫面變數一定要命名為 imgmksg ,
要顯示是否有相關文件的圖示的話,就必須要另外寫程式了。

以 aqct110 當範例:

FUNCTION t110_show_attache()
   DEFINE   l_cnt              LIKE   type_file.num5,
            ls_sql             STRING,
            l_wc               STRING
   DEFINE   ls_pic_url         STRING,
            ls_imgmksg         STRING
   DEFINE   lr_key       RECORD
               gca01 LIKE gca_file.gca01,
               gca02 LIKE gca_file.gca02,
               gca03 LIKE gca_file.gca03
            END RECORD
           
   IF g_qcs.qcs01 IS NOT NULL THEN
      LET l_cnt = 0
      LET g_doc.column1 = "qcs01"
      LET g_doc.column2 = "qcs02"
      LET g_doc.column3 = "qcs05"
      LET g_doc.value1 = g_qcs.qcs01
      LET g_doc.value2 = g_qcs.qcs02
      LET g_doc.value3 = g_qcs.qcs05
   
      LET lr_key.gca01 = g_doc.column1 CLIPPED || "=" || g_doc.value1 CLIPPED
      LET lr_key.gca02 = g_doc.column2 CLIPPED || "=" || g_doc.value2 CLIPPED
      LET lr_key.gca03 = g_doc.column3 CLIPPED || "=" || g_doc.value3 CLIPPED
   
      LET l_wc = "gca01 = '", lr_key.gca01 CLIPPED, "' AND gca02 = '", lr_key.gca02 CLIPPED,
                         "' AND gca03 = '", lr_key.gca03 CLIPPED, "'  AND gca04 = ' ' AND gca05 = ' '"
   
      # 是否有相關文件的紀錄
      LET ls_sql = "SELECT COUNT(*) FROM gca_file WHERE ", l_wc,
                   " AND gca08 IN ('DOC','TXT') AND gca11 = 'Y' ORDER BY gca06"
   
      PREPARE t110_doc_p FROM ls_sql
      DECLARE t110_doc_d CURSOR FOR t110_doc_p
   
      OPEN t110_doc_d
      FETCH t110_doc_d INTO l_cnt
      # 有的話顯示迴紋針圖檔,不然就顯示空白的圖檔,
      #因為下一筆可能沒有要給他一個空白來更新畫面
      IF SQLCA.SQLCODE = 0 AND l_cnt > 0 THEN
        LET ls_pic_url = FGL_GETENV("FGLASIP") || "/tiptop/pic/"
        LET ls_imgmksg = ls_pic_url.trim() || "attache.jpg"
        # 傳送網址給畫面變數  imgattch 
        DISPLAY ls_imgmksg TO imgattach
      ELSE
        LET ls_pic_url = FGL_GETENV("FGLASIP") || "/tiptop/pic/"
        LET ls_imgmksg = ls_pic_url.trim() || "space_0.gif"
        DISPLAY ls_imgmksg TO imgattach
      END IF
   END IF
END FUNCTION

最後只要再 show 的 Function 加 CALL t110_show_attache() 就可以了。

2013年1月25日

指定畫面單身資料匯出 Excel

TIPTOP 提供一個的 Library 可以將畫面單身的資料匯出到 Excel ,
例如:CALL cl_export_to_excel(ui.Interface.getRootNode(),base.TypeInfo.create(g_sfq),'','')

但是有程式在視窗上是有二個單身,像是工單發料、工單退料、付款沖帳、收款沖帳…等,
或是有程式會開啟另一個視窗的明細資料,像出貨單的單價,應付帳款的多發票…等。
同一支程式會呼叫多個畫面或是有多單身的情況時,
用 cl_export_to_excel 就會發現,怎麼匯出到 Excel 的資料是對的,但是欄位的名稱是錯的。
造成匯出來就會不整齊,不會依序排好。
因為 ui.Interface.getRootNode() 都是抓第一個顯示的單身來當作匯出的資料陣列。
所以我們就要把 ui.Interface.getRootNode() 改成正確的 node (節點)。
TIPTOP 並非 MDI 視窗架構,所以一個 Windows 只會有一個 Form。

畫面的架構:
UserInterface
 |--Windows
 |        |-- Form
 |        |        |--Table
 |        |--Dialog
 |--TopMenu
 |--ToolBar

如下圖所示:


參考 4js 網站說明,提供 ui.window 的函數說明:
CLASS Method:
forName( name STRING ):回傳目前是否有開啟 name 視窗,並傳回位置
getCurrent():回傳目前的視窗位置

OBJECT Method:
findNode( t STRING, n STRING ):找尋類型為 t,且名稱為 n 的節點
createForm( n STRING ):開啟一個新的畫面,且傳回新畫面的位置
getForm():回傳目前畫面的位置
getNode():回傳目前節點的位置
setText( t STRING ) :設定視窗的標題名稱
getText():回傳視窗的標題名稱
setImage( n STRING ):設定視窗的 icon 圖示
getImage():回傳視窗的 icon 圖示

首先要先宣告二個變數:
DEFINE w ui.Window
DEFINE n om.DomNode

再來就是要取得目前的視窗和要匯出 Excel 的 Node:
如果是新的視窗來顯示的話,只要用 getNode 即可,
LET w = ui.Window.getCurrent()
LET n = w.getNode()

如果是多單身的視窗,就要告訴系統是那一個單身 Node:
LET w = ui.Window.getCurrent()
LET n = w.findNode("Table","s_sfs")

然後再修改  cl_export_to_excel 把 ui.Interface.getRootNode() 改為 n 就可以了,
CALL cl_export_to_excel(n,base.TypeInfo.create(g_sfs),'','')

SQL 語法將群組的資料全部列出來

我們都知道 GROUP 可以用 AVG、SUM、COUNT、MIN、MAX…等,
可以做數字、日期或是字串的運算。

但是如果群組不想要運算,要合起來全部都列出來呢 ?
通常我們就用程式跑迴圈的方式來將資料連接成一個字串。
我們知道 Oracle 有一個函數叫 concat 也就是將二個字串連接在一起,
這時候也可以用在 group 囉~~

介紹一個函數 wm_concat ,但重複資料不會排除,所以再加上 distinct 就更完美了。
欄位內容會以逗號來分隔。
不想用逗號來分隔的話,就只能用 replace 的方式來取代了。

範例:顯示部門內的所有員工資料。
SELECT gen03,wm_concat(gen01) FROM gen_file
GROUP BY gen03
ORDER BY 1

要注意,wm_concat 的欄位是以 CLOB 格式來呈現的,
所以如果有建立 view 或是直接在 p_query 使用的話,記得要轉成 VARCHAR 的格式。
原因就是 p_query 所有欄位都是依照 gaq_file.gaq03 欄位來宣告的。
用 CAST 將 CLOB 改為 VARCHAR 的格式。
範例:
SELECT gen03,cast(wm_concat(gen01) as varchar(255)) FROM gen_file
GROUP BY gen03
ORDER BY 1

另一個需注意就是 wm_concat 的資料是不能排序,
所以有可能資料所列出來的順序會不一樣。必需改為先 wm_concat 合併再 GROUP來處理。
範例:
SELECT gen03,max(cast(gen01 as varchar(255))) FROM (
SELECT gen03,wm_concat(gen01) OVER (PARTITION BY gen03 ORDER BY gen03,gen01) gen01 FROM gen_file
)
GROUP BY gen03

2013年1月23日

當站下線的成本該如何計算?

當站下線的意思就是,工單生產的途中要進行在製品的入庫。
可能是要抽原料來變更生產料號,可能是設計變更換新料號生產…情況。
因已經在製程中生產了,用挪料、退料的方式也是可行,
但所投入的人工、製費在成本計算,就會造成舊工單結案變調整金額,
新工單的在製金額也會低估,就不會是那麼的恰當。
需要進行在製成本的調整,料件的單位成本才能夠準確。
所以當站下線的料號就必須要再編一個新的料號,一般都是工單生產的料號+製程碼。

我們瞭解了當站下線的意思,報工有當站下線時製程移轉和 WIP 量是不會有問題的,
比較有問題的就是工單是不會自動結案,
另一個就是下線的庫存單位必須要和工單製程的轉出單位要一樣。
但是成本計算時在製金額是如何表示的呢?

在 TIPTOP 系統當站下線是以退料的方式來呈現的,
也就是在製的本期投入的數量和金額為負數,
和正常完工入庫是在轉出數量和金額概念是不同的。
再加上成本計算時沒辦法知道當站下線的金額是多少,所以我們就要附予單價啦~~

當站下線在成本計算前要先在 axct510 先填入材料、人工、製費、加工的單價,
然後要注意一點,如果是同月當站下線所入庫當月又被另一工單領用接下去生產的時候,
則需要在 axct001 庫存開帳補上此料件的前期單價資料,避免領用當線下線的料號單價為 0 ,
因 axcp500 執行的先後順序的關係,當站下站是退料並非入庫而計算月加權平均,
所以工單領用的單價就抓不到的關係。

最後成本計算後檢查相關資料時,
當站下線和拆件工單在 axcr370 成本計算後勾稽報表都會出現本期投入金額為負的錯誤訊息,
就把他當作是正常吧~~~

2013年1月17日

開啟 GDC 的 Debug 模式

找系統問題的時候,需要找畫面的欄位對應到資料庫那一個 Column、
action 按鈕對應是 4gl 程式那一個 action 名稱…等,這些除錯常常會遇到。

GDC 有提供一個 Debug 的功能,可以直接看到目前視窗所有欄位或按鈕的變數值 or 名稱。

1. 在程式集的捷徑加上參數 -aD (-M 是最小化開啟)。


GDC 就會多出二個按鈕。


2. 使用 ssh 或是 telnet 的方式來登入 TIPTOP,開啟你所要的畫面。

3. 將滑鼠游標移到你要看的上方,然後按下組合鍵 Ctrl + 滑鼠右鍵,就出現 Debug 視窗。
    然後你所點選的地方也會有紅色的標示框在那閃動著,就可以快速知道 action 的名稱。
    也試試看看其他欄位,可以顯示欄位的變數值喔~





讓 TIPTOP 下拉式選單可以動態的顯示

在 Genero Studio 設計 ComboBox 下拉式選單的時候,
通常都是直接在 4fd 檔上面定義固定的 item 項目。
然後再到 p_perlang 修改多國語言顯示的名稱。

提供一個方式可以讓 ComboBox 物件可以隨著其他欄位的變化,來改變下拉式選單的項目。
就是用 ui.ComboBox 的方法。

參考 Genero 的說明文件,ui.ComboBox 提供下式的 Function。
CLASS Method:
1. forname :物件名稱

2. setDefaultInitializer:設定初始的項目

OBJECT Method:
1. addItem(name,text) :增加項目,name 是值,text 是顯示名稱

2. getColumnName():回傳欄位的名稱

3. getIndexOf(name):回傳 name 是在項目的第幾個,沒有回傳 0

4. getItemCount():回傳項目的數量

5. getItemName(index):回傳第 index 個的項目 name 值

6. getItemText(index):回傳第 index 個的項目 text 值

7. getTableName():回傳欄位的 table_name 或是 FORMONLY

8. getTag():回傳欄位的 tag 值

9. getTextOf(name) :回傳 name 是在項目的第幾個的 text 值,沒有回傳 null

10. removeItem(name):刪除名稱是 name 的項目

11. clear():清除所有的項目

範例1:ComboBox 依開啟時可以顯示 1~5 的項目選單
方法1:
1. 在 4fd 檔中的 ComboBox 欄位 Initializer 屬性加上 cb_load 字串

2. 在 4gl 檔中增加一個 Function
    FUNCTION cb_load(combox)
         DEFINE combox ui.ComboBox
         LET combox = ui.ComboBox.forname("ima12")
         CALL combox.clear()
         FOR g_num = 1 TO 5
             CALL combox.addItem(g_num ,g_num)
         END FOR
    END FUNCTION

方法2:
1. 在 OPEN WINDOW 之前加上:
     CALL ui.ComboBox.setDefaultInitializer("cb_load")

2. 同上增加 cb_load 的 Function

範例2:ComboBox 可以隨著某個欄位所選擇的,列出對應的選單
 1. 新增 cb_load 的 Function
    FUNCTION cb_load(combox)
         DEFINE combox ui.ComboBox
         DEFINE l_azf01   LIKE  azf_file.azf01,
                       l_azf03    LIKE azf_file.azf03
         LET combox = ui.ComboBox.forname("ima12")
         DECLARE ima12 CURSOR FOR
            SELECT azf01,azf03 FROM azf_file
                WHERE azf02 = g_ima.ima12
         FOREACH ima12  INTO l_azf01,l_azf03
                 CALL combox.addItem( l_azf01,l_azf03)
         END FOREACH
    END FUNCTION

2. 然後再 INPUT 段的 BEFORE FIELD 欄位的時候,呼叫就可以變化了
    BEFORE FIELD ima12
           CALL cb_load("")

當欄位 ima12 選擇是那一個的時候,跳到 ComboxBox 欄位的時候,
就會帶出不同的下拉式選單

總結:在 Genero 的 ComboBox 有一些可惜的地方,只能在查詢的時候才能夠輸入資料
(要開啟 queryEditabile ),不能提供新增的時候可以輸入選單以外的值。
再來就是如果是用此方式的話,多國語言的功能就不能再維護了,
如果是帶 Table 的資料多國語言就沒問題,但是是自定的話,當然也可以用 p_ze 來定義啦~~~
要注意的另一點,如果下拉式選單因其他欄位的變化而造成值是相同但是顯示的名稱不同,
再查詢的時候就要再多判斷要帶出來的顯示名稱,並上下筆做查詢的時候也要判斷。

Genero 的檔案管理指令

需要檔案管理的時候,都要再查一次 Genero 的文件,
所以就把常用的指令都整理一下啦~~

其實就是可以用 Genero 進行檔案或資料夾的新增、修改、刪除,或是權限設定的功能。
有時候報表的資料太多,或是其他系統做資料交換的時候,就可以轉文字檔的方式來匯出。

首先要記得在程式最上方加上 Library:
IMPORT os

再來利用 CALL Function RETURNING value 的方式來使用。
以下就是常用的 Function:

1. os.Path.separator() :路徑的系統符號,傳回 / 或是 \ 符號

2. ps.Path.pathseparator() :磁碟的系統符號,傳回  : 或是 ; 的符號

3. os.Path.exists(filename) :傳回 true 或是 false

4. os.Path.basename(Fullname) :去除路徑只回傳檔案名稱

5. os.Path.dirname(Fullname) :去除檔案名稱只回傳路徑

6. os.Path.rootname(filename):回傳路徑+檔案名稱

7.  os.Path.extension(filename):回傳檔案的 extension

8.  os.Path.join ( "/u1", "topprod") :回傳 /u1/topprod

9.  os.Path.chrwx(file,511) :變更檔案的權限,回傳 true 或是 false,十進位的 511 = 八進位的 777

當然也可以不要用 os 的 Library ,大多的指令也是直接也可用 RUN 的指令來達成。

例:
LET l_cmd = "chmod 777 ", filename
RUN l_cmd

2012年12月13日

完整複製和還原 Oracle 的 Instance 資料

要將 Oracle 資料庫要複製到新的機器,或是將正式區的資料複製到測試區,
當然可以用 rman 的備份還原方式,或是用 impdp/expdp、imp/exp …等方式。
好處就是資料會重新整理過,資料就會連續性的匯入,效能會好一點。

不過也有更快速的方式,就是直接將 dbf 檔案複製過去,然後再重建 control file ,
好處就是速度快,也不用再重新設定,而且可以修改 instance 名稱。
但是在異機做還原時,只能限定相同的 Oracle 版本和版次才行。

以下範例就是將 TOPPROD 完整的資料複製到 TOPTEST 。

1. 依照  topprod 的 control file 的參數產生 trace 檔案:
    切換到 topprod: export ORACLE_SID=topprod
    使用 sys 使用登入  sqlplus sys/sys_dsc as sysdba
    執行:SQL> alter database backup controlfile to trace;
     再來就是看檔案產生的位置:
select c.value || '/' || d.instance_name || '_ora_' || to_char(a.spid,'fm99999') || '.trc' from v$process a, v$session b, v$parameter c, v$instance d where a.addr = b.paddr and b.audsid = userenv('sessionid') and c.name = 'user_dump_dest';

出現:/u2/oracle/diag/rdbms/topprod/topprod/trace/topprod_ora_28140.trc

2. 停止 topprod 資料庫,並複製檔案到 toptest
    關閉資料庫:shutdown immediate
    複製檔案:rsync /u2/oracle/oradata/topprod/* /u2/oracle/oradata/toptest
    先刪除 control01.ctl、control02.ctl …檔案,之後會再重建。

3.  建立新的 inittoptest.ora 參數檔案 (檔名必需是 init[資料庫名].ora ,且必須放在 dbs 目錄)
     此作業是要建立一個新的 Instance ,如果已經有建立就可以不用 3、4、5 的步驟。
     進入到 sqlplus 並執行:
     create pfile='/u2/oracle/product/11.2.0/dbhome_1/dbs/inittoptest.ora' from spfile; 

4. 修改 initoptest.ora 檔案,將所有的  topprod 都改為 toptest。

5. 切換到  toptest 資料庫,建立 spfile 資料,並啟動資料庫。
    到 toptest 資料庫:export ORACLE_SID=toptest
    進入 sqlplus 。
    執行:SQL> create spfile from pfile;
    使用 nomount 方式啟動資料庫:startup nomount 
    查看 instance name :select instance_name from v$instance;

6. 複製 trace 檔案並修改為 sql 指令,再執行。
    修改 trc 檔:vi /u2/oracle/diag/rdbms/topprod/topprod/trace/topprod_ora_28140.trc
    修改 SET #1 的部份,並REUSE 改為SET,其餘全部刪除,只剩下如下所示:
 STARTUP NOMOUNT
CREATE CONTROLFILE SET DATABASE "TOPTEST" NORESETLOGS  NOARCHIVELOG
    MAXLOGFILES 16
    MAXLOGMEMBERS 3
    MAXDATAFILES 100
    MAXINSTANCES 8
    MAXLOGHISTORY 292
LOGFILE
  GROUP 1 '/u2/oracle/oradata/toptest/redo01.log'  SIZE 50M BLOCKSIZE 512,
  GROUP 2 '/u2/oracle/oradata/toptest/redo02.log'  SIZE 50M BLOCKSIZE 512,
  GROUP 3 '/u2/oracle/oradata/toptest/redo03.log'  SIZE 50M BLOCKSIZE 512
-- STANDBY LOGFILE
DATAFILE
  '/u2/oracle/oradata/toptest/system01.dbf',
  '/u2/oracle/oradata/toptest/sysaux01.dbf',
  '/u2/oracle/oradata/toptest/undotbs01.dbf',
  '/u2/oracle/oradata/toptest/users01.dbf',
  '/u2/oracle/oradata/toptest/dbs1-01.dbf',
  '/u2/oracle/oradata/toptest/dbs1-02.dbf',
  '/u2/oracle/oradata/toptest/temptabs.dbf',
  '/u2/oracle/oradata/toptest/rptdbs1-01.dbf'
CHARACTER SET AL32UTF8
   執行 trace 的 SQL 檔就會產生 control file: SQL>@/u2/oracle/trace.sql

7. 開啟資料庫。
    執行:SQL> alter database open resetlogs;  

    如果執行失敗的話,出現ORA-01194: file 1 needs more recovery to be consistent,
    就是日誌的問題,解決方式:
    執行: recover database using backup controlfile until cancel;
    出現 Specify log: {<RET>=suggested | filename | AUTO | CANCEL}
    再輸入:/u2/oracle/oradata/toptest/system01.dbf


8. 再把 temporary 的 tablespace 重建,先建一個  TEMP1 然後再移轉再刪除即可。
    先建立:CREATE TEMPORARY TABLESPACE TEMP2 TEMPFILE
  '/u2/oracle/oradata/toptest/temp02.dbf' SIZE 20M AUTOEXTEND ON NEXT 640K MAXSIZE   UNLIMITED
   TABLESPACE GROUP ''
   EXTENT MANAGEMENT LOCAL UNIFORM SIZE 1M;

  再切換為預設:alter database default temporary tablespace temp2;
  再刪除:DROP TABLESPACE TEMP INCLUDING CONTENTS AND DATAFILES;

9. 完成,查看 datafile 。
    執行:select file_name,file_id,tablespace_name,status from dba_data_files;


10. 如果是複製到新的機器上或是新的 Instance 名稱,就要建立 toptest 資料庫的密碼檔案。     
      執行:orapwd file=orapwtopptest password=oracle entries=30

11. 新的機器上就要再設定監聽 listener.ora 和連線 tnsnames.ora的檔案,
      然後再重新啟動 Listener: lsnrctl reload listener 。

2012年12月5日

備援機制: TIPTOP 主機虛擬化

公司的 ERP 系統能夠允許中斷多久呢 ?
有沒有想過當 TIPTOP 主機掛了該怎麼辦,
中斷的時間的長短其實就取決在能夠花費多少金額。
最好就是 HA 再加上異地備援啦,當然也是所費不貲囉~~

在有限的資源下當然就是將 TIPTOP 主機虛擬化是最經濟的作法。
當 ERP 系統發生無法立即排除的情況時,
就可以馬上啟動虛擬的 TIPTOP 主機,再還原備份的檔案和資料庫,
就足以應付急迫的公司作業。

再來就是說明如何將目前的 TIPTOP 主機做線上遷移到虛擬主機,
Linux 的好處就是所有的設定都是文字檔,不像 windows 還有 register 註冊資料庫 。
所以只要檔案複製過去就可以使用了。
要先切換到 root 的使用者。

1. 把 TIPTOP 主機的資料 image 到檔案,先用 df -h 查看 partition 的空間大小,
    或是佔用的空間有多少。

2. 將磁碟、partition 或是建立空的映象檔做 image,以下二選一即可 。
    A.假設將 sda1 的 partition 做成 sda.img 映象檔,執行 dd if=/dev/sha1 of=/u2/sda1.img 。
        (此指令是破壞性磁碟指令,需注意。如果 sda1 為 100G,就會建立 100G 的檔案)
    B.或是建立空的 image 檔,再做資料同步複製也可以。
       建立空的 20 G 大小的 image 檔: dd if=/dev/zero of=/u2/sda1.img bs=1G count=20。
       格式化成 ext3 格式:mkfs.ext3 sda1.img。
       掛載到 \mnt 目錄: mount /u2/sda1.img /mnt。
       同步複製: rsync -av /u1/* /mnt。

3.  將檔案傳到虛擬主機上,然後轉換成 vhd 檔,利用 vhdtool.exe (需下載)做 convert。
     指令:   vhdtool /convert sda1.vhd。

4.  建立新的 guest virtual machine , 建立傳統網路卡需綁網卡的 mac ,並掛載 vhd 檔。

5.  開機,可以開啟喔~~~完成一半了。

6.  再來就是修改設定檔了。我會做下列的修改。
     修改登入是 text mode: vi /etc/init.tab 的 id:5:initdefault: 將 5 改為 3。
     設定網卡: vi /etc/sysconfig/network-scripts/ifcfg-eth0
      (應該會有一個 bak 檔,直接複製就好 cp ifcfg-eth0.bak ifcfg-eth0 )
     修改開機掛載的磁碟,有可能原本是 sda1 變成 hda1,先用 fdisk -l 查看 。
     設定開機自動掛載: vi /etc/fstab,將 sda 改為 hda。

7.   如果虛擬主機的記憶體設定太小,Oracle 有可能會開不起來,
      因為 SGA 設定必須在 tmpfs (ram disk) 以內,
      查看 tmpfs 空間: df -h 。
      到原本的 TIPTOP 主機查看 Global SGA memory ,
      進入 sqlplus 執行 show parameter mem,查看 memory_target 和 memory_max_target。
      然後再修改 tmpfs 的大小,執行: vi /etc/fatab ,在tmpfs 加上 size=4G。

8. 重新啟動,完成。記得千萬要隔離二台主機的網路,不然 mac 位置一樣會造成異常。

9. 平時就保持 Standby 的狀態,有問題時,將備份的程式直接蓋過去,就不多說明了。

10. 再來就是將 DB 也直接 import 進去,備援主機就完成。

如果能夠將資料庫獨立出去是最好了,還原就快多了,
平常第二台 Oracle 主機待機,利用第一台 Oracle 主機的 redo log 或 archive log 做資料的同步。
資料有損失的時間點就可以大幅的減少。
只需要修改 oracle 的連線檔:/u2/oracle/product/11.2.0/dbhome_1/network/admin/tnsnames.ora
就可以切換到第二台 Oracle 主機做存取。

如果想要把測試區和標準區的資料庫刪除來節省磁碟空間和記憶體空間的,
在 Oracle 10g 以後就有提供 drop database 的指令,
以 sys 身份用 sysdba 的方式來登入 sqlplus,
sqlplus sys/sys as sysdba
alter database close;        關閉 database
alter system enable restricted session;        改為 restrict 模式
drop database;        刪除資料庫
刪除資料庫會連同 dbf 檔和 control file、redo file 全部刪除。

因為是 RHEL 5.5 版架設在 Hyper-V 上,所以需安裝 Linux Integration Services v2.1。
# mkdir /media/cdrom
# mount /dev/cdrom /media/cdrom
# mkdir /opt/linux_ic_v21_rtm
# cp -R /media/cdrom/* /opt/linux_ic_v21_rtm
# cd /opt/linux_ic_v21_rtm
# make
# make install
# reboot
下載 adjtimex RPM 套件確保時間的準確性
# rpm -ivh /media/cdrom/Server/adjtimex-1.20-2.1.x86_64.rpm

因為是 P2V 所以需要將 /boot/grub/grub.conf 的 hda=noprobe hdb= norpobe 去掉,不然會無法開機。
然後再放 RHEL 5.5 或是 CentOS 5.5 的安裝光碟重新編譯 Kernel。
1. 使用安裝光碟開機
2. 進入救援模式,輸入 linux rescue
3. chroot /mnt/sysimage 指令將目前的檔案系統環境,由光碟掛載點切換回虛擬主機的硬碟掛載點
4. cd /boot 指令切換目錄
5. ls /lib/modules」指令或「cat /etc/grub.conf」指令查看檔案內容來得知Linux核心版本
6. mkinitrd –v –f initrd-2.6.18-194.el15.img 2.6.18-194.el15
7. 重新開機

取消螢幕保護
修改 /etc/rc.d/rc.local 加入 setterm -powersave off -blank 0

備註:如果想要修改 IP ,需修改以下的檔案:
/etc/hosts
/etc/sysconfig/network-scripts/ifcfg-eth0
/etc/sysconfig/network
/etc/resolv.conf
/u1/genero/fgl/etc/fglprofile
/u1/genero/fgl.ws/etc/fglprofile
/u1/genero/fgl.dev/etc/fglprofile
/u1/topprod/tiptop/bin/tiptop_env
/u1/usr/tiptop/.rhosts
license.sh 或是修改 cl_user.4gl

其他 License 相關問題就不在此說明了。

2012年11月7日

常用的 PL/SQL 函數備忘

常用的 PL/SQL 函數備忘,以免日後要用到的時候要再花時間查。
後續有常用的 SQL 函數的話會再補充。

ABS(數值) :取絕對值

CIEL(數值) :無條件進位

FLOOR(數值) :無條件捨去

MOD(數值1,數值2) :取餘數

POWER(數值1,數值2) :次方

ROUND(數值,小數位) :四捨五入

SIGN(數值) :判斷正負值,正 = 1 ,負 = -1,零 = 0

SQRT(數值) :平方根

LPAD(字串,長度,字元) :向左補字元

RPAD(字串,長度,字元) :向右補字元

LTRIM(字串,字元) :去除最左邊的連續字元或空白

RTRIM(字串,字元) :去除最右邊的連續字元或空白

LOWER(字串) :轉小寫

UPPER(字串) :轉大寫

REPLACE(字串1,字串2,字串3) :取代字串1裡面有內容是字串2,改為字串3

SUBSTR(字串,開始,第幾個) :取字串的部份內容

INSTR(字串1,字串2) :找出字串1裡面有字串2的第一次出現位置

GREATEST(數值1,數值2) :取二數的最大值,有時候還蠻好用的

LEAST(數值1,數值2) :取二數的最小值,有時候還蠻好用的

2012年11月6日

常用的 Genero 指令備忘

一些常用的 Genero 函數和指令備忘,避免找的時候又花了不少的時間。

Genero 函數:

ARG_VAL(數值) :呼叫其他程式時的參數值:參數1、參數2…

NUM_ARGS( ) :回傳總共有多少個參數

ARR_CURR() :目前畫面單身所選擇第 n 筆資料。

ARR_COUNT() :目前畫面單身的全部筆數。

SCR_LINE() :目前在畫面的第幾行,但資料超過單身的資料量要小心,
                          改用 ARR_CURR() 會比較好

SET_COUNT(數值) :設定有多少的陣列要顯示或是輸入

DOWNSHIFT(字串) :字串轉為小寫字母

UPSHIFT( 字串) :字串轉為大寫字母

FGL_LASTKEY() :最近的鍵盤輸入鍵值的 ASCII 值

FGL_KEYVAL(字元) :字元改為 ASCII

GET_FLDBUF(欄位名) :抓取 CONSTRUCT 所輸入的欄位內容

FIELD_TOUCHED(欄位名): 改變了畫面中的欄位時就回傳 TRUE

INFIELD(欄位名) :在指定的欄位時,回傳 TRUE

FGL_GETENV(字串) :環境變數值,用在判斷是 GUI 介面或是 EasyFlow 的環境

FGL_DRAWBOX(高, 寬, 線粗細, 邊距, [顏色] ) :畫方框,(測試是沒有效果?)
                              顏色:0-白,1-黃,2-黃,3-紅,4-青,5-綠,6-藍,7-黑


Genero 指令:

LENGTH(字串) :回傳字串的長度值

ASCII + 空白 + 數字 :將 ASCII 轉換為字元

ORD(字元) :將字元轉換為 ASCII

CURRENT :回傳目前的系統日期 + 時間 + 毫秒,例:2012-11-06 12:00:00 500

DATE :回傳目前的系統日期 (月  日  年) ,例: NOV 6 2012

TODAY :回傳目前的系統日期 (年 / 月 / 日),例: 12/11/06

TIME :回傳目前的系統時間

WEEKDAY(日期) :回傳是星期幾,星期日是 0

YEAR(日期): 回傳年度

MONTH(日期): 回傳月份

DAY(日期): 回傳月的第幾天

MDY(月,日,年) :回傳所指定的日期格式

MOD :二數相除取餘數

** : A 的 B 次方

LSTR(字串或數值) :不知道作用,不過數字可以靠左對齊

SFMT(字串,參數…) :將字串的參數內容,依參數值替換其位置,
                                  範例:DISPLAY SFMT("One = %1, Two = %2.",1,2)

EXTEND( ) :將日期的格式轉換為完整日期+時間格式,
                    範例:DISPLAY EXTEND ( TODAY, YEAR TO FRACTION(4) )

UNITS :將日期的格式做運算,範例: DISPLAY TODAY + (1 UNITS MONTH)
                 但是如果是 10/31 要加一個月,會顯示成空白,
                 提供 YEAR、MONTH、DAY、HOUR、MINUTE、SECOND 參數配合使用

USING :格式化數值欄位,例: DISPLAY 1000 USING '#,##&.&&'
                 #  有數字才顯示,不然空白
                 & 有數字顯示,無數字顯示 0
                 * 有數字顯示,無數字顯示 *
                 < 有數字顯示,無數字為 NULL
                 -  有數字顯示,正數為空白,負數顯示 -
                 + 有數字顯示,正數為 +,負數顯示 -
                 $ 有數字顯示,靠右第一個顯示 $ ,其餘向左空白
                 ( )  有數字顯示,負數才顯示 (數值)

CLIPPED :去除字串結尾空白,開頭的空白是不去除的

SPACES :空幾格,例: DISPLAY 10 SPACES

SQLERRMESSAGE :顯示資料庫的 SQL 執行的訊息,正確顯示 NULL

SQLSTATE :顯示 SQL 執行的訊息,正確顯示 NULL

字串和數值的處理: + 加,- 減,* 乘,/ 除
                                    (逗號) 字串連接 (數字會前方補空白),|| 字串連接 (數字沒有空白)

字串連接:, (逗號) 數字會有空白 || (Pipe) 數字不會有空白

算數運算:+-*/ 就不多說了

邏輯判斷:AND、OR、NOT 也不多說了

其他配合 OUTPUT 列印的指令就不再多加介紹:
PAGENO
LINENO
WORKWARP
COLUMN


詳細的內容請參考 4js 的網站

2012年10月25日

比對二個 Table 的資料, 並進行資料更新

系統做資料同步,新的資料就用 Insert ,原本的資料就用 Update 指令,
當然也可以全部資料 delete 掉再全部重新 Insert 進去,
從 Oracle 10g 的版本開始就提供了一個 Marge 的指令,可以解決很多資料比對上的問題。
Merge 指令的缺點就是,只能二個 Table 進行資料異動。
優點是使用 Update 更新時要注意找不到資料會更新為 Null 的情況,用 Merge 可以避免。

例如:TableA 要依據 TableB 的欄位更新過來,一般都會寫:
            update TableA set (ColumnA2,ColumnA3) =
             (select ColumnB2,ColumnB3 from TableB where ColumnB1 = ColumnA1)
            但是如果要再增加判斷 TableB 的 ColumnB2 不是空的才更新,這樣要改成:
            update TableA set (ColumnA2,ColumnA3) =
             (select nvl(ColumnB2,ColumnA1),ColumnB3 from TableB where ColumnB1 = ColumnA1)

有了 Merge 的指令就可以比較有直覺的 SQL 語法了。
merge into TableA a  a using TableB b  on (a.ColumnA1= b.ColumnB1)
when matched then
   update set a.ColumnA2= b.ColumnB2, a.ColumnA3 = b.ColumnB3 where b.ColumnB2 is not null
when not matched then
   insert (ColumnA1,ColumnA2,ColumnA3) values (ColumnB1,ColumnB2,ColumnB3)

Marge 指令要如何變化不彷就研究試試看吧~~

2012年10月8日

將 CONSTRUCT 所輸入的欄位拆開來

通常報表要判斷多個欄位的時候,
都是用 CONSTRUCT BY NAME tm.wc ON 欄位1,欄位2,欄位3…
有時候欄位1要另外判斷,欄位2又要另外判斷的時候怎麼辦,
這樣 tm.wc 已經組好 SQL 的語法,總不能用字串去拆吧,可能會有錯誤的情況。
當然你也可以分寫開三次 CONSTRUCT ,只是使用者要按三次確定才行。

山不轉路轉,就變化一下吧~~
這時候 GET_FLDBUF 的指令就可以派上用場了。
只是抓出來的卻是使用者輸入查詢的字串,SQL 根本不能拿來用。

只在原本的 CONSTRUCT 加上:
  AFTER CONSTRUCT
          LET tm.wc1 = GET_FLDBUF(imd01)
          LET tm.wc2 = GET_FLDBUF(sfa01)
          LET tm.wc3 = GET_FLDBUF(sfa03)

再來就是增加三個 CONSTRUCT,這樣就分別對應到 tm.wc1, tm.wc2, tm.wc3 。
之後的 SQL 就只要抓 wc1,wc2,wc3 的值就好了。

      CONSTRUCT BY NAME tm.wc1 ON imd01
         BEFORE CONSTRUCT
            DISPLAY tm.wc1 TO FORMONLY.imd01
            EXIT CONSTRUCT
      END CONSTRUCT
     
      CONSTRUCT BY NAME tm.wc2 ON sfa01
         BEFORE CONSTRUCT
            DISPLAY tm.wc2 TO FORMONLY.sfa01
            EXIT CONSTRUCT
      END CONSTRUCT

      CONSTRUCT BY NAME tm.wc3 ON sfa03
         BEFORE CONSTRUCT
            DISPLAY tm.wc3 TO FORMONLY.sfa03
            EXIT CONSTRUCT
      END CONSTRUCT

雖然是很白痴的用法,不過至少也是可以解決不少特殊報表的需求。

2012年9月17日

使用 COM 方式實現 Excel 資料匯入匯出的功能

對使用者來說,在 Excel 整理大量的資料還是比較方便又快速的,
傳統 TIPTOP 的匯入 Excel 資料的方式都是需要先轉為文字檔,
利用分隔符號來區分資料的欄位,但是這樣已是古代的作業方式了。

通常應用程式開發工具可以使用 COM 的方式是跟 Excel Application 進行通訊,
GDC 是 Clinet 端的軟體,所以可以使用 COM 的方式來讀取 Excel 的資料,
參考 4js 提供的 Genero 使用說明,找到 ui,interface.frontcall() 的使用方式。

建立 COM
1:CALL ui.interface.frontcall("WinCOM","CreateInstance",[program],[handle]
  A. program - 系統中註冊的 COM 名稱
  B. handle - 回傳狀態 -1 表示有錯誤,此值在 API 可以使用

使用指定的方法
2:CALL ui.interface.frontcall("WinCOM","CallMethod",[handle,method,arg1,...],[result])
  A.handle - 使用宣告的 handle 值。
  B. method - 函數的名稱。
  C. arg1 - 傳送給方法用的參數
  D. result - 回傳狀態 -1 表示有錯誤,0 表示沒有錯誤

讀取屬性值
3.CALL ui.interface.frontcall("WinCOM","GetProperty",[Handle,member],[result]
  A. handle - 使用宣告的 handle 值
  B. member - 取得屬性的
  C. result - 回傳狀態 -1 表示有錯誤,0 表示沒有錯誤

設定屬性值
4.CALL ui.interface.frontcall("WinCOM","SetProperty",[handle,member,value],[result]
  A. handle - 使用宣告的 handle 值
  B. member - 設定屬性的名稱
  C. value - 屬性的值。
  D. result - 回傳狀態 -1 表示有錯誤,0 表示沒有錯誤

錯誤訊息內容
5.CALL ui.interface.frontcall("WinCOM","GetError",[],[result]
  A. result - 錯誤的說明,如果沒有錯誤就為 Null

關閉 COM
6.CALL ui.interface.frontcall("WinCOM","ReleaseInstance",[handle],[result])
  A. handle - 使用宣告的 handle 值
  B. result - 回傳狀態 -1 表示有錯誤,0 表示沒有錯誤

以下就為 4GL 程式 Excel 資料匯入的範例:
DEFINE l_excelapp INTEGER,
               l_excelwb INTEGER,
               l_result INTEGER,
               l_str STRING,
               l_filename STRING,
               l_target STRING,
               l_range STRING

DEFINE l_i LIKE type_file.num5,
               l_j LIKE type_file.num5,
               l_column LIKE type_file.chr10
LET l_excelapp = -1
LET l_excelwb = -1

# 選擇檔案的位置和檔案名稱
LET l_filename = cl_browse_file()
# 建立 Excel Application 的 COM
CALL ui.interface.frontcall("WinCOM","CreateInstance",["Excel.Application"],[l_excelapp])
# 開啟所選擇的 Excel 檔案
CALL ui.interface.frontcall("WinCOM","CallMethod",[l_excelapp,"WorkBooks.Open",l_filename],[l_excelwb])
# 設定 Excel 要顯示
CALL ui.interface.frontcall("WinCOM","SetProperty",[l_excelapp,"Visible",true],[l_result])
# 讀取 Excel 的行數
CALL ui.interface.frontcall("WinCOM","GetProperty",[l_excelwb,'activesheet.UsedRange.Rows.Count'],[l_result])

FOR l_i=1 TO l_result
   LET l_column = l_i
   # 所要讀取的欄位,A 列第 n 行
   LET l_range = 'activesheet.Range("A',l_column,'").Value'
   # 讀取欄位的值
   CALL ui.interface.frontcall("WinCOM","GetProperty",[l_excelwb,l_range],[l_str])
   LET g_bmd[l_i].bmd01 = l_str
   # 所要讀取的欄位,B 列第 n 行
   LET l_range = 'activesheet.Range("B',l_column,'").Value'
   # 讀取欄位的值
   CALL ui.interface.frontcall("WinCOM","GetProperty",[l_excelwb,l_range],[l_str])
   LET g_bmd[l_i].bmd02 = l_str
END FOR

# 關閉 Excel 的 檔案和 COM
CALL ui.interface.frontcall("WinCOM","CallMethod",[l_excelapp,"WorkBooks.Close"],[l_excelwb])
CALL ui.interface.frontcall("WinCOM","ReleaseInstance",[l_excelapp],[l_result])

2012年8月21日

在畫面加上資料清單,從此不必再下一筆查了

用過 TIPTOP 的剛開始最不習慣的就是查詢資料都要下一筆、下一筆的查詢,
不但查詢耗時,而且要匯出 Excel 需要跑報表才可以列出清單,
讓使用者感覺操作不是那麼的流暢和便利。

可以在程式上面加上資料清單囉~~
而且調整的程式的部份不會很多,大約半小時就可以完成一支程式。

以動態料件數量明細查詢 aimq102 為例:


當在主畫面的時候匯出 Excel 可以匯出庫存明細,
在資料清單匯出 Excel 則匯出資料清單資料,且 Double Click 就會跳到主畫面。
在資料清單選擇料件時,下面的庫存明細也會跟著更新。

這樣是不是就方便許多,也不需要另外在做報表程式做明細資料的匯出。

修改程式步驟:
1. 修改畫面檔,增加新的頁籤,並指定 action ,記得 Main 也要加上 action。

2. 再來就是修改程式了,需要增加二個 Function。
    q102_list_fill() 在顯示資料清單用的。
    q102_bp1(p_ud) 在跳到資料清單時等待操作用的,才能停留畫面在資料清單。                                           
                          如果需要能夠在資料清單做過帳、確認的動作,也是加在此 Function。

3. 最後就是一些小調整,讓功能更完整啦,
   可以在主畫面查詢到第幾筆,資料清單也可以移動到那一筆。
   還有匯出 Excel 依畫面在主畫面還是資料清單來匯出到 Excel。
   需要修改 q102_menu()、q102_bp(p_ud)、q102_cs()、全域變數。