Difference between revisions of "Working With TSQLQuery/fr"

From Lazarus wiki
Jump to navigationJump to search
m (Fixed syntax highlighting)
 
(9 intermediate revisions by one other user not shown)
Line 14: Line 14:
  
 
Beaucoup de documentation sensible au contexte est maintenant disponible dans Lazarus. Malheureusement, TSQLQuery n'apparaît pas dans l'index de l'aide de Lazarus 1.0. SI vous placez votre curseur sur les méthodes et propriétés de TSQLQuery, essayer de presser sur F1 pour voir si le code est documenté ; p.ex. cela marchera :
 
Beaucoup de documentation sensible au contexte est maintenant disponible dans Lazarus. Malheureusement, TSQLQuery n'apparaît pas dans l'index de l'aide de Lazarus 1.0. SI vous placez votre curseur sur les méthodes et propriétés de TSQLQuery, essayer de presser sur F1 pour voir si le code est documenté ; p.ex. cela marchera :
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
var
 
var
 
Q: TSQLQuery
 
Q: TSQLQuery
Line 84: Line 84:
 
Normalement TSQLQuery utilisera des ordres SQL génériques basés sur les propriétés discutées ci-dessus. Pourtant, le §SQL générique produit par sqldb peut ne pas être correct dans votre cas. TSQLQuery vous permet de personnaliser les ordres SQL utilisés pour des actions divserses, pour mieux s'accorder avec votre cas de mise à jour. Pour cela, vous devez les propriétés ''SQL'', ''InsertSQL'', ''UpdateSQL'' et ''DeleteSQL''.
 
Normalement TSQLQuery utilisera des ordres SQL génériques basés sur les propriétés discutées ci-dessus. Pourtant, le §SQL générique produit par sqldb peut ne pas être correct dans votre cas. TSQLQuery vous permet de personnaliser les ordres SQL utilisés pour des actions divserses, pour mieux s'accorder avec votre cas de mise à jour. Pour cela, vous devez les propriétés ''SQL'', ''InsertSQL'', ''UpdateSQL'' et ''DeleteSQL''.
  
Toutes ces propriétés sint de type TStringList, une liste de chaînes, qui accepte du SQL sur plusieurs lignes. Toutes ces propriétés viennent avec un éditeur de propriété dans l'EDI. Dans l'EDI, sélectionnez la propriété et ouvrez l'éditeur en cliquant que le bouton 'points de suspension'. Dans cet éditeur, ([[Database_metadata#Lazarus_TSQLQuery_metadata_tool|TSQLQuery metadata tool]]), vous pourrez aussi retrouver de l'information sur la table etc.
+
Toutes ces propriétés sont de type TStringList, une liste de chaînes, qui acceptent du SQL sur plusieurs lignes. Toutes ces propriétés viennent avec un éditeur de propriété dans l'EDI. Dans l'EDI, sélectionnez la propriété et ouvrez l'éditeur en cliquant que le bouton 'points de suspension'. Dans cet éditeur, ([[Database_metadata#Lazarus_TSQLQuery_metadata_tool|TSQLQuery metadata tool]]), vous pourrez aussi retrouver de l'information sur la table etc.
  
 
Dans le code, utilisez par exemple <tt>InsertSQL.Text</tt> ou <tt>InsertSQL.Add()</tt> pour définir les lignes des ordres SQL. Un ordre peut s'étendre sur plusieurs lignes et se termine par un point-virgule.
 
Dans le code, utilisez par exemple <tt>InsertSQL.Text</tt> ou <tt>InsertSQL.Add()</tt> pour définir les lignes des ordres SQL. Un ordre peut s'étendre sur plusieurs lignes et se termine par un point-virgule.
Line 91: Line 91:
  
 
===SQL - Personnalisation SQL de base ===
 
===SQL - Personnalisation SQL de base ===
The SQL property is normally used to fetch the data from the database. The generic SQL for this property is <tt>SELECT * FROM fpdev</tt> where <tt>fpdev</tt> is the table as set in the database.
+
La propriété SQL est normalement utilisée pour aller chercher les données dans la base de données. Le SQL générique est <tt>SELECT * FROM fpdev</tt> <tt>fpdev</tt> est la table définie dans la base de données.
 +
 
 +
Le DataSet retourné par l'ordre SQL générique sera sous la forme de brouillon. Si vous le montrez dans un DBGrid, l'ordre des enregistrements semblera aléatoire, l'ordre des colonnes ne sera pas celui que vous atttendez, le nom des colonne sera techniquement correct mais pas orienté utilisateur. En utilisant le SQL personnalisé, vous pouvez améliorer ceci.
 +
 
 +
Pour une table appelée fpdev avec des colonnes id, UserName et InstEmail, vous pouvez faire quelque chose comme ceci :
  
The dataset returned by the generic SQL statement will be kind of rough. If you show the result in a TDBGrid, the order of the records may seem random, the order of the columns may not be what you want and the field names may be technically correct but not user friendly. Using customized SQL you can improve this.
 
For a table called fpdev with columns id, UserName and InstEmail, you could do something like this:
 
 
<syntaxhighlight lang="sql">SELECT id AS 'ID', UserName AS 'User', InstEmail AS 'e-mail' FROM fpdev ORDER BY id;</syntaxhighlight>
 
<syntaxhighlight lang="sql">SELECT id AS 'ID', UserName AS 'User', InstEmail AS 'e-mail' FROM fpdev ORDER BY id;</syntaxhighlight>
  
The dataset that results from the above query uses the field names as given in the query (ID, User and e-mail), the column order as given in the query and the records are sorted by their id.
+
Le DataSet qui résulte de cette requête utilise les noms de champ donnés dans la requête (ID, User et e-mail), l'ordre des colonnes est donné par la requête et les enregistrements sont triés selon leur id.
  
