運行perl DBI

來源:互聯網
上載者:User
  在這裡,已經看到許多涉及DBI 程式設計的概念,所以讓我們繼續做一些範例資料庫能處理事情。最初,第1章簡述了我們的目標。本章通過編寫DBI 指令碼,我們將處理的那些問題在這裡列出。
    對於學分儲存方案,我們想能夠檢索任何給定的測驗或測試的分數。
    對於曆史聯盟,我們想做下面的事情:
    以不同格式產產生員目錄。我們想在年度宴會程式中,以可以用於產生顯示目錄的格式使用一個只有名稱的列表。
    尋找不久就要更新其成員資格的League 成員,然後寄送電子郵件通知他們。
    編輯成員項目(畢竟,在更新成員資格時,我們將要更新他們的終止日期)。
    尋找分享共同興趣的成員。
    使這個目錄聯機。
    對於這樣一些任務,我們將編寫從命令列上啟動並執行指令碼。其他任務,我們將在7 . 4節“在Web 應用程式中使用DBI”中建立指令碼,可以與Web 服務器配合使用。在本章的最後,我們將仍有許多有待完成的目標。將在第8章“PHP API”中,完成剩餘的目標。

產生曆史同盟目錄

    我們的目標之一是能以不同格式產生曆史同盟目錄的資訊。我們將產生的最簡單格式是一個年度宴會程式的成員名列表。那可能是一個簡單的無格式文本列表。它將成為建立這個程式的一部分較大文檔,所以,我們所需要的就是可以粘貼到文檔中的一些內容。
    對於可顯示的目錄,則需要一種比無格式文本更好的表示方法,原因是我們想把一些內容更精細地格式化。這裡一個合理的選擇為RT F(豐富的文字格式設定Rich Text Format),它是由Microsoft 開發的一種格式,可以由許多文書處理程式來識別。當然, Word 就是這種程式之一,但是許多其他的軟體,如WordPerfect 和A p p l e Work 也是可以識別的。不同的文書處理程式對RTF 的支援程度也有所不同,但是我們將使用由即使對最低層級RT F都確信的任何文書處理程式所支援的全部RTF 規定的一個基本子集。
    產生宴會列表和RTF 目錄格式的過程本質上是一樣的:發布查詢來檢索這些項目,然後運行將每個項目提取和格式化的迴圈。給出了基本的相似之處湍芎芎玫乇苊獗嘈戳礁齜摯慕瘧盡K裕頤潛嘈匆桓齙ザ賴慕瘧緂 e n _ d i r,它可以以不同的格式從這個目錄產生輸出。我們可以這樣組織這個指令碼:
    1) 在編寫出項目內容之前,完成這個輸出格式可能需要的任何初始化。宴會程式成員列表不需要任何特殊的初始化,但是我們需要為這個RTF 版本編寫一些初始的控制語言。
    2) 提取和顯示每個項目,將我們要輸出的類型適當地格式化。
    3) 處理完所有的項目之後,還要完成任何必需的清除和終止。除了這個RTF 版本需要的一些關閉控制語言以外,宴會列表不需要特殊的處理。
    將來,我們可能想使用這個指令碼以其他格式編寫輸出,所以我們通過設定“轉換盒”——每個輸出格式都有一個元素的散列,使它成為可擴充的。每個元素都指定對給定格式產生適當輸出的函數:初始化函數、編寫項目函數和清除函數如下所示:

    由一個格式名(在這種情況下的“ b a n q ue t”和“r t f”)標識轉換盒的每個元素。我們將編寫這個指令碼,以便在運行它時可以在命令列中指定想要的格式:
    % gen_dir banquet
    % gen_dir rtf
    通過以這種方式設定轉換盒,我們可以很容易地增加新格式的效能:
    1) 編寫三個格式化函數。
    2) 向轉換盒增加一個指向那些函數的新元素。
    3) 為了以新的格式產生輸出,調用g e n _ d i r,並在命令列中指定這個格式名。
    按照命令列中的第一個參數所選擇的適當轉換盒項目的代碼如下所示。它是由於輸出格式的名稱為%switchbox 散列中關鍵字。如果在轉換盒中不存在這樣的關鍵字,則這個格式是無效的。不需要這個代碼中的硬連線格式;如果向轉換盒增加新的格式,則自動地檢測它。如果在命令列中沒有指定格式名,或者指定了一個無效的名稱,則這個指令碼產生錯誤訊息,並顯示一列允許的名稱:

    如果在命令列指定了一個有效格式名,則前述的代碼設定$ f un c _ h a s h r e f。它的值將是指向選擇了格式輸出的編寫函數的散列引用。然後我們可以運行這個選擇項目的查詢。之後,我們調用初始化函數、提取和顯示這些項目,並啟用清除函數:

    因為某種原因,提取項目的迴圈使用了fetchrow_hashref( )。如果這個迴圈提取數組,則這個格式化函數必須知道列的次序。它可能通過訪問$sth->{NAME} 屬性(它含有返回次序的列名)來得到,但為什麼煩擾呢?通過使用散列引用,格式化函數將只能命名那些想使用$entry_ref->{col_name} 的列值。那樣效率就非常低,但它容易做到,並可用於想產生的任何格式,因為我們知道我們需要的任何域都在散列中。
    剩餘的工作就是為每種輸出格式編寫這些函數(也就是說,通過轉換盒項目為這些函數命名)。
    1. 產生宴會程式成員列表
    對於這種輸出格式,我們只想要成員的姓名。不需要初始化或清除調用。只需要一個項目格式化函數:


    format_banquet_entry( ) 的參數是行的列值的散列引用。這個函數將名和姓連在一起,加上可能出現的任何尾碼。這裡的竅門是如“ J r.”或“S r.”尾碼的前面應該有一個逗號或空格,但是如“I I”或“I I I”尾碼的前面只能為一個空格:

    因為字母‘I’、‘V’和‘X’覆蓋了所有產生的數字,從第1到第3 9,所以我們可以使用下面的測試來確定是否增加一個逗號:

    和名稱放在一起的format_banquet_entry( ) 的代碼也是這個目錄的RTF 版本將需要的一些內容。然而,並不是複製format_rtf_entry( ) 中的代碼,讓我們將它填入函數中:

    將確定名稱的字串放在format_name( ) 函數中,將把format_banquet_entry( ) 函數減少到幾乎沒有:

    2. 產生顯示格式的目錄
    產生這個目錄的RTF 版本比產生宴會程式成員列表更要棘手一些。首先,我們需要從每個項目中顯示更多的資訊。其次,我們需要用每個項目產生一些RTF 控制語言來完成我們想要的作用。RTF 文檔的最小架構是這樣的:


    這個文檔用花括弧‘ {’和‘ }’作為開始和結束。RTF 關鍵字用反斜線符號開始,並且文檔的第一個關鍵字必須為\ r t f n,n為這個文檔對應的RTF 規定的版本號碼。如果按我們的目的,0就比較合適。
    在這個文檔的內部,我們指定字型表來說明這些項目所使用的字型。字型表資訊列在組中,由含有前置的\fonttbl 關鍵字和一些字型資訊的花括弧組成。在架構中說明的這個字型表把字型號0 定義為Ti m e s(我們只需要一個字型,但是如果想顯示得更好一些,可以使用多種字型)。
    下面的一些指示設定了預設格式風格: \plain 選擇無格式的格式, \f0 選擇字型0(我們已經在字型表中定義為Times ),\fs24 設定字型大小為12個點陣(\fs 後面的數量表示半個點陣的大小)。設定頁邊空白並不是必需的;大多數的文書處理程式將提供合理的預設值。
    要想得到一個非常簡單的方法,可以將每個項目顯示為一系列的行,每行上都有一個標號。如果對應於特定輸出行的資訊缺失,則忽略這個行(例如,沒有電子郵件地址的成員沒有顯示“ E m a i l :”行)。一些行(如“ A d d r e s s :”行)由多個列(街道、城市、州、郵遞區號)中的資訊構成,所以這個指令碼必須能夠處理缺失值的各種組合。這裡是我們將使用的輸出格式的範例:

    對於顯示的格式化項目,RTF 的表示方法如下所示:

    要想使“Name :”行為粗體,則在它的前面加\b (後面有個空格)來開啟粗體,並用\ b 0來關閉粗體。每行在末端都有一個區段標記符( \ p a r)來告訴文書處理程式移到下一行——沒有太複雜的事情。
    初始化函數產生前置RTF 控制語言(請注意,兩個反斜線符號獲得輸出中的一個反斜線符號):

    類似地,清除函數產生終止控制語言(並不太多!):
    sub rtf_cleanup
    {
          print '}\n";
    }
    真正的工作與格式化這個項目有關,即使這個任務相對簡單。主要複雜點是將地址字串格式化,並確定應該顯示哪個輸出行:

    當然,不用限於這種特殊的格式化風格。可以更改如何顯示任何域的方法,所以通過簡單地更改format_rtf_entry( ),可以幾乎任意地更改顯示的目錄。用它原始格式的目錄(一個文書處理文檔),是多麼不容易做的事情!
    gen_dir 指令碼現在完成了。通過運行以下這些命令,我們可以以任意一種輸出格式產生這個目錄:
    % gen_dir banquet > name.txt
    % gen_dir rtf > directory.rtf
    在Windows 中,我可以運行g e n _ d i r,則這些檔案準備從基於Windows 文書處理程式的內部使用。在UNIX 中,我可就以運行上面那些命令,然後將這些輸出檔案以郵件形式發給自己作為附件,以便可以從我的Macintosh 中擷取它們,並將它們載入到文書處理程式中。我偶爾使用mutt 郵寄程式,它允許使用-a 選項從命令列指定附件。可以如下發送給自己一個具有這兩個附加檔案的訊息:
    % mutt -a name.txt -a directory.rtf paul0snake.net
    其他郵寄程式可能也允許建立附件。或者,可以以其他意思傳輸這些檔案,如F T P。無論如何,在這些檔案被放到想放的地方之後,讀取這個名稱列表,並將它粘貼到年度程式文檔,或者在可識別RTF 的任何文書處理程式中讀取RTF 檔案,這都是較容易的。DBI 使我們從MySQL中抽取想要的資訊很容易, Perl 的文本處理能力使我們將這些資訊放在指定的格式中很容易。MySQL不提供資訊輸出的任何特殊方式,但沒有關係,因為將MySQL的資料庫處理能力整合到如Perl 的語言中並不費力,而這些語言具有極好的文本處理能力。

