Sign in Join
Wiki copytemporary

SQLdb Tutorial3/fr: what changed

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

OldNewLine
11## Vue d'ensemble
22
33Dans ce tutoriel, vous allez apprendre comment :
44
55- Rendre votre application adapté à de multiples bases de données, y compris avec une fiche de connexion.
66- Obtenir des données de la base dans des contrôle normaux (au lieu de contrôles sensibles aux données).
77- Extraire les données des contrôles et les retourner dans la base de données
88- Exécuter des requêtes paramétrées.
99
1010## Support Multi-database
1111
1212En utilisant n'importe quelle base de données et une fiche de connexion, vous pouvez prendre en charge plusieurs serveurs de base de données/bibliothèques intégrées compatibles avec SQLDB.
1313
1414Avantages :
1515
1616- L'utilisateur/programmeur peut utiliser dynamiquement toute T*Connection de SQLdb, donc il peut choisir entre plusieurs SGBD.
1717
1818Inconvénients :
1919
2020- Un SQL plus compliqué sera demandé pour être encore adapté. Chaque SGBD a son propre dialecte ; c'est bien sûr possible d'appeler du SQL spécifique au SGBD, mais cela peut augmenter les problèmes de maintenance.
2121- Vous ne pouvez pas utiliser les propriétés spécifiques des T*Connection (telles que *TIBConnection.Dialect* pour définir le dialecte Firebird).
2222
2323Pour utiliser la prise en charge des bases multiples, à la place de votre T*Connection spécifique tel que *TIBConnection*, utilisez *TSQLConnector* (et non pas *TSQLConnection*), qui, à l'exécution du programme, choisit le T*Connection spécifique qui doit être utilisé dans sa propriété *ConnectorType* :
2424
2525``` pascal
2626uses
2727...
2828var
2929 Conn: TSQLConnector;
3030begin
3131 Conn:=TSQLConnector.Create(nil);
3232 try
3333 // ...actual connector type is determined by this property.
3434 // Make sure the ChosenConfig.DBType string matches
3535 // the connectortype (e.g. see the string in the
3636 // T*ConnectionDef.TypeName for that connector .
3737 Conn.ConnectorType:=ChosenConfig.DBType;
3838 // the rest is as usual:
3939 Conn.HostName:='DBSERVER';
4040 Conn.DatabaseName:='bigdatabase.fdb';
4141 Conn.UserName:='SYSDBA';
4242 Conn.Password:='masterkey';
4343 try
4444 Conn.Open;
4545```
4646
4747## Fiche de connexion
4848
4949Comme indiqué dans [[SQLdb Tutorial1/fr|SQLdb Tutorial1]], un utilisateur devrait se connecter à une base de données via une fiche (ou peut-être un fichier de configuration enregistré de manière sécurisée), et non à travers des accréditations (*credentials*) dans l'application. Outre les considérations de sécurité, avoir à recompiler l'application dès que les informations de sécurité sur serveur de base de données changent n'est pas une bonne idée.
5050
5151Dans *dbconfiggui.pas*, nous implanterons une fiche de connexion qui tire les valeurs par défaut d'un fichier ini, s'il existe. Cela vous permet de configurer une connexion par défaut avec quelques détails (serveur et nom de la base de données) rempli pour les dépliements en entreprise p.ex.
5252
5353Dans la fiche, l'utilisateur peut ajouter/modifier son nom d'utilisateur et son mot de passe et tester la connexion avant d'aller plus loin.
5454
55-<figure>
56-<img src="dbloginform.png" title="dbloginform.png" />
57-<figcaption>dbloginform.png</figcaption>
58-</figure>
55+![](/wiki-images/b/b2/Dbloginform.png)
5956
6057Nous utilisons une unité dbconfig.pas séparée avec une classe *TDBConnectionConfig* pour stocker les détails de notre connexion choisie. Cette classe prend en charge la lecture des réglages par défaut du fichier ini.
6158
6259Ceci permet l'emploi sans fiche de connexion IHM (p.ex. l'exécution d'opérations batch), et permet la réutilisation dans les applications Web.
6360
6461Cette classe *TDBConnectionConfig* apparaît dans la fiche de connexion comme propriété *Config*, afin que le programme principal puisse afficher le formulaire de configuration de manière modale, détecter un clic OK par l'utilisateur et récupérer la configuration sélectionnée avant de fermer le formulaire de configuration.
6562
6663### Fonction de rappel de test de connexion
6764
6865Pour garder la fiche de connexion flexible (elle pourrait être utilisée avec d'autres couches d'accès aux données comme Zeos), nous implémentons la section test comme une fonction de rappel et laissons le programme principal traiter avec.
6966
7067La définition dans la fiche de connexion dans *dbconfiggui.pas*:
7168
7269``` pascal
7370type
7471 TConnectionTestFunction = function(ChosenConfig: TDBConnectionConfig): boolean of object;
7572```
7673
7774La fiche principale doit implémenter une fonction avec la même signature pour gérer la requête de test depuis la fiche de configuration.
7875
7976La fonction de rappel prend l'objet config transmis par la fiche de configuration et l'utilise pour construire une connexion avec le type de base de données choisie. Il essaie simplement de se connecter au serveur ; si il réussit, le résultat de la fonction prend la valeur True, sinon le résultat reste False.
8077
8178Parce que des essais de connexion de base de données à es serveurs inexistant peut avoir de longs temps morts (*timeout*), nous indiquons à l'utilisateur qu'il doit attendre en mettant le sablier comme curseur de souris.
8279
8380``` pascal
8481uses
8582...
8683dbconfig, dbconfiggui
8784...
8885procedure TForm1.FormCreate(Sender: TObject);
8986 LoginForm:=TDBConfigForm.Create(self);
9087 try
9188 // The test button on dbconfiggui will link to this procedure:
9289 ... this links the callback in ''dbconfiggui.pas'' to the ConnectionTest function here...
9390 LoginForm.ConnectionTestCallback:=@ConnectionTest;
9491...
9592function TForm1.ConnectionTest(ChosenConfig: TDBConnectionConfig): boolean;
9693// Callback function that uses the info in dbconfiggui to test a connection
9794// and return the result of the test to dbconfiggui
9895var
9996 // Generic database connector...
10097 Conn: TSQLConnector;
10198begin
10299 result:=false;
103100 Conn:=TSQLConnector.Create(nil);
104101 Screen.Cursor:=crHourglass;
105102 try
106103 // ...actual connector type is determined by this property.
107104 // Make sure the ChosenConfig.DBType string matches
108105 // the connectortype (e.g. see the string in the
109106 // T*ConnectionDef.TypeName for that connector .
110107 Conn.ConnectorType:=ChosenConfig.DBType;
111108 Conn.HostName:=ChosenConfig.DBHost;
112109 Conn.DatabaseName:=ChosenConfig.DBPath;
113110 Conn.UserName:=ChosenConfig.DBUser;
114111 Conn.Password:=ChosenConfig.DBPassword;
115112 try
116113 Conn.Open;
117114 result:=Conn.Connected;
118115 except
119116 // Result is already false
120117 end;
121118 Conn.Close;
122119 finally
123120 Screen.Cursor:=crDefault;
124121 Conn.Free;
125122 end;
126123end;
127124```
128125
129126Finalement, le code dans *dbconfiggui.pas* qui appelle réellement la fonction de rappel est relie au bouton de Test. Il test si la fonction de rappel est affectée (pour éviter les plantages), pour la complétude, contrôlez aussi qu'il y a un objet de configuration valide et puis appelez la fonction de rappel :
130127
131128``` pascal
132129...
133130TDBConfigForm = class(TForm)
134131...
135132 private
136133 FConnectionTestFunction: TConnectionTestFunction;
137134 public
138135 property ConnectionTestCallback: TConnectionTestFunction write FConnectionTestFunction;
139136...
140137procedure TDBConfigForm.TestButtonClick(Sender: TObject);
141138begin
142139 // Call callback with settings, let it figure out if connection succeeded and
143140 // get test result back
144141 if assigned(FConnectionTestFunction) and assigned(FConnectionConfig) then
145142 if FConnectionTestFunction(FConnectionConfig) then
146143 showmessage('Connection test succeeded.')
147144 else
148145 showmessage('Connection test failed.')
149146 else
150147 showmessage('Error: connection test code has not been implemented.');
151148end;
152149```
153150
154151### Ajouts/modifications
155152
156153Possibles ajouts/modifications pour la fiche de connexion :
157154
158155- Ajouter les arguments de ligne de commande pour dbconfig pour pré-charger les valeurs par défaut adaptées, ainsi le programme peut être utilisé dans des scripts batch, raccourcis, etc...
159156- Ajout d'une combobox "Sélection de profil" dans la fiche de connexion : utiliser de multiples profils dans le fichier ini qui spécifient le type de base de données et les détais de connexion.
160157- Masquer la combobox type de base de données quand celle-ci est unique.
161158- Masquer les nom de connexion/mot de passe quand vous êtes sûr qu'une base de données incorporée est sélectionnée.
162159- Ajouter la prise en charge du numéro de port ou du nom d'instance avec le connecteur MS SQL Server.
163160- Ajouter la prise en charge de l'authentification de confiance (*trusted authentication*) pour la prise en charge des SGBD qui la supporte (Firebird, MS SQL) : désactiver les contrôles nom de connexion/mot de passe.
164161- Si une base de donnée incorporée est sélectionnée mais que le fichier n'existe pas : Montrer une boîte de confirmation et créer la base de données.
165162- Créer une version en ligne de commande (Text UI) de la fiche de connexion (p.ex. en utilisant la bibliothèque Curses) pour les applications en mode console.
166163
167164Mettre à jour cet article/le code serait chaleureusement apprécié.
168165
169166## Obtention de données de la base dans des contrôles non liés aux données
170167
171168> **Note:** Before starting this section, please make sure you have set up the sample employee database as specified in [[SQLdb Tutorial0|SQLdb Tutorial0#Requirements]]
172169
173170In previous tutorials, data-bound controls were covered: special controls such as the [[TDBGrid]] that can bind its contents to a [[TDataSource]], get updates from that source and send user edits back.
174171
175172It is also possible to programmatically retrieve database content and fill any kind of control (or variable) with that content. As an example, we will look at filling a stringgrid with some salary details for the sample employee database table.
176173
177174On the main form, let's add a [[TStringGrid]] and retrieve the data (e.g. via a procedure *LoadSalaryGrid* called in the *OnCreate* event):
178175
179176``` pascal
180177 // Load from DB
181178 try
182179 if not FConn.Connected then
183180 FConn.Open;
184181 if not FConn.Connected then
185182 begin
186183 ShowMessage('Error connecting to the database. Aborting data loading.');
187184 exit;
188185 end;
189186
190187 // Lowest salary
191188 // Note: we would like to only retrieve 1 row, but unfortunately the SQL
192189 // used differs for various dbs. As we'll deal with db dependent SQL later
193190 // in the tutorial, we leave this for now.
194191 // MS SQL: 'select top 1 '...
195192 FQuery.SQL.Text:='select ' +
196193 ' e.first_name, ' +
197194 ' e.last_name, ' +
198195 ' e.salary ' +
199196 'from employee e ' +
200197 'order by e.salary asc ';
201198 // ISO SQL+Firebird SQL: add
202199 //'rows 1 '; here and below... won't work on e.g. PostgreSQL though
203200 FTran.StartTransaction;
204201 FQuery.Open;
205202 SalaryGrid.Cells[1,1]:=FQuery.Fields[0].AsString; // i.e. Cells[Col,Row]
206203 SalaryGrid.Cells[2,1]:=FQuery.Fields[1].AsString;
207204 SalaryGrid.Cells[3,1]:=FQuery.Fields[2].AsString;
208205 FQuery.Close;
209206 // Always commit(retain) an opened transaction, even if only reading
210207 // this will allow updates by others to be seen when reading again
211208 FTran.Commit;
212209...
213210 end;
214211 except
215212 on D: EDatabaseError do
216213 begin
217214 MessageDlg('Error', 'A database error has occurred. Technical error message: ' +
218215 D.Message, mtError, [mbOK], 0);
219216 end;
220217 end;
221218```
222219
223220Things to note: we catch database errors using try..except. You'll notice we forgot to roll back the transaction in case of errors - which is left as an exercise to the reader.
224221
225222We *Open* the query object, thereby asking *FQuery* to query the database via its *SQL* statement. Once this is done, we're on the first row of data. We simply assume there is data now; this is actually a programming error: it would be tidier to check for *FQuery.EOF* being true (or *FQuery.RecordCount* being \>0).
226223
227224Next, we retrieve the data from the first row of results. If we wanted to move to the next row, we'd use *FQuery.Next*, but that is not necessary here. We put the results in the stringgrid, giving the lowest salary in the list. A similar approach can be taken for the highest salary.
228225
229226## Adaptations du SQL aux divers SGBD
230227
231228As we noticed above, various databases support various versions of SQL (either in addition to or in contradiction to the official ISO SQL standards). Fortunately, you can customize your application based on which DB it ends up using, which will be demonstrated by getting the standard deviation of the employees' salaries - built into e.g. PostgreSQL SQL but not available by default in e.g. Firebird.
232229
233230In our LoadSalaryGrid procedure, we'll use the SQL for PostgreSQL and build a code solution for all other databases. First detect which database is loaded, below the other lines add:
234231
235232``` pascal
236233 ...
237234 SalaryGrid.Cells[3,2]:=FQuery.Fields[2].AsString;
238235 FQuery.Close;
239236 // Always commit(retain) an opened transaction, even if only reading
240237 FTran.Commit;
241238//end of existing code
242239
243240 if FConn.ConnectorType = 'PostGreSQL' then
244241 begin
245242 // For PostgreSQL, use a native SQL solution:
246243 FQuery.SQL.Text:='select stddev_pop(salary) from employee ';
247244 FTran.StartTransaction;
248245 FQuery.Open;
249246 if not FQuery.EOF then
250247 SalaryGrid.Cells[3,3]:=FQuery.Fields[0].AsString;
251248 FQuery.Close;
252249 // Always commit(retain) an opened transaction, even if only reading
253250 FTran.Commit;
254251 end
255252 else
256253 begin
257254 // For other database, use the code approach:
258255 ....see below...
259256 end;
260257```
261258
262259Notice the use of *ConnectorType*; the string used must match exactly. We also properly check for empty results from the query (which might happen if the employee table is empty).
263260
264261... now let's implement a code-based solution for other databases that do not support standard deviation:
265262
266263``` pascal
267264 // For other databases, use the code approach:
268265 // 1. Get average of values
269266 FQuery.SQL.Text:='select avg(salary) from employee ';
270267 FQuery.Open;
271268 if FQuery.EOF then
272269 SalaryGrid.Cells[3,3]:='No data'
273270 else
274271 begin
275272 Average:=FQuery.Fields[0].AsFloat;
276273 FQuery.Close;
277274 // 2. For each value, calculate the square of (value-average), and add it up
278275 FQuery.SQL.Text:='select salary from employee where salary is not null ';
279276 FQuery.Open;
280277 while not FQuery.EOF do
281278 begin
282279 DifferencesSquared:=DifferencesSquared+Sqr(FQuery.Fields[0].AsFloat-Average);
283280 Count:=Count+1;
284281 FQuery.Next;
285282 end;
286283 // 3. Now calculate the average "squared difference" and take the square root
287284 if Count>0 then //avoid division by 0
288285 SalaryGrid.Cells[3,3]:=FloatToStr(Sqrt(DifferencesSquared/Count))
289286 else
290287 SalaryGrid.Cells[3,3]:='No data';
291288 end;
292289 FQuery.Close;
293290```
294291
295292Note that we use *FQuery.EOF* to check for empty data (and avoid division by zero errors etc). The loop shows how to:
296293
297294- retrieve a database value into a variable
298295- use *FQuery.Next* to move to the next record
299296- properly check if the query dataset has hit the last record, then stop retrieving data.
300297
301298The resulting screen should show something like this - note the use of a decimal comma - while your computer may show a decimal point depending on your locale:
302299
303-<figure>
304-<img src="sqldbtutorial3mainform.png" title="sqldbtutorial3mainform.png" />
305-<figcaption>sqldbtutorial3mainform.png</figcaption>
306-</figure>
300+![](/wiki-images/6/6a/Sqldbtutorial3mainform.png)
307301
308302### Repenser notre salaire plus élévé/plus bas
309303
310304*This section gives some more useful details on SQL but is not required to work through for the rest of the tutorial*
311305
312306Now we know how to deal with detecting various database connections, we can adjust the SQL that gets the lowest and highest salary as well to make use of db specific functionality.
313307
314308An example: this would work for MS SQL Server by limiting the number of returned rows to just the first:
315309
316310``` SQL
317311select top 1
318312e.first_name, e.last_name, e.salary
319313from employee e
320314order by e.salary asc
321315```
322316
323317to get the lowest salary.
324318
325319This efficiently returns one record. Other databases use other syntax, such as the ISO ROWS 1. The diligent SQL student will soon learn not to miss out that important part and request entire large recordsets just for one required record!
326320
327321Let's briefly examine other ways to achieve the same thing, that are worth knowing.
328322
329323Another way to retrieve the record(s) with the minimum salary would be :''
330324
331325``` SQL
332326SELECT e.first_name, e.last_name, e.salary FROM employee e WHERE e.salary=(SELECT min(salary) FROM employee)
333327```
334328
335329SQL students would greatly benefit from researching Common Table Expressions.
336330
337331A CTE allows a virtual temporary table to be used in a following expression, allowing you to clearly code some very complex queries that otherwise may not be possible. Knowing about CTEs will catapult you ahead of colleagues who have never heard of them! For example the above may be rewritten (example in Microsoft SQL Server syntax) as : ''
338332
339333``` SQL
340334WITH TheMinimum as
341335(
342336 SELECT min(salary) as MinimumPay FROM Employee
343337)
344338SELECT e.first_name, e.last_name, e.salary FROM Employee e WHERE e.salary=(SELECT MinimumPay FROM TheMinimum)
345339```
346340
347341Several such temporary tables may be chained together, each using the results from the previous tables. You can treat these virtual tables as though they were real tables in the database, using JOINs to link recordsets together. And it can be very useful for quick tests using hardcoded data - this can be run without any database connection :
348342
349343``` SQL
350344WITH TestEmployee as
351345(
352346 SELECT 'Fred' as first_name, 'Bloggs' as last_name, 10500 as salary
353347 UNION
354348 SELECT 'Joe' as first_name, 'Public' as last_name, 10000 as salary
355349 UNION
356350 SELECT 'Mike' as first_name, 'Mouse' as last_name, 11000 as salary
357351),
358352 TheMinimum as
359353(
360354 SELECT min(salary) as MinimumPay FROM TestEmployee
361355)
362356SELECT e.first_name, e.last_name, e.salary FROM TestEmployee e WHERE e.salary=(SELECT MinimumPay FROM TheMinimum)
363357```
364358
365359You can end up with quite long strings for the code of such SQL queries, but it is only *one* query and may be called from anywhere where you are limited to a simple single expression - it can be useful to answer complex queries without resorting to functions or stored procedures.
366360
367361## Obtention de données hors des contrôles normaux dans la base de données
368362
369363Previously, we have seen:
370364
371365- how to let SQLDB update the database with data-bound controls (earlier tutorials)
372366- how to get data out of the database using queries (the section above)
373367
374368You can also execute SQL to get arbitrary data back into the database via code. This allows you to use variables or controls that have no db aware equivalent such as sliders or custom controls to enter data into the database, at the expense of a bit more coding.
375369
376370As an example, we are going to allow the user to change the lowest and highest salary in the stringgrid.
377371
378372For ease of editing, set the grid's *Options*/*goEditing* to true; then assign the procedure below to the OnValidate event for the grid, which will be called every time a user has finished updating the grid.
379373
380374### Requête paramétrée
381375
382376The following code also demonstrates how to use parameterized queries to avoid SQL injection, fiddling with quoting for string values, date formatting, etc.
383377
384378As you can see in the code, you can name your parameters whatever you wish and prefix them with : in the SQL. In code, you can set/get their values by *\<somequery\>.Params.ParamByName('\<thename\>').As'\<variabletype\>'; the code demonstrates*.AsFloat*and*.AsString''.
385379
386380Parameterized queries are especially useful (and can be much faster) if you run the same query, only with different parameters, in a loop (think e.g. bulk loading of data).
387381
388382Continuing with our example: after having set up the query SQL and parameters, the transaction is started (and later on committed) as usual, then the query is run by calling *ExecSQL* (which does not return a result set; if the SQL statement were e.g. a SELECT or INSERT...RETURNING that does return data, you would use *Open* as in the examples above):
389383
390384``` pascal
391385procedure TForm1.SalaryGridValidateEntry(sender: TObject; aCol, aRow: Integer;
392386 const OldValue: string; var NewValue: String);
393387begin
394388 // Only these cells have min and max salary:
395389 if (aCol=3) and ((aRow=1) or (aRow=2)) then
396390 begin
397391 // Allow updates to min and max salary if positive numerical data is entered
398392 if StrToFloatDef(NewValue,-1)>0 then
399393 begin
400394 // Storing the primary key in e.g. a hidden cell in the grid and using that in our
401395 // update query would be cleaner, but we can do it the hard way as well:
402396 FQuery.SQL.Text:='update employee set salary=:newsalary '+
403397 ' where first_name=:firstname and last_name=:lastname and salary=:salary ';
404398 FQuery.Params.ParamByName('newsalary').AsFloat:=StrToFloatDef(NewValue,0);
405399 FQuery.Params.ParamByName('firstname').AsString:=SalaryGrid.Cells[1,aRow];
406400 FQuery.Params.ParamByName('lastname').AsString:=SalaryGrid.Cells[2,aRow];
407401 FQuery.Params.ParamByName('salary').AsFloat:=StrToFloatDef(OldValue,0);
408402 FTran.StartTransaction;
409403 FQuery.ExecSQL;
410404 FTran.Commit;
411405 LoadSalaryGrid; //reload standard deviation
412406 end
413407 else
414408 begin
415409 // Notify user that his input was wrong... he'll be wondering otherwise:
416410 Showmessage('Invalid salary entered.');
417411 NewValue:=OldValue;
418412 end;
419413 end
420414 else
421415 begin
422416 // Silently discard edits to any other cells
423417 NewValue:=OldValue;
424418 end;
425419end;
426420```
427421
428422Note how we forgot to add a try..except block to this code to nicely catch database errors and display a sensible error message. If you are running the Firebird sample EMPLOYEE database for this tutorial, try to change the salary to a very low value (say 1) and see what happens.
429423
430424Finally, while this example showed an UPDATE SQL query, you could just as well run INSERT queries to insert new data programmatically. Also, you can use parameters in any kind of SQL query (SELECT, UPDATE, etc) as long as you use them for fields, not for table/view/procedure names.
431425
432426## Résumé
433427
434428Ce tutoriel a expliqué :
435429
436430- Comment coder pour de multiples types de base de données.
437431- Comment utiliser un fiche de connexion pour découpler votre programme la configuration de l'accès à la base de données.
438432- comment récupérer et mettre à jour vos données par la programmation.
439433
440434## Code
441435
442436Depuis Novembre 2012, le code peur être trouvé dans \$(lazarusdir)examples/database/sqldbtutorial3.
443437
444438Si vous avez une version plus ancienne (p.ex. Lazarus 1.0.2), vous pouvez aussi télécharger le code via le [site web SVN de Lazarus](http://svn.freepascal.org/svn/lazarus/trunk/examples/database/sqldbtutorial3/).
445439
446440## Voir aussi
447441
448442- [[SQLdb Tutorial0/fr|SQLdb Tutorial0]] : Instructions pour configurer les tables et les données d'exemple pour la série de tutoriels.
449443- [[SQLdb Tutorial1/fr|SQLdb Tutorial1]] : 1re partie de la série de tutoriels sur les bases de données, montrant comment configurer une grille pour afficher les données.
450444- [[SQLdb Tutorial2/fr|SQLdb Tutorial2]] : 2e partie de la série de tutoriels, montrant l'édition, l'insertion, etc.
451445- [[SQLdb Tutorial3/fr|SQLdb Tutorial3]] : 3e partie de la série de tutoriels, montrant comment développer pour des bases de données multiples et utiliser une fiche de connexion.
452446- [[SQLdb Tutorial4/fr|SQLdb Tutorial4]] : 4e partie de la série de tutoriels, montrant comment utiliser les modules de données.
453447- [[Lazarus Database Overview/fr|Vue d'ensemble des bases de données pour Lazarus]] : Information sur les bases de données supportées par Lazarus. Redirige vers des notes spécifiques à certaines bases de données.
454448- [[SQLdb Package/fr|Paquet SQLdb]] : Information sur le paquetage SQLdb.
455449- [[SQLdb Programming Reference/fr|Référence de programmation SQLdb]] : Une vue d'ensemble de l'interaction entre les composants de bases de données SQLdb.
456450- [[SqlDBHowto/fr|SqlDBHowto]] : Information sur l'utilisation du paquet SQLdb.
457451- [[Working With TSQLQuery/fr|Travailler avec TSQLQuery]] : Informations sur TSQLQuery.
458452- [utilisation des paramètres](http://www.freepascal.org/docs-html/fcl/sqldb/usingparams.html)
459453
460454---
461455*From the Free Pascal wiki, page "SQLdb Tutorial3/fr", 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