Clipper 教程:开源 Clipper(s) 使用指南/数据库操作
让我们回到维基百科条目 数据库应用程序。
也存在不同类型的数据库应用程序。如果你将朋友的电话号码和地址存储在文字处理器中,你将拥有所谓的自由格式数据库(然而,在计算机科学中,类似的表达是矛盾的)——myBase®、askSam®、Personal Knowbase®、MyInfo®、Info Select® 和 GeneralKB® 是一系列专门的自由格式数据库应用程序,实际上是指 PIM(个人信息管理器)。现在,文字处理器允许我们搜索信息,但其他操作,例如按字母或数字顺序对它们进行排序,通常无法由文字处理器自动完成。
尝试将其存储到电子表格中怎么样?我们可以使用一列用于姓名,一列用于姓氏,一列用于电话号码,一列用于城市。这个存储在电子表格中的快速数据库可以进行搜索和排序:例如,我们可以按城市和人员姓名按字母顺序进行排序。这是一个平面数据库,http://www2.research.att.com/~gsf/man/man1/cql.html:平面数据库是由分隔符分隔的字段的新行终止记录的序列,并且电子表格显示了其在数据输入和报告方面的局限性(如果你想使用表中的数据在信封上打印地址,电子表格不是一个好工具)。一个例子是 MyDatabase(http://www.pcmag.com/article2/0,2817,760833,00.asp)。
电子表格更适合做会计:如果记账员的数据存储在文字处理程序中,他的工作会变得多么困难?这里的目的是以某种方式组织我们的数据:所有成本在一个地方,所有收入在另一个地方。
在 1970 年之前,复杂的数据库使用层次数据库进行管理(只需要很少的信息——例如,参见 http://www.extropia.com/tutorials/sql/hierarchical_databases.html 和 http://people.cs.pitt.edu/~chang/156/14hier.html)。层次数据库的一个例子是 IBM IMS(信息管理系统,该系统于 20 世纪 60 年代中期开发,用于航空航天行业的应用程序)。它们的实现基于树,一种层次数据结构。层次数据库和网络数据库一起构成了如今所说的传统数据库系统。网络数据库是由数据系统语言会议(CODASYL)作为对编程语言 COBOL 的扩展而产生的。网络数据模型基于称为图的数据结构。
如今的标准是关系数据库(RDBMS),它是“一个数据库,其记录表之间存在基于公共字段的关系”。我们将详细讨论它们,但我们将简要提及第四种方法:面向对象数据库。这些数据库存储对象(与在面向对象编程表达式中使用的意义相同)。它们的使用并不多,主要是因为对象比关系数据库在其表中存储的简单字段更复杂。有关此主题的更多信息,请访问 http://www.odbms.org/odmg-standard/。
关于 dBase 的维基百科条目写道:“dBase 是一种应用程序开发语言和集成导航数据库管理系统,Ashton-Tate 将其标记为“关系型”,但它不符合 Edgar F. Codd 博士定义的关系模型标准”。Codd 的标准(所谓的 12 条规则,实际上是 13 条,因为编号为“0”的规则实际上存在)非常严格,以至于在实践中,真正的关系数据库系统甚至不存在,但关键是 dBase 以另一种方式访问数据库,因此它被认为是导航数据库(其工作方式模拟关系数据库)。
http://www.databasedev.co.uk/design_basics.html
由于 dBase 及其的巨大成功,DBF 文件格式成为行业标准。许多其他数据库程序都使用它们来存储数据,例如 Lotus Approach。我们还有许多小程序可以查看和转换为其他格式的文件。这里有一堆 URL:https://dbfview.com/、http://www.alexnolan.net/software/dbf.htm、https://dbfviewer.com/en/、https://www.dbf2002.com/、http://www.whitetown.com/dbf2sql/(“DBF 到 SQL 转换器允许您将 dbf 文件转换为 SQL 脚本。个人许可证 29.95 美元”,但请比较 https://www.vlsoftware.net/exportizer/)。它使用如此广泛,以至于各种语言都提供了用于与其交互的接口,例如
- C++
- https://sourceforge.net/projects/xdb/、http://linux.techass.com/projects/xdb/xbasedocs/xbase_c1.html 和 http://linux.techass.com/projects/xdb/ Xbase(以前称为 xdb,也以前称为 xBase)是一组规范、程序、实用程序和 C++ 类库,用于处理 Xbase 类型的数据文件和索引。
- Python
- https://sourceforge.net/projects/xbase-py/ 用于管理 dbf 文件的 Python 接口
- Java
- https://sourceforge.net/projects/xbasej/ xBaseJ - 用于 Java 的 xBase 引擎
- PHP 接口链接
好的,现在我们将了解如何按照预期的方式使用 DBF 文件。
&& it was done this way at the Dot Prompt
&& we can type this interactively in hbrun
CREATE TMPNAMES
USE TMPNAMES
APPEND BLANK
REPLACE FIELD_NAME WITH "NAME"
REPLACE FIELD_TYPE WITH "C"
REPLACE FIELD_LEN WITH 15
APPEND BLANK
REPLACE FIELD_NAME WITH "ADDRESS"
REPLACE FIELD_TYPE WITH "C"
REPLACE FIELD_LEN WITH 30
CLOSE
CREATE NAMES FROM TMPNAMES && https://www.itlnet.net/programming/program/Reference/c53g01c/ngc785e.html
ERASE TMPNAMES.DBF && we get rid of the temporary file
上面的代码创建了一个 DBF 文件,names.dbf,供以下代码使用。它将向 DBF 文件添加一条记录。它等效于我旧的 PC 指南中的“第一个示例程序”,该程序缺少现代 xBase 中必需的一行。
CLEAR
? "First Sample Program"
SELECT 1
USE NAMES
APPEND BLANK
REPLACE NAME WITH "MIKE BROWN"
REPLACE ADDRESS WITH "ROME STREET, 56"
CLOSE && this line is missing in my PC GUIDE but is needed in a compiled Harbour program
QUIT
CLOSE 命令等效于 dbCloseArea() 函数,该函数关闭工作区:写入挂起的更新,释放挂起的锁。
下面的简短代码执行上一节中两段代码相同的工作(它只生成不同的文件名,namesdb.dbf 而不是 names.dbf)。
local aStruct := { { "NAME", "C", 15, 0 }, ;
{ "ADDRESS", "C", 30, 0 }}
REQUEST DBFCDX
dbCreate( "namesdb", aStruct, "DBFCDX", .t., "NAMESDB" )
&& http://www.fivetechsoft.com/harbour-docs/api.html
USE NAMESDB
NAMESDB->(DbAppend())
NAMESDB->NAME := "MIKE BROWN"
NAMESDB->ADDRESS := "ROME STREET, 56"
此示例使用别名运算符,->。http://www.ousob.com/ng/clguide/ngcf412.php
别名->字段名 表示法用于访问已加载但未激活的数据库的字段。别名可以使用工作区编号(例如2->std_id)、工作区别名(例如B->std_id)或数据库名称(例如STUDENTS->std_id)指定。
此代码的结果是一个名为namesdb.dbf的文件。有关 DBF 文件的信息可以在DBF 文件结构中找到,http://www.dbf2002.com/dbf-file-format.html,在这里我们可以找到这个字段类型列表。
- C – 字符型
- Y – 货币型
- N – 数值型
- F – 浮点型
- D – 日期型
- T – 日期时间型
- B – 双精度型
- I – 整型
- L – 逻辑型
- M – 备注型
- G – 通用型
- C – 字符型(二进制)
- M – 备注型(二进制)
- P – 图像型
- + – 自动增量(dBase 7 级)
- O – 双精度型(dBase 7 级)
- @ – 时间戳(dBase 7 级)
我的 PC 指南展示了如何使用数据库工具DBU创建 .dbf 文件。此工具的克隆版本包括FiveDBU(带有源代码),位于https://code.google.com/archive/p/fivewin-contributions/downloads,DBF Viewer Plus,位于http://www.alexnolan.net/software/dbf.htm,CLUT,位于http://www.scovetta.com/archives/simtelnet/msdos/clipper。Harbour 包含其自身的HbDBU(源代码位于\hb32\addons\hbdbu)和HbIDE的组件IdeDBU(另外两个组件是IdeEDITOR和IdeREPORTS)。
从https://code.google.com/archive/p/fivewin-contributions/downloads我们可以获取 fivedbu_20130930.zip(增强了 ADO 字段编辑功能的新版 FiveDBU)。它支持 ADO、3 个 RDD(DBFNTX、CBFCDX 和 RDDADS)和 6 种语言 - 选择“Bases de datos -> Preferencias -> Lenguaje: Inglés”以将其设置为英文。
让我们看看我们的小文件目前包含什么内容。
USE NAMES
LIST DATE(), TIME(), NAME, ADDRESS
上一节中的工作旨在精确地复制我的 PC 指南中提供的数据库。但是,这样做也有一些缺点:只有一个 NAME 字段,此数据库无法按姓氏对数据进行排序。此外,粗心的用户可能会将某些人的数据以姓氏开头插入,而将其他一些数据以名字开头插入。在设计数据库时,应注意这些可能性。第一范式 (http://www.1keydata.com/database-normalization/first-normal-form-1nf.php,http://www.sqa.org.uk/e-learning/SoftDevRDS02CD/page_14.htm) 要求您定义其信息不能细分为更小部分的字段。因此,我们应该使用 FIRST_NAME 和 LAST_NAME 字段,而不是 NAME 字段。遵守第一范式,我们的小型数据库将朝着成为规范化数据库的方向发展。
数据库设计是工作的重要组成部分,并且并非总是显而易见应该如何进行。请参阅https://www.ntu.edu.sg/home/ehchua/programming/sql/relational_database_design.html。
用于辅助数据库设计的图形工具是实体关系图:https://www.lucidchart.com/pages/er-diagrams,https://www.guru99.com/er-diagram-tutorial-dbms.html。
Harbour 包含一个名为 test.dbf 的文件。在它的目录中启动 hbrun 并输入
use test browse()
此时,我们看到它是一个包含 500 条记录的表。使用光标键移动,完成后,按 Esc 键退出此交互式表浏览器和编辑器。要获取名为 Ted 的人的记录编号,请发出
locate for first="Ted" ? recno()
我们现在可以尝试使用 hbrun 交互式地解决 ECDL 教材中的一道练习题。ECDL(欧洲计算机驾驶执照,https://www.findcourses.co.uk/inspiration/articles/why-does-ecdl-matter-17580)是一个资格认证,证明某人可以使用计算机,并具备不同程度的熟练程度。多年前当我获得此资格时,数据库模块的任务非常非常简单。它是处理数据库的第 5 个模块。我买了一本小书,重新阅读后发现该模块中的第 2 项练习很有启发性:创建一个表来管理电影库。我在 ReactOS 0.4.13 下的 Harbour 3.0 中完成了此操作,几乎以交互方式使用 hbrun - 这给它带来了愉悦的 dBase 风格。但是,有必要使用一些辅助文件,因为某些命令在单行中输入非常困难 - 或不可能。这没什么不好,我们将看到如何从提示符运行存储在文件中的代码片段。
要点 1。打开电脑并打开数据库管理器 - 虽然看起来很荒谬,但我的练习册中每道题的第一步都是打开电脑。
C:\videolib>c:\hb30\bin\hbrun
第一个要发出的命令将窗口设置为 25×80 个字符,以便它在屏幕上看起来不错,并且没有任何内容超出视野。
.setmode(25,80)
此时,我们应该或多或少看到以下内容(您的屏幕可能不是意大利语)
第一行显示 hbrun 执行的最后一个命令,setmode(25,80),底行显示了传奇的点提示符。
要点 2。创建一个表来管理电影库,包含以下字段
- 电影代码,
- 电影标题,
- 类型,
- 制作年份,
- 主演,
- 欧元价格。
我们可以发出以下所有命令在点提示符处创建具有此结构的数据库
.aDbf := {}
.AADD(aDbf, { "MOVIEID", "C", 6, 0 })
.AADD(aDbf, { "TITLE", "C", 30, 0 })
.AADD(aDbf, { "GENRE", "C", 30, 0 })
.AADD(aDbf, { "YEAR", "N", 4, 0 })
.AADD(aDbf, { "LEADACTOR", "C", 30, 0 })
.AADD(aDbf, { "PRICE_EUR", "N", 5, 2 })
.DBCREATE("videolib", aDbf)
或者我们可以将它们存储在文件中,例如 creadb.prg,并使用以下命令从命令行调用:.do creadb
现在我们可以检查工作目录中的内容
.dir
此命令将显示当前目录中所有数据库的名称。我们应该看到类似以下内容
Database Files # Records Last Update Size videolib.dbf 0 09/15/21 227
并开始使用我们的数据库
.use videolib
前两行现在应该显示为
PP: use videolib
RDD: DBFNTX | Area: 1 | Dbf: VIDEOLIB | Index: | # 1/ 0 o
要点 3。创建表单以输入数据 - 我们稍后将执行此操作。我们将看到它是如何完成的:@...GET 命令将输入字段放置在屏幕上,并允许用户输入数据。一个典型的过程如下所示
PROCEDURE EnterMovieData
LOCAL MOVIEID, TITLE, GENRE, YEAR, LEADACTOR, PRICE_EUR
@ 5, 10 SAY "Movie Code:" GET MOVIEID
@ 7, 10 SAY "Movie Title:" GET TITLE
@ 9, 10 SAY "Genre:" GET GENRE
@ 11, 10 SAY "Year of Production:" GET YEAR
@ 13, 10 SAY "Leading Actor:" GET LEADACTOR
@ 15, 10 SAY "Price in Euro:" GET PRICE_EUR
// Save data
INSERT INTO videolib VALUES (MOVIEID, TITLE, GENRE, YEAR, LEADACTOR, PRICE_EUR)
RETURN
要点 4。使用不同的数据填充表,至少包含 10 条记录。
我们将从下面的 CSV 文件“movies.txt”中获取我们的示例数据,使用以下命令
.append from movies delimited
1,"Jurassic Park","azione",1993,"Jeff Goldblum",35.99 2,"Jumanji","avventura",1995,"Robin Williams",8.49 3,"Navigator","fantascienza",1986,"Joey Cramer",11.39 4,"Mortal Kombat","azione",1995,"Christopher Lambert",10 5,"Karate Kid 4","azione",1994,"Hilary Swank",4.9 6,"Ritorno al futuro","fantascienza",1985,"Michael J. Fox",6.95 7,"2001: Odissea nello Spazio","fantascienza",1968,"Keir Dullea",7.9 8,"Il pianeta proibito","fantascienza",1956,"Leslie Nielsen",9.99 9,"Interstellar","fantascienza",2014,"Matthew McConaughey",6.95 10,"Prometheus","fantascienza",2012,"Michael Fassbender",7
我们可以使用以下命令检查当前数据库中的数据
.browse()
现在我们可以对示例数据进行一些小的更改。例如,Genre 字段中的数据为意大利语,但我们希望将其更改为英语。执行替换的命令非常简单
.replace genre with "science fiction" for genre="fantascienza" .replace genre with "action" for genre="azione" .replace genre with "adventure" for genre="avventura"
以同样的方式,我们可以将意大利语电影标题翻译成英语等效项(“Il pianeta proibito”是“The Forbidden Planet”,“2001: Odissea nello spazio”是“2001: A Space Odyssey” - 但这很容易猜到 -,“Ritorno al futuro”是“Back to the Future”)。
movieid 字段是一个简单的数字,这很好,但为了使事情变得更有趣,让我们尝试使用以下命令更改它
.replace movieid with left(title,4)+right(str(year),2) all
.list movieid, title
此时,我们已经可以尝试提取 2000 年之前制作的电影列表
.cls ; ? ; ? ; ?; list title, leadactor, year for year < 2000
要查看如何在点提示符处输入多个命令,只需用分号分隔它们即可。在这里,我们还可以看到 dBase 命令是如何构建的
list - a verb title, leadactor, year - an expression list for year < 2000 - a condition
但是,这三个问号是为了腾出一些空间来查看所有记录(hbrun 的前三行被其他信息占用),这看起来很丑陋。下次我们将使用 setpos()。
现在我们仔细想想,记录 7 中的 movie code 字段 Movieid 仅包含数字,而记录 8 中包含一个空格,这同样很丑陋。我们可以通过在点提示符处输入以下命令以交互方式更正它
.browse()
并将这些字段替换为“Odys68”和“Forb56”。
既然我们正在进行更正,我突然想起我把2001: A Space Odyssey换成了一部更好的电影,因此我们不妨将其删除
.delete record 7
.list title for deleted()
.pack
现在我们可以再次浏览()并手动添加一条新记录:“Miss86”,“Mission”,“history,drama”,“1986”,“Jeremy Irons”,“6.95”。
要点 5。按电影标题对表进行排序。
.index on title to titlesort
再次,.browse()
将允许我们确保文件外观上的内容确实发生了变化。
要点 6. 查询数据库中 2000 年之前制作的电影
我们已经做过这个了,但这次我们在 Title 字段上启用了索引。
.cls ; setpos(3,1) ; list title, leadactor, year for year < 2000
我们还能做什么?我们还可以计算电影的平均年龄(以年为单位)
.average year(date()) - year to yearavg ; ? yearavg
(也许我应该看看最近的电影……),或者看看我们在电影收藏上花了多少钱。
.sum price_eur to totalprice ; ? totalprice
要点 7. 创建一个名为“电影列表”的报表,其中包含按类型和标题排序的电影记录,并包含代码、类型、标题、欧元价格字段。
DOS dBase 有自己的报表生成器,Clipper 提供了一个实用程序 RL.EXE 来创建报表和标签。这些程序创建了一个用于生成报表的格式文件,在那些古老的时代,报表使用点阵打印机打印在连续表单纸上。但是我们今天如何创建报表呢?如何在发送到打印机之前在屏幕上预览它?最明显的选项可能是创建 PDF 或 HTML 文件(然后需要一个样式表才能观看,否则它会太乏味)。由于我从一开始就使用的是普通的 Harbour 3.0,我们将遵循最简单的方法:生成一个 HTML 文件。我们将尝试一个表格报表(此代码需要更正,因为它生成的 HTML 虽然可以解释,但由于缺少许多结束标签而无法验证)
** report.prg
use videolib
index on genre+title to iReport
set printer on
set printer to 'rMovie.html'
?[<html><body>]
?[<h1>],"Movie Report",[</h1>]
?[<table>]
?[<tr><th>Movie Code<th>Genre<th>Title<th>Price in Euro</tr>]
do while .not. eof()
?[<tr>]
?[<td>],movieid
?[<td>],genre
?[<td>],title
?[<td>],price_eur
?[</tr>]
skip
enddo
.do report
这没什么好看的,但 CSS 会大大改善它的外观。即使像这样的凌乱的 CSS(这也需要清理)
h1 {
display: block;
font-size: 2em;
margin-top: 0.67em;
margin-bottom: 0.67em;
margin-left: 0;
margin-right: 0;
font-weight: bold;
margin: auto;
width: 50%;
border: 3px solid green;
padding: 10px;
text-align: center;
font-family: Arial, Helvetica, sans-serif;
background-color: lightgreen;
}
table, th, td {
border: 1px solid black;
margin: auto;
width: 75%;
border: 3px solid green;
padding: 10px;
}
table.center {
margin-left: auto;
margin-right: auto;
}
th, td {
padding: 10px;
width: 25%;
text-align: center;
}
th {
padding: 10px;
width: 25%;
font-family: Arial, Helvetica, sans-serif;
border: 3px solid blue;
background-color: lightblue;
}
可以让我们得到一个色彩鲜艳的打印报表,看起来不那么单调——尽管不专业。
要点 8. 打印“电影列表”报表。
要点 9. 关闭数据库和程序。在 hbrun 中,这两件事分别由这两个命令完成
.use
.quit
在我们的工作目录中还剩下什么?.dir *.*
报告如下
iReport ntx 2048 09/15/21 movies txt 605 09/15/21 report prg 414 09/15/21 rMovie htm 1290 09/15/21 styles css 743 09/15/21 titlesor ntx 2048 09/15/21 videolib dbf 1287 09/15/21
注释 1. 使用 HTML 进行输出很有趣,但这不是一个好主意,因为报表通常会超过一页纸,而 HTML+CSS 表格无法正确处理这种多页输出。使用 libHaru (http://libharu.org/)(或 extras\hbvpdf)的绑定创建报表的 PDF 以进行打印将是一个更好的选择。
注释 2. 这个例子非常基础。我们可以在不到 20 分钟的时间内使用点提示符完成几乎所有操作。ECDL 在过去的几个五年里发生了很大变化。现在我正在查看ECDL 高级数据库教学大纲 2.0。它看起来不错,有一些有趣的练习。
让我们暂时回忆一下过去,那时常用的计算机没有鼠标和 GUI。然而,如果我们想为一项任务使用多个表,我们必须使用某种方法来告诉计算机它应该考虑哪些表。例如,让我们以一个图书馆为例。简化地说,我们需要使用至少三个表来管理它(一个用于书籍,一个用于客户,一个用于借阅)。
我们可能会发出这些命令
SELECT 1 USE Books SELECT 2 USE Customers SELECT 3 USE Loans SELECT Books
我们可以将结果可视化为下图中的三个窗口。
这里 SELECT 命令的作用就像单击窗口以激活它,而工作区本身看起来像具有编号(工作区编号)和名称(工作区别名)的窗口。
这是来自 \hb30\tests 的 testdbf.prg 源代码。应该详细讨论它。这是一段注释不良的 GPL 代码片段。
/*
* $Id: testdbf.prg 1792 1999-11-10 10:17:19Z bcantero $
*/
function main()
local nI, aStruct := { { "CHARACTER", "C", 25, 0 }, ;
{ "NUMERIC", "N", 8, 0 }, ;
{ "DOUBLE", "N", 8, 2 }, ;
{ "DATE", "D", 8, 0 }, ;
{ "LOGICAL", "L", 1, 0 }, ;
{ "MEMO1", "M", 10, 0 }, ;
{ "MEMO2", "M", 10, 0 } }
REQUEST DBFCDX
dbCreate( "testdbf", aStruct, "DBFCDX", .t., "MYALIAS" )
? "[" + MYALIAS->MEMO1 + "]"
? "[" + MYALIAS->MEMO2 + "]"
? "-"
MYALIAS->( dbAppend() )
MYALIAS->MEMO1 := "Hello world!"
MYALIAS->MEMO2 := "Harbour power"
? "[" + MYALIAS->MEMO1 + "]"
? "[" + MYALIAS->MEMO2 + "]"
MYALIAS->( dbAppend() )
MYALIAS->MEMO1 := "This is a test for field MEMO1."
MYALIAS->MEMO2 := "This is a test for field MEMO2."
? "[" + MYALIAS->MEMO1 + "]"
? "[" + MYALIAS->MEMO2 + "]"
MYALIAS->NUMERIC := 90
MYALIAS->DOUBLE := 120.138
? "[" + Str( MYALIAS->DOUBLE ) + "]"
? "[" + Str( MYALIAS->NUMERIC ) + "]"
? ""
? "Press any key..."
InKey( 0 )
? ""
? "Append 50 records with memos..."
for nI := 1 to 50
MYALIAS->( dbAppend() )
MYALIAS->MEMO1 := "This is a very long string. " + ;
"This may seem silly however strings like this are still " + ;
"used. Not by good programmers though, but I've seen " + ;
"stuff like this used for Copyright messages and other " + ;
"long text. What is the point to all of this you'd say. " + ;
"Well I am coming to the point right now, the constant " + ;
"string is limited to 256 characters and this string is " + ;
"a lot bigger. Do you get my drift ? If there is somebody " + ;
"who has read this line upto the very end: Esto es un " + ;
"sombrero grande rid¡culo." + Chr( 13 ) + Chr( 10 ) + ;
"/" + Chr( 13 ) + Chr( 10 ) + "[;-)" + Chr( 13 ) + Chr( 10 )+ ;
"\"
next
MYALIAS->( dbCommit() )
? "Records before ZAP:", MYALIAS->( LastRec() )
? "Size of files (data and memo):", Directory( "testdbf.dbf" )[1][2], ;
Directory( "testdbf.fpt" )[1][2]
MYALIAS->( __dbZap() )
MYALIAS->( dbCommit() )
? "Records after ZAP:", MYALIAS->( LastRec() )
? "Size of files (data and memo):", Directory( "testdbf.dbf" )[1][2], ;
Directory( "testdbf.fpt" )[1][2]
? "Value of fields MEMO1, MEMO2, DOUBLE and NUMERIC:"
? "[" + MYALIAS->MEMO1 + "]"
? "[" + MYALIAS->MEMO2 + "]"
? "[" + Str( MYALIAS->DOUBLE ) + "]"
? "[" + Str( MYALIAS->NUMERIC ) + "]"
? "Press any key..."
InKey( 0 )
dbCloseAll()
dbCreate( "testdbf", aStruct,, .t., "MYALIAS" )
for nI := 1 to 10
MYALIAS->( dbAppend() )
MYALIAS->NUMERIC := nI
? "Adding a record", nI
if nI == 3 .or. nI == 7
MYALIAS->( dbDelete() )
? "Deleting record", nI
endif
next
MYALIAS->( dbCommit() )
? ""
? "With SET DELETED OFF"
? "Press any key..."
InKey( 0 )
MYALIAS->( dbGoTop() )
do while !MYALIAS->( Eof() )
? MYALIAS->NUMERIC
MYALIAS->( dbSkip() )
enddo
SET DELETED ON
? ""
? "With SET DELETED ON"
? "Press any key..."
InKey( 0 )
MYALIAS->( dbGoTop() )
do while !MYALIAS->( Eof() )
? MYALIAS->NUMERIC
MYALIAS->( dbSkip() )
enddo
? ""
? "With SET DELETED ON"
? "and SET FILTER TO MYALIAS->NUMERIC > 2 .AND. MYALIAS->NUMERIC < 8"
? "Press any key..."
InKey( 0 )
MYALIAS->( dbSetFilter( { || MYALIAS->NUMERIC > 2 .AND. MYALIAS->NUMERIC < 8 }, ;
"MYALIAS->NUMERIC > 2 .AND. MYALIAS->NUMERIC < 8" ) )
MYALIAS->( dbGoTop() )
do while !MYALIAS->( Eof() )
? MYALIAS->NUMERIC
MYALIAS->( dbSkip() )
enddo
SET DELETED OFF
? ""
? "With SET DELETED OFF"
? "and SET FILTER TO MYALIAS->NUMERIC > 2 .AND. MYALIAS->NUMERIC < 8"
? "Press any key..."
InKey( 0 )
MYALIAS->( dbSetFilter( { || MYALIAS->NUMERIC > 2 .AND. MYALIAS->NUMERIC < 8 }, ;
"MYALIAS->NUMERIC > 2 .AND. MYALIAS->NUMERIC < 8" ) )
MYALIAS->( dbGoTop() )
do while !MYALIAS->( Eof() )
? MYALIAS->NUMERIC
MYALIAS->( dbSkip() )
enddo
? "dbFilter() => " + dbFilter()
? ""
? "Testing __dbPack()"
? "Records before PACK:", MYALIAS->( LastRec() )
? "Size of files (data and memo):", Directory( "testdbf.dbf" )[1][2], ;
Directory( "testdbf.dbt" )[1][2]
SET FILTER TO
MYALIAS->( __dbPack() )
MYALIAS->( dbCommit() )
? "Records after PACK:", MYALIAS->( LastRec() )
? "Size of files (data and memo):", Directory( "testdbf.dbf" )[1][2], ;
Directory( "testdbf.dbt" )[1][2]
? "Press any key..."
InKey( 0 )
? "Value of fields:"
MYALIAS->( dbGoTop() )
do while !MYALIAS->( Eof() )
? MYALIAS->NUMERIC
MYALIAS->( dbSkip() )
enddo
? ""
? "Open test.dbf and LOCATE FOR TESTDBF->SALARY > 145000"
? "Press any key..."
InKey( 0 )
dbUseArea( ,, "test", "TESTDBF" )
locate for TESTDBF->SALARY > 145000
do while TESTDBF->( Found() )
? TESTDBF->FIRST, TESTDBF->LAST, TESTDBF->SALARY
continue
enddo
? ""
? "LOCATE FOR TESTDBF->MARRIED .AND. TESTDBF->FIRST > 'S'"
? "Press any key..."
InKey( 0 )
dbUseArea( ,, "test", "TESTDBF" )
locate for TESTDBF->MARRIED .AND. TESTDBF->FIRST > 'S'
do while TESTDBF->( Found() )
? TESTDBF->FIRST, TESTDBF->LAST, TESTDBF->MARRIED
continue
enddo
return nil
一个简单的数据库输入掩码(来自维基百科 Clipper 条目)
USE Customer SHARED NEW
clear
@ 1, 0 SAY "CustNum" GET Customer->CustNum PICT "999999" VALID Customer->CustNum > 0
@ 3, 0 SAY "Contact" GET Customer->Contact VALID !empty(Customer->Contact)
@ 4, 0 SAY "Address" GET Customer->Address
READ
http://harbourlanguage.blogspot.de/2010/06/understanding-harbour-rdd.html
https://searchsqlserver.techtarget.com/definition/ActiveX-Data-Objects http://cch4clipper.blogspot.com/2009/10/using-adordd-with-harbourxharbour.html
这是来自 x2c-base.zip\Tutor.zip 的 CHECKD.PRG。
Simple checkbook entry and edit program
Use the Checkbook file, indexed on Check number
USE CHECKS
DO WHILE .t.
DONE = 'D'
CLEAR
@ 1,0 SAY "New Check (N), Old Check (O), Done (D), List(L)" ;
GET DONE PICT "!"
READ
DO CASE
CASE DONE='L'
LINE = 2
GOTO TOP
DO WHILE .NOT. EOF()
IF LINE=2
CLEAR
@ 0, 1 say "Date Number Name"
@ 1, 1 say " $$$ Clr Catgy Description"
ENDIF
* DATE NUMBER NAME
* AMT CLR CAT DESC
@ LINE , 1 SAY CHECKDATE && MM/DD/YY
@ LINE ,12 SAY CHECKNO && NNNNNNNN
@ LINE ,22 SAY RECNO() picture "(9999)"
@ LINE ,30 SAY CHECKNAME && CHAR 30
@ LINE+1, 3 SAY CHECKAMT picture "99999.99"
@ LINE+1,14 SAY CHECKCLR && X
@ LINE+1,18 SAY CHECKCAT && XX
@ LINE+1,25 SAY CHECKDESC && Char 30
LINE = LINE + 2
IF LINE>22
Wait "Enter a key to continue"
LINE = 2
ENDIF
SKIP
enddo
IF LINE>0
ACCEPT "Enter a key to continue" TO DONE
ENDIF
LOOP
CASE DONE='D'
USE
EXIT
CASE DONE='O'
REQNO = 0
CLEAR
@ 1, 0 SAY "Record Number: " GET REQNO Picture "###"
READ
IF REQNO>RECCOUNT()
? "Check beyond end of file"
WAIT " Press any key"
LOOP
ENDIF
Goto reqno
LDATE = checkdate
LNO = checkno
LDESC = checkdesc
LNAME = checkname
LAMT = checkamt
LCLR = checkclr
LCAT = checkcat
CASE DONE='N'
APPEND BLANK
LDATE = date()
LNO = ' '
LDESC = space(30)
LNAME = space(30)
LAMT = 0.0
LCLR = "N"
LCAT = ' '
OTHERWISE
? CHR(7)
LOOP
ENDCASE
Now enter Check info or edit it
done = 'N'
DO WHILE done='N'
Make date into editable string
EDATE = DTOC(LDATE)
CLEAR
@ 1, 0 SAY "Check no.:" GET LNO
@ 1, 30 SAY "Date:" GET EDATE Pict "99/99/99"
@ 1, 60 SAY "Record number: "+str(RECNO(), 3)
@ 3, 0 SAY "Check to:" GET LDESC
@ 4, 0 SAY "Check Description:" GET LNAME
@ 6, 0 SAY "Amount of Check:" GET LAMT pict "99999.99"
@ 8, 0 SAY "Check Cleared?" GET LCLR
@ 8, 22 SAY "Check Catagory:" GET LCAT
READ
@ 10, 0 Say "All ok (Yes/No/Cancel) ?" get DONE pict "!"
READ
LDATE = CTOD(EDATE)
ENDDO
IF DONE='Y'
REPLACE checkdate WITH LDATE, checkno WITH LNO, ;
checkdesc WITH LDESC, checkname WITH LNAME, ;
checkamt WITH LAMT, checkclr WITH LCLR, ;
checkcat WITH LCAT
ENDIF
ENDDO
? LASTREC()
DELETE RECORD 4
PACK
? LASTREC()
在这段代码中,DELETE 命令将第四条记录标记为要删除。但文件没有被更改,即使是通过 CLOSE 命令也没有。PACK 命令实际上删除了标记为要删除的记录(并且还做了一些额外的工作)。RECALL 命令删除了已删除的标记。如果当前记录被标记为要删除,则函数 DELETED() 返回 .T.,否则返回 .F.。
PACK 命令执行实际的数据删除操作,PACK 要求当前数据库被独占使用。如果在调用 PACK 命令时不满足此条件,CA-Clipper 会生成运行时错误。PACK 执行的其他工作是更新其修改的表上的索引(如果有)。
DELETE ALL 和 PACK 命令由一个名为 ZAP 的单个命令执行。
&& This example demonstrates a typical ZAP operation in a network
&& environment:
USE Sales EXCLUSIVE NEW
IF !NETERR()
SET INDEX TO Sales, Branch, Salesman
ZAP
CLOSE Sales
ELSE
? "Zap operation failed"
BREAK
ENDIF
USE Clients NEW
INDEX ON Name TO Clients UNIQUE
假设一个包含以下数据的表
FSTNAME LSTNAME John Doe John Doe John Doe Jane Doe
我们可以使用这段代码创建一个小的索引文件。
SELECT 1
USE ind
? FILE("ind.ntx")
INDEX ON FstName TO ind
? FILE("ind.ntx") // we verify that a NTX file has been created