發送成員資格更新通知

    當作為文書處理文檔維護曆史同盟目錄時,確定需要通知哪個成員其成員資格應該更新,這是件耗費時間並且容易出現錯誤的事情。既然我們在資料庫中有資訊,那麼讓我們看看如何自動地處理更新通知。我們想標識需要經過電子郵件更新的成員,這樣我們就不必通過電
話或郵件與他們聯絡了。
    我們需要做的事情就是確定哪個成員在某些天以內快到更新的時間了。這個的查詢涉及一個相對簡單的日期計算:
    SELECT ... FROM member
    WHERE expiration < DATE_ADD (CURRENT_DATE,INTERVAL cutoff DAY)
    c ut o ff 表示我們同意的可允許誤差的天數。這個查詢選擇在幾天之內快到更新時間的成員項目。作為特殊情況,終止點值為0,尋找終止日期已過的成員(也就是說,實際上已經終止了的那些成員)。
    我們標識了限制通知的這些記錄之後,我們對它們應該怎麼辦呢?一個選擇是直接從同樣的指令碼中發送郵件,但是,首先審閱不發送任何訊息的列表可能有用。由於這個原因,我們將使用一個兩階段的方法:
    階段1:運行指令碼need_renewal 來標識需要更新的成員。可檢查這個列表,或者可以使用它作為將更新通知發送到第2 階段的輸入。
    階段2:運行指令碼r e n e w a l _ n o t i f y,它通過電子郵件向成員發送“請更新”的通知。這個指令碼應該通知您不具有電子郵件地址的成員,以便可以用其他方式與他們聯絡。
    在此任務的第一部分中, need_renewal 指令碼必須標識哪個成員需要更新。它的操作如下所示:

    need_renewal 指令碼的輸出如下所示(因為是針對當前日期確定的結果,而您讀這本書的時間和我書寫它的時間將是不同的,所以將獲得不同的輸出)

    可以觀察到,處於負數天數的那些成員資格需要更新。負數意味著我們已經到期了(當手工地維護記錄時,就可能發生這種情況;有些人從縫隙中滑掉了。既然我們在資料庫中有了這些資訊,那麼我們要尋找在前面丟失的幾個人)!
    更新通知任務的第二部分涉及了通過電子郵件發送通知的指令碼r e n e w a l _ n o t i f y。要想使renewal_notify 更容易使用,則我們可以使它支援三類命令列參數:成員關係識別碼,電子郵件地址和檔案名稱。數值的參數表示成員資格ID 值,帶有字元‘@’的參數表示電子郵件的地址。其他任何事情都解釋為應該讀取的檔案名稱,以便找到他們的識別碼或電子郵件地址,可以直接在命令列中這樣做,或者通過將它們在檔案中列出來去做(特別是,可以使用need_renewal 的輸出作為renewal_notify 的輸入)。
    對於要發送通知的每個成員,此指令碼尋找相應的member 表項目,抽取電子郵件地址,並向那個地址發送一條訊息。如果此項中沒有電子郵件地址,則renewal_notify 產生一條訊息,通知您需要以一些其他方式與這些成員聯絡。
    要想寄送電子郵件, renewal_notify 開啟與sendmail 程式的管道,並將這封郵件推入此管道中(在Windows 下不能這樣操作,Windows 中沒有s e n d m a i l。可能需要尋找發送郵件的模組來代替它使用)。在此指令碼開頭附近,將到sendmail 的路徑名設定為參數。可能需要更改該路徑,因為sendmail 的位置隨系統的變化而變化:
    # change path to match your system
    my ($sendmail)="/usr/lib/sendmail -t -oi";
   主要參數處理迴圈的操作如下所示。如果在命令列沒有指定參數,則我們讀取標準的輸出作為輸入。否則,我們通過將參數傳遞給i n ter p r e t _ a rgument( ),將它分類為識別碼、電子郵件地址或者檔案名稱來處理每個參數:

    函數read_file( ) 讀取了檔案的內容(假設已經開啟),並查看每行的第一個域(如果我們將need_renewal 的輸出作為renewal_notify 的輸入,則每行都有若干域,但是我們只想查看第一個域)。

    i n ter p r e t _ a rgument( ) 函數將每個參數分類,以便確定它是識別碼、電子郵件地址還是檔案名稱。對於識別碼和電子郵件地址,它尋找適當的成員項目,並將它傳遞給n o t i f y _ member ()。我們必須注意由電子郵件所指定的成員。兩個成員具有同樣的地址是可能的(例如,丈夫和妻子),並且我們不想將一條訊息發送給不能用這條訊息的人。為了避免這一點,我們尋找了與電子郵件地址相對應的成員的識別碼,來確保內容的正確。如果此地址和一個以上的識別碼匹配,則它是不確定的,我們在顯示一條警告訊息後忽略它。
    如果參數看起來不像ID號碼或電子郵件地址,則將它作為檔案名稱讀取為進一步的輸入。在這裡,我們也必須小心——為了避免無窮迴圈的可能性,如果我們已經讀取一個檔案,則我們不想再讀取檔案:

    實際上,發送更新通知的notify_member( ) 函數的代碼如下所示。如果得出這個成員沒有電子郵件地址,則什麼也不做,但是notify_member( ) 顯示一條警告訊息,以便知道需要以其他某種方式與該成員聯絡。可以調用具有這條訊息中所顯示的這個成員資格識別碼的s h o w _ member,來查看全部項目—例如,找出這個成員的電話號碼和通訊地址。

    用它可能獲得更好的內容—例如,通過向member 表中增加一列來記錄最新動向的提示是何時發送出去的。這樣做將有助於避免過於頻繁地發送通知。實際上,我們只需假設不存在大約每月運行一次以上的程式。
    現在運行這兩個指令碼,從而可以這樣使用它們:
    % need_renewal > junk
    % (看一看junk,檢查它是否合理)
    % renewal_notify junk
    要想通知單個的成員,可以通過識別碼或電子郵件地址指定它們:
    % need_renewl 18 g.steve@pluto.com