=== InsertSQL, UpdateSQL et DeleteSQL -  Utilisaiton basique des paramètres ===
+
=== InsertSQL, UpdateSQL et DeleteSQL -  Utilisation basique des paramètres ===
When you assign a SELECT query to an SQLQuery's '''SQL''' property the SQLQuery knows how to get data from the database.
 
However, when using databound controls such as a DBGrid, SQLQuery will also need to be able to insert, update and delete rows from the database based on the user's actions.
 
  
In order to speed development, SQLQuery can try and deduce the required SQL statements. If the SQL property exists and the '''ParseSQL''' property is true (which it is by default), SQLQuery will try to generate these statements by parsing the '''SQL''' property.
+
Quand vous affectez une requête SELECT dans la propriété '''SQL''' du SQLQuery, celui-ci sait comment récupérer les données depuis la base de données. Cependant, en utilisant des contrôles liés aux données tels qu'un DBGrid, SQLQuery devra savoir aussi comment insérer, mettre à jour ou supprimer des lignes dans la base de données selon les actions de l'utilisateur.
SQLDB stores these statements in the '''InsertSQL''', '''UpdateSQL''' and '''DeleteSQL''' properties.
 
  
However, sometimes the generated statements will not work (e.g. when inserting in tables with auto-increment/autonumber primary keys) or will be very slow. If needed, you can manually assign the statements.
+
En vue d'accélerer le développement, SQLQuery peut essayer de déduire les ordres SQL requis. Si la propriété SQL est définie et que la propriété '''ParseSQL''' est ''True'' (ce qu'elle est par défaut), SQLQuery tentera de générer ces ordres en analysant la propriété '''SQL'''. SQLDB stocke ces ordres dans les propriétés '''InsertSQL''', '''UpdateSQL''' et '''DeleteSQL'''.
  
The statements in the ''InsertSQL'', ''UpdateSQL'' and ''DeleteSQL'' properties accept parameters that represent fields in the dataset. The following rules apply:
+
Pourtant, parfois les ordres générés ne marcheront pas (p.ex. en insérant dans une table avec clé primaire auto-incrémentée) ou sera très lent. Au besoin, vous pouvez affecter manuellement ces propriétés.
* Parameter names must be exactly the same as the field names used in the dataset. The field names in the dataset may be different from the column names in the table, depending on the used select statement (see above).
 
* Just as parameters in other SQLDB queries, parameter names must be written preceded by a colon.
 
* For use in update/delete statements, precede the dataset field name with '''OLD_''' ('''strictly uppercase''', at least in Lazarus v. 1.0) to get the value of the record before it was edited instead of the new value.
 
  
If you have a table called fpdev and columns id, UserName and InstEmail, linked to a dataset with fields ID, User and e-mail (see example in select statement), you could write this '''InsertSQL''' query:
+
Les ordres dans les proriétés ''InsertSQL'', ''UpdateSQL'' et ''DeleteSQL'' acceptent des paramètres qui représentent des champs du DataSet. Les règles suivantes s'appliquent :
 +
* Les noms de paramètres doivent être exactement identiques aux noms de champs utilisés dans le DataSet, les noms de champ dans le DataSet pouvant différer des noms de colonne dans la table, selon l'ordre SQL SELECT (voir ci-dessus).
 +
* Tout comme les paramètres dans d'autres requêtes SQLDB, les noms de paramètres doivent être écrits précédés d'un signe deux-points.
 +
* Pour l'utilisation dans les ordres update/delete, '''OLD_''' ('''strictement en majuscules''', au moins dans Lazarus v. 1.0) précède le nom de champ du DataSet pour obtenir la valeur de l'enregistrement avant modification au lieu de la nouvelle valeur.
 +
 
 +
Si vous avez une table appelée fpdev et des colonnes id, UserName et InstEmail, liés à un DataSet avec des champs ID, User et e-mail (voir l'exemple dans l'ordre SELECT), vous pourrez écrire cette requête '''InsertSQL''' :
 
<syntaxhighlight lang="sql">INSERT INTO fpdev(id, UserName, InstEmail) VALUES(:ID,:User,:e-mail);</syntaxhighlight>
 
<syntaxhighlight lang="sql">INSERT INTO fpdev(id, UserName, InstEmail) VALUES(:ID,:User,:e-mail);</syntaxhighlight>
This statement will insert the values of <tt>ID</tt>, <tt>User</tt> and <tt>e-mail</tt> from the current record of the dataset into the respective fields of table <tt>fpdev</tt>.
+
Cet ordre insérera les valeurs de  <tt>ID</tt>, <tt>User</tt> et <tt>e-mail</tt> de l'enregistrement courant du DataSet dans les champs respàectifs de la table <tt>fpdev</tt>.
  
This example statement is actually more or less what SQLDB itself would autogenerate. The given statement may result in errors when the <tt>id</tt> field is an auto-increment field in a unique key. Different databases solve this problem in different ways. For example, the following works for MySQL.
+
Cet exemple d'ordre est actuellement plus ou moins celui que SQLDB auto-généréra lui-même. L'ordre donné peut provoquer des erreurs quand le champ <tt>id</tt> est un champ auto-incrémenté d'une clé unique. Les différents SGBDR résolvent ce problème de différentes façons. P. ex., le code suivant tourne dans MySQL.
 
<syntaxhighlight lang="sql">INSERT INTO fpdev(id, UserName, InstEmail) VALUES(0,:User,:e-mail)
 
<syntaxhighlight lang="sql">INSERT INTO fpdev(id, UserName, InstEmail) VALUES(0,:User,:e-mail)
 
  ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID();</syntaxhighlight>
 
  ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID();</syntaxhighlight>
The above statement tries to insert a new record using 0 (zero) for the <tt>id</tt> column. If zero is already used as a key, then a duplicate is detected and the <tt>id</tt> is updated to use the last inserted id. Well, actually an id one increment higher than the last one used.
 
  
For Firebird, if you emulate autoincrement keys [http://www.firebirdfaq.org/faq29/] something like this should work:
+
L'ordre du dessus tente d'insérer un nouvel enregistrement en utilisant 0 (zéro) pour la colonne <tt>id</tt>. Si zéro est déjà utilisé comme clé, alors un doublon est détecté et le champ <tt>id</tt> est mis à jour pour utiliser le dernier id inséré. Eh bien, en fait un id incrémenté de un que le dernier utilisé.
 +
 
 +
Pour FireBird, si vous émulez les clés auto-incrémentées [http://www.firebirdfaq.org/faq29/], quelque chose comme ceci devrait fonctionner :  
 
<syntaxhighlight lang="sql">INSERT INTO fpdev(UserName, InstEmail) VALUES(:User,:e-mail);(</syntaxhighlight>
 
<syntaxhighlight lang="sql">INSERT INTO fpdev(UserName, InstEmail) VALUES(:User,:e-mail);(</syntaxhighlight>
The statement inserts everything except the primary key and lets the Firebird before insert trigger use a generator/sequence to insert an <tt>id</tt> value for you.  
+
Cet ordre insère tout sauf la clé primaire et laisse le trigger FireBird Before Insert utiliser un générateur/une séquence pour insérer la valeur de la colonne <tt>id</tt> pour vous.
  
For an <tt>INSERT</tt> statement you may want to use the current field values of the selected record. For <tt>UPDATE</tt> statements, you will want to use the field values as they were before editing in the <tt>WHERE</tt> clause. As mentioned before, the field values before editing must be written as the field name precede by '''OLD_''' ('''strictly uppercase''', at least in Lazarus v. 1.0). For example, this query:
+
Pour un ordre <tt>INSERT</tt> vous pouvez vouloir utiliser les valeurs courantes des champs de l'enregistrement sélectionné. Pour les ordres <tt>UPDATE</tt>, vous voudrez utiliser les valeurs de champ telles qu'elles étaient avant édition dans la clause <tt>WHERE</tt>. Comme indiqué plus haut, les valeurs de champ avant édition doivent être écrites avec le nom de champ précédé par '''OLD_''' ('''en majuscules strictement''', au moins dans Lazarus v. 1.0). Par exemple, cette requête :  
 
<syntaxhighlight lang="sql">UPDATE fpdev SET UserName=:User, InstEmail=:e-mail WHERE UserName=:OLD_User;</syntaxhighlight>
 
<syntaxhighlight lang="sql">UPDATE fpdev SET UserName=:User, InstEmail=:e-mail WHERE UserName=:OLD_User;</syntaxhighlight>
The above statement updates the <tt>UserName</tt> and <tt>InstEmail</tt> columns of all records where <tt>User</tt> equals the old <tt>User</tt> value.
+
mettra à jour les colonnes <tt>UserName</tt> et <tt>InstEmail</tt> dans tous les enregistrements où <tt>User</tt> vaut l'ancienne valeur de <tt>User</tt>.
  
We leave it as an exercise to the reader to use the current field values and the old field values in DELETE statements.
+
Nous laissons en exercice au lecteur l'utilisation des valeurs courantes des champs et des anciennes valeurs dans les ordres DELETE.
  
See also the official documentation:
+
Voir aussi la documentation officielle :
* [http://www.freepascal.org/docs-html/fcl/sqldb/tsqlquery.insertsql.html InsertSQL documentation]
+
* [http://www.freepascal.org/docs-html/fcl/sqldb/tsqlquery.insertsql.html Documentation InsertSQL]
* [http://www.freepascal.org/docs-html/fcl/sqldb/tsqlquery.updatesql.html UpdateSQL documentation]
+
* [http://www.freepascal.org/docs-html/fcl/sqldb/tsqlquery.updatesql.html Documentation UpdateSQL]
* [http://www.freepascal.org/docs-html/fcl/sqldb/tsqlquery.deletesql.html DeleteSQL documentation]
+
* [http://www.freepascal.org/docs-html/fcl/sqldb/tsqlquery.deletesql.html Documentation DeleteSQL]
  
 
===Paramètres dans TSQLQuery.SQL===
 
===Paramètres dans TSQLQuery.SQL===
In most situations, the SQL property of TSQLQuery will contain the select statement which in most situations doesn't need parameters. However, it can contain them. This allows a very easy and powerful way to filter your records.
+
La plupart du temps, la propriété SQL du TSQLQuery contiendra  l'ordre SELECT qui ne demandera pas de paramètre. Pourtant, il peut les contenir. Ceci permet une façon simple et puissante pour filtrer vos enregistrements.
  
Parameters have the following advantages:
+
Les paramères ont les avantages suivants :
* no need to format your data as SQL text, date etc arguments (i.e. no need to remember how to format a date for MySQL, which might differ from the Firebird implementation; no need to escape text data like ''O'Malley's "SQL Horror"''
+
* Nul besoin de formatter vos données comme texte de requête SQL, les arguments date (i.e. pas besoin de savoir comment formatter une date dans MySQL, qui peut différer de l'implémentation de Firebird ; pas besoin d'échapper la donnée texte comme ''O'Malley's "SQL Horror"''.
* possibly increased performance
+
* Performance potentiellement augmentée (requête préparée).
* protection against SQL injection attacks
+
* Protection contre les injections SQL.
  
The use of parameters may help performance of the database. Most databases support prepared statements, which means that the statement is prepared and cached in the database. A prepared statement can be used more than once and doesn't require parsing and query planning every time it is used, only the parameters are changed each time it is used.
+
L'utilisation de paramètres peut aider à la performance d'une base de données. La plupart des SGBDR supportent les ordres préparés, ce qui signifie que l'ordre est péparé et mis en cache dans la base de données. Un ordre préparé peut être utilisé plus d'une fois et ne nécessite plus d'analyse ni de détermination du plan à chaque fois qu'il est utilisé, seuls les paramètres sont changés chaque fois qu'il est utilisé.
In situations where the same statement is used a large number of times (where only the values of the parameters differ), prepared statements can help performance considerably.
+
Dans les cas où le même ordre est utilisé un grand nombre de fois (où seules les valeurs de paramètres changent), les ordres préparés peuvent grandement améliorer la performance. En outre, les attaques par injection SQL peuvent être atténués par l'emploi de paramètres.
Additionally, SQL injection attacks can be mitigated by use of parameters.
 
  
The ''InsertSQL'', ''UpdateSQL'' and ''DeleteSQL'' properties have predefined parameters for current and old field values. However, the ''SQL'' property does not. You can create your own parameters in the ''Params'' property.
+
Les propriétés ''InsertSQL'', ''UpdateSQL'' et ''DeleteSQL'' ont des paramètres prédéfinis pour les valeurs de champ anciennes et actuelles mais pas dans la propriété SQL (seules les valeurs actuelles sont permises). Vous pouvez créer vos propres paramètres dans la propriété ''Params''.
  
 
====Exemple de requête Select====
 
====Exemple de requête Select====
This example shows how to select data using parameters. It also demonstrates using aliases (... AS ...) in SQL.
+
Cet exemple montre comment sélectionner des données en utilisant des paramètres. Il montre aussi l'utilisation d'alias de nom de champ (... AS ...) en SQL.
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
  sql_temp.sql.text := 'SELECT id AS ''ID'', UserName AS ''User'', InstEmail AS ''e-mail'' FROM fpdev WHERE InstEmail=:emailsearch;'
 
  sql_temp.sql.text := 'SELECT id AS ''ID'', UserName AS ''User'', InstEmail AS ''e-mail'' FROM fpdev WHERE InstEmail=:emailsearch;'
 
  ...
 
  ...
Line 169: Line 170:
  
 
====Exemple de requête Insert====
 
====Exemple de requête Insert====
This example shows how to insert a new record into the table using parameters:
+
Cet exemple montre comment insérer un nouvel enregistrement dans la table en employant des paramètres :
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
  sql_temp.sql.text := 'insert into PRODUCTS (ITEMNR,DESCRIPTION) values (:OURITEMNR,:OURDESCRIPTION)'
 
  sql_temp.sql.text := 'insert into PRODUCTS (ITEMNR,DESCRIPTION) values (:OURITEMNR,:OURDESCRIPTION)'
 
  ...
 
  ...
Line 179: Line 180:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Another way of doing this is something like:
+
Une autre façon de faire est aussi :
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
  tsqlquery1.appendrecord(['XXXX', 'description'])  
 
  tsqlquery1.appendrecord(['XXXX', 'description'])  
 
  tsqltransaction1.commit; //or commitretaining
 
  tsqltransaction1.commit; //or commitretaining
Line 186: Line 187:
  
 
===Requête avec la fonction Format===
 
===Requête avec la fonction Format===
Using parameterized queries is the preferred approach, but in some situations the format function can be an alternative. (see warning below). For example, parameters can't be used when you execute statements with the connection's ExecuteDirect procedure (''of course, you can just as well use a query to run the SQL statement in question''). Then this can come in handy:
+
L'emploi de rerquêtes paramétrées est l'approche à privilégier, mais dans certaines situations, la fonction Format peut être une alternative (voir l'avertissement plus bas). Par exemple, des paramètres ne peuvent pas utilisés quand vous exécutez des ordres avec la procédure ExecuteDirect de la connection (''Bien sûr, vous pouvez tout aussi bien utiliser une requête pour exécuter l'instruction SQL en question''); Ensuite, cela peut être pratique :
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
procedure InsertRecord
 
procedure InsertRecord
 
var
 
var
Line 201: Line 202:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
The values of the variables can change and the query values will change with them, just as with parameterized queries.
+
Les valeurs des variables peuvent changer et les valeurs des requêtes vont changer avec elles, tout comme avec les requêtes paramétrées.
  
The parameter %d is used for integers, %s for strings; etc. See the documentation on the Format function for details.
+
Le paramètre %d est utilisé pour les entiers, %s pour les chaînes ; etc. Voir la documentation de la fonction Format pour les détails.
  
{{Warning|Be aware that you may run into issues with text containing ' and dates using this technique!}}
+
{{Warning|Soyez conscient que vous pouvez rencontrer des problèmes avec du texte contenant ' et les dates en utilisant cette technique !}}
  
 
== Exécution de votre propre SQL et obtention de métadonnée ==
 
== Exécution de votre propre SQL et obtention de métadonnée ==
If you want to just check some SQL statements, troubleshoot, or get metadata (e.g. list of tables) from the database, you can do so from within the IDE.
+
Si voulez juste contrôler certains ordres SQL, dépanner ou obtenir de la métadonnée (p.ex. la liste des tables) de la base de données, vous pouvez faire ainsi dans l'EDI.
In your program, with your T*Connection, transaction, query object etc set up at design-time, go into the SQL property for the query object, then click the ... button.
+
Dans votre programme, avec votre objet TConnection, Transaction, objet requête etc. en conception, aller dans la propriété SQL de l'objet TQuery et cliquer sur le bouton '...'.
  
You'll see a window with SQL code, and you can run some statements like
+
Vous verrez une fenêtre avec le code SQL et vous pouvez exécuter des ordre comme cela :
 
<syntaxhighlight lang="sql">
 
<syntaxhighlight lang="sql">
 
SELECT * FROM EMPLOYEE
 
SELECT * FROM EMPLOYEE
 
</syntaxhighlight>
 
</syntaxhighlight>
by pressing the play icon:
 
  
 +
en pressant sur l'icône 'play' :
 
[[Image:runsqlstatements.png]]
 
[[Image:runsqlstatements.png]]
  
You can also get metadata: table names, column names etc (if the sqldb connector supports it but most of them do nowadays):
+
Vous pouvez aussi obtenir de la métadonnée : noms de table, noms de colonne etc. (si le connecteur SQLDB le support, mais la plupart d'entre eux le font actuellement) :
 
+
[[Image:sqlquerymetadata.png]]<br/>
[[Image:sqlquerymetadata.png]]
+
(Voir aussi : [[Database metadata#Lazarus Outil de métadonnée de TSQLQuery]])
 
 
(See also: [[Database metadata#Lazarus TSQLQuery metadata tool]])
 
  
 
== Dépannage  ==
 
== Dépannage  ==
Line 230: Line 229:
  
 
=== Performance pauvre ===
 
=== Performance pauvre ===
* Make sure your database queries are optimized (use proper indexes etc). Use your database tools (e.g. providing query plans) for this.
+
* Assurez-vous que vos requêtes sont optimisées (utiliser des index adaptés). Utilisez vos outils de base de données (p.ex. ceux fournissant les plans d'exécution des requêtes) pour cela.
* See [[#Out of memory errors]] below for possible performance improvements when moving forward through an SQLQuery.
+
* Voir [[#Erreurs de dépassement de mémoire]] ci-dessous pour des amélioration de performance possibles quand vous avancerez avec le SQLQuery.
  
 
=== Messages d'erreur ===
 
=== Messages d'erreur ===
 
=== Erreurs de dépassement de mémoire ===
 
=== Erreurs de dépassement de mémoire ===
TSQLQuery is a descendant of BufDataset, a dataset that buffers the data it receives into memory. If you retrieve a lot of records (e.g. when looping through them for an export), your heap memory may become full (with records you already looped through) and you will get out of memory errors.
+
TSQLQuery est un descendant de TBufDataSet, un DaatSet qui tamponne en mémoire les données qu'il recçoit. Si vous récupérez beaucoup d'enregistrements (p.ex. en les parcourant pour un export), votre mémoire de tas (''heap'') peut se remplir (avec les enregistrements que vous avez déjà parcouru) et vous aurez des erreurs de mémoire.
  
Although this situation has improved in the FPC development version, a workaround is to tell bufdataset to discard the records you have already read by setting the Unidirectional property to true before opening the query:
+
Bien que cette situation se soit améliorée dans la version de développement FPC, une solution de contournement est d'indiquer au TBufDataSet de jeter les enregistrements que vous avez déjà lus en définissant la propriété unidirectionnelle sur 'True' avant d'ouvrir la requête:
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
MySQLQuery.UniDirectional:=True;
 
MySQLQuery.UniDirectional:=True;
 
</syntaxhighlight>
 
</syntaxhighlight>
 
+
Ceci peut améliorer la performance.
This may also improve performance.
 
  
 
=== Dataset en lecture seule ===
 
=== Dataset en lecture seule ===
This may happen if you specify a query that you know is updatable but FPC doesn't.
+
Ceci peut survenir quand vous spécifier une requête que vous savez modifiable mais que FPC ignore.
 
+
Exemple:
Example:
 
 
<syntaxhighlight lang="sql">
 
<syntaxhighlight lang="sql">
 
select p.dob, p.surname, p.sex from people p;
 
select p.dob, p.surname, p.sex from people p;
 
</syntaxhighlight>
 
</syntaxhighlight>
The SQL parser in FPC is quite simplistic and when it finds a comma or space in the FROM part it considers multiple tables are involved and sets the dataset to read only. To its defense, aliasing tables is usually not done when only one table is involved.
+
L'analyseur SQL dans FPC est plutôt simpliste et quand il trouve une virgule ou une espace dans la partie FROM, il considère que plusieurs tables entrent en jeu et place le DataSet en lecture seule. Pour sa défense, la définition d'un alias de table est rarement employé pour une table unique.
Solution: rewrite the query or specify your own <code>InsertSQL</code>, <code>UpdateSQL</code> and <code>DeleteSQL</code>.
+
 
 +
Solution : réécrivez la requête ou spécifiez votre propre code <code>InsertSQL</code>, <code>UpdateSQL</code> et <code>DeleteSQL</code>.

Latest revision as of 12:38, 4 March 2020

English (en) español (es) français (fr) 日本語 (ja) polski (pl) 中文(中国大陆)‎ (zh_CN)

Portail de la base de données

Références:

Tutoriels/articles pratiques :

Bases de données

Advantage - MySQL - MSSQL - Postgres - Interbase - Firebird - Oracle - ODBC - Paradox - SQLite - dBASE - MS Access - Zeos

Général

TSQLQuery est un objet qui peut incarner un ensemble de données provenant d'une base de données (SGDBR qui utilise SQL, tel que FireBird, MS SQL Server, Oracle...). En utilisant un ordre SQL SELECT dans la propriété SQL du TSQLQuery, vous pouvez déterminer quelle donnée récupérer de la base de données vers le DataSet. Quand le DataSet est modifié par le programme (ou l'utilisateur), les modifications peuvent être soumises en retour à la base de données.

Un TSQLQuery peut aussi être utilisé pour modifie directement la donnée : si vous spécifiez l'ordre SQL INSERT, UPDATE, DELETE etc. désiré dans la propriété SQL et appelez la méthode ExecSQL de TSQLQuery, l'objet requête enverra le SQL vers la base de données sans retourner de résultat.

Outre son utilisation dans FPC, Lazarus fournit également un composant: TSQLQuery tsqlquery.png.

Documentation officielle

Voir Documentation TSQLQuery.

Beaucoup de documentation sensible au contexte est maintenant disponible dans Lazarus. Malheureusement, TSQLQuery n'apparaît pas dans l'index de l'aide de Lazarus 1.0. SI vous placez votre curseur sur les méthodes et propriétés de TSQLQuery, essayer de presser sur F1 pour voir si le code est documenté ; p.ex. cela marchera :

var
Q: TSQLQuery
...
  Q.Open; //<--- place cursor on Open and press F1

Contrôles communément utilisés

Le DataSet retourné par TSQLQuery peut être commodément visualisé avec une instance de TDBGrid, mais ce n'est pas très adapté pour éditer les données dans des champs et cellules individuels. Pour cet usage, vous avez besoin de placer quelques contrôles champs simples sensibles aux données tels que TDBEdit sur votre fiche, et définir la propriété DataSource avec la DataSource à utiliser. La propriété DataField devrait être aussi définie avec un nom de champ du DataSet (p.ex. 'IDENTITY') ou une expression qui retourne une chaîne convenable.

L'ajout d'une barre d'outils TDBNavigator rend la navigation très facile entre les enregistrements, et pour sélectionner l'enregistrement à mettre à jour. Quand un enregistrement est sélectionné par la barre d'outil ou en bougeant la souris à travers la grille de données, la donnée pour les colonne et ligne pertinentes apparaît dans la zone d'édition TDBEdit et si le bouton Edit est cliqué le contenu de la zone d'édition peut être modifiée. Cliquer sur le bouton 'Post' confirme la mise à jour, le clic sur le bouton 'Cancel' annule les mises à jour.

En général, le processus est le suivant :

  1. Placer un composant TSQLQuery sur la fiche/le module de données, et définissez les propriétés Database, Transaction et SQL.
  2. Placer un composant TDataSource, et définissez la propriété DataSet dans l'instance TSQLQuery.
  3. Placer un composant TDBGrid sur la fiche et définissez sa propriété DataSource par l'instance de TDataSource.
  4. Eventuellement, placer une instance de TDBNavigator sur la fiche et définissez sa propriété Datasource par l'instance de TDataSource.

Après ceci, la propriété Active peut être mise à 'True', et il devient possible de voir les données récupérées par la requête. (À condition que les composants TSQLConnection et TSQLTransaction soient actifs)

Mise à jour de données

Si vous voulez être en mesure de supprimer ou de modifier des enregistrements, votre table devrait soit :

  1. contenir une colonne PRIMARY KEY (clé primaire)
  2. ou avoir un ensemble de champs qui détermine de manière unique l'enregistrement. Normalement, ils devraient constituer un index unique. Cela n'est pas obligatoire, mais accélérera beaucoup les requêtes.

S'il n'y a pas de champ de clé primaire ou aucun ensemble champ qui identifie votre enregsitrement, alors une clé primaire devrait être ajoutée. Ceci est fait de préférence quand la structure de la table est conçue, au moment du CREATE, mais peut être ajouté plus tard.

Par exemple, le code exemple suivant dans votre client MySQL ajoutera un index unique à votre table :

alter table testrig 
add column autoid int 
primary key auto_increment;

L'ajout de ce champ ne fera pas de mal et permettra à vos applications de mettre à jour le champ.

Mise à jour en cache

Le composant TSQLQuery met en cache toutes les modifications. Autrement dit, les mises à jour ne sont pas envoyées immédiatement à la base de données, mais sont conservées en mémoire jusqu'à ce que la méthode ApplyUpdates soit appelée. A ce stade, les mises à jour seront transformées en ordre SQL de mise à jour et seront appliquées à la base de données. Si vous n'appelez pas ApplyUpdates, la base de données ne sera pas mise à jour avec les modifications locales.

Champs clé primaire

Lors de la mise à jour des enregistrements, TSQLQuery doit savoir quels sont les champs constituant la clé primaire qui peuvent être utilisés pour mettre à jour l'enregistrement et quels sont les champs qui doivent être mis à jour : à partir de ces informations, il construit un ordre SQL UPDATE, INSERT ou DELETE.

La construction de l'ordre SQL est contrôlée par la propriété UsePrimaryKeyAsKey du composant TSQLQuery et les propriétés ProviderFlags de ses composants TField.

La propriété Providerflags est un ensemble de 3 drapeaux :

pfInkey
Le champ fait partie de la clé primaire.
pfInWhere
Le champ devrait être utilisé dans la clause WHERE des ordres SQL.
pfInUpdate
Ce champ devrait être inclus dans les mises à jour ou insertions.

Par défaut, ProviderFlags ne contient que pfInUpdate.

Si votre table a une clé primaire (comme décrit au dessus) alors vous n'aurez qu'à mettre la propriété UsePrimaryKeyAsKey à True et tout sera fait pour vous. Ceci mettra le drapeau pfInkey pour les champs de clé primaire.

Si la table n'a pas d'index de clé primaire, mais possède des champs qui peuvent identifier de manière unique l'enregistrement, alors vous pouvez inclure l'option pfInKey dans la propriété ProviderFlags de tous les champs qui déterminent uniquement cet enregistrement.

La propriété UpdateMode déterminera alors quels champs seront utilisés dans la clause WHERE :

upWhereKeyOnly
Quand TSQLQuery a besoin de construire la clause WHERE pour une mise à jour, il collectera tous les champs qui ont le drapeau pfInKey dans la propriété ensemble ProviderFlags, et utilisera les valeurs pour construire une clause WHERE qui déterminera l'enregistrement à mettre à jour -- normalement cela est nécessaire uniquement pour un ordre UPDATE ou DELETE.
upWhereChanged
En plus des champs qui ont le drapeau pfInKey dans la propriété ProviderFlags, tous les champs qui ont aussi le drapeau pfInWhere et qui ont été changés seront aussi inclus dans la clause WHERE.
upWhereAll
En plus des champs qui ont le drapeau pfInKey dans la propriété ProviderFlags, tous les champs qui ont aussi le drapeau pfInWhere, seront aussi ajoutés dans la clause WHERE.clause.

Contrôler la mise à jour

Il est possible de spécifier quels champs devront être mis à jour, comme indiqué ci-dessus : seuls les champs qui ont pfInUpdate dans leur propriété ProviderFlags seront inclus dans les ordres SQL INSERT ou UPDATE. Par défaut, pfInUpdate est toujours inclus dans la propriété ProviderFlags.

Personnalisation du SQL dans le TSQLQuery

Normalement TSQLQuery utilisera des ordres SQL génériques basés sur les propriétés discutées ci-dessus. Pourtant, le §SQL générique produit par sqldb peut ne pas être correct dans votre cas. TSQLQuery vous permet de personnaliser les ordres SQL utilisés pour des actions divserses, pour mieux s'accorder avec votre cas de mise à jour. Pour cela, vous devez les propriétés SQL, InsertSQL, UpdateSQL et DeleteSQL.

Toutes ces propriétés sont de type TStringList, une liste de chaînes, qui acceptent du SQL sur plusieurs lignes. Toutes ces propriétés viennent avec un éditeur de propriété dans l'EDI. Dans l'EDI, sélectionnez la propriété et ouvrez l'éditeur en cliquant que le bouton 'points de suspension'. Dans cet éditeur, (TSQLQuery metadata tool), vous pourrez aussi retrouver de l'information sur la table etc.

Dans le code, utilisez par exemple InsertSQL.Text ou InsertSQL.Add() pour définir les lignes des ordres SQL. Un ordre peut s'étendre sur plusieurs lignes et se termine par un point-virgule.

Aussi, les quatre propriétés acceptent des paramètres expliqués plus bas.

SQL - Personnalisation SQL de base

La propriété SQL est normalement utilisée pour aller chercher les données dans la base de données. Le SQL générique est SELECT * FROM fpdevfpdev est la table définie dans la base de données.

Le DataSet retourné par l'ordre SQL générique sera sous la forme de brouillon. Si vous le montrez dans un DBGrid, l'ordre des enregistrements semblera aléatoire, l'ordre des colonnes ne sera pas celui que vous atttendez, le nom des colonne sera techniquement correct mais pas orienté utilisateur. En utilisant le SQL personnalisé, vous pouvez améliorer ceci.

Pour une table appelée fpdev avec des colonnes id, UserName et InstEmail, vous pouvez faire quelque chose comme ceci :

SELECT id AS 'ID', UserName AS 'User', InstEmail AS 'e-mail' FROM fpdev ORDER BY id;

Le DataSet qui résulte de cette requête utilise les noms de champ donnés dans la requête (ID, User et e-mail), l'ordre des colonnes est donné par la requête et les enregistrements sont triés selon leur id.

InsertSQL, UpdateSQL et DeleteSQL - Utilisation basique des paramètres

Quand vous affectez une requête SELECT dans la propriété SQL du SQLQuery, celui-ci sait comment récupérer les données depuis la base de données. Cependant, en utilisant des contrôles liés aux données tels qu'un DBGrid, SQLQuery devra savoir aussi comment insérer, mettre à jour ou supprimer des lignes dans la base de données selon les actions de l'utilisateur.

En vue d'accélerer le développement, SQLQuery peut essayer de déduire les ordres SQL requis. Si la propriété SQL est définie et que la propriété ParseSQL est True (ce qu'elle est par défaut), SQLQuery tentera de générer ces ordres en analysant la propriété SQL. SQLDB stocke ces ordres dans les propriétés InsertSQL, UpdateSQL et DeleteSQL.

Pourtant, parfois les ordres générés ne marcheront pas (p.ex. en insérant dans une table avec clé primaire auto-incrémentée) ou sera très lent. Au besoin, vous pouvez affecter manuellement ces propriétés.

Les ordres dans les proriétés InsertSQL, UpdateSQL et DeleteSQL acceptent des paramètres qui représentent des champs du DataSet. Les règles suivantes s'appliquent :

  • Les noms de paramètres doivent être exactement identiques aux noms de champs utilisés dans le DataSet, les noms de champ dans le DataSet pouvant différer des noms de colonne dans la table, selon l'ordre SQL SELECT (voir ci-dessus).
  • Tout comme les paramètres dans d'autres requêtes SQLDB, les noms de paramètres doivent être écrits précédés d'un signe deux-points.
  • Pour l'utilisation dans les ordres update/delete, OLD_ (strictement en majuscules, au moins dans Lazarus v. 1.0) précède le nom de champ du DataSet pour obtenir la valeur de l'enregistrement avant modification au lieu de la nouvelle valeur.

Si vous avez une table appelée fpdev et des colonnes id, UserName et InstEmail, liés à un DataSet avec des champs ID, User et e-mail (voir l'exemple dans l'ordre SELECT), vous pourrez écrire cette requête InsertSQL :

INSERT INTO fpdev(id, UserName, InstEmail) VALUES(:ID,:User,:e-mail);

Cet ordre insérera les valeurs de ID, User et e-mail de l'enregistrement courant du DataSet dans les champs respàectifs de la table fpdev.

Cet exemple d'ordre est actuellement plus ou moins celui que SQLDB auto-généréra lui-même. L'ordre donné peut provoquer des erreurs quand le champ id est un champ auto-incrémenté d'une clé unique. Les différents SGBDR résolvent ce problème de différentes façons. P. ex., le code suivant tourne dans MySQL.

INSERT INTO fpdev(id, UserName, InstEmail) VALUES(0,:User,:e-mail)
 ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID();

L'ordre du dessus tente d'insérer un nouvel enregistrement en utilisant 0 (zéro) pour la colonne id. Si zéro est déjà utilisé comme clé, alors un doublon est détecté et le champ id est mis à jour pour utiliser le dernier id inséré. Eh bien, en fait un id incrémenté de un que le dernier utilisé.

Pour FireBird, si vous émulez les clés auto-incrémentées [1], quelque chose comme ceci devrait fonctionner :

INSERT INTO fpdev(UserName, InstEmail) VALUES(:User,:e-mail);(

Cet ordre insère tout sauf la clé primaire et laisse le trigger FireBird Before Insert utiliser un générateur/une séquence pour insérer la valeur de la colonne id pour vous.

Pour un ordre INSERT vous pouvez vouloir utiliser les valeurs courantes des champs de l'enregistrement sélectionné. Pour les ordres UPDATE, vous voudrez utiliser les valeurs de champ telles qu'elles étaient avant édition dans la clause WHERE. Comme indiqué plus haut, les valeurs de champ avant édition doivent être écrites avec le nom de champ précédé par OLD_ (en majuscules strictement, au moins dans Lazarus v. 1.0). Par exemple, cette requête :

UPDATE fpdev SET UserName=:User, InstEmail=:e-mail WHERE UserName=:OLD_User;

mettra à jour les colonnes UserName et InstEmail dans tous les enregistrements où User vaut l'ancienne valeur de User.

Nous laissons en exercice au lecteur l'utilisation des valeurs courantes des champs et des anciennes valeurs dans les ordres DELETE.

Voir aussi la documentation officielle :

Paramètres dans TSQLQuery.SQL

La plupart du temps, la propriété SQL du TSQLQuery contiendra l'ordre SELECT qui ne demandera pas de paramètre. Pourtant, il peut les contenir. Ceci permet une façon simple et puissante pour filtrer vos enregistrements.

Les paramères ont les avantages suivants :

  • Nul besoin de formatter vos données comme texte de requête SQL, les arguments date (i.e. pas besoin de savoir comment formatter une date dans MySQL, qui peut différer de l'implémentation de Firebird ; pas besoin d'échapper la donnée texte comme O'Malley's "SQL Horror".
  • Performance potentiellement augmentée (requête préparée).
  • Protection contre les injections SQL.

L'utilisation de paramètres peut aider à la performance d'une base de données. La plupart des SGBDR supportent les ordres préparés, ce qui signifie que l'ordre est péparé et mis en cache dans la base de données. Un ordre préparé peut être utilisé plus d'une fois et ne nécessite plus d'analyse ni de détermination du plan à chaque fois qu'il est utilisé, seuls les paramètres sont changés chaque fois qu'il est utilisé. Dans les cas où le même ordre est utilisé un grand nombre de fois (où seules les valeurs de paramètres changent), les ordres préparés peuvent grandement améliorer la performance. En outre, les attaques par injection SQL peuvent être atténués par l'emploi de paramètres.

Les propriétés InsertSQL, UpdateSQL et DeleteSQL ont des paramètres prédéfinis pour les valeurs de champ anciennes et actuelles mais pas dans la propriété SQL (seules les valeurs actuelles sont permises). Vous pouvez créer vos propres paramètres dans la propriété Params.

Exemple de requête Select

Cet exemple montre comment sélectionner des données en utilisant des paramètres. Il montre aussi l'utilisation d'alias de nom de champ (... AS ...) en SQL.

 sql_temp.sql.text := 'SELECT id AS ''ID'', UserName AS ''User'', InstEmail AS ''e-mail'' FROM fpdev WHERE InstEmail=:emailsearch;'
 ...
 //This will create a parameter called emailsearch.

 //If we want to, we can explicitly set what kind of parameter it is... which might only be necessary if FPC guesses wrong:
 //sql_temp.params.parambyname('emailsearch').datatype:=ftWideString
 
 //We can now fill in the parameter value:
 sql_temp.params.parambyname('emailsearch').asstring := 'mvancanneyt@freepascal.org';
 ...
 //Then use your regular way to retrieve data,
 //optionally change the parameter value & run it again

Exemple de requête Insert

Cet exemple montre comment insérer un nouvel enregistrement dans la table en employant des paramètres :

 sql_temp.sql.text := 'insert into PRODUCTS (ITEMNR,DESCRIPTION) values (:OURITEMNR,:OURDESCRIPTION)'
 ...
 sql_temp.Params.ParamByName('OURITEMNR').AsString := 'XXXX';
 sql_temp.Params.ParamByName('OURDESCRIPTION').AsString := 'description';
 sql_temp.ExecSQL;
 SQLTransaction1.Commit; //or possibly CommitRetaining, depending on how your application is set up

Une autre façon de faire est aussi :

 tsqlquery1.appendrecord(['XXXX', 'description']) 
 tsqltransaction1.commit; //or commitretaining

Requête avec la fonction Format

L'emploi de rerquêtes paramétrées est l'approche à privilégier, mais dans certaines situations, la fonction Format peut être une alternative (voir l'avertissement plus bas). Par exemple, des paramètres ne peuvent pas utilisés quand vous exécutez des ordres avec la procédure ExecuteDirect de la connection (Bien sûr, vous pouvez tout aussi bien utiliser une requête pour exécuter l'instruction SQL en question); Ensuite, cela peut être pratique :

procedure InsertRecord
var
  aSQLText: string;
  aSQLCommand: string;
begin
  aSQLText:= 'INSERT INTO products(item_no, description) VALUES(%d, %s)';
  aSQLCommand:= Format(aSQLText, [strtoint(Edit1.Text), Edit2.Text]);
  aConnection.ExecuteDirect(aSQLCommand);
  aTransaction.Commit;
end;

Les valeurs des variables peuvent changer et les valeurs des requêtes vont changer avec elles, tout comme avec les requêtes paramétrées.

Le paramètre %d est utilisé pour les entiers, %s pour les chaînes ; etc. Voir la documentation de la fonction Format pour les détails.

Warning-icon.png

Avertissement: Soyez conscient que vous pouvez rencontrer des problèmes avec du texte contenant ' et les dates en utilisant cette technique !

Exécution de votre propre SQL et obtention de métadonnée

Si voulez juste contrôler certains ordres SQL, dépanner ou obtenir de la métadonnée (p.ex. la liste des tables) de la base de données, vous pouvez faire ainsi dans l'EDI. Dans votre programme, avec votre objet TConnection, Transaction, objet requête etc. en conception, aller dans la propriété SQL de l'objet TQuery et cliquer sur le bouton '...'.

Vous verrez une fenêtre avec le code SQL et vous pouvez exécuter des ordre comme cela :

SELECT * FROM EMPLOYEE

en pressant sur l'icône 'play' : runsqlstatements.png

Vous pouvez aussi obtenir de la métadonnée : noms de table, noms de colonne etc. (si le connecteur SQLDB le support, mais la plupart d'entre eux le font actuellement) : sqlquerymetadata.png
(Voir aussi : Database metadata#Lazarus Outil de métadonnée de TSQLQuery)

Dépannage

Journalisation

Voir ici : SqlDBHowto/fr#Dépannage : Journalisation par TSQLConnection pour plus de détails.

Performance pauvre

  • Assurez-vous que vos requêtes sont optimisées (utiliser des index adaptés). Utilisez vos outils de base de données (p.ex. ceux fournissant les plans d'exécution des requêtes) pour cela.
  • Voir #Erreurs de dépassement de mémoire ci-dessous pour des amélioration de performance possibles quand vous avancerez avec le SQLQuery.

Messages d'erreur

Erreurs de dépassement de mémoire

TSQLQuery est un descendant de TBufDataSet, un DaatSet qui tamponne en mémoire les données qu'il recçoit. Si vous récupérez beaucoup d'enregistrements (p.ex. en les parcourant pour un export), votre mémoire de tas (heap) peut se remplir (avec les enregistrements que vous avez déjà parcouru) et vous aurez des erreurs de mémoire.

Bien que cette situation se soit améliorée dans la version de développement FPC, une solution de contournement est d'indiquer au TBufDataSet de jeter les enregistrements que vous avez déjà lus en définissant la propriété unidirectionnelle sur 'True' avant d'ouvrir la requête:

MySQLQuery.UniDirectional:=True;

Ceci peut améliorer la performance.

Dataset en lecture seule

Ceci peut survenir quand vous spécifier une requête que vous savez modifiable mais que FPC ignore. Exemple:

select p.dob, p.surname, p.sex from people p;

L'analyseur SQL dans FPC est plutôt simpliste et quand il trouve une virgule ou une espace dans la partie FROM, il considère que plusieurs tables entrent en jeu et place le DataSet en lecture seule. Pour sa défense, la définition d'un alias de table est rarement employé pour une table unique.

Solution : réécrivez la requête ou spécifiez votre propre code InsertSQL, UpdateSQL et DeleteSQL.