Sign in Join
Wiki copytemporary

SQLdb Tutorial1/zh CN: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## 介绍(Introduction)
22
33编撰目的: 通过实例演示[[SQLDB包]]的使用。
44
55阅读对象: 数据库编程的初学者/新手。如果对文中涉及的数据库和SQL的基础知识不太清楚,请阅读相应的书籍。
66
77使用的数据库: Firebird和她自带的示例数据库employee.fdb。不用Firebird也可以使用其它数据库,但需要作些调整,文中相应位置有补充说明。
88
99文章太长?: 虽然本教程看起来很长,但笔墨大多用于解释要求你键入相关内容的原因。在文章末尾你可以看到,创建一个能够正常运行的应用程序,你实际上编写的代码量不是很大。对于许多有经验的开发者而言,你可以略读(就是忽略已经熟知的细节而快速翻阅各操作步骤的主要内容),并能迅速理解下一步将要做的事情。还嫌累赘的话,读到基本示例一节结尾处即可停止阅读,而且你已经创建了一个能够工作的程序。
1010
1111译文来源: Swen编撰了[[SQLdb Tutorial1/de|德语版]],BigChimp做了英译,但对内容做了扩展,特别是基本示例后的部分。 lioncn根据英文版翻译为[[SQLdb Tutorial1/zh CN|简体中文]](翻译ing,请耐心等待)。德语用户请注意:Swen希望保留德语版的原样。如果你想修改德语版,你可以将Swen的版本重新命名并在此基础上重新编撰一个新的德语版。
1212
1313致谢: 没有Joost 和 Michael 的帮助本教程可能永远不会出现。
1414
1515(原文:This tutorial intends to show the usage of the [[SQLdb Package]] on the basis of practical examples. It is primarily targeted at newbies. If somebody is looking for basics about databases and SQL, he should read the corresponding books. For this tutorial I use Firebird with the example database employee.fdb. Other databases can also be used; some adjustments will need to be made which are mentioned in the text.
1616
1717While this tutorial may seem long, it mostly is just a lot of text that explains why you should type what you type. As you can see at the end, the amount of actual code you will need for a working application is not that great. More experienced developers will hopefully be able to glance through the instructions and quickly understand what's going on. Also, you can stop at the end of the Basic example chapter and have a working program.
1818
1919This tutorial is based on a [[SQLdb Tutorial1/de|German tutorial]] by Swen, but it is extended, especially after the Basic example. Swen wants the German version to remain as-is. If this is a problem, we can rename this version and base a new German translation on that.
2020
2121Thanks to Joost and Michael. Without their help this tutorial probably never would have come about.)
2222
2323## 系统需求(Requirements)
2424
2525Lazarus: 为减少示范实例出错的可能,尽量使用最新版的Lazarus (要求随带的FPC 不低于2.2.2)。如果找不到SQLdb包,现在就安装(Package -\> Install/Uninstall packages ... -\> SQLDBLaz 1.0.1)
2626
2727Firebird数据库: 安装不低于2.0版的[Firebird](http://sourceforge.net/project/showfiles.php?group_id=9028)。示范实例假定你使用标准安装而未作任何修改(比如管理员用户名为 SYSDBA,密码为 masterkey),而且安装了示例数据库 employee.fdb 。
2828
2929其他数据库: 也可使用MySQL, PostgreSQL, Oracle, SQLite 或其他的使用 ODBC 桥接的数据库: 要求数据库/表结构必须根据所使用的数据库而作相应修改(见下文),并使用继承自“TSQLConnector”的对应数据库组件(也如下所示)进行连接。如果所使用的数据库对于事务处理等方面与Firebird有显著差异,请在有关条文的做说明以方便后来者。
3030
3131(原文:If possible you should use a recent Lazarus version (with FPC 2.2.2 or newer) for this tutorial. If the SQLdb package isn't already installed, you should do it now (Package -\> Install/Uninstall packages ... -\> SQLDBLaz 1.0.1).
3232
3333Furthermore you need [Firebird](http://sourceforge.net/project/showfiles.php?group_id=9028) (if possible version 2.0 or newer). The examples assume that the standard settings (e.g user name SYSDBA and password masterkey) were not changed, and that you have the employee sample database installed.)
3434
3535You can use another database (e.g. MySQL, PostgreSQL, Oracle, SQLite or another database using ODBC): you'd have to have the proper database /table structure (see below), and use the relevant *TSQLConnector* descendant (also indicated below). If your database has significant differences regarding transactions etc. please make a note in the relevant section.)
3636
3737### Win平台下使用Firebird数据库必备的库文件(Firebird database libraries on Windows)
3838
3939Firebird客户端DLL: 在win平台的开发机上必须安装Firebird客户端DLL。安装位置可以是win的系统目录(如\windows\system32),但最好是安装在开发项目的目录和可执行文件的输出目录中,这样做的好处是,一来是在集成开发环境中开发设计时就能看到数据,二来是能够正确无误地运行编译过的程序。
4040
4141安装Firebird客户端DLL的最容易的方法是:[下载安装Firebird 2.5的嵌入版](http://www.firebirdsql.org/en/firebird-2-5/)
4242
4343方法如下:
4444
4545展开下载的压缩包,将下列文件复制到开发项目目录: \<bash\> fbembed.dll firebird.msg ib_util.dll icudt30.dll icuin30.dll icuuc30.dll Microsoft.VC80.CRT.manifest msvcp80.dll msvcr80.dll \</bash\> Firebird分为两种结构:嵌入式和C/S结构。它们所使用的客户端DLL是通用的。所以再将嵌入式使用的fbembed.dll 改名为C/S结构使用的 fbclient.dll即可,或者将fbembed.dll复制为新文件fbclient.dll,就完成了Firebird客户端DLL的安装。
4646
4747最后,编译项目--就算项目为空也编译--会自动创建输出目录,复制DLL到输出目录。
4848
4949(原文:Finally, on Windows you will need to have the Firebird client DLLs present, optionally in your system directory, but preferably in your project directory (for design time support in the IDE) and in the output directory where the executable is (to run the compiled program). One easy way to get the client DLLs is: download Firebird Embedded 2.5 from [1](http://www.firebirdsql.org/en/firebird-2-5/) Extract these files to your application directory: \<bash\> fbembed.dll firebird.msg ib_util.dll icudt30.dll icuin30.dll icuuc30.dll Microsoft.VC80.CRT.manifest msvcp80.dll msvcr80.dll \</bash\> Rename fbembed.dll to fbclient.dll (the name for a regular, client-server Firebird client). The embedded Firebird DLL can also act as a regular Firebird client.
5050
5151Finally, compile your project (even if it empty) once to create the output directory, and copy the dlls into that directory.)
5252
5353### 其他平台下使用Firebird数据库必备的库文件(Firebird database libraries on other systems)
5454
5555在Linux/OSX下,也需要安装Firebird客户端共享库文件。 在linux下,使用对应发行版的程序安装方法来安装Firebird客户端包,如Debian中执行 \<bash\> aptitude install libfbclient2 \</bash\> 来完成安装。
5656
5757(原文:On Linux/OSX, you will also need the Firebird client shared libraries. On Linux you can use your distribution's method of getting programs to get the Firebird client packages, e.g. on Debian: \<bash\> aptitude install libfbclient2 \</bash\> )
5858
5959### 未安装employee.fdb数据库?(No employee.fdb installed?)
6060
6161如果没有安装示例数据库 employee.fdb,或者使用其它数据库,请使用如下SQL语句生成最小版本的数据表CUSTOMER,文中后面会利用到。
6262
6363``` sql
6464/* 创建数据表: */
6565CREATE TABLE CUSTOMER
6666(
6767 CUST_NO CUSTNO NOT NULL,
6868 CUSTOMER VARCHAR(25) NOT NULL,
6969 CITY VARCHAR(25),
7070 COUNTRY VARCHAR(15),
7171 CONSTRAINT INTEG_60 PRIMARY KEY (CUST_NO)
7272);
7373
7474/* 录入几条数据: */
7575INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2001', 'Michael Design', 'San Diego', 'USA');
7676INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2002', 'VC Technologies', 'Dallas', 'USA');
7777INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2003', 'Klämpfl, Van Canneyt and Co.', 'Boston', 'USA');
7878INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2004', 'Felipe Bank', 'Manchester', 'England');
7979INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2005', 'Joost Systems, LTD.', 'Central Hong Kong', 'Hong Kong');
8080INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2006', 'Van der Voort International', 'Ottawa', 'Canada');
8181INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2007', 'Mrs. Mauvais', 'Pebble Beach', 'USA');
8282INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2008', 'Asinine Vacation Rentals', 'Lihue', 'USA');
8383INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2009', 'Fax', 'Turtle Island', 'Fiji');
8484INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2010', 'FPC Corporation', 'Tokyo', 'Japan');
8585INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2011', 'Dynamic Intelligence Corp', 'Zurich', 'Switzerland');
8686INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2012', '3D-Pad Corp.', 'Paris', 'France');
8787INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2013', 'Swen Export, Ltd.', 'Milan', 'Italy');
8888INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2014', 'Graeme Consulting', 'Brussels', 'Belgium');
8989INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2015', 'Klenin Inc.', 'Den Haag', 'Netherlands');
9090```
9191
9292(原文:If you don't have the employee sample database installed or are using a different database, here is a minimal version of the table we'll be using:
9393
9494``` sql
9595CREATE TABLE CUSTOMER
9696(
9797 CUST_NO CUSTNO NOT NULL,
9898 CUSTOMER VARCHAR(25) NOT NULL,
9999 CITY VARCHAR(25),
100100 COUNTRY VARCHAR(15),
101101 CONSTRAINT INTEG_60 PRIMARY KEY (CUST_NO)
102102);
103103```
104104
105105Some data so you can at least show something:
106106
107107``` sql
108108INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2001', 'Michael Design', 'San Diego', 'USA');
109109INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2002', 'VC Technologies', 'Dallas', 'USA');
110110INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2003', 'Klämpfl, Van Canneyt and Co.', 'Boston', 'USA');
111111INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2004', 'Felipe Bank', 'Manchester', 'England');
112112INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2005', 'Joost Systems, LTD.', 'Central Hong Kong', 'Hong Kong');
113113INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2006', 'Van der Voort International', 'Ottawa', 'Canada');
114114INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2007', 'Mrs. Mauvais', 'Pebble Beach', 'USA');
115115INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2008', 'Asinine Vacation Rentals', 'Lihue', 'USA');
116116INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2009', 'Fax', 'Turtle Island', 'Fiji');
117117INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2010', 'FPC Corporation', 'Tokyo', 'Japan');
118118INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2011', 'Dynamic Intelligence Corp', 'Zurich', 'Switzerland');
119119INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2012', '3D-Pad Corp.', 'Paris', 'France');
120120INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2013', 'Swen Export, Ltd.', 'Milan', 'Italy');
121121INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2014', 'Graeme Consulting', 'Brussels', 'Belgium');
122122INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES ('2015', 'Klenin Inc.', 'Den Haag', 'Netherlands');
123123```
124124
125125)
126126
127127## 基本示例(Basic example)
128128
129129### 项目与所需组件(Project and components)
130130
131131首先,创建一个新Lazarus项目。
132132
133133在组件面板的SQLdb控件页拖拉一个*TIBConnection*, 一个 *TSQLTransaction* 和一个 *[[Working With TSQLQuery|TSQLQuery]]* 控件到窗体。
134134
135135*TIBConnection*: 是 Interbase/Firebird 专用连接组件,使用其它数据库,就必须换用 'SQLDB' 页的相应的其它数据库连接组件,如使用 *TSQLite3Connection* 连接 SQLite 数据库。关于设置其它数据库连接的方法不在本文讨论范围,感兴趣的可参见 [[Databases]] 详细讨论。
136136
137137*TSQLTransaction* 和 *TSQLQuery*: SQLdb 连接所支持的所有数据库都会利用到这两个组件。
138138
139139显示数据: 利用 'Data Controls' 页的 *TDBGrid* 组件。先添加 'Data Access' 页内一个 *TDatasource* 控件后才能将 *TDBGrid* 连接到数据库。例中使用 'Standard' 页的 *TButton* 来触发这个动作。
140140
141141现在已经添加好了首个示例中必须的所有组件。加大 TDBGrid 大小以有足够空间显示表中所有数据。
142142
143143(原文:First you should create a new Lazarus project.
144144
145145To get access to our database we need one *TIBConnection*, one *TSQLTransaction* and one *[[Working With TSQLQuery|TSQLQuery]]* component from the 'SQLdb' tab in the component palette. *TIBConnection* is an Interbase/Firebird specific connection component. If you are using a different database, substitute the proper component from the 'SQLDB' tab, e.g. a *TSQLite3Connection* for an SQLite database. Discussion of setting up any database access libraries is out of scope for this tutorial; see e.g. [[Databases]] for that.
146146
147147The other two components, *TSQLTransaction* and *TSQLQuery*, can be used for all databases that are supported by SQLdb.
148148
149149To display the data, we use a *TDBGrid* component, which can be found on the 'Data Controls' tab. To connect this component to the database components we need a *TDatasource* component from the 'Data Access' tab. To trigger the actions we use a *TButton* from the 'Standard' tab.
150150
151151Now we have all components needed for the first example. You can enlarge the TDBGrid to have enough space to display all data.)
152152
153153### 组件连接关系(Link the components)
154154
155155连接组件:如果使用对象检查器是非常简单的,也可以使用代码来实现。
156156
1571571、设置 IBConnection1 的 'Transaction' 属性指向 'SQLTransaction1',这会使得 SQLTransaction1 的 'Database' 属性自动变更为 'IBConnection1';
158158
1591592、设置 SQLQuery1 的 'Database' 属性指向 'IBConnection1',Lazarus 会自动添加 'Transaction' 属性值;
160160
1611613、设置 Datasource1 的 'Dataset' 属性指向 'SQLQuery1';
162162
1631634、设置 DBGrid1 的 'Datasource1' 属性指向 'Datasource'。
164164
165165(原文:Next we need to connect our components. A very simple way is to use the object inspector, but you can also do this in your source code. Change the 'Transaction' property of IBConnection1 to 'SQLTransaction1'. This causes the 'Database' property of SQLTransaction1 to automatically change to 'IBConnection1'. In the next step you should change the 'Database' property of SQLQuery1 to 'IBConnection1'. Lazarus automatically adds the value for the 'Transaction' property. Now we change the 'Dataset' property of Datasource1 to 'SQLQuery1'. Finally we change the 'Datasource' property of DBGrid1 to 'Datasource1'.)
166166
167167### 连接到数据库(Connecting to the database)
168168
169169How can we now show the data from our database on the screen? First we need to tell IBConnection1 where the employee.fdb database is located (usually in the .../examples/empbuild/ subdirectory of your Firebird installation). Again you have the choice, if you want to use the object inspector to assign the path or if you want to do it directly in your source code.
170170
171171We choose to use the object inspector: set the IBConnection1 'HostName' property to the Firebird server name or IP adddress; use localhost if Firebird is running on your development machine. Change the 'DatabaseName' property of IBConnection1 to the path to the employee.fdb file on the database server(e.g. C:\Program Files\Firebird\Firebird_2_0\examples\empbuild\EMPLOYEE.FDB on a Windows machine).
172172
173173Before the database server grants access to the data, it will check authorisation via username and password. A serious database application will ask the user for both values, when the application is started, and send these to the server when connecting.
174174
175175However, for now, to simplify matters, we use the object inspector again to hard code these. Change the 'UserName' property to 'SYSDBA' and 'Password' to 'masterkey'.
176176
177177Now check if all settings so far are correct: set the 'Connected' property to 'True'. If the database path isn't correct or if username or password are wrong, you will get an error message. If the connection was successful, you should cut it now (set it to 'False').
178178
179179You should now have something like the following screenshot: [framed|center|Form and components set up](/wiki-images/b/b2/SQLTut1ComponentsSetUp.png)
180180
181181### 选择欲显示的数据(Choosing what data to show)
182182
183183Although the connection was successful, no data was displayed. The reason is simple. We haven't told the database server which data to return: the employee.fdb database contains several tables, and we haven't told Firebird the table we want to see. If you don't know the structure of a database, you can use tools like [FlameRobin](http://www.flamerobin.org/index.php), to display the contents. Lazarus also provides such a tool - the DataDesktop. You can find it in the /tools/lazdatadesktop/ subdirectory of Lazarus. Open the project lazdatadesktop.lpi and compile it (FPC 2.3.x is currently required) (of course you have saved our example project before).
184184
185185[framed|center|The DataDesktop in action](/wiki-images/d/da/DataDictonary1.png)
186186
187187Back to our example. We want to display all data from the table 'CUSTOMER'. The SQL instruction for that is:
188188
189-`select * from CUSTOMER`
189+```text
190+select * from CUSTOMER
191+```
190192
191193We need to assign this command to the 'SQL' property of SQLQuery1. In the source code of our project this would look like:
192194
193-`SQLQuery1.SQL.Text := 'select * from CUSTOMER';`
195+``` pascal
196+SQLQuery1.SQL.Text := 'select * from CUSTOMER';
197+```
194198
195199The SQL instruction must be enclosed by single quotes. You also have the ability to assign the content of another component (e.g. Edit1.Text). This is not always a good idea; see [[Secure programming]] (a more advanced text) for details on SQL injection.
196200
197201When the user clicks on the button on our form, data retrieval should start. Let's start coding this. Double click on Button1. Lazarus then creates the skeleton of the necessary procedure. In our source code we should find the following lines:
198202
199203``` pascal
200204procedure TForm1.Button1Click(Sender: TObject);
201205begin
202206
203207end;
204208```
205209
206210Between *begin* and *end* we must enter the instructions needed to display the data. The 'SQL' property of SQLQuery1 can only be changed, if SQLQuery1 is not active. That's why we close the component first:
207211
208212``` pascal
209213SQLQuery1.Close;
210214```
211215
212216Then we assign our SQL instruction to the 'SQL' property, overwriting any previous SQL commands:
213217
214218``` pascal
215219SQLQuery1.SQL.Text := 'select * from CUSTOMER';
216220```
217221
218222Now we need to establish the connection to the database, activate the transaction and open the query:
219223
220224``` pascal
221225IBConnection1.Connected := True;
222226SQLTransaction1.Active := True;
223227SQLQuery1.Open;
224228```
225229
226230You can omit the first two instructions, because they are done automatically by the third instruction (but this will not happen in the contrary case, when you close the connection). If you compile the project at this point, you could already see the data from the 'CUSTOMER' table.
227231
228232However, a serious application must take care that all open database connections are properly closed when not needed anymore. Otherwise the secondary effects would not be foreseeable. So, we use the OnClose event of our form (create it with a double click in the object inspector):
229233
230234``` pascal
231235procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
232236begin
233237
234238end;
235239```
236240
237241To close the connection we use the reverse order compared to our opening code:
238242
239243``` pascal
240244SQLQuery1.Close;
241245SQLTransaction1.Active := False;
242246IBConnection1.Connected := False;
243247```
244248
245249### 小结(Summary)
246250
247251到目前为止,你已经学会使用 SQLdb 包连接到Firebird数据库,还有如何在屏幕上显示数据表的内容。
248252
249253遵循前面的步骤,目前代码应该如下所示:
250254
251255(原文:Up to now we have learned, how to connect to a Firebird database using the SQLdb package and how to display the contents of a table on the screen.
252256
253257If you followed the previous steps, then your code should look like:)
254258
255259``` pascal
256260unit Unit1;
257261
258262{$mode objfpc}{$H+}
259263
260264interface
261265
262266uses
263267 Classes, SysUtils, IBConnection, sqldb, db, FileUtil, Forms, Controls,
264268 Graphics, Dialogs, DBGrids, StdCtrls;
265269
266270type
267271
268272 { TForm1 }
269273
270274 TForm1 = class(TForm)
271275 Button1: TButton;
272276 Datasource1: TDatasource;
273277 DBGrid1: TDBGrid;
274278 IBConnection1: TIBConnection;
275279 SQLQuery1: TSQLQuery;
276280 SQLTransaction1: TSQLTransaction;
277281 procedure Button1Click(Sender: TObject);
278282 procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
279283 private
280284 { private declarations }
281285 public
282286 { public declarations }
283287 end;
284288
285289var
286290 Form1: TForm1;
287291
288292implementation
289293
290294{$R *.lfm}
291295
292296{ TForm1 }
293297
294298procedure TForm1.Button1Click(Sender: TObject);
295299begin
296300 SQLQuery1.Close;
297301 SQLQuery1.SQL.Clear;
298302 SQLQuery1.SQL.Text:= 'select * from CUSTOMER';
299303 IBConnection1.Connected:= True;
300304 SQLTransaction1.Active:= True;
301305 SQLQuery1.Open;
302306end;
303307
304308procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
305309begin
306310 SQLQuery1.Close;
307311 SQLTransaction1.Active:= False;
308312 IBConnection1.Connected:= False;
309313end;
310314
311315end.
312316```
313317
314318## 加强版的基本示例(Enhancement of the basic example)
315319
316320While the application works, we can add some refinements.
317321
318322### 动态数据库连接(Dynamic database connection)
319323
320324Up to now, we used a fixed database server name, database location, username and password for simplicity. As mentioned, "real" applications often let users specify their own username and password.
321325
322326Let's change the form so we can specify them: add two TEdits from the standard menu. Set their name properties to Username and Password. Set the Password's PasswordChar property to * (the asterisk) for some security. If you want to make it easier (and less secure, of course) to connect, you can set the UserName Text property to a valid database user, such as SYSDBA. You could even set the Password Text property to masterkey, easy for testing on developer machines if security doesn't matter... Cosmetically, adding some labels so people know what they're supposed to type is useful.
323327
324328Also, to make it easier to connect to any 'employee.fdb' sample database on any Firebird/Interbase server, we add two textboxes for server name and database path. Add another two TEdits, and name them ServerName and DatabaseName. If you want, you can set the 'Text' property to default sensible values for your situation, e.g. localhost and C:\Program Files\Firebird\Firebird_2_5\examples\empbuild\EMPLOYEE.FDB Labels to explain what users need to enter would help here, too.
325329
326330For clarity, we're going to remove the connection info from our designtime components: on the IBConnection1 component, remove all text from the UserName, Password, DatabaseName and HostName properties.
327331
328332Now, finally, we need to tell our database connection component how to connect. This should normally only be necessary only at the beginning of an application run. In our case the existing 'Button1' code is a good way to set up the connection: Add code until you get:
329333
330334``` pascal
331335procedure TForm1.Button1Click(Sender: TObject);
332336begin
333337 SQLQuery1.Close;
334338 //Connection settings for Firebird/Interbase database
335339 //only needed when we have not yet connected:
336340 if IBConnection1.Connected = false then
337341 begin
338342 IBConnection1.HostName := ServerName.Text;
339343 IBConnection1.DatabaseName := DatabaseName.Text;
340344 IBConnection1.Username := UserName.Text;
341345 IBConnection1.Password := Password.Text;
342346 // Now we've set up our connection, visually show that
343347 // changes are not possibly any more
344348 ServerName.ReadOnly:=true;
345349 DatabaseName.ReadOnly:=true;
346350 UserName.ReadOnly:=true;
347351 Password.ReadOnly:=true;
348352 end;
349353 SQLQuery1.SQL.Text:= 'select * from CUSTOMER';
350354 IBConnection1.Connected:= True;
351355 SQLTransaction1.Active:= True;
352356 SQLQuery1.Open;
353357end;
354358```
355359
356360Now run and test if you can connect.
357361
358362- Other databases: obviously, where it says IBConnection1, use your specific connection component. Also, you might need to set other properties on your components, so you might need more textboxes, or different contents.
359363
360364### 过滤数据(Filtering data)
361365
362366Often, tables contain a huge amount of data that the user doesn't want to see (and that might take a long time to query from the database and travel over the network). Let's assume, that only the customers from the USA should be displayed. Therefore the SQL instruction in 'SQLQuery1' would look like:
363367
364368``` sql
365369select * from CUSTOMER where COUNTRY = 'USA'
366370```
367371
368372... which would translate to something like:
369373
370374``` pascal
371375SQLQuery1.SQL.Text := 'select * from CUSTOMER where COUNTRY = 'USA'';
372376```
373377
374378There are two reasons why we will not use this instruction for our example application:
375379
376380First there is a problem with the usage of the single quote. The compiler would interpret the quote before USA as a closing quote (the first quote is before the select from...) and so the SQL instruction would become invalid. Solution: double the inside quotes:
377381
378382``` pascal
379383SQLQuery1.SQL.Text := 'select * from CUSTOMER where COUNTRY = ''USA''';
380384```
381385
382386The second, more important reason is the fact, that we probably don't know what constraints the user will want to filter on. We don't want to limit the flexibility of the user.
383387
384388To get this flexibility, first we change our SQL query statement and replace 'USA' by a placeholder (a parameter in SQL speak): change the Button1click procedure and replace
385389
386390``` pascal
387391SQLQuery1.SQL.Text := 'select * from CUSTOMER';
388392```
389393
390394with
391395
392396``` pascal
393397 SQLQuery1.SQL.Text:= 'select * from CUSTOMER where COUNTRY = :COUNTRY';
394398```
395399
396400The SQL parameter is marked by the leading colon. To allow the user to enter a value for the filter, we place a *TEdit* component ('Standard' tab in the component palette) on our form. Delete the value of its 'Text' property. We can now fill the SQL parameter with the text, which was entered in TEdit by using the 'Params' property of TSQLQuery. Add this below the previous statement:
397401
398402``` pascal
399403SQLQuery1.Params.ParamByName('COUNTRY').AsString := Edit1.Text;
400404```
401405
402406The parameter can be specified by its position or name. Using the name should improve the readability of the source code, and obviously helps if you insert more parameters in the middle of existing parameters.
403407
404408Overall the procedure should now look like:
405409
406410``` pascal
407411procedure TForm1.Button1Click(Sender: TObject);
408412begin
409413 SQLQuery1.Close;
410414 //Connection settings for Firebird/Interbase database
411415 //only needed when we have not yet connected:
412416 if IBConnection1.Connected = false then
413417 begin
414418 IBConnection1.HostName := ServerName.Text;
415419 IBConnection1.DatabaseName := DatabaseName.Text;
416420 IBConnection1.Username := UserName.Text;
417421 IBConnection1.Password := Password.Text;
418422 // Now we've set up our connection, visually show that
419423 // changes are not possibly any more
420424 ServerName.ReadOnly:=true;
421425 DatabaseName.ReadOnly:=true;
422426 UserName.ReadOnly:=true;
423427 Password.ReadOnly:=true;
424428 end;
425429 SQLQuery1.SQL.Text:= 'select * from CUSTOMER where COUNTRY = :COUNTRY';
426430 SQLQuery1.Params.ParamByName('COUNTRY').AsString := Edit1.Text;
427431 IBConnection1.Connected:= True;
428432 SQLTransaction1.Active:= True;
429433 SQLQuery1.Open;
430434end;
431435```
432436
433437Now you can play around a bit with filtering using Edit1. If you enter a country that's not present in the database, an empty grid is shown.
434438
435439### 错误处理(Error handling)
436440
437441The application should run, but sometimes, serious problems can occur. Because client and server are usually physically separated, it's often not clear at first sight why a problem occurred. Was the server shut down or has somebody unplugged a network connection? Databases, even embedded databases can crash (e.g. when the disk is full, or just due to a bug), leaving the application hanging.
438442
439443Access to a database should therefore **always** be integrated in a try ... except and/or try ... finally loop. Only that way is it ensured, that database errors can be handled and the user isn't left out in the rain. A rudimental routine for our example application could look as follows:
440444
441445``` pascal
442446begin
443447 try
444448 SQLQuery1.Close;
445449 ...
446450 SQLQuery1.Open;
447451 except
448452 //EDatabaseError is a general error, but we're dealing with Firebird/Interbase, so:
449453 on E: EIBDatabaseError do
450454 begin
451455 MessageDlg('Error','A database error has occurred. Technical error message: ' + E.Message,mtError,[mbOK],0);
452456 Edit1.Text:='';
453457 end;
454458 end;
455459end;
456460```
457461
458462- Other database users: if you don't use Firebird/Interbase, you can either use the more generic EDatabaseError, or your own specialized databaseerror, if you need more details.
459463
460464## 使用表格编辑数据(Editing data using the grid
461465
462466### 编辑(Editing)
463467
464468Up to now, if you tried to edit data in the grid, the changes would not be saved. This is because the *SQLQuery1* is not instructed to send the changes to the database transaction at the right moment. We need to fix this, and then commit the transaction in the database, so all changes get written. For this, you would use code like this:
465469
466470``` pascal
467471SQLQuery1.ApplyUpdates; //Pass user-generated changes back to database...
468472SQLTransaction1.Commit; //... and commit them using the transaction.
469473//SQLTransaction1.Active now is false
470474```
471475
472476We want to make sure any edits (inserts, updates, deletes) are written to the database:
473477
474478- when the users changes the filtering criteria and presses the button to query the database
475479- when the form is closed
476480
477481It makes sense to make a separate procedure for this that is called in those two instances. Go to the code, and add an empty line here:
478482
479483``` pascal
480484 TForm1 = class(TForm)
481485 Button1: TButton;
482486 Datasource1: TDatasource;
483487 DBGrid1: TDBGrid;
484488 Edit1: TEdit;
485489 IBConnection1: TIBConnection;
486490 SQLQuery1: TSQLQuery;
487491 SQLTransaction1: TSQLTransaction;
488492*****insert the empty line here****
489493 procedure Button1click(Sender: TObject);
490494 procedure Formclose(Sender: TObject; var Closeaction: Tcloseaction);
491495 private
492496```
493497
494498then type
495499
496500``` pascal
497501 procedure SaveChanges;
498502```
499503
500504press shift-ctrl-c (default combination) to let code completion automatically create the corresponding procedure body.
501505
502506We need to add error handling and check that the transaction is active - remember, this code also gets called when pressing the button the first time, when the transaction is not active yet. We get:
503507
504508``` pascal
505509procedure Tform1.Savechanges;
506510// Saves edits done by user, if any.
507511begin
508512 try
509513 if SQLTransaction1.Active=true then
510514 // Only if we are within a started transaction
511515 // otherwise you get "Operation cannot be performed on an inactive dataset"
512516 begin
513517 SQLQuery1.ApplyUpdates; //Pass user-generated changes back to database...
514518 SQLTransaction1.Commit; //... and commit them using the transaction.
515519 //SQLTransaction1.Active now is false
516520 end;
517521 except
518522 on E: EIBDatabaseError do
519523 begin
520524 MessageDlg('Error', 'A database error has occurred. Technical error message: ' +
521525 E.Message, mtError, [mbOK], 0);
522526 Edit1.Text := '';
523527 end;
524528 end;
525529end;
526530```
527531
528532Now we need to call this procedure at the appropriate moments:
529533
530534``` pascal
531535procedure Tform1.Button1click(Sender: TObject);
532536begin
533537 SaveChanges; //Saves changes and commits transaction
534538 try
535539 SQLQuery1.Close;
536540....
537541```
538542
539543and
540544
541545``` pascal
542546procedure Tform1.Formclose(Sender: TObject; var Closeaction: Tcloseaction);
543547begin
544548 SaveChanges; //Saves changes and commits transaction
545549 SQLQuery1.Close;
546550....
547551```
548552
549553Now test and see if edits made in the dbgrid are saved to the database.
550554
551555### 隐藏主键列(Hiding primary key column)
552556
553557Often, you don't want your users to see autonumber/generated primary keys as they are only meant to maintain referential integrity. If users do see them, they might want to try the edit the numbers, get upset that the numbers change, there are gaps in the numbers, etc.
554558
555559In our example, CUST_NO is the primary key, with content auto-generated by Firebird using triggers and a sequence/generator. This means that you can insert a new record without specifying the CUST_NO; Firebird will create one automatically.
556560
557561We could simply change our SQLQuery1.SQL.Text property to not include CUST_NO, but this would lead to problems when editing data - a primary key is handy in those circumstances for uniquely identifying the row/record in question.
558562
559563Therefore, let's use a trick to query for all columns/fields in the table, but keep the grid from showing the first field, CUST_NO: in the Button1Click procedure, add code so it looks like:
560564
561565``` pascal
562566procedure Tform1.Button1click(Sender: TObject);
563567begin
564568...
565569 SQLQuery1.Open;
566570 // Hide the primary key column which is the first column in our queries.
567571 // We can only do this once the DBGrid has created the columns
568572 DBGrid1.Columns[0].Visible:=false;
569573```
570574
571575Recompile, and check to see if the primary key column is really hidden.
572576
573577- Other databases: a lot of other databases use an 'autonumber' or 'autoinc' type of field to provide auto-generated field content. Try changing your table definition and see if it works.
574578
575579### 插入新数据(Inserting new data)
576580
577581If you insert new rows/records without any CUST_NO information you may have noticed that you get an error message: `Field CUST_NO is required, but not supplied`. This also happens if you hid the CUST_NO column, as in the previous section.
578582
579583The reason: Lazarus thinks that CUST_NO is required. That's not so strange, because it is a primary key and the underlying table definition in the database does say it is required.
580584
581585If we can instruct Lazarus that this field is not actually required, we can pass empty values (=NULL values) to the database. Fortunately, a query's field object has a *Required* property that does exactly that.
582586
583587Change the code to something like:
584588
585589``` pascal
586590 SQLQuery1.Open;
587591 {
588592 Make sure we don't get problems with inserting blank (=NULL) CUST_NO values, e.g.:
589593 Field CUST_NO is required, but not supplied
590594 We need to tell Lazarus that, while CUST_NO is a primary key, it is not required
591595 when inserting new records.
592596 }
593597 SQLQuery1.FieldByName('CUST_NO').Required:=false;
594598 // Hide the primary key column which is the first column in our queries.
595599 // We can only do this once the DBGrid has created the columns
596600 DBGrid1.Columns[0].Visible:=false;
597601```
598602
599603### 删除数据(Deleting data)
600604
601605Up to now, I haven't found a way to delete records/rows in the grid. While there might be a way to do this using keyboard combinations, you can make it easier for your users by letting them use the mouse to do this. You don't even need to code a single line for this functionality...
602606
603607On the 'Data Controls' tab, select a *TDBNavigator* component and drop it on the form, above the grid.
604608
605609To indicate what the navigator should be linked to, set its *DataSource* property to your existing datasource ('DataSource1') using the Object Inspector. Now you can use the button on the *DBNavigator* to delete records, but also insert them, and move around. Also, when editing cells/fields, you can use the *Cancel* button to cancel your edits.
606610
607611### 小结(Summary)
608612
609613如果按上文的步骤照做,你已经学会从数据库取回数据,过滤数据,在表格中编辑和删除数据。
610614
611615目前的代码应类似如下所示:
612616
613617(原文:If you followed along up to now, you can retrieve data from the database, filter it, and edit and delete data in the grid. Your code should look something like this:)
614618
615619``` pascal
616620unit sqldbtutorial1unit;
617621
618622{$mode objfpc}{$H+}
619623
620624interface
621625
622626uses
623627 Classes, SysUtils, IBConnection, sqldb, DB, FileUtil, Forms, Controls,
624628 Graphics, Dialogs, DBGrids, StdCtrls, DbCtrls;
625629
626630type
627631
628632 { TForm1 }
629633
630634 TForm1 = class(TForm)
631635 Button1: TButton;
632636 DatabaseName: TEdit;
633637 Datasource1: TDatasource;
634638 DBGrid1: TDBGrid;
635639 Dbnavigator1: Tdbnavigator;
636640 Edit1: TEdit;
637641 Label2: Tlabel;
638642 Label3: Tlabel;
639643 Label4: Tlabel;
640644 Label5: Tlabel;
641645 Password: TEdit;
642646 UserName: TEdit;
643647 ServerName: TEdit;
644648 IBConnection1: TIBConnection;
645649 Label1: TLabel;
646650 SQLQuery1: TSQLQuery;
647651 SQLTransaction1: TSQLTransaction;
648652 procedure SaveChanges;
649653 procedure Button1click(Sender: TObject);
650654 procedure Formclose(Sender: TObject; var Closeaction: Tcloseaction);
651655 private
652656 { private declarations }
653657 public
654658 { public declarations }
655659 end;
656660
657661var
658662 Form1: TForm1;
659663
660664implementation
661665
662666{$R *.lfm}
663667
664668{ TForm1 }
665669
666670procedure Tform1.Savechanges;
667671// Saves edits done by user, if any.
668672begin
669673 try
670674 if SQLTransaction1.Active=true then
671675 // Only if we are within a started transaction
672676 // otherwise you get "Operation cannot be performed on an inactive dataset"
673677 begin
674678 SQLQuery1.ApplyUpdates; //Pass user-generated changes back to database...
675679 SQLTransaction1.Commit; //... and commit them using the transaction.
676680 //SQLTransaction1.Active now is false
677681 end;
678682 except
679683 on E: EIBDatabaseError do
680684 begin
681685 MessageDlg('Error', 'A database error has occurred. Technical error message: ' +
682686 E.Message, mtError, [mbOK], 0);
683687 Edit1.Text := '';
684688 end;
685689 end;
686690end;
687691
688692procedure Tform1.Button1click(Sender: TObject);
689693begin
690694 SaveChanges; //Saves changes and commits transaction
691695 try
692696 SQLQuery1.Close;
693697 //Connection settings for Firebird/Interbase database
694698 //only needed when we have not yet connected:
695699 if IBConnection1.Connected = false then
696700 begin
697701 IBConnection1.HostName := ServerName.Text;
698702 IBConnection1.DatabaseName := DatabaseName.Text;
699703 IBConnection1.Username := UserName.Text;
700704 IBConnection1.Password := Password.Text;
701705 // Now we've set up our connection, visually show that
702706 // changes are not possibly any more
703707 ServerName.ReadOnly:=true;
704708 DatabaseName.ReadOnly:=true;
705709 UserName.ReadOnly:=true;
706710 Password.ReadOnly:=true;
707711 end;
708712 SQLQuery1.SQL.Text := 'select * from CUSTOMER where COUNTRY = :COUNTRY';
709713 SQLQuery1.Params.ParamByName('COUNTRY').AsString := Edit1.Text;
710714 IBConnection1.Connected := True;
711715 SQLTransaction1.Active := True; //Starts a new transaction
712716 SQLQuery1.Open;
713717 {
714718 Make sure we don't get problems with inserting blank (=NULL) CUST_NO values, i.e. error message:
715719 "Field CUST_NO is required, but not supplied"
716720 We need to tell Lazarus that, while CUST_NO is a primary key, it is not required
717721 when inserting new records.
718722 }
719723 SQLQuery1.FieldByName('CUST_NO').Required:=false;
720724 {
721725 Hide the primary key column which is the first column in our queries.
722726 We can only do this once the DBGrid has created the columns
723727 }
724728 DBGrid1.Columns[0].Visible:=false;
725729 except
726730 //EDatabaseError is a general error, but we're dealing with Firebird/Interbase, so:
727731 on E: EIBDatabaseError do
728732 begin
729733 MessageDlg('Error', 'A database error has occurred. Technical error message: ' +
730734 E.Message, mtError, [mbOK], 0);
731735 Edit1.Text := '';
732736 end;
733737 end;
734738end;
735739
736740procedure Tform1.Formclose(Sender: TObject; var Closeaction: Tcloseaction);
737741begin
738742 SaveChanges; //Saves changes and commits transaction
739743 SQLQuery1.Close;
740744 SQLTransaction1.Active := False;
741745 IBConnection1.Connected := False;
742746end;
743747
744748end.
745749```
746750
747751## 无需改变代码就能在程序中嵌入数据库(Embedded database without code changes)
748752
749753### Win平台下的Firebird(Firebird on Windows)
750754
751755Win平台Firebird用户的奖赏:如你按本文照做(哪怕你仅仅完成了基本示例),只要将嵌入式Firebird的`fbembed.dll` 改名为C/S结构Firebird所用的 `fbclient.dll`,Lazarus程序就可以连接C/S结构Firebird(一般是安装在另一台机器上或者是你的开发机上)。
752756
753757此外,您也可以复制 `employee.fdb`数据库到你的应用程序目录,再运行应用程序,既不需安装数据库服务器,也不需要设置“服务器名称”编辑框,就能使用嵌入式Firebird来直接连接到数据库文件。
754758
755759这为一类用户带来了极大的方便:想给终端用户部署数据库应用程序,但又不想自找麻烦地去安装服务器,免除了烦琐的部署步骤:如检查是否已经安装了服务器,数据库版本是否是正确,防火墙是否开启等。
756760
7577612011年9月:在近期的 FreePascal的 SVN 开发版本,FPC 尝试首先加载`fbembed.dll`,所以你不需要再为此而重命名为`fbclient.dll`。
758762
759763(原文:A bonus for Firebird users on Windows: if you have been following this tutorial (even if you only did the basic example), you renamed the `fbembed.dll` embedded Firebird library to `fbclient.dll`. With this, Lazarus could connect to regular Firebird servers (either on another machine or on your local machine). However, you can also copy the `employee.fdb` database to your application directory, run the application, clear the *Server name* TEdit and use Firebird embedded to directly connect to the database file, without any servers set up.
760764
761765This is great if you want to deploy database applications to end users, but don't want the hassle of installing servers (checking if a server is already installed, if it's the right version, having users check firewalls, etc).
762766
763767September 2011: in recent development (SVN) versions of FreePascal, FPC tries to first load `fbembed.dll`, so you need not rename `fbclient.dll` anymore for this to work.)
764768
765769### Linux/OSX/Unix平台下的Firebird(Firebird on Linux/OSX/Unix)
766770
767771Linux/OSX 平台下应该也能够找到方法来进行嵌入式部署。参看 [[Firebird]] 技巧提示。欢迎修改、完善本节内容。
768772
769773(原文:There must be a way to get this to work on Linux/OSX. See [[Firebird]] for hints and links. Updates to the wiki are welcome.)
770774
771775### 其他数据库(Other databases)
772776
773777你所使用的数据库可能提供同样的功能。 Sqlite 数据库如同它的名字所示,理所当然的可以进行嵌入式部署。欢迎针对你所使用的数据库来修改、完善本节内容。
774778
775779(原文:Your database might offer similar functionality. Sqlite certainly does as it is meant for embedded deployment. Updates of this wiki for other database systems are welcome.)
776780
777781## 参见(See also)
778782
779783- [[Firebird]]: another Firebird tutorial/how to.
780784- [[SQLdb Package]]: information about the SQLdb package
781785- [[SQLdb Programming Reference]]: an overview of the interaction of the SQLdb database components
782786- [[SqlDBHowto]]: informationen about the usage of the SQLdb package
783787- [[Working With TSQLQuery]]: information about TSQLQuery
784788
785789---
786790*From the Free Pascal wiki, page "SQLdb Tutorial1/zh CN", as it stood on 2020-02-26 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history