曆史同盟成員項目編輯

    我們開始發送更新通知之後,假設我們通知的一些人將更新他們的成員資格是個安全的措施。當這種情況發生時,我們將需要一種更新其所具有的新的終止日期項的方法。下一章中,我們將開發一種方法,在網頁瀏覽器上編輯成員記錄,但是在這裡,我們將建立一個命
令行指令碼e d i t _ member,允許用提示項的各部分新值的方法來更新項目。其操作如下:
    如果在命令列上無參數調用,則edit_member 假設您想輸入一個新的號碼,提示放在成員項目中的初始資訊,並建立新的項目。
    如果在命令列上調用時帶有成員識別碼,則edit_member 尋找這個項目的已有內容,然後提示更新每一列。如果輸入一列的值,則其替換當前的值。如果按Enter 鍵,這列並不更改(如果不知道成員的識別碼,可以運行show_member last_name 來尋找其內容)。
    如果只想更新成員的終止日期,則允許編輯全部項目的這種方式可能是不必要的過度行動。另一方面,類似這樣的指令碼也提供了一種簡單的通用目的方式,來更新一個項目的任何部分而不必瞭解SQL 的任何知識(一種特殊的情況為edit_member 不允許更改member_id 域,因為當建立一個項目時,自動地分配這個域,並且在以後不能更改)。
    edit_member 需要瞭解的第一件事為member 表中這些列的名稱:

    然後我們可以輸入主體迴圈:

    建立新成員項目的代碼如下所示。它請求每個member 表列,然後發布一條INSERT 語句以增加一條新記錄:


    new_member( )所用的提樣本程如下所示:

    col_prompt( ) 帶有$show_current 參數的原因是,當這個指令碼用於更新項目時,我們也對已有成員項目請求的列值使用這個函數。當建立新的項目時, $show_current 將為0,因為當前沒有值可以顯示。在編輯一個已有項目時,它將為非零。後一種情況中的提示將顯示當前的值,使用者可以簡單地通過按Enter 鍵來接受。
    編輯已有成員的代碼類似於建立新成員的代碼。然而,我們有一個可操作的項目,所以提樣本程顯示當前項目的值,並且edit_member( ) 函數發布一條UPDATE 語句,而不是INSERT語句:

    edit_member 的問題為它不進行任何輸入值校正。對於member 表中的大多數域,都沒有什麼校正——它們只是字串域。但是對於expiration 列,實際上應該檢查輸入值,以便確保它們看起來像日期。在一般目標的資料輸入應用程式中,可能想抽取有關表的資訊,以便確定它的所有列的類型。然後,可能按照那些類型上的約束條件來校正。那就比我在這裡想探求的內容涉及得更多,所以我只在col_prompt( ) 函數中增加一個快速方法,以便如果列名為“e x p i r a t i o n”,則檢查輸入的格式。最低限度的日期值檢查可以這樣來做:


    這個模板測試了非數字字元分隔的三個序列的數字。這隻是檢查的一部分,因為它沒有偵測如“ 1999 - 14 - 2 2”的值為無效。要想使指令碼更好,則應該給它更嚴格的日期檢查以及其他檢查,如需要名和姓的域,就應該給非空值。
    一些其他的改進可能是,如果沒有更改列,則跳過這個更新,當使用者正在編輯它時,如果其他一些人已經更改了這條記錄,則通知這個使用者。可以通過儲存成員項目列的未經處理資料來做到這一點,然後,編寫UPDATE語句來只更新那些已經更改的列。如果沒有,則甚至不需要發布這條語句。同樣,對於每個原始列值,可以編寫WHERE 子句來包括A N D col_name = col_val。如果其他一些人已經更改了這條記錄,則這可能導致UPDATE失敗,此時它的反饋為,兩個人要同時更改這個項目。

尋找共同興趣的曆史同盟成員

    曆史同盟秘書的責任之一就是處理成員的請求,這些成員可能要求對美國歷史領域內特殊時期或特殊人物(如在大蕭條中或者亞伯拉罕·林肯的生命)感興趣的其他人清單。當在文書處理程式文檔中維護這個目錄時,使用文書處理程式的“ F i n d”功能,可以非常容易地找到這樣的成員。然而,產生一列只含有合格成員的項就要困難一些,因為它涉及大量的拷貝和粘貼。使用MySQL,工作就變得容易得多,因為我們可以只運行如下這樣的查詢:
    SELECT * FROM member WHERE interests LIKE "%lincoln%"
    ORDER BY last_name,first_name
    不幸的是,如果在mysql客戶機程式運行這個查詢,則結果看上去並不是非常好。讓我們把少量的DBI 指令碼和產生較漂亮的輸出的interests 放在一起。首先,檢查一下指令碼,確保在命令列至少有一個命名的參數,因為如果沒有一個命名的參數就沒有內容可以搜尋。然後,對於每個參數,指令碼在member 表的interests 列上運行一個查詢:

    為了搜尋索引鍵字串,我們在每一邊都放了萬用字元‘ %’,以便可以在interests 列的任何地方都可以找到這個字串。然後,我們顯示相匹配的項:


    這裡沒有出現format_entry( ) 函數。它與gen_dir 指令碼的函數format_rtf_entry( ) 在本質上是相同的,但format_entry( ) 函數去掉了RTF 控制字。

聯機曆史同盟目錄

    在7 . 4節中,我們將開始編寫串連到MySQL伺服器並抽取資訊的指令碼,還要編寫以Web頁面形式在客戶機的網頁瀏覽器中出現的資訊。那些指令碼按照客戶機請求動態地產生了H T M L。在我們到達那一點之前,讓我們通過編寫產生能裝載到Web 服務器文檔樹中的靜態
HTML 文檔的DBI 代碼,開始考慮有關的H T M L。以HTML 格式建立的曆史同盟目錄是最好的選擇,因為我們的目標之一就是無論如何要使目錄聯機。
    一般來說,HTML 文檔有點像下面這樣的結構:

    為了以這種格式組建目錄,編寫完整的指令碼對於你來講並不必要。回想一下,當我們編寫gen_dir 指令碼時,我們使用了可擴充的架構,因此,為了以其他格式產生目錄而插入了代碼。這意味著假如代碼產生了HTML 輸出,我們則需要編寫文檔初始化和清除的函數,和格式化單獨項一樣。然後我們需要建立轉換盒元素來指向這些函數。
    只顯示出的聯機文檔非常容易地分解為可以由初始化函數和清除函數處理的序言和收尾部分,以及由項目格式化函數產生的中間部分。HTML 初始化函數產生層級1標題的每一部分,而清除函數產生關閉</BODY> 和</HTML> 標記的部分:

    一般來說,真正的工作在于格式化項目。但即使這樣也不太困難。我們可以拷貝format_rtf_entry( ) 函數,確保項目中的任何特殊字元都被編碼,並且用HTML 標出的標誌替換RTF 控制字:

    現在我們把另一個元素加到轉換盒中,指出編寫HTML 的函數,並且完成對g e n _ d i r的更正:

    為了產生HTML 格式的目錄,運行下面的命令並在Web 服務器的文檔樹中安裝結果輸出檔案:
    % gen_dir html > directory.html
    當更新目錄時,可以再次運行命令來更新聯機版本。另一個方案是建立周期性執行的cron 作業。那就是說,聯機目錄將被自動地更新。例如,我可能使用類似於這個的crontab 項在每天早晨4點運行g e n _ d i r:
    04****/u/paul/samp_db/gen_dir>/usr/local/apache/htdocs/directory.html
    這個cron 作業所啟動並執行使用者必須允許它們都執行位於samp_db 目錄中的指令碼,並將檔案編寫到Web伺服器的文檔樹中。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.