首页 > 编程 > Perl > 正文

运行perl DBI

2024-07-21 02:13:11
字体:
来源:转载
供稿:网友
  在这里,已经看到许多涉及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 号码,电子邮件地址和文件名。数值的参数表示成员资格id 值,带有字符‘@’的参数表示电子邮件的地址。其他任何事情都解释为应该读取的文件名,以便找到他们的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( ),将它分类为id 号、电子邮件地址或者文件名来处理每个参数:

    函数read_file( ) 读取了文件的内容(假设已经打开),并查看每行的第一个域(如果我们将need_renewal 的输出作为renewal_notify 的输入,则每行都有若干域,但是我们只想查看第一个域)。

    i n ter p r e t _ a rgument( ) 函数将每个参数分类,以便确定它是id 号码、电子邮件地址还是文件名。对于id 号码和电子邮件地址,它查找适当的成员项目,并将它传递给n o t i f y _ member ()。我们必须注意由电子邮件所指定的成员。两个成员具有同样的地址是可能的(例如,丈夫和妻子),并且我们不想将一条消息发送给不能用这条消息的人。为了避免这一点,我们查找了与电子邮件地址相对应的成员的id 号码,来确保内容的正确。如果此地址和一个以上的id 号码匹配,则它是不确定的,我们在显示一条警告消息后忽略它。
    如果参数看起来不像id号码或电子邮件地址,则将它作为文件名读取为进一步的输入。在这里,我们也必须小心——为了避免无穷循环的可能性,如果我们已经读取一个文件,则我们不想再读取文件:

    实际上,发送更新通知的notify_member( ) 函数的代码如下所示。如果得出这个成员没有电子邮件地址,则什么也不做,但是notify_member( ) 显示一条警告消息,以便知道需要以其他某种方式与该成员联系。可以调用具有这条消息中所显示的这个成员资格id 号码的s h o w _ member,来查看全部项目—例如,找出这个成员的电话号码和通信地址。

    用它可能获得更好的内容—例如,通过向member 表中增加一列来记录最近更新的提示是何时发送出去的。这样做将有助于避免过于频繁地发送通知。实际上,我们只需假设不存在大约每月运行一次以上的程序。
    现在运行这两个脚本,从而可以这样使用它们:
    % need_renewal > junk
    % (看一看junk,检查它是否合理)
    % renewal_notify junk
    要想通知单个的成员,可以通过id 号码或电子邮件地址指定它们:
    % need_renewl 18 [email protected]

历史同盟成员项目编辑

    我们开始发送更新通知之后,假设我们通知的一些人将更新他们的成员资格是个安全的措施。当这种情况发生时,我们将需要一种更新其所具有的新的终止日期项的方法。下一章中,我们将开发一种方法,在web 浏览器上编辑成员记录,但是在这里,我们将建立一个命
令行脚本e d i t _ member,允许用提示项的各部分新值的方法来更新项目。其操作如下:
    如果在命令行上无参数调用,则edit_member 假设您想输入一个新的号码,提示放在成员项目中的初始信息,并创建新的项目。
    如果在命令行上调用时带有成员id 号码,则edit_member 查找这个项目的已有内容,然后提示更新每一列。如果输入一列的值,则其替换当前的值。如果按enter 键,这列并不更改(如果不知道成员的id 号码,可以运行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页面形式在客户机的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服务器的文档树中。
  • 本文来源于网页设计爱好者web开发社区http://www.html.org.cn收集整理,欢迎访问。
  • 发表评论 共有条评论
    用户名: 密码:
    验证码: 匿名发表