Sign in Join
Wiki copytemporary

SQLdb Tutorial2: what changed

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

OldNewLine
11## Overview
22
33If you have followed [[SQLdb Tutorial1]], you have a basic grid showing database information. While this application works, we can add some refinements on this.
44
55### Dynamic database connection
66
77Up to now, we used a fixed database server name, database location, username and password for simplicity. As mentioned, "real" applications normally let users specify their own username and password.
88
99Let's change the form so we can specify them: add two [[TEdit|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 against people looking over your shoulder.
1010
1111If 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 a default value like masterkey, easy for testing on developer machines if security doesn't matter...
1212
1313Cosmetically, adding some labels so people know what they're supposed to type is useful.
1414
1515Also, to make it easier to connect to any employee 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.
1616
1717If 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`
1818
1919Labels to explain what users need to enter would help here, too.
2020
2121For clarity, we're going to remove the connection info from our design time components: on the [[TSQLConnector]] component, remove all text from the UserName, Password, DatabaseName and HostName properties.
2222
2323Now, finally, we need to tell our database connection component how to connect. This should normally only be necessary at the beginning of an application run. In our case the existing 'Button1' code is a good way to set up the connection.
2424
2525Add code until you get:
2626
2727``` pascal
2828procedure TForm1.Button1Click(Sender: TObject);
2929begin
3030 SQLQuery1.Close;
3131 //Connection settings for Firebird/Interbase database
3232 //only needed when we have not yet connected:
3333 if not DBConnection.Connected then
3434 begin
3535 DBConnection.HostName := ServerName.Text;
3636 DBConnection.DatabaseName := DatabaseName.Text;
3737 DBConnection.Username := UserName.Text;
3838 DBConnection.Password := Password.Text;
3939 // Now we've set up our connection, visually show that
4040 // changes are not possibly any more
4141 ServerName.ReadOnly:=true;
4242 DatabaseName.ReadOnly:=true;
4343 UserName.ReadOnly:=true;
4444 Password.ReadOnly:=true;
4545 end;
4646 SQLQuery1.SQL.Text:= 'select * from CUSTOMER';
4747 DBConnection.Connected:= True;
4848 SQLTransaction1.Active:= True;
4949 SQLQuery1.Open;
5050end;
5151```
5252
5353Now run and test if you can connect.
5454
5555#### SQLite, other databases
5656
5757Adjust the Text property in the DatabaseName TEdit as needed; e.g. `employee.sqlite` for SQLite.
5858
5959For sqlite, specifying HostName, Username and Password doesn't make sense, so you can omit these TEdits. Obviously, leave out/comment out assigning the corresponding values to DBConnection in the code above. For Firebird embedded, please hardcode the Username to SYSDBA; specifying this when sqlite is used won't hurt.
6060
6161The code will look something like:
6262
6363``` pascal
6464procedure TForm1.Button1Click(Sender: TObject);
6565begin
6666 SQLQuery1.Close;
6767 //Connection settings for embedded databases
6868 //only needed when we have not yet connected:
6969 if not DBConnection.Connected then
7070 begin
7171 DBConnection.DatabaseName := DatabaseName.Text;
7272 DBConnection.UserName := 'SYSDBA'; //Firebird embedded needs this; doesn't harm if using SQLite
7373 // Now we've set up our connection, visually show that
7474 // changes are not possibly any more
7575 DatabaseName.ReadOnly:=true;
7676 end;
7777 SQLQuery1.SQL.Text:= 'select * from CUSTOMER';
7878 DBConnection.Connected:= True;
7979 SQLTransaction1.Active:= True;
8080 SQLQuery1.Open;
8181end;
8282```
8383
8484### Filtering data
8585
8686Often, 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:
8787
8888``` SQL
8989select * from CUSTOMER where COUNTRY = 'USA'
9090```
9191
9292... which would translate to something like this in our code:
9393
9494``` pascal
9595SQLQuery1.SQL.Text := 'select * from CUSTOMER where COUNTRY = 'USA'';
9696```
9797
9898There are two reasons why we will not use this instruction for our example application:
9999
100100First 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:
101101
102102``` pascal
103103SQLQuery1.SQL.Text := 'select * from CUSTOMER where COUNTRY = ''USA''';
104104```
105105
106106The 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.
107107
108108To 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:
109109
110110``` pascal
111111SQLQuery1.SQL.Text := 'select * from CUSTOMER';
112112```
113113
114114with:
115115
116116``` pascal
117117SQLQuery1.SQL.Text:= 'select * from CUSTOMER where COUNTRY = :COUNTRY';
118118```
119119
120120In FPC SQLDB, the SQL parameter is marked by the leading colon (other languages/environments use other conventions like ?). To allow the user to enter a value for the filter, we place a *TEdit* component on our form. Delete the value of its 'Text' property.
121121
122122We can now take the text entered in the TEdit and fill the SQL COUNTRY parameter by using the 'Params' property of TSQLQuery. Add this below the previous statement:
123123
124124``` pascal
125125SQLQuery1.Params.ParamByName('COUNTRY').AsString := Edit1.Text;
126126```
127127
128128The 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.
129129
130130We use .AsString to assign a string value to the parameter; there are equivalent property assignments for integer parameters, boolean parameters etc.
131131
132132The code up to now forces us to use a filter. If a user specifies an empty value in the edit box, no record will be displayed. This is probably not what we want. Let's test for an empty value and build our query accordingly. We should end up with a procedure like this:
133133
134134``` pascal
135135procedure TForm1.Button1Click(Sender: TObject);
136136begin
137137 SQLQuery1.Close;
138138 //Connection settings for Firebird/Interbase database
139139 //only needed when we have not yet connected:
140140 if not DBConnection.Connected then
141141 begin
142142 DBConnection.HostName := ServerName.Text;
143143 DBConnection.DatabaseName := DatabaseName.Text;
144144 DBConnection.Username := UserName.Text;
145145 DBConnection.Password := Password.Text;
146146 // Now we've set up our connection, visually show that
147147 // changes are not possibly any more
148148 ServerName.ReadOnly:=true;
149149 DatabaseName.ReadOnly:=true;
150150 UserName.ReadOnly:=true;
151151 Password.ReadOnly:=true;
152152 end;
153153 // Show all records, or filter if user specified a filter criterium
154154 if Edit1.Text='' then
155155 SQLQuery1.SQL.Text := 'select * from CUSTOMER'
156156 else
157157 begin
158158 SQLQuery1.SQL.Text := 'select * from CUSTOMER where COUNTRY = :COUNTRY';
159159 SQLQuery1.Params.ParamByName('COUNTRY').AsString := Edit1.Text;
160160 end;
161161 DBConnection.Connected:= True;
162162 SQLTransaction1.Active:= True;
163163 SQLQuery1.Open;
164164end;
165165```
166166
167167Now 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.
168168
169169### Error handling
170170
171171The application should run, but sometimes problems can occur. Databases, even embedded databases can crash (e.g. when the database server crashes, the disk is full, or just due to a bug), leaving the application hanging.
172172
173173Access to a database (any external process, really) should therefore **always** be integrated in a try ... except and/or try ... finally construct. This ensures that database errors are handled and the user isn't left out in the cold. A rudimentary routine for our example application could look like this:
174174
175175``` pascal
176176begin
177177 try
178178 SQLQuery1.Close;
179179 ...
180180 SQLQuery1.Open;
181181 except
182182 //We could use EDatabaseError which is a general database error, but we're dealing with Firebird/Interbase, so:
183183 on E: EDatabaseError do
184184 begin
185185 MessageDlg('Error','A database error has occurred. Technical error message: ' + E.Message,mtError,[mbOK],0);
186186 Edit1.Text:='';
187187 end;
188188 end;
189189end;
190190```
191191
192192#### SQLite, PostgreSQL, other databases
193193
194194You can either use the more generic EDatabaseError, or - if available - your own specialized databaseerror, if you need more details. E.g. SQLite and the PostgreSQL driver in FPC 2.6.1 and lower doesn't have a specialized E*DatabaseError; you'd have to use EDatabaseError. PostgreSQL on FPC trunk (development version) has EPQDatabaseError.
195195
196196## Editing data using the grid
197197
198198### Editing
199199
200200Up 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:
201201
202202``` pascal
203203SQLQuery1.ApplyUpdates; //Pass user-generated changes back to database...
204204SQLTransaction1.Commit; //... and commit them using the transaction.
205205//SQLTransaction1.Active now is false
206206```
207207
208208We want to make sure any edits (inserts, updates, deletes) are written to the database:
209209
210210- when the users changes the filtering criteria and presses the button to query the database
211211- when the form is closed
212212
213213It 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:
214214
215215``` pascal
216216 TForm1 = class(TForm)
217217 Button1: TButton;
218218 Datasource1: TDatasource;
219219 DBGrid1: TDBGrid;
220220 Edit1: TEdit;
221221 DBConnection: TIBConnection;
222222 SQLQuery1: TSQLQuery;
223223 SQLTransaction1: TSQLTransaction;
224224*****insert the empty line here****
225225 procedure Button1click(Sender: TObject);
226226 procedure Formclose(Sender: TObject; var Closeaction: Tcloseaction);
227227 private
228228```
229229
230230then type:
231231
232232``` pascal
233233 procedure SaveChanges;
234234```
235235
236236press shift-ctrl-c (default combination) to let code completion automatically create the corresponding procedure body.
237237
238238We 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:
239239
240240``` pascal
241241procedure Tform1.SaveChanges;
242242// Saves edits done by user, if any.
243243begin
244244 try
245245 if SQLTransaction1.Active then
246246 // Only if we are within a started transaction;
247247 // otherwise you get "Operation cannot be performed on an inactive dataset"
248248 begin
249249 SQLQuery1.ApplyUpdates; //Pass user-generated changes back to database...
250250 SQLTransaction1.Commit; //... and commit them using the transaction.
251251 //SQLTransaction1.Active now is false
252252 end;
253253 except
254254 on E: EDatabaseError do
255255 begin
256256 SQLTransaction1.Rollback;
257257 MessageDlg('Error', 'A database error has occurred. Technical error message: ' +
258258 E.Message, mtError, [mbOK], 0);
259259 Edit1.Text := '';
260260 end;
261261 end;
262262end;
263263```
264264
265265Now we need to call this procedure at the appropriate moments:
266266
267267``` pascal
268268procedure Tform1.Button1click(Sender: TObject);
269269begin
270270 SaveChanges; //Saves changes and commits transaction
271271 try
272272 SQLQuery1.Close;
273273....
274274```
275275
276276and:
277277
278278``` pascal
279279procedure Tform1.Formclose(Sender: TObject; var Closeaction: Tcloseaction);
280280begin
281281 SaveChanges; //Saves changes and commits transaction
282282 SQLQuery1.Close;
283283....
284284```
285285
286286Now test and see if edits made in the dbgrid are saved to the database.
287287
288288### Hiding primary key column
289289
290290Often, 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, that there are gaps in the numbers, etc.
291291
292292In 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.
293293
294294We 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 needed in those circumstances for uniquely identifying the row/record in question.
295295
296296Therefore, 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:
297297
298298``` pascal
299299procedure Tform1.Button1click(Sender: TObject);
300300begin
301301...
302302 SQLQuery1.Open;
303303 // Hide the primary key column which is the first column in our queries.
304304 // We can only do this once the DBGrid has created the columns
305305 DBGrid1.Columns[0].Visible:=false;
306306```
307307
308308Recompile, and check to see if the primary key column is really hidden.
309309
310310#### SQLite, other databases
311311
312312- 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.
313313- Sqlite: the example above works for SQLite as is because we're using an integer primary key. See the [documentation](http://www.sqlite.org/autoinc.html) for details.
314314
315315### Inserting new data
316316
317317If 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.
318318
319319The 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.
320320
321321If 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.
322322
323323Change the code to something like:
324324
325325``` pascal
326326 SQLQuery1.Open;
327327 {
328328 Make sure we don't get problems with inserting blank (=NULL) CUST_NO values, e.g.:
329329 Field CUST_NO is required, but not supplied
330330 We need to tell Lazarus that, while CUST_NO is a primary key, it is not required
331331 when inserting new records.
332332 }
333333 SQLQuery1.FieldByName('CUST_NO').Required:=false;
334334 // Hide the primary key column which is the first column in our queries.
335335 // We can only do this once the DBGrid has created the columns
336336 DBGrid1.Columns[0].Visible:=false;
337337```
338338
339339### Deleting data
340340
341341You can let your users use the mouse to do this. You don't even need to code a single line for this functionality...
342342
343343On the 'Data Controls' tab, select a *TDBNavigator* component and drop it on the form, above the grid.
344344
345345To 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 the records. Also, when editing cells/fields, you can use the *Cancel* button to cancel your edits.
346346
347-To allow users to delete the row they're in on the grid using the <span class="kbd">Delete</span> key, add *LCLType* (this contains definitions for key codes) to your uses clause:
347+To allow users to delete the row they're in on the grid using the `Delete` key, add *LCLType* (this contains definitions for key codes) to your uses clause:
348348
349349``` pascal
350350uses
351351 Classes, SysUtils, sqldb, pqconnection, DB, FileUtil, Forms,
352352 Controls, Graphics, Dialogs, DBGrids, StdCtrls, DbCtrls, LCLType;
353353```
354354
355-... then handle the KeyUp event for the grid, which occurs when a key is released if in the grid. However, we do need to check that the user is not editing a field - as he'll probably use the <span class="kbd">Delete</span> key to delete letters rather than the record he's working on.
355+... then handle the KeyUp event for the grid, which occurs when a key is released if in the grid. However, we do need to check that the user is not editing a field - as he'll probably use the `Delete` key to delete letters rather than the record he's working on.
356356
357357Select the grid, then go to events and create an OnKeyUp event like this:
358358
359359``` pascal
360360procedure TForm1.DBGrid1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState
361361 );
362362begin
363363 // Check for del key being hit and delete the current record in response
364364 // as long as we're not editing data
365365 if (key=VK_DELETE) and (not(DBGrid1.EditorMode)) then
366366 begin
367367 //... delete current record and apply updates to db:
368368 SQLQuery1.Delete;
369369 SQLQuery1.ApplyUpdates;
370370 end;
371371end;
372372```
373373
374-> **Note:** By default TDBGrid property Options / dgDisableDelete is set to false, this means a user can delete any record with the <span class="kbd">ctrl</span>-<span class="kbd">delete</span> key combo. You may not want this behaviour.
374+> **Note:** By default TDBGrid property Options / dgDisableDelete is set to false, this means a user can delete any record with the `ctrl`-`delete` key combo. You may not want this behaviour.
375375
376376## Summary
377377
378378If 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:
379379
380380``` pascal
381381unit sqldbtutorial1unit;
382382
383383{$mode objfpc}{$H+}
384384
385385interface
386386
387387uses
388388 Classes, SysUtils, sqldb, pqconnection, DB, FileUtil, Forms,
389389 Controls, Graphics, Dialogs, DBGrids, StdCtrls, DbCtrls, LCLType;
390390type
391391
392392 { TForm1 }
393393
394394 TForm1 = class(TForm)
395395 Button1: TButton;
396396 DatabaseName: TEdit;
397397 Datasource1: TDatasource;
398398 DBGrid1: TDBGrid;
399399 Dbnavigator1: Tdbnavigator;
400400 Edit1: TEdit;
401401 Label2: Tlabel;
402402 Label3: Tlabel;
403403 Label4: Tlabel;
404404 Label5: Tlabel;
405405 Password: TEdit;
406406 UserName: TEdit;
407407 ServerName: TEdit;
408408 DBConnection: TIBConnection;
409409 Label1: TLabel;
410410 SQLQuery1: TSQLQuery;
411411 SQLTransaction1: TSQLTransaction;
412412 procedure SaveChanges;
413413 procedure Button1click(Sender: TObject);
414414 procedure Formclose(Sender: TObject; var Closeaction: Tcloseaction);
415415 private
416416 { private declarations }
417417 public
418418 { public declarations }
419419 end;
420420
421421var
422422 Form1: TForm1;
423423
424424implementation
425425
426426{$R *.lfm}
427427
428428{ TForm1 }
429429
430430procedure Tform1.Savechanges;
431431// Saves edits done by user, if any.
432432begin
433433 try
434434 if SQLTransaction1.Active then
435435 // Only if we are within a started transaction
436436 // otherwise you get "Operation cannot be performed on an inactive dataset"
437437 begin
438438 SQLQuery1.ApplyUpdates; //Pass user-generated changes back to database...
439439 SQLTransaction1.Commit; //... and commit them using the transaction.
440440 //SQLTransaction1.Active now is false
441441 end;
442442 except
443443 on E: EDatabaseError do
444444 begin
445445 SQLTransaction1.Rollback;
446446 MessageDlg('Error', 'A database error has occurred. Technical error message: ' +
447447 E.Message, mtError, [mbOK], 0);
448448 Edit1.Text := '';
449449 end;
450450 end;
451451end;
452452
453453procedure TForm1.DBGrid1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState
454454 );
455455begin
456456 // Check for del key being hit and delete the current record in response
457457 // as long as we're not editing data
458458 if (key=VK_DELETE) and (not(DBGrid1.EditorMode)) then
459459 begin
460460 //... delete current record and apply updates to db:
461461 SQLQuery1.Delete;
462462 SQLQuery1.ApplyUpdates;
463463 end;
464464end;
465465
466466procedure Tform1.Button1click(Sender: TObject);
467467begin
468468 SaveChanges; //Saves changes and commits transaction
469469 try
470470 SQLQuery1.Close;
471471 //Connection settings for Firebird/Interbase database
472472 //only needed when we have not yet connected:
473473 if not DBConnection.Connected then
474474 begin
475475 DBConnection.HostName := ServerName.Text;
476476 DBConnection.DatabaseName := DatabaseName.Text;
477477 DBConnection.Username := UserName.Text;
478478 DBConnection.Password := Password.Text;
479479 // Now we've set up our connection, visually show that
480480 // changes are not possibly any more
481481 ServerName.ReadOnly:=true;
482482 DatabaseName.ReadOnly:=true;
483483 UserName.ReadOnly:=true;
484484 Password.ReadOnly:=true;
485485 end;
486486 // Show all records, or filter if user specified a filter criterium
487487 if Edit1.Text='' then
488488 SQLQuery1.SQL.Text := 'select * from CUSTOMER'
489489 else
490490 begin
491491 SQLQuery1.SQL.Text := 'select * from CUSTOMER where COUNTRY = :COUNTRY';
492492 SQLQuery1.Params.ParamByName('COUNTRY').AsString := Edit1.Text;
493493 end;
494494 DBConnection.Connected := True;
495495 SQLTransaction1.Active := True; //Starts a new transaction
496496 SQLQuery1.Open;
497497 {
498498 Make sure we don't get problems with inserting blank (=NULL) CUST_NO values, i.e. error message:
499499 "Field CUST_NO is required, but not supplied"
500500 We need to tell Lazarus that, while CUST_NO is a primary key, it is not required
501501 when inserting new records.
502502 }
503503 SQLQuery1.FieldByName('CUST_NO').Required:=false;
504504 {
505505 Hide the primary key column which is the first column in our queries.
506506 We can only do this once the DBGrid has created the columns
507507 }
508508 DBGrid1.Columns[0].Visible:=false;
509509 except
510510 // EDatabaseError is a general error;
511511 // you could also use one for your specific db, e.g.
512512 // use EIBDatabaseError for Firebird/Interbase
513513 on E: EDatabaseError do
514514 begin
515515 MessageDlg('Error', 'A database error has occurred. Technical error message: ' +
516516 E.Message, mtError, [mbOK], 0);
517517 Edit1.Text := '';
518518 end;
519519 end;
520520end;
521521
522522procedure Tform1.Formclose(Sender: TObject; var Closeaction: Tcloseaction);
523523begin
524524 SaveChanges; //Saves changes and commits transaction
525525 SQLQuery1.Close;
526526 SQLTransaction1.Active := False;
527527 DBConnection.Connected := False;
528528end;
529529
530530end.
531531```
532532
533533## Embedded database without code changes
534534
535535### Firebird on Windows
536536
537537A 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.
538538
539539This 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).
540540
541541See [[Firebird embedded]] for more details.
542542
543543September 2011: in recent development (SVN) versions of Free Pascal, FPC tries to first load `fbembed.dll`, so you need not rename `fbclient.dll` anymore for this to work.
544544
545545### Firebird on Linux/macOS/Unix
546546
547547There must be a way to get this to work on Linux/macOS. See [[Firebird]] for hints and links. Updates to the wiki are welcome.
548548
549549### SQLite
550550
551551SQLite certainly offers embedded functionality - it does not allow a client/server setup on the other hand. By following the tutorial above, you can see that switching between databases (e.g. SQLite and Firebird) is not so much work at all.
552552
553553### Other databases
554554
555555Your database might offer similar functionality. Updates of this wiki for other database systems are welcome.
556556
557557## See also
558558
559559- [[SQLdb Tutorial0]]: Instructions for setting up sample tables/sample data for the tutorial series.
560560- [[SQLdb Tutorial1]]: First part of the DB tutorial series, showing how to set up a grid that shows database data
561561- [[SQLdb Tutorial3]]: Third part of the DB tutorial series, showing how to program for multiple databases and use a login form
562562- [[SQLdb Tutorial4]]: Fourth part of the DB tutorial series, showing how to use data modules
563563- [[Lazarus Database Overview]]: Information about the databases that Lazarus supports. Links to database-specific notes.
564564- [[SQLdb Package]]: information about the SQLdb package
565565- [[SQLdb Programming Reference]]: an overview of the interaction of the SQLdb database components
566566- [[SqlDBHowto]]: information about using the SQLdb package
567567- [[Working With TSQLQuery]]: information about TSQLQuery
568568
569569---
570570*From the Free Pascal wiki, page "SQLdb Tutorial2", as it stood on 2024-03-29 after a change by Ariben, 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