Préambule▲
Les requêtes, aussi appelées "extractions", sont des mécanismes d'interrogations des données. Elles peuvent porter sur une ou plusieurs tables et produisent toujours une table réponse qui peut se traduire par :
- une table vide : aucune réponse ne satisfait les conditions de l'interrogation (cela se produit souvent chez les débutants qui posent parfois des questions illogiques sans s'en apercevoir comme "je désire obtenir la liste des clients qui sont domiciliés à PARIS et à LYON", car notre "et" habituel cache souvent un OU logique…) ;
- une table à une seule ligne et un seul champ : résultat d'une interrogation portant sur un calcul (montant moyen des commandes de l'année par exemple) ;
- une table d'une seule ligne avec plusieurs champs : recherche d'une occurrence particulière (trouver la commande la plus importante de l'année) ;
- une table de plusieurs lignes et plusieurs champs : recherche de caractéristiques communes à plusieurs occurrences (recherche des coordonnées des clients dont les commandes portent sur telle ou telle référence d'article pour les informer d'une malfaçon).
Voici quelques exemples de requêtes possibles :
- rechercher les clients domiciliés à Paris ;
- rechercher les commandes passées au premier trimestre 1994 ;
- rechercher les articles de moins de 3000 F ;
- calculer le montant moyen des commandes de l'année ;
- rechercher les coordonnées des clients ayant effectué des commandes portant sur plus de 20 000 F ;
- rechercher les coordonnées des clients domiciliés à Paris et ayant fait des commandes de plus de 10 000 F lors du mois d'août ;
- rechercher les clients dont le montant cumulé des commandes de l'année ont dépassé de 50 % le montant moyen des commandes de cette même année, mais n'ayant pas commandé une table ;
- etc.
I. Requêtes monotabulaires▲
Les requêtes monotabulaires sont des requêtes interrogeant les données d'une seule table.
Par exemple, la recherche des clients domiciliés à PARIS ne nécessite que l'interrogation de la table des clients. C'est donc une requête monotabulaire.
I-A. Le système QBE▲
QBE est un système mis au point concurremment à SQL, mais qui n'a connu de succès que grâce à l'introduction de Paradox.
QBE signifie Query By Exemple, c'est-à-dire "interrogation par l'exemple". Il a été inventé par Moshe Zloof (IBM 1977). Il a été mis en place dès la première version de Paradox (1.0 pour DOS) en 1985.
Pour interroger une table avec ce système, il suffit de proposer une image de la réponse, et par un mécanisme d'analogie Paradox va rechercher les occurrences dans la table incriminée, qui correspondent exactement ou partiellement à la réponse.
- Exactement si l'utilisateur ne spécifie rien de plus que ce qu'il désire.
- Partiellement si l'utilisateur spécifie des opérateurs particuliers.
La simplicité de ce système de requête fait qu'à conditions équivalentes, le QBE possède une rapidité inégalée…
En 1992, Borland améliore sensiblement son moteur de requête (ODAPI) développé en partenariat avec IBM, lors de la sortie de Paradox 4.0 pour DOS. Lorsque Paradox 5.0 pour Windows sort en 1994, ce moteur est encore amélioré et devient IDAPI. C'est la première version qui permet de travailler avec du SQL. Le SQL était traduit en QBE pour être exécuté sur le moteur, mais cette version de SQL n'admettait ni les sous-requêtes, ni les jointures externes, ni les clauses HAVING et un certain nombre d'opérateurs manquaient. Avec l'arrivée de l'OS 32 bits pour Windows, Borland remanie son moteur et l'intitule BDE. Le SQL est alors interprété et exécuté directement. De nombreuses clauses supplémentaires et opérateurs divers y sont implantés. Le SQL utilisé par le BDE est conforme à la norme version 92. En comparaison Access possède un SQL basé sur la norme version 89 qui ne respecte pas certains standards, comme les jokers…
Notons que désormais tout éditeur de SGBD SQL délivre un utilitaire permettant de créer des requêtes interactivement par un système QBE équivalent, à la différence près que ce pseudo QBE doit être transformé en SQL pour pouvoir être exécuté sur le moteur du SGBD, d'où perte de temps et traduction souvent peu optimisée…
Pour créer une requête, on clique droit sur l'icône QBE, puis en sélectionnant 'Nouveau…'
S'ouvre alors la fenêtre permettant de sélectionner la table à interroger :
Après sélection de la table (CLIENT par exemple), s'ouvre la fenêtre de l'éditeur de requête :
Il s'agit d'une copie de la table à interroger dans laquelle chaque champ est pourvu d'une case à cocher. Seules les cases cochées figureront en tant que colonne de la table réponse. Il est possible de cocher toutes les cases d'un coup en cochant la case située sous le nom de la table.
I-B. Interrogation par égalité▲
Exemple n° 1 : correspondance exacte
Nous désirons sélectionner les n° des clients domiciliés à PARIS
Notez que nous avons coché les cases "check" des champs NoCli et Ville, afin de voir si le résultat correspond bien à notre attente. Nous aurions pu nous contenter d'obtenir uniquement la clef de la table (NoCli).
En fait nous avons utilisé un opérateur de requête sans le savoir : l'opérateur d'égalité. Ce que nous voulions c'était trouver les enregistrements de la table client dans lesquels la valeur du champ 'Ville' est égale à la chaîne de caractères "PARIS". Cet opérateur implicite est l'opérateur logique d'égalité. Nous aurions dû formuler notre requête de la manière suivante :
Mais cette manière de faire n'apporte pas plus de fonctionnalités. Elle rend seulement plus lisible logiquement la requête.
Si l'on veut être pointilleux à l'extrême (et nous verrons que cela est parfois absolument nécessaire), il convient d'entourer le critère de guillemets :
Nota : pour demander l'exécution de la requête et générer la table la réponse, il faut cliquer sur l'icône d'exécution :
La réponse fournie par la requête précédente donnera tous les clients qui sont domiciliés à Paris et non ceux qui sont à PARIS (en toutes majuscules), ou à Paris la Défense…
I-C. Correspondance approchée▲
Pour remédier à ce problème, Paradox fournit deux opérateurs spéciaux pour la stipulation approchée des chaînes de caractères. Ce sont les opérateurs :
.. |
n'importe quelle chaîne de caractères (y compris une chaîne vide) sans discrimination entre majuscules et minuscules |
@ |
n'importe quel caractère, mais obligatoirement un (le caractère blanc n'étant pas significatif en fin de chaîne de caractères) |
Exemple n° 2 : correspondance approchée
Nous désirons sélectionner les n° des clients domiciliés à PARIS ou Paris, etc.
On constate alors que deux nouveaux clients sont apparus qui habite Paris, mais c'est un Paris doté de minuscules que la première interrogation par correspondance exacte n'avait pas trouvée et un "Paris la défense"…
Conséquence : l'emploi des jokers permet de s'affranchir de la casse employée pour la saisie des littéraux.
NOTA : les requêtes utilisant des jokers sont moins rapides que celles qui n'en utilisent pas…
CONSEIL : dans tous les cas, il vaut mieux formater les données lors de la saisie par des contrôles de validités… Ainsi l'obligation d'écrire en majuscules dans le champ ville aurait évité l'oubli de la sélection de Paris en minuscules.
Exemple n° 3 : correspondance approchée
Nous désirons sélectionner les n° des clients qui ont été saisis un premier du mois
Mais nous aurions aussi pu utiliser le critère : 01..
En revanche si nous avions voulu obtenir tous les clients saisis au mois de janvier, alors l'emploi du caractère arobase (ampersand ou en français esperluette) aurait été nécessaire : @@/01/@@@@, parce que si nous avions employé le critère ..01.. nous aurions eu les clients saisis un premier du mois ainsi que les clients saisis en janvier et les clients saisis une année comportant la suite de caractères 01…
Exemple n° 4 : extraction avec 2 critères
Nous désirons sélectionner les n° des clients domiciliés à PARIS ou Paris, etc., et dont la forme de société est de type SARL
Exemple n° 5 : extraction de toutes les valeurs d'un champ
Nous désirons savoir quelles sont les différentes formes de société en usage chez nos clients…
Cette technique est très pratique quand on désire se rendre compte de l'homogénéité de la saisie dans un champ, et pourquoi pas, introduire une table de référence à postériori.
I-D. Les différents types de coches▲
Paradox propose différents types de coches correspondant à différentes fonctionnalités de la réponse.
La coche simple (check en anglais) permet de n'avoir dans les colonnes de la réponse que les valeurs distinctes. Dans l'exemple n° 4, nous avons obtenu 5 réponses toutes différentes. Mais il est probable qu'il y ait plusieurs clients dont la société est de forme SA ou SARL…
Pour obtenir toutes les réponses et donc les doublons, il faut cocher les champs avec la marque (checkPlus en anglais) :
Pour cela vous devez cliquer dans la case à cocher, et tout en maintenant la pression sur le bouton de la souris, descendre jusqu'à la marque adéquate.
Exemple 6 : Reformulons la requête de l'exemple 5 avec cette marque :
Nous obtenons toutes les réponses, dans l'ordre et avec des doublons.
Cette technique peut s'avérer intéressante pour compter ensuite le nombre de SA de SARL, etc.
Si l'on désire faire ordonner les valeurs de la table réponse en ordre inverse, il convient d'utiliser la marque (checkDescending) :
Exemple 7 : Reformulons la requête de l'exemple 5 avec cette marque :
Les valeurs de la réponse apparaissent dans l'ordre alphabétique décroissant.
La dernière marque est très particulière, elle permet de grouper des valeurs. Nous y reviendrons lorsque nous traiterons les requêtes permettant de manipuler des ensembles.
I-E. Les opérateurs logiques▲
Paradox utilise de nombreux opérateurs logiques pour traiter des requêtes. Les deux principaux sont les opérateurs ET et OU qui peuvent s'exprimer de deux façons, suivant que l'on se trouve à poser la condition logique dans le champ ou dans deux champs différents.
Nous avons déjà utilisé le ET implicitement lorsque nous avons traité l'exemple 4.
Exemple 8 : opérateur ET dans le même champ
Nous désirons obtenir le n° des clients qui sont domiciliés à Paris la Défense ou PARIS la Défense ou PARIS LA DEFENSE, etc.
Ici la condition ET est symbolisée par une virgule dans le champ 'Ville'.
Si nous avions formulé la requête comme suit :
Nous n'aurions pas obtenu le client n° 12…, et si nous avions demandé '..La Défense..' nous aurions aussi obtenu ceux qui sont domiciliés à Puteaux La Défense.
Exemple 9 : opérateur ET dans 3 champs différents
Nous désirons obtenir le n° des clients qui sont domiciliés à Paris et dont la forme de société est SARL et qui ont été saisis en 1994.
Exemple 10 : opérateur OU dans le même champ
Nous désirons obtenir le n° des clients qui sont domiciliés à Paris ou à Créteil.
Exemple 11 : opérateur OU dans deux champs différents
Nous désirons obtenir le n° des clients qui sont domiciliés à Créteil ou bien dont la forme de société est une SARL.
Pour spécifier une opération OU sur la globalité d'une requête, il est nécessaire de créer une ligne supplémentaire dans la requête. Pour créer une ligne supplémentaire dans l'éditeur de requête, il faut appuyer sur la touche :
(ou encore sur la touche 'Inser' comme pour insérer une ligne dans une table Paradox)
et cocher les mêmes champs dans les deux lignes de la requête.
Si les différentes lignes de coches de la requête ne sont pas homogènes, alors Paradox ne peut l'exécuter et renvoie un message d'erreur comme celui-ci :
I-F. Opérateurs de comparaisons▲
Les opérateurs de comparaisons disponibles dans l'éditeur de requête sont les opérateurs suivants :
= |
égal |
> |
supérieur à |
>= |
supérieur ou égal à |
< |
inférieur à |
<= |
inférieur ou égal à |
SAUF |
différent de |
BLANC |
champ vide (null) |
COMME |
correspondance approchée dans la chaîne de caractères |
En outre, on peut utiliser l'opérateur CEJOUR qui renvoie la date du jour.
CEJOUR |
date du jour (dans les champs date uniquement) |
Exemple 12 : utilisation des opérateurs de comparaison
Recherchons les n° des commandes dont le montant a dépassé 35 000 F
Exemple 13 : utilisation des opérateurs de comparaison avec une fourchette
Recherchons les clients qui ont commandé pour plus de 25 000 F au cours du 2e trimestre 1994.
C'est ce que l'on appelle une fourchette de valeurs.
NOTA : une fourchette de valeurs est une plage qui possède une borne supérieure et une borne inférieure de définition des valeurs. Le principe de la fourchette de valeurs est très utile dans les champs dates pour spécifier un intervalle déterminé comme un mois, un trimestre ou un semestre.
La forme en est toujours la même :
l'opérateur 'supérieur' ou 'supérieur ou égal' |
> >= |
la valeur basse de l'intervalle |
|
l'opérateur et |
, |
la valeur haute de l'intervalle |
|
l'opérateur 'inférieur' ou 'inférieur ou égal' |
< <= |
Exemple 14 : utilisation de l'opérateur CEJOUR.
L'opérateur CEJOUR permet de récupérer la date du jour (date système). Assurez-vous que votre machine est à la bonne date avant d'implanter un tel opérateur dans une requête.
Recherchons les clients qui ont commandé pour moins de 10 000 F dans les 90 derniers jours.
Exemple 15 : utilisation de l'opérateur COMME (correspondance approchée)
Recherchons les clients dont le nom de la société ressemble à MASON.
ATTENTION : l'opérateur COMME de Paradox est un soundex qui est plus axé sur la phonétique anglaise que sur la phonétique française. Des résultats parfois surprenants peuvent se produire. Ainsi la recherche avec l'opérateur comme de Paradox sur un mot comme PHOTO ne prendra jamais en compte les mots comme FOTO, car le PH n'est pas phonétiquement équivalent à la lettre F lors de l'utilisation de l'opérateur COMME de Paradox.
Exemple 16 : utilisation de l'opérateur SAUF (exclusion)
Recherchons les n° des sociétés dont la forme n'est ni une SA ni une SARL.
Exemple 17 : utilisation de l'opérateur BLANC (pas d'occurrence)
Recherchons les n° des sociétés dont aucun nom de contact n'a été saisi.
I-G. Les calculs dans les requêtes▲
Il est possible d'effectuer des requêtes permettant de calculer des valeurs ou de comparer les valeurs d'un champ a une valeur calculée.
Voici la liste des opérateurs de calculs :
+ |
addition |
- |
soustraction |
* |
multiplication |
/ |
division |
MOYENNE |
moyenne des valeurs d'un champ |
MIN |
valeur minimale d'un champ |
MAX |
valeur maximale d'un champ |
NOMBRE |
nombre d'occurrences d'une valeur |
SOMME |
somme des valeurs d'un champ |
Les opérateurs décrits ci-dessus peuvent être combinés avec les opérateurs et mots clefs suivants :
() |
hiérarchisation des opérateurs mathématiques |
TOUTES |
calcul de toutes les valeurs d'un groupe (combiné à NOMBRE) |
UNIQUE |
calcul des valeurs uniques d'un groupe (combiné à SOMME, MOYENNE) |
EN |
donne un nom de champ à la valeur calculée |
Enfin, pour produire un résultat de calcul, il faut utiliser l'opérateur spécial CALC.
CALC |
indique une formule de calcul |
Nous avons déjà utilisé une expression calculée lors de l'exemple n° 14 :
NOTA : lorsque l'expression calculée doit renvoyer une valeur (création d'une nouvelle colonne dans la table réponse), il faut toujours la faire précéder du mot clef CALC, sinon c'est le résultat du calcul qui sert de valeur comparative. Le nom de la colonne peut être spécifié à l'aide de l'opérateur En sinon Paradox assigne un nom par défaut.
Exemple 18 : utilisation de l'opérateur MOYENNE
Calcul de la valeur moyenne d'une commande.
Exemple 19 : utilisation de l'opérateur SOMME
Calcul du montant total des commandes du mois d'août 1994.
Exemple 20 : utilisation des opérateurs MIN et MAX
Quels ont été les montants de la plus faible et de la plus forte commande du mois de février 1994 ?
Exemple 21 : utilisation de l'opérateur NOMBRE
Quel est le nombre de formes de société différentes des clients.
Il y a donc 9 formes différentes de société (SARL, SA, EURL…).
Mais cette question n'a pas beaucoup d'intérêt. Une requête qui a beaucoup plus d'intérêt consiste à demander quel est le nombre de clients pour chacune des différentes formes de société.
Exemple 22 : dénombrement d'occurrences avec NOMBRE
NOTA : le résultat de cette requête montre à quel point une saisie plus cohérente (formatage des données) aurait permis d'éviter des erreurs de comptage. En effet le système a détecté à juste titre que "sarl" et "SARL" sont deux occurrences différentes…
Attention : souvent l'opérateur NOMBRE doit porter sur la clef avec une coche dans le champ que l'on veut dénombrer.
I-H. Utilisation de valeurs exemples▲
Paradox offre la possibilité de mettre en place des valeurs exemples. Ce mécanisme est accessible par l'emploi de la touche F5. Il permet d'assigner à une variable exemple la valeur d'un champ qui peut dès lors être utilisée dans un autre champ, par exemple pour des calculs. Les valeurs exemples sont affichées en rouge dans l'éditeur QBE.
Pour mieux comprendre ce mécanisme, prenons un cas simple.
Exemple 23 : Nous voulons calculer le montant TTC de chaque commande or les différents paramètres des commandes sont disposés dans les différentes colonnes de la table. Il faut donc les reprendre des différents champs pour écrire une expression mathématique permettant de calculer le montant TTC dans l'une quelconque des colonnes de la table…
Exemple 24 : nous voulons contrôler que le montant TTC de toutes les commandes correspond bien à la somme du montant HT + la TVA
Nous voyons dans la table réponse que la commande n° 6 comporte vraisemblablement une erreur puisque le résultat est différent de zéro…
Nota : lorsque l'on appuie sur la touche F5, les caractères frappés à la suite pour donner un nom à la valeur exemple s'impriment en rouge dans l'éditeur de requête. Nous conviendrons de noter toutes les valeurs exemples de cette documentation avec un nom commençant par EX suivi d'un chiffre (il peut encore y avoir des écrans noirs et blancs ou une panne de la couleur… :-) ). Mais un nom plus approprié serait préférable…
I-I. Renommer un champ calculé▲
En utilisant le mot clef 'EN' vous pouvez renommer un champ calculé.
Exemple 25 : nous voulons connaître le montant moyen des ventes pour chaque trimestre
Ici, non seulement nous avons donné 2 noms de champs aux champs calculés, mais nous avons aussi fabriqué un champ calculé à partir d'une constante (champ trimestre).
NOTA : pour éviter la recopie fastidieuse du critère dans le champ Montant, vous pouvez utiliser la combinaison de touche CTRL+D (ditto).
II. Requêtes multitables▲
Jusqu'ici nous avons vu comment extraire des informations d'une seule et même table. Mais une base de données est un assemblage de différentes tables liées entre elles par un modèle relationnel. Aussi est-il justifié de vouloir lier différentes tables afin d'extraire des données répondant à différentes conditions exprimées à différents niveaux du modèle des données.
II-A. Lier les tables▲
Supposez que nous voulions connaître les clients qui ont effectué une commande en janvier 1994. La seule table CLIENT ne suffit plus. Il y faut introduire dans l'espace de l'éditeur de requêtes la table des commandes, et relier les deux tables.
Pour placer une seconde table dans l'éditeur de requête, il faut cliquer sur l'icône :
et sélectionner la (ou les) tables) que l'on désire obtenir.
Pour lier les tables, il convient de placer une valeur exemple permettant de faire la jointure entre une table et l'autre. Cette valeur exemple doit porter sur le ou les champs communs aux deux tables. C'est le lien.
Le moyen le plus simple pour créer un lien avec Paradox, c'est de cliquer sur l'icône :
puis pointer avec la souris les champs communs aux deux tables.
Paradox introduit par défaut des valeurs exemples qui ont pour nom 'jointure1', 'jointure2', etc. et sont marquées en rouge.
NOTA : si la jointure des tables est faite sur plusieurs champs, alors il convient de placer autant de couples de valeurs dans chacun des champs composant le lien.
Exemple 26 : (deux tables liées dans l'éditeur de requête) quel est le nom des sociétés qui ont effectué une commande en janvier 1994 ?
Exemple 27 : comment éviter que ne soit porté plusieurs fois le nom des sociétés ayant effectué plusieurs commandes dans ce même mois de janvier ?
Simple : il suffit de ne pas cocher le champ 'Date com'.
Exemple 28 : comment savoir le nombre de commandes effectué par chacune des sociétés dans cette même période ?
Simple : il suffit de calculer le nombre d'occurrences de n° de commande pour chaque société, dans la table des commandes.
Exemple 29 : (3 tables et des valeurs exemples) calculons le montant HT de chaque ligne des commandes, c'est-à-dire la quantité commandée par le montant HT de l'article.
Exemple 30 : (4 tables et des valeurs exemples) même chose, mais avec le montant TTC de chaque ligne des commandes, c'est-à-dire la quantité commandée par le montant HT de l'article par le taux de TVA
Exemple 30 bis : pour calculer le montant total de chaque commande, il faut calculer la somme par numéro de commande du champ 'Montant TTC', tout ceci se faisant directement par requête sur la table réponse :
NOTA : le langage QBE de requête ne supporte pas les requêtes imbriquées. Pour pallier cet inconvénient, il faut faire des requêtes chaînées, c'est-à-dire partir de la table réponse pour une nouvelle requête. Dans ce cas je vous invite à donner un nom spécifique à la table réponse plutôt que d'accepter le nom "REPONSE" par défaut, car la table réponse est écrasée à chaque nouvelle requête.
II-B. Jointures externes▲
Nous considérons habituellement l'univers des données dans lequel nous faisons nos requêtes, comme un univers fermé (clos).
Si nous posons la question "Est-ce que votre voiture est rouge ?" à une personne qui ne possède pas de voiture, il y a deux attitudes possibles pour que l'intéressé réponde. Soit il ne répond pas (univers clos) soit il répond non, puisqu'il n'a pas de voiture…
Soyons encore plus précis supposons que nous voulons offrir une promotion spéciale aux clients n'ayant pas commandé plus de 100 000 F au cours de l'année 1994. Nous avons besoin de lier les tables CLIENT et COMMANDE. Cependant si nous interrogeons les données à l'aide de la requête suivante :
La réponse sera :
Or les clients 14 et 15 par exemple n'ont encore jamais rien commandé !!! Ils cadrent donc bien avec cette offre promotionnelle…
Pour récupérer ces clients, il convient de faire une jointure externe en considérant que l'absence de liens entre une occurrence de la table CLIENT et des occurrences de la table COMMANDE est pertinente.
Cela se matérialise par une jointure externe, dont l'opérateur dans QBE est le caractère point d'exclamation…
Exemple 31 :
Réponse :
Là, nous venons de faire une jointure externe unilatérale dite "gauche".
Si le point d'exclamation avait été dans la jointure de la seconde table, on l'aurait appelé jointure externe unilatérale dite "droite". Ce classement droite/gauche dépend uniquement du sens de lecture habituel.
Enfin il est possible de faire une jointure externe bilatérale en plaçant le point d'exclamation dans les deux champs joints.
II-C. Utiliser un modèle relationnel prédéfini▲
Il est possible d'utiliser le modèle relationnel d'une fiche, d'un état ou même d'un fichier de modèle relationnel pour créer une requête.
Exemple 32 : nous avons établi la fiche QBE.FSL qui contient le modèle relationnel suivant :
Commençons une nouvelle requête en prenant comme fichier, non plus une table, mais le modèle relationnel d'une fiche :
Et le tour est joué :
Notez que dans ce cas, le modèle relationnel est pourvu d'une jointure externe gauche portant sur la table maître… C'est logique, sinon nous ne pourrions jamais insérer aucun nouvel enregistrement vierge pour la saisie…
III. Requêtes de mise à jour▲
Jusqu'ici nous avons vu des requêtes permettant d'extraire au sein d'une grande masse d'informations certaines informations plus précises et l'art et la manière d'effectuer des calculs à partir de données existantes. À aucun moment les données de la base n'ont été altérées par les requêtes que nous avons effectuées.
Les requêtes de mise à jour permettent de modifier les données d'une ou plusieurs bases. Notamment d'insérer de nouveaux enregistrements, d'en supprimer d'autres, de modifier des données existantes.
III-A. Insérer▲
Une requête d'insertion permet d'injecter depuis une table source des données dans une table cible. Pour cela, il faut spécifier l'opérateur INSERER dans la première colonne de la table dans laquelle on désire insérer les données. Pour mettre en place un tel opérateur, il suffit de cliquer dans cet espace :
Exemple 33 : on désire insérer les clients d'une table contact dans la table des clients
Pour pouvoir revenir en arrière (rollback), le système génère une table dite "table auxiliaire" de nom INSERE.DB dans le répertoire privé.
III-B. Supprimer▲
Le principe est le même que pour l'insertion : spécifier l'opérateur SUPPRIMER dans la première colonne de la table depuis laquelle on désire supprimer les données. Pour mettre en place un tel opérateur, il suffit de cliquer droit dans l'espace sous le nom de la table, et de sélectionner dans la liste déroulante, le mot clef SUPPRIMER.
Exemple 34 : on désire annuler la manœuvre précédente
C'est le principe du rollback d'insertion que nous venons d'utiliser…
Une table auxiliaire SUPPRIME est générée. Vous pouvez donc refaire l'insertion et par conséquent gérer un nouveau rollback concernant cette suppression.
III-C. Modifier▲
On peut modifier les données d'une table par l'emploi de l'opérateur "remplacerPar".
Exemple 35 : augmentation de 10 % de tous les articles du rayon PAPETERIE
Une table auxiliaire MODIFIE est générée.
NOTA : on peut ne pas générer les tables auxiliaires (gain de temps) en cochant le champ approprié dans le menu Propriétés/options de requête… et cocher la case Requêtes rapides (pas de table auxiliaire).
IV. Requêtes sur des ensembles de valeurs▲
Les requêtes d'ensemble permettent d'écrire en une requête des extractions qui nécessiteraient ordinairement plusieurs requêtes, voire des opérations sur les tables réponse (soustraction, addition…).
La technique consiste à définir un ensemble de valeurs (donc une première extraction) que l'on compare à la requête ordinaire.
L'exemple le plus classique consiste à rechercher les clients qui ont fait des commandes, mais n'ont jamais acheté tel ou tel article.
IV-A. Opérateur ENSEMBLE▲
Pour définir un ensemble de données, il suffit de faire porter l'opérateur ENSEMBLE en début de la ligne de requête (comme dans le cas des requêtes d'insertion ou de suppression).
Exemple 36 : nous voudrions savoir quels sont les modes de paiement qui font plus de chiffres que le mode de paiement espèces.
Nous définissons l'ensemble des modes de paiement portant sur les espèces, et définissons une valeur exemple x dans le champ montant.
Dans la seconde ligne de la table, nous faisons la comparaison et recherchons les modes de paiement pour lesquels la somme des montants est supérieure à la somme du montant des paiements en espèces.
Les requêtes d'ensemble peuvent être combinées avec les opérateurs d'ensembles RienQue, Aucun, Chacun et Identique… et combiner plusieurs tables.
Exemple 37 : on cherche les clients qui n'ont jamais commandé de Jean Levis 501… dont la référence est PANT-20501
On définit dans la table COMPOSEE l'ensemble de valeurs que l'on veut exclure. Il s'agit des numéros des commandes portant la référence PANT-20501.
On exclut cet ensemble des commandes à l'aide de la jointure et de l'opérateur "aucun", et on coche le numéro du client dans la table des commandes.
IV-B. Coche GrouperPar▲
Ce type de coche sert à définir un groupe dans un ensemble, autrement dit un sous-ensemble.
Exemple 38 : vous souhaitez trouver les clients qui n'ont fait que des commandes portant sur des vêtements
Dans la table ARTICLE on définit l'ensemble des articles du rayon VETEMENTS, et l'on effectue une jointure sur la référence avec la table COMPOSEE.
Dans la table COMPOSEE, on définit un sous-ensemble avec la coche GrouperPar, qui ne comporte que les n° des commandes pour lesquels un vêtement a été commandé, et l'on effectue cette jointure de groupe vers la table COMMANDE.
Dans la table COMMANDE, on définit un sous-ensemble avec la coche GrouperPar, qui ne comporte que les n° des clients pour lesquels la commande est comprise dans l'ensemble des commandes composées uniquement de vêtements, et l'on effectue cette jointure de groupe vers la table CLIENT.
Dans la table CLIENT on sélectionne le n° du client.
V. Quelques requêtes croustillantes… et autres problèmes▲
Vous trouverez en vrac dans ce chapitre quelques techniques pour des requêtes un peu particulières, telles que les parcours d'arbres, les requêtes d'exclusion, les requêtes permettant d'extraire les n meilleurs lignes, les requêtes sur les tables d'historisation ainsi qu'un paragraphe pour traiter les erreurs de syntaxe et d'exécution.
V-A. Requêtes réflexives et arborescences▲
Le principe même de la réflexivité se trouve en général dans les hiérarchies arborescentes d'information. L'exemple le plus simple est la notion de nomenclature d'un équipement.
Par exemple un téléviseur est composé de différents éléments, eux-mêmes composés de différents éléments, etc.
Le tout peut être mis dans une seule et même table à condition de prévoir dans cette table un champ permettant de savoir à quel ensemble un sous-ensemble appartient. Dans notre exemple, ce champ est intitulé "inclus dans". L'équipement premier est celui qui n'est inclus dans aucun autre élément. Dès lors on peut filtrer la table maître de la relation en spécifiant que le champ inclus dans doit être à blanc (filtre).
NOTA : pour inclure plusieurs fois la même table dans une fiche, un état ou une requête, il faut définir autant de nouveaux alias portant sur la même base de données que de fois la table à inclure dans le modèle relationnel.
Pour une fiche ou un état on peut, plus simplement, changer le nom de la table dans le modèle relationnel en cliquant droit sur le nom de la table au sein de la boîte de dialogue de définition du modèle relationnel.
V-A-1. Les racines de l'arbre…▲
Exemple 39 : obtenir la liste des équipements premiers, c'est-à-dire des appareils complets…
Mais comment obtenir la liste des sous-ensembles de premier niveau d'un équipement particulier ?
Lorsque l'on tente d'insérer une deuxième fois une même table dans le modèle relationnel d'une requête, l'éditeur refuse ce petit jeu. Pour pallier cela, il faut créer un alias supplémentaire pour chacun des doublons de table que l'on veut insérer dans la requête… (ici les alias ont été appelés R1, R2, R3…).
V-A-2. Les nœuds et feuilles de niveau 1▲
Exemple 40 : obtenir la liste des sous-ensembles de niveau 1 du téléviseur
V-A-3. Les nœuds et feuilles de niveau 2▲
On n’aura pas plus de problèmes pour le niveau 2, il suffit de rajouter encore une fois la table dans le modèle relationnel :
Exemple 41 : obtenir la liste des sous-ensembles de niveau 2 du téléviseur
V-A-4. Les feuilles de l'arbre▲
Exemple 42 : Obtenir la liste des éléments terminaux, c'est-à-dire des composants de base, en d'autres termes les composants atomiques qui ne peuvent ou ne sont pas eux-mêmes composés d'autres éléments…
V-A-5. Le nombre de branches partant de tous les nœuds▲
Exemple 43 : obtenir le nombre de composants inclus dans chaque composant
Ou encore en partant d'un composant précis, exemple 44 :
V-B. Requêtes d'exclusion▲
Quels sont les éléments d'un ensemble qui n'appartiennent pas à un autre ensemble ?
Tel est le type de requête d'exclusion facile à comprendre et peu évidente à mettre en œuvre.
En fait l'astuce consiste à compter le nombre de fois ou l'on trouve notre élément dans une table, mais pas dans l'autre, autrement dit lorsque l'opérateur NOMBRE renvoie la valeur zéro…
Mais attention, un tel calcul suppose une jointure externe unilatérale…
Exemple 45 : recherchons les clients qui ne figurent pas dans la table CLIENTS2
V-C. Requêtes des n meilleurs▲
Exemple 46 : comment rechercher les 3 meilleures commandes en montant quel que soit le client ?
Une première solution, assez évidente j'en conviens, consiste à rechercher le maximum, puis définir un sous-ensemble contenant toutes les données moins le maximum et en extraire le maximum, etc.
Notez qu'il s'agit là d'une procédure récursive.
Cette recherche est-elle faisable en une seule requête ? Oui :
Exemple 47 : mais plus vicieux… Est-il possible de généraliser cette requête ?
Encore oui…
Il ne vous suffit plus que de changer la valeur du paramètre (ici 5) pour calculer les n meilleurs…
NOTA : cette seconde manière d'opérer, bien que très élégante, multiplie le temps d'exécution par un facteur quadratique ! Il y a donc à préférer la première manière dans les cas où le nombre n est fixé d'avance, sinon de construire la chaîne de requête, à la première manière, à l'aide d'un programme ObjectPal…
V-D. Requêtes sur table d'historisation▲
Il arrive fréquemment que l'on doive historiser des données dans des tables d'historisation. En général une table d'historisation comporte au moins 2 champs pour la clef et les champs nécessaires à l'enregistrement de l'événement. Par exemple, voici une table d'historisation très simple concernant les événements qui surviennent à nos clients :
Dès lors un problème consiste à rechercher les derniers événements survenus à nos clients à une date donnée…
Exemple 48 : il faut procéder en deux requêtes
La première recherche les dates maximales inférieures à la date butoir (ici la date du jour obtenue par l'opérateur CEJOUR) pour chacun des clients de la base.
La seconde reprend les dates maximales et les numéros des clients ainsi obtenus pour les relier à la table d'historisation et en extraire la réponse.
Lorsque la table d'historisation contient non plus des événements, mais des états (par exemple marié, veuf, divorcé), certains auteurs conseillent de faire l'état en cours sans aucune mention de date. Ainsi pour obtenir le statut courant de clients, il suffit de placer un critère BLANC dans le champ date, ce qui évite l'enchaînement des 2 requêtes. Dès lors on stocke non pas le début de l'état, mais la fin de l'état du client.
Par exemple, dans la table suivante on a stocké le statut d'adhérent d'une association dans une table d'historisation :
Exemple 49 : Dès lors, pour savoir quel est le statut courant, il suffit d'utiliser la requête suivante :
V-E. Erreurs syntaxiques dans les requêtes▲
Il arrive parfois que dans de simples requêtes, l'exécution de celles-ci s'avère impossible.
Exemple 50 : nous voulons calculer la production d'or par année des différentes sociétés depuis la table PRODUIT représentée ci-dessous.
À l'exécution cette requête provoque une erreur :
Parce que le mot OR est l'opérateur "ou" version anglaise…
Exemple 51 : il suffit de le placer entre guillemets pour que l'erreur disparaisse…
Il arrive aussi parfois qu'une requête ait été enregistrée dans un fichier .QBE et qu'elle ne veuille plus s'ouvrir.
En général deux cas sont courants :
- une ou plusieurs tables sont inaccessibles parce que le chemin n'est plus bon ou la table a été supprimée ;
- une erreur de syntaxe apparait du fait d'un changement (en général un upgrade du moteur BDE).
Dans ces deux cas, pas de panique. Une requête QBE stockée dans un fichier .QBE n'est autre qu'un fichier texte. Vous pouvez alors ouvrir ce fichier dans n'importe quel éditeur de base (éviter absolument les Word et autre Wordpad, car ils enregistrent en plus du texte des caractères nécessaire à leur propre usage) comme NOTEPAD. Bricolez alors le fichier comme vous le voulez, puisque c'est un fichier texte, sauvegardez-le puis utilisez cette requête dans Paradox, après l'avoir testée…
NOTA : cette possibilité de bidouiller ou de créer des nouvelles requêtes est très intéressante en run-time puisqu'elle permet aux utilisateurs de créer de nouvelles requêtes sans avoir à utiliser la version complète de Paradox et donc sans payer de licence !
Sauvegardée sous TEST.QBE puis ouvert dans le notepad de Windows :
NOTA : vous pouvez supprimer la ligne REPONSE: :PRIVE:REPONSE.DB, car elle provoque souvent une erreur lorsque vous exécutez votre requête dans une version Paradox dotée d'une interface non française…
VI. Paramétrage de l'exécution des requêtes▲
Paradox offre différentes possibilités pour paramétrer la table réponse et pour exécuter les requêtes. La plupart sont disponibles dans la boîte de dialogue qui s'affiche après avoir sélectionné l'item du menu Requête / Propriétés.
D'autres paramétrages sont accessibles depuis l'item de menu Edition / Préférences du même menu.
VI-A. Propriété / Réponse▲
Accès : item de menu "Requête / Propriété", boîte de dialogue "Propriété de la requête", onglet "Réponse".
Les options de la table réponse permettent de spécifier s'il faut envoyer la réponse dans une table ou dans une vue de requête liée.
La vue de requête reliée est un accès direct aux données de la base, contrairement à la table réponse qui est une copie à un instant t des données figurant dans la base. Dans le cas d'une vue de requête reliée, les données peuvent être accessibles en lecture et en écriture et toute modification faite dans la vue de réponse est directement répercutée dans la table originale.
Pour effectuer une requête avec vue reliée, il faut impérativement utiliser des coches CheckPlus afin de voir toutes les occurrences, même redondantes, de la réponse, et ne pas spécifier d'opérateurs provoquant des calculs, des regroupements ou de la mise à jour, de l'insertion ou de la suppression.
On peut comparer les vues de requêtes reliées aux vues du SQL. Seul inconvénient, les vues de requêtes reliées ne peuvent porter que sur une seule table (3 pour leur équivalent en SQL Paradox).
Exemple 52 : nous recherchons les clients, les montants et la TVA des commandes passées après le 01/01/1999 :
NOTA : quand Paradox crée une vue de requête reliée, des indicateurs de champ relié apparaissent dans les entêtes de colonne de la réponse.
Parmi les autres paramètres de la réponse, notons que l'on peut spécifier le type de table (dBase ou Paradox) et le nom de la table réponse, ainsi que son alias ou le chemin physique de l'emplacement des fichiers de données constituant la table réponse.
Attention : Paradox verrouille le répertoire privé dans lequel les tables réponse sont stockées pour toutes les applications utilisant le moteur Borland/Inprise BDE. Cela est rendu nécessaire afin que la réponse d'une requête d'une session d'une application utilisant le BDE n'aille pas écraser une réponse déjà enregistrée par une autre application ou une autre session utilisant le même moteur. En revanche ce mécanisme n'est pas utilisé si vous spécifiez un alias ou un répertoire différent du répertoire privé défini pour la session en cours de votre application afin de stockers les données résultantes de vos requêtes. Veuillez vous rendre au Chapitre 8 pour des explications sur cette particularité du moteur BDE.
VI-B. Propriété / QBE▲
Accès : item de menu "Requête / Propriété", boîte de dialogue "Propriété de la requête", onglet "QBE".
Les options sur les "Requêtes sur tables distantes" ne s'appliquent qu'aux requêtes exécutées sur des serveurs SQL. L'exécution de requêtes localement est généralement plus lente, mais peut s'avérer nécessaire notamment si vous exécutez une requête sur des tables jointes à partir de plusieurs serveurs (requêtes hétérogènes).
La requête peut être locale ou distante |
Paradox tente d'exécuter la requête de façon distante. En cas d'échec, Paradox exécute la requête localement |
Exécuter la requête de façon distante |
Paradox demande au serveur d'exécuter la requête et de ne renvoyer que les données constituant la réponse |
Exécuter la requête localement |
Paradox exécute la requête localement : il demande au serveur toutes les données de toutes les tables impliquées dans la requête et il exécute la requête sur le poste local |
Les options de table auxiliaire permettent de s'affranchir des possibilités de "RollBack" qu'offre le mécanisme QBE de requête de Paradox. En effet, lorsque vous effectuez des requêtes modifiant les données (voir chapitre 4) le moteur BDE de Paradox recopie dans une table auxiliaire les données avant modification, ou les données insérées ou bien encore les enregistrements supprimés. Choisissez l'une des options pour déterminer de quelle manière Paradox exécute les requêtes qui modifient les données (requêtes INSERER, SUPPRIMER et REMPLACERPAR).
NOTA : la création des tables auxiliaires ralentit l'exécution d'une requête. Aussi, lorsqu'aucun problème de rollback n'est susceptible de survenir, abstenez-vous de produire des tables qui ne seront pas exploitées, l'exécution de la requête en sera plus rapide.
Requêtes rapides (pas de tables auxiliaires) |
Empêche Paradox de générer des tables auxiliaires lors de l'exécution de requêtes qui modifient les données |
Générer les tables auxiliaires |
Paradox crée des tables auxiliaires lors de l'exécution de requêtes qui modifient les données |
Les tables auxiliaires sont stockées par défaut dans votre répertoire privé. Elles ont pour noms : SUPPRIME, MODIFIE, INSERE, ERRINSER, ERRMODIF, ERRSUPPR, DOUBLON… en fonction du type de requête exécuté.
VI-C. Propriété / Tri de la réponse▲
Accès : item de menu "Requête / Propriété", boîte de dialogue "Propriété de la requête", onglet "Tri".
Nous savons en outre qu'une table réponse ne possède pas de clef. Il est donc possible de trier la table réponse en même temps que la requête s'effectue. Pour cela, vous pouvez spécifier l'ordre dans lequel les données apparaitront dans les lignes de la réponse. L'ordre ne peut être que croissant. Pour un ordre décroissant, il faut effectuer un tri interactif ou par une commande SORT dans le code ObjectPal.
VI-D. Propriété / Structure de la réponse▲
Accès : item de menu "Requête / Propriété", boîte de dialogue "Propriété de la requête", onglet "Tri".
Indépendamment du tri opéré sur les données de la table réponse, il peut paraître judicieux de modifier l'emplacement des colonnes. Pour cela il suffit de redéfinir l'ordre de ces dernières.
Redémarrer la requête en cas de modifications |
Relance la requête si un autre utilisateur a modifié les données après l'exécution de la requête, mais avant l'affichage de la table réponse |
Verrouiller les tables pour empêcher les modifications |
Verrouillez les tables requises pour tous les autres utilisateurs pendant l'exécution de la requête. Si Paradox ne peut verrouiller une table, il n'exécute pas la requête. C'est la moindre des politesses envers les autres utilisateurs. Vous devez attendre que tous les verrous soient placés avant d'exécuter la requête |
Ignorer les modifications sur les tables originales |
Exécute la requête même si un utilisateur modifie les données pendant son exécution. Cette option détermine si la réponse d'une requête est basée sur les données les plus récentes |
VI-E. Préférence d'exécution des requêtes▲
Accès : item de menu "Edition / Préférences", boîte de dialogue "Préférences", onglet "Requête".
Les préférences de requête permettent de paramétrer en standard (par défaut) l'ensemble des paramètres d'exécution des requêtes pour le moteur.
En particulier on peut exiger du moteur de faire les mises à jour de différentes manières. Dans un environnement multiutilisateur, un utilisateur peut modifier les données dans des tables utilisées dans une requête lors de l'exécution de cette requête. Différents modes d'exécution des mises à jour sont alors disponibles.
Outre la possibilité de définir par défaut les paramètres d'exécution sur serveur SQL, cette boîte de dialogue permet aussi de définir par défaut le mode de gestion des tables auxiliaires et le type de coche affichée en standard.
Enfin, la case à cocher "Contraintes de réponse SQL" permet de contrôler la mise à jour des tables SQL depuis les vues de requête reliée. Lorsque cette case à cocher est activée, vous ne pouvez que mettre à jour une table SQL dans une vue de requête reliée avec des valeurs qui satisfont aux conditions de sélection pour la requête. Vous ne pouvez pas entrer des valeurs qui ne satisfont pas aux conditions de la requête.
VI-F. Préférence de base de données▲
Accès : item de menu "Edition / Préférences", boîte de dialogue "Préférences", onglet "Base de données".
Dans cet onglet on peut changer le répertoire privé de réception des données des requêtes par défaut. On peut aussi définir ce paramétrage au lancement de l'application à l'aide de la ligne de commande en spécifiant le paramètre -p.
Exemple :
C:\ProGram\PdoxWin\V7_32\PDXWIN32.EXE -p C:\temp\temp2 -w D:\Paradox7\Themis\!doc !decode.fsl
Qui signifie : lancement de Paradox avec comme répertoire privé "C:\temp\temp2", comme répertoire de travail "D:\Paradox7\Themis\"et ouverture de la fiche !decode.fsl pour démarrer l'application.
La case à cocher "Traiter les champs vides comme zéro" permet de considérer les champs vides dans les tables comme contenant la valeur zéro. Ainsi, lorsque des requêtes comportent des calculs et que certains champs à calculer sont vides, l'activation de cette option ne provoquera pas d'erreur d'exécution, et la requête continuera son exécution jusqu'à bonne fin.
Vous pouvez aussi modifier l'intervalle de rafraîchissement des données. Ainsi, avec le taux de rafraîchissement, les données visualisées sur votre poste seront mises à jour toutes les n secondes, si de nouvelles valeurs ont été validées par les autres utilisateurs du réseau.
La période de tentative est la durée pendant laquelle Paradox essaye de verrouiller les tables nécessitées par un traitement (requête par exemple). À bout du temps déterminé, si Paradox n'a pas réussi à obtenir le verrou souhaité, il abandonne et retourne un code d'erreur.
Enfin cette boîte de dialogue vous indique le nom que vous utilisez sur le réseau virtuel Paradox et les utilisateurs qui sont en concurrence avec vous pour l'application que vous utilisez ou pour n'importe quelle application utilisant le moteur BDE (ces informations sont extraites du fichier pdoxusrs.net auquel chaque BDE des postes clients doit accéder).
VI-G. Préférence du BDE▲
Accès : item de menu "Edition / Préférences", boîte de dialogue "Préférences", onglet "BDE".
Cet écran permet de visualiser les paramètres du moteur BDE.
Répertoire du fichier de contrôle du réseau : c'est l'emplacement du fameux fichier "pdoxusrs.net" qui gère la concurrence du réseau privé virtuel Paradox/BDE.
On peut aussi y voir les pilotes des langues utilisés par défaut pour Paradox dBase et le système, les pilotes de base de données installés, les tailles des tampons mémoire et si le partage des tables (LOCAL SHARE) est activé ou non.
Ces paramètres sont modifiables uniquement dans le BDE.
VI-H. Obtenir le SQL équivalent au QBE▲
Notons en outre la possibilité d'obtenir le texte d'une requête QBE sous forme SQL, grâce à l'icône :
Exemple 53 :
Vous noterez que la traduction SQL d'une requête QBE fait toujours apparaitre le mot clef DISTINCT et la clause ORDER BY.
VII. Accélérer l'exécution des requêtes▲
Voici maintenant quelques règles pour accélérer l'exécution des requêtes QBE
VII-A. Indexation▲
La mise en place d'index secondaires sur les champs sur lesquels on effectue les recherches permet d'accélérer sensiblement les requêtes. Attention cependant à ne pas indexer de champs trop volumineux en termes de longueur, car les fichiers d'index peuvent devenir énormes voire dépasser la taille des données contenues dans la table.
Une technique consiste à dénormaliser pour optimiser les performances. Par exemple, lorsque doit figurer le département dans une adresse, il vaut mieux utiliser un champ de type A2, A3 ou smallint plutôt qu'un champ alpha avec le code postal sur 5 caractères et utiliser une recherche approchée.
VII-B. Tampon mémoire▲
Les tampons de données du moteur de Paradox peuvent être paramétrés. À l'origine les paramètres sont :
minBufSize=256
MaxBufSize=2048
Il ne sert à rien d'augmenter la taille minimum, mais la taille du buffer max peut être augmentée jusqu'au 2/3 de la mémoire vive.
Ainsi, pour 16 Mo de ram la taille de MaxBufSize peut être portée à 10 240 Ko.
VII-C. Éviter les jokers et champs mémo▲
L'emploi des jokers @ et .. provoque des calculs plus importants que l'utilisation de conditions exactes. Il vaut donc mieux formater correctement les données lors de la saisie des informations, par exemple en stipulant que certains champs ne peuvent être écrits qu'en majuscules.
De même l'extraction des champs mémo, graphique, etc. nécessite beaucoup plus de ressources que l'extraction des champs ordinaires. Dans la mesure du possible n'extraire que les champs absolument nécessaires à la réponse.
VII-D. N'extraire que la clef▲
Une bonne technique consiste à ne récupérer que la clef des enregistrements concernés. Dès lors on peut toujours relier ensuite la table réponse à la table concernée (ou la table maître).
Exemple 54 :
Peut être transformée en, exemple 55 :
puis liée dans le modèle relationnel suivant :
La requête n'en sera exécutée que plus rapidement, et les éventuels changements en réseau seront immédiatement visibles.
VII-E. Disque compressé▲
Des résultats surprenants en accélération de la vitesse de traitement peuvent être obtenus en utilisant une partition de disque compressée pour le stockage des données. Il n'est pas rare dans ce cas de voir les requêtes s'exécuter 3 à 4 fois plus vite…
Explication : des fichiers compressés sont moins longs à véhiculer que des fichiers non compressés, à condition que la décompression s'effectue sur le poste client !
VII-F. Utilisez du SQL !!!▲
Certaines requêtes s'exécutent notablement plus vite en SQL qu'en QBE. C'est en particulier le cas des requêtes d'exclusion.
Exemple 56 : on recherche les clients du 92 n'ayant pas commandé au mois de janvier
QBE |
Sélectionnez
|
SQL |
Sélectionnez
|
En particulier la comparaison entre l'exécution de ces 2 requêtes ne laisse aucun doute :
Sur une même base de données, la première met 10,25 secondes à s'exécuter, tandis que pour la seconde (SQL) le temps est de 0,87 seconde…
De plus certains mécanismes et fonctions ne sont présents que dans SQL, c'est le cas de cette requête qui utilise la fonction EXTRACT qui permet d'obtenir le mois, le jour ou l'année à partir d'une date…
De même on trouvera des opérateurs de manipulation de chaîne de caractères et des opérateurs de transtypage dans le SQL du BDE que l'on ne trouve pas dans le QBE.
En revanche on peut s'étonner de ne pas voir figurer un opérateur tel que CURRENT_DATE ou CURRENT_TIME, c'est-à-dire l'équivalent du TODAY du QBE dans le SQL de Paradox…
VIII. Les requêtes dans le code▲
Nous voici arrivés à un moment où la simple requête ne suffit plus…
Dès lors, une manière de traiter les données consiste à inclure les requêtes dans du code ObjectPal afin d'augmenter les possibilités de traitement des données.
Voici quelques règles et pièges à éviter…
VIII-A. Les variables de type QBE▲
Les variables de type QBE permettent de définir une chaîne de caractères qui sera envoyée au moteur de requête (BDE) pour être exécutée.
Le texte de la requête ainsi que les paramètres d'exécution doivent être inclus entre deux mots clefs qui sont :
"query" et "endQuery".
Le moyen le plus simple pour construire une requête en ObjectPal, moyen que je vous invite TOUJOURS à respecter est de la commencer en interactif, de la tester en interactif avec des valeurs bidons, puis de la sauvegarder. Une fois dans votre éditeur de code, vous pouvez copier le texte de la requête sauvegardée en activant l'item de menu : "Edition / Coller depuis" et de chercher le bon fichier .QBE.
C'est ce qu'il y a de plus simple et de plus efficace.
Exemple 57 :
var
RQ query
endVar
RQ =
Query
REPONSE: :PRIVE:REPONSE.DB
PdoxAddOn\QueryBar\P_empmas.db | EMPLOYEE ID# | GRADE LEVEL |
| Check | Check SAUF BLANC |
EndQuery
Pour exécuter une requête, vous disposez d'une méthode et d'une procédure.
Version objet (utilisation de la méthode) :
RQ.executeQBE()
Version procédurale :
db.open() ; ouvre une base de données sur la base courante (le répertoire de travail)
executeQBE(db,RQ)
Comme ces deux techniques renvoient une valeur logique, je vous invite systématiquement à tester la valeur de retour, à afficher un message en cas d'insuccès et à arrêter l'exécution du code. Le mieux étant la boîte de dialogue d'erreur par défaut de Paradox :
if not RQ.executeQBE()
then
errorShow()
return
endif
CONSEILS
La méthode, comme la procédure, permet de définir en paramètre la variable qui va recevoir la réponse. Je vous invite à toujours spécifier explicitement le paramètre de la réponse dans la méthode plutôt que de laisser la ligne de paramètre spécifiant la table réponse dans le corps du texte de la requête. En effet, dans les différentes versions de Paradox, cette ligne peut poser des problèmes d'exécution. |
De plus je vous invite à utiliser systématiquement les alias pour spécifier l'emplacement des tables, que ce soit dans le texte de la requête comme dans le paramètre de la réponse. La portabilité des requêtes n'en sera que plus aisée. |
Enfin, si vous travaillez sur un développement qui est susceptible de tourner sur des versions non françaises de Paradox, il faut utiliser tous les équivalents anglais des alias et mots clefs des requêtes. En effet les opérateurs en version anglaise sont disponibles sur toutes les versions du moteur ce qui n'est pas le cas des opérateurs en langue française. |
Il est préférable dans les noms des champs des tables de ne jamais utiliser d'autres caractères que les lettres sans accent et les chiffres ainsi que le blanc souligné. En effet certains caractères ne sont pas traduits correctement lors des manœuvres de copier-coller… |
NOTA : les alias PRIVE et TRAVAIL s'écrivent respectivement en anglais : PRIV et WORK. La ligne de paramétrage de la réponse dans le corps de la requête peut s'écrire :
ANSWER: :PRIV:REPONSE.DB
Pour une meilleure compatibilité sur le plan des versions et de l'international.
Exemple 58 : avec ces conseils, une bonne requête s'écrit :
RQ =
Query
:maBase:P_empmas.db | EMPLOYEE_ID | GRADE_LEVEL |
| Check | Check NOT BLANK |
EndQuery
if not RQ.executeQBE(":PRIV:REPONSE.DB")
then
errorShow()
return
endif
NOTA : veuillez noter la présence de lignes blanches indispensables entre les mots query et endQuery…
VIII-B. Autres paramètres dans le corps de la requête▲
Exemple 59 :
Query
ANSWER: :PRIV:REPONSE.DB
OPTIONS: NO AUXILIARY TABLES, LOCAL
:maBase:P_empmas.db->"GRADE LEVEL",
:maBase:P_empmas.db->"EMPLOYEE ID#"
SORT: :maBase:P_empmas.db->"GRADE LEVEL",
:maBase:P_empmas.db->"EMPLOYEE ID#"
:maBase:P_empmas.db | EMPLOYEE ID# | GRADE LEVEL |
DELETE | NOT 00.. | SAUF BLANC |
EndQuery
La ligne OPTIONS permet de définir les options d'exécution pour le moteur BDE. Ici on a choisi de ne pas gérer les tables auxiliaires et d'obliger l'exécution en local de la requête.
Les deux lignes situées en dessous de la ligne d'option permettent de spécifier l'ordonnancement de la vue des colonnes. Ainsi l'ordre des colonnes sera "GRADE LEVEL" puis "EMPLOYEE ID".
Les spécifications de la ligne SORT permettent de définir l'ordre des données.
NOTA : il est possible de définir en même temps l'ordre des données et la structure de la vue de la table en ordonnant directement les colonnes de la table dans le texte même de la requête. Les lignes de structuration de la table et d'ordre des données ne sont alors plus nécessaires.
Exemple 60 :
Query
ANSWER: :PRIV:REPONSE.DB
:maBase:P_empmas.db | GRADE LEVEL | EMPLOYEE ID# |
DELETE | SAUF BLANC | NOT 00.. |
EndQuery
Mais cette technique n'est pas garantie à 100 %… même si elle marche dans des requêtes multitables.
VIII-C. Utiliser des variables dans les requêtes▲
Pour spécifier des variables dans une requête, il suffit de créer autant de variables que nécessaire et de les assigner avant l'exécution de la requête.
Dans le corps de la requête, les variables doivent apparaitre précédées d'un caractère tilde ~.
Exemple 61 :
var
RQ query
grd longint
endVar
grd = 423
RQ =
Query
:maBase:P_empmas.db | EMPLOYEE_ID | GRADE_LEVEL |
| Check | Check >~grd |
EndQuery
if not RQ.executeQBE(":PRIV:REPONSE.DB")
then
errorShow()
return
endif
VIII-D. Conseils importants pour les variables de requête▲
Pour bien comprendre comment programmer proprement une interface d'interrogation par requête, livrons-nous à un exemple complet.
Voici la table qui servira dans notre exemple, ainsi que la fiche d'interrogation et sa structure objet :
Le modèle relationnel de la fiche est vide. Autrement dit, le cadre de table de la fiche n'est associé à aucune table d'aucune base…
VIII-D-1. Toujours utiliser des variables string▲
Le contenu d'une variable requête est une chaîne de caractères. Aussi est-il plus sain d'utiliser systématiquement des variables de type string comme paramètre de requête…
Voici le code contenu dans la méthode pushButton du bouton, avec des commentaires…
Exemple 62 :
method pushButton(var eventInfo Event)
var
NoSte Longint ; n° de société (entier)
NoSteS String ; n° de société converti en string
DateMaj Date ; date de mise à jour
DateMajS String ; date de mise à jour convertie en string
ProduitS String ; produit (chaîne de caractères)
Qte Number ; quantité (réel)
QteS String ; quantité convertie en string
nomRep string ; nom de la table réponse
RQ query ; la chaîne de requête
endvar
; on rend invisible le cadre de table durant le traitement
TFReponse.visible = false
; on donne un nom à la table réponse
nomRep = ":PRIV:REPONSE.DB"
; on retire la table du modèle relationnel de la fiche
; et on délie la table au cadre
if dmHastable(nomRep)
then
TFReponse.tablename = blank()
dmRemoveTable(nomRep)
endif
; test et conversion des valeurs des critères
if FldNo.isBlank()
then
NoSteS = ""
else
TRY
NoSte = Longint(FldNo.value)
ONFAIL
ErrorClear()
MsgStop("ERREUR","Format incorrect de données pour le paramètre n° sté.")
return
ENDTRY
NoSteS = String(NoSte)
endif
if FldDate.isBlank()
then
DateMajS = ""
else
TRY
DateMaj = Date(FldDate.value)
ONFAIL
ErrorClear()
MsgStop("ERREUR","Format incorrect de données pour le paramètre date MAJ.")
return
ENDTRY
DateMajS = string(DateMaj)
endif
ProduitS = iif(FldProd.isBlank(),"","\""+FldProd.value+"\"")
; notez le rajout des guillemets autour des valeurs string pour différencier les
; valeurs des opérateurs...
if FldQte.isBlank()
then
QteS = ""
else
TRY
Qte = number(FldQte.value)
ONFAIL
ErrorClear()
MsgStop("ERREUR","Format incorrect de données pour le paramètre quantité.")
return
ENDTRY
QteS = string(Qte)
endif
; voici la requête paramétrée
RQ =
Query
:myBase:Produit.DB | No ste | Date MAJ |
| Check ~NoSteS | Check ~DateMAJS |
:myBase:Produit.DB | Produit | Qte |
| Check ~ProduitS | Check ~QteS |
EndQuery
; exécution de la requête
if not RQ.executeQBE(nomRep)
then
errorShow()
return
endif
; si table vide : il n'y a pas de réponse... on en informe l'utilisateur
if isEmpty(nomRep)
then
msgInfo("REPONSE","Aucune réponse ne satisfait à votre demande...")
return
endif
; sinon on va afficher les résultats
dmAddTable(nomRep)
TFreponse.tableName = nomRep
TFReponse.visible = true
endMethod
Veuillez noter comment on a manipulé le modèle relationnel de la fiche : en premier on dissocie le cadre de table de la table sous-jacente si cette table est dans le modèle relationnel et on la supprime du modèle relationnel de la fiche. À la fin du traitement si des données sont présentes dans la table réponse, on ajoute la table réponse au modèle relationnel de la fiche et on lie cette table au cadre de table que l'on rend visible pour que l'utilisateur puisse voir le résultat.
VIII-D-2. Une seule variable par champ/ligne▲
De même qu'il est recommandé d'utiliser des variables string dans les requêtes, il est tout aussi recommandé de n'utiliser qu'une seule variable par ligne et champ de la requête. Ainsi le cas des fourchettes doit être traité en une seule variable.
Exemple : dans notre interface d'interrogation, on désire faire une fourchette entre deux dates. Dans ce cas, l'utilisateur peut soit :
- rentrer aucune date ;
- ne rentrer que la date de début ;
- ne rentrer que la date de fin ;
- rentrer la date de début et de fin.
Exemple 63 : voici comment traiter ce cas par le code en utilisant des champs de nom FldDateDebut et FldDateFin dans la fiche :
method pushButton(var eventInfo Event)
var
DateMajD Date ; date de mise à jour début
DateMajF Date ; date de mise à jour fin
FourchetteDateS String ; fourchette des dates de mise à jour
; convertie en string
nomRep string ; nom de la table réponse
RQ query ; la chaîne de requête
endvar
[...] ; même code pour la manipulation du cadre de table
; que l'exemple précédent
; test de validité des dates
if not FldDateDebut.isBlank()
then
TRY
DateMajD = date(FldDateDebut)
ONFAIL
errorClear()
MsgStop("ERREUR","Format de date de début incorrect")
return
ENDTRY
endif
if not FldDateFin.isBlank()
then
TRY
DateMajF = date(FldDateFin)
ONFAIL
errorClear()
MsgStop("ERREUR","Format de date de fin incorrect")
return
ENDTRY
endif
; construction de la fourchette
SWITCH
CASE not DateMajD.isAssigned() and not DateMajF.isAssigned() :
; les deux dates sont à blanc
FourchetteDateS = ""
CASE DateMajD.isAssigned() and not DateMajF.isAssigned() :
; seule la date de début est spécifiée
FourchetteDateS = ">="+string(DateMajD)
CASE not DateMajD.isAssigned() and DateMajF.isAssigned() :
; seule la date de fin est spécifiée
FourchetteDateS = "<="+string(DateMajF)
CASE DateMajD.isAssigned() and DateMajF.isAssigned() :
; les deux dates sont assignées, notez la présence de la virgule...
FourchetteDateS = ">="+string(DateMajD)+", <="+string(DateMajF)
ENDSWITCH
; voici la requête paramétrée
RQ =
Query
:myBase:Produit.DB | No ste | Date MAJ |
| Check | Check ~FourchetteDateS |
:myBase:Produit.DB | Produit | Qte |
| Check | Check |
EndQuery
Nous vous avons épargné la fin du code qui est la même que dans l'exemple précédent…
VIII-D-3. Format des nombres réels et des dates▲
Mais il existe un piège dans la manipulation des requêtes par le code. En effet, il faut s'assurer du bon formatage des champs date et nombre. Par exemple s'assurer que le format de date est bien avec une année à 4 chiffres…
Pour les dates il faut impérativement que le format de date du BDE soit le même que celui de Windows.
Par défaut on prendra toujours le format suivant : jj/mm/aaaa.
S'assurer dans les paramètres régionaux de Windows, onglet Date que le format de date courte est bien jj/mm/aaaa.
Le code suivant permet de tester la validité de ce paramétrage :
if size(string(date("01/01/1901"))) <> 10
then
msgStop("ATTENTION","Format de date 'Windows Court' par défaut incorrectement défini.\n"
+"Veuillez paramétrer le format de date de Windows pour que l'année soit"
+" définie sur 4 chiffres\nPour effectuer cette manoeuvre, allez dans le panneau de"
+" configuration de Windows 95 : Menu Démarrer/Paramètres/Panneau de configuration"
+" puis cliquez sur l'icône 'Paramètres régionaux', onglet Date. Assurez-vous que le"
+" style de date courte est bien jj/mm/aaaa, sinon choisissez-le dans la liste déroulante"
+" et validez votre choix.")
exit()
return
endif
De même dans le BDE, le format de date doit être paramétré comme suit :
Pour ce qui concerne le format des nombres réels, on veillera à ce que, dans le BDE, comme dans les paramètres régionaux de Windows, la virgule fasse office de séparateur décimal, que le nombre de décimales soit le même (en général 2) ainsi que l'éventuel séparateur des milliers et le symbole des chiffres négatifs…
VIII-E. Manipulation des requêtes sous forme de chaînes de caractères▲
Comme une requête paradox est une chaîne de caractères, il est possible de manipuler n'importe quelle partie de cette chaîne y compris le nom de la table, le nom des champs, les paramètres d'exécution de la requête…
Exemple 64 : dans une fiche figure 3 champs, FldTable qui contient le nom de la table, FldChamp qui contient le nom du champ et FldVal qui contient le critère. Le code suivant permet de créer une requête simple avec paramétrage du nom de la table, du champ visé et du critère…
var
NomTable string ; nom de la table
NomChamp string ; nom du champ
Val String ; critère
nomRep string ; nom de la table réponse
RQ query ; la chaîne de requête
endvar
[...] ; voir exemple précédent
; test de validité des critères de requête
; récupération du nom de la table
if FldTable.isBlank()
then
MsgStop("ERREUR","Table non définie")
return
endif
NomTable = FldTable.value
; récupération du nom du champ
if FldChamp.isBlank()
then
MsgStop("ERREUR","Champ non défini")
return
endif
NomChamp = FldChamp.value
; récupération du critère
Val = iif(FldVal.isBlank(),"",FldVal.value)
; voici la requête paramétrée
RQ =
Query
:myBase:~NomTable | ~NomChamp |
| Check ~Val |
EndQuery
[...] ; voir exemple précédent
Mais il y a mieux encore.
Objectpal permet de manipuler des chaînes de requêtes directement à partir de chaînes de caractères… Dans ce cas, l'exécution de la requête repose sur la méthode executeQBEstring.
Exemple 65 : poussons le précédent exemple un peu plus loin… Obligeons-nous à récupérer tous les champs de la table pour visualiser la réponse.
method pushButton(var eventInfo Event)
var
NomTable string ; nom de la table
NomChamp string ; nom du champ
Val String ; critère
nomRep string ; nom de la table réponse
T Table ; table visée par la requête
Champs array[] string
; les champs de la table visée par la requête
i smallint ; variable de boucle
RQS string ; la chaîne de requête en string
endvar
[...] ; voir exemple précédent
; on récupère tous les champs de la table dans un tableau
T.attach(":mybase:"+nomtable)
T.enumFieldNames(Champs)
; construction de la chaîne de requête
; ligne d'entête et nom de la table
RQS = "Query\n\n:myBase:"+NomTable
; ajout de tous les champs de la table séparés par une barre
for i from 1 to champs.size()
RQS = RQS + " | "+champs[i]
endFor
RQS = RQS + "|\n"
; ajout de tous les 'check' sous les champs de la table
; et du critère si val est non vide
for i from 1 to champs.size()
RQS = RQS + " | Check "
if Champs[i] = NomChamp and val <> ""
then
RQS = RQS + val
endif
endFor
; ajout de la ligne de pied de requête
RQS = RQS + "|\n\nendQuery"
; exécution de la requête
if not RQS.executeQBEString(nomRep)
then
errorShow()
return
endif
[...] ; voir exemple précédent
Notez la présence des séquences d'échappement " " permettant de définir un retour chariot comme en C.
CONSEIL : vous pouvez toujours enregistrer le texte d'une requête sous forme d'un fichier QBE ne serait-ce que pour des fins de contrôle ou de mise au point. Pour cela vous pouvez utiliser les méthodes : writeQBE appliqué sur le type QUERY et writeToFile appliqué au type string transtypé en memo.
Exemple :
writeToFile(memo(RQS),":PRIV:_test.QBE")
VIII-F. Manipulation des fichiers de requête▲
Il est bien entendu possible d'ouvrir, de lire et d'exécuter un fichier de requête de type *.QBE à l'aide de la méthode readFromFile appliqué au type query :
rq.readFromFile(":myQueries:newCust.QBE")
rq.executeQBE()
Mais cette méthode offre peut d'intérêt, si ce n'est que d'éviter une manipulation interactive.
Notez aussi la méthode readFromString qui permet de lire une requête depuis une simple chaîne de caractères.
VIII-G. Manipulation des requêtes à l'aide des méthodes▲
Il est possible de manipuler les requêtes à l'aide de fonctions spécifiques.
Personnellement je n'aime pas utiliser ces méthodes parce qu'elles apparaissent un peu lourdes comparées à une manipulation de chaîne de caractères…
Voici un tableau qui résume les méthodes appliquées au type query :
appendRow |
ajoute une ligne à une chaîne de requête |
appendTable |
ajoute une table à une chaîne de requête |
checkField |
coche un champ d'une chaîne de requête |
checkRow |
coche tous les champs d'une ligne d'une chaîne de requête |
clearCheck |
décoche le champ d'une chaîne de requête |
createAuxTables |
active ou désactive la création des tables auxiliaires |
createQBEString |
renvoie la chaîne de caractères équivalente à la chaîne de requête |
enumFieldStruct |
énumère la structure de la table réponse |
getAnswerFieldOrder |
renvoie les noms des champs de la réponse dans l'ordre de la structure |
getAnswerName |
renvoie le nom de la table réponse |
getAnswerSortOrder |
renvoie l'ordre des données de la réponse |
getCheck |
renvoie le type de coche d'un champ |
getCriteria |
renvoie l'ensemble des critères d'un champ |
getQueryRestartOptions |
renvoie l'option de redémarrage de la requête |
getRowID |
renvoie l'identifiant d'une ligne d'une table de requête |
getRowNo |
renvoie le numéro d'ordre d'une ligne de table de requête |
getRowOp |
renvoie l'opérateur global d'une ligne d'une table (Insert, delete, set …) |
getTableID |
renvoie l'identifiant d'une table de requête |
getTableNo |
renvoie le numéro d'ordre d'une table de requête |
hasCriteria |
indique si un critère a été spécifié pour un champ d'une table |
insertRow |
insère une nouvelle ligne dans une table de requête |
insertTable |
insère une nouvelle table de requête |
isCreateAuxTables |
indique si l'option de création des tables auxiliaires est active ou non |
isEmpty |
indique si la chaîne de requête est vide ou non |
isExecuteQBELocal |
indique si le moteur exécute localement ou non la requête |
isQueryValid |
indique si la requête est compilable ou non |
removeCriteria |
supprime un critère d'un champ d'une ligne d'une table de la requête |
removeRow |
supprime une ligne d'une table de la requête |
removeTable |
supprime une table de la requête |
setAnswerFieldOrder |
spécifie l'ordre de création des champs de la table réponse |
setAnswerName |
spécifie le nom de la table réponse |
setAnswerSortOrder |
spécifie l'ordre des valeurs des champs de la table réponse |
setCriteria |
spécifie le critère d'un champ d'une ligne d'une table de la requête |
setLanguageDriver |
spécifie le pilote de langue utilisé par défaut |
setQueryRestartOptions |
spécifie les options de redémarrage de la requête |
setRowOp |
spécifie l'opérateur global d'une ligne d'une table (Insert, delete, set …) |
wantInMemoryTCursor |
spécifie l'option de création d'un TCursor pour la table réponse |
VIII-H. Optimiser les requêtes dans le code▲
L'ensemble des remarques que nous avons formulées pour la création et l'exécution interactive des requêtes est bien entendu valable pour le code.
Nous y rajouterons la possibilité de créer des réponses sous forme de TCursor sans passer par une écriture d'un quelconque fichier disque. Cette technique est intéressante dès lors qu'un traitement ne peut être fait qu'au travers d'un TCursor suite à une requête.
Dans ce cas il convient d'utiliser les trois méthodes suivantes :
Sélectionnez
|
Oblige la création du TCursor en mémoire seulement |
Sélectionnez
|
Exécution de la requête et obtention de la réponse dans un TCursor |
Sélectionnez
|
Code effectuant un traitement sur le TCursor |
Sélectionnez
|
Recopie des données du TCursor dans une table physique |
Exemple 66 : nous désirons formater des noms de clients sélectionnés par une requête
var
RQ query
TC Tcursor
endVar
RQ =
Query
CLIENT.db | NOM_CLI | PRENOM_CLI | TITRE_CLI |
| Check | Check | Check M. |
EndQuery
RQ.WantInMemoryTCursor(yes)
RQ.executeQBE(TC)
TC.edit()
scan TC :
TC.NOM_CLI = lTrim(rTrim(TC.NOM_CLI))
TC.PRENOM_CLI = lTrim(rTrim(upper(TC.PRENOM_CLI)))
TC.postRecord()
endScan
TC.endEdit()
TC.instantiateView(":PRIV:REPONSE.DB")
VIII-I. Des requêtes, des applications… un seul moteur▲
Différentes applications utilisant le même moteur BDE de requêtes peuvent tourner simultanément sur le même poste de travail.
Cependant, le BDE doit obligatoirement utiliser un répertoire privé dans lequel sont stockées les réponses des requêtes, différent pour chaque session de chaque application accédant au moteur. Pour cela il pose un verrou de répertoire (un peu à la manière de la procédure SetDirLock). Sans cette disposition, les réponses d'une application pourraient écraser les réponses d'une autre application, surtout si les tables réponse portent le même nom (ANSWER ou REPONSE par défaut).
Pour contourner ce problème et faire tourner ces différentes applications ou instances simultanément sur le même poste, il est possible de rediriger le répertoire privé.
On peut alors utiliser la procédure SetPrivDir qui permet de rediriger le répertoire privé de l'application en cours. Mais le moyen le plus simple et le plus direct est d'effectuer cette manœuvre dans la ligne de lancement de l'application.
Exemple :
C:\ProGram\PdoxWin\V7_32\PDXWIN32.EXE -p C:\temp\temp2 -w D:\Paradox7\Themis\!doc !decode.fsl
Dans cette ligne de commande, le paramètre "-p C:\temp\temp2" indique au moteur de rediriger le répertoire privé de l'application vers le répertoire "C:\temp\temp2".
IX. Encore quelques trucs en vrac▲
Pour terminer, quelques problèmes assez courants pour être traités par des requêtes et du code d'appoint. Des traitements sur les mots, la gestion et la linéarisation de lignes filles, la recherche du vide et enfin quelques problèmes sur les dates…
IX-A. Jeux de mots… laids▲
Comment effectuer des recherches sur des mots afin de répondre aux exigences des cruciverbistes et des scrabblelistes ?
Quelle est la bonne méthode pour trouver rapidement les mots qu'il faut placer au bon endroit ? Quelles requêtes adopter ? Quelle structure de tables choisir ?
Voici comment nous avons résolu le problème. Nous avons créé 2 tables.
La première appelée DICO.DB permet de stocker les mots. Elle a la structure suivante :
NO_DICO |
+ |
Clef |
CAR_DICO |
S |
Nombre de caractères du mot |
MOT_MAJ_DICO |
A40 |
Mot en majuscules sans accent |
MOT_MIN_DICO |
A40 |
Mot en minuscules sans accent |
Afin d'optimiser ses performances en matière de requête, elle possède trois index secondaires sur chacun des 3 champs (CAR_DICO, MOT_MAJ_DICO et MOT_MIN_DICO).
Le seconde s'appelle LETTRE.DB et contient l'ensemble des lettres de l'alphabet avec la fréquence d'apparition de ces lettres dans l'ensemble des mots placés dans le dictionnaire.
LETTRE_LET |
A1 |
La lettre |
FREQUENCE_LET |
N |
Son classement de la plus à la moins fréquente |
IX-A-1. Quelques recherches de mots … commençant par, se terminant par, contenant ou encore avec les lettres placées…▲
Exemple 67 : Recherchons par exemple les mots commençants par "DET". Rien de plus simple :
QBE |
Sélectionnez
|
SQL |
Sélectionnez
|
Exemple 68 : recherche les mots se terminant par "DET" :
QBE |
Sélectionnez
|
SQL |
Sélectionnez
|
Exemple 69 : recherche les mots contenant "DET" :
QBE |
Sélectionnez
|
SQL |
Sélectionnez
|
Exemple 70 : recherche les mots contenant "D?T" ou ? figure n'importe quel caractère :
QBE |
Sélectionnez
|
SQL |
Sélectionnez
|
IX-A-2. Les mots possédant les lettres…▲
La principale difficulté est de trouver rapidement la solution. C'est pourquoi on utilise la table de fréquences de lettres. Dans cette table on trouve les 26 lettres de l'alphabet avec la fréquence à laquelle on les retrouve dans le dictionnaire. Pour faciliter les requêtes, les lettres ont été classées de la manière suivante : la plus fréquente reçoit la valeur 1 et la moins fréquente la valeur 26.
On commence par alimenter un tableau dynamique qui reçoit en indice chaque lettre et en valeur le nombre de fois où cette lettre est présente dans le modèle servant de recherche.
Ensuite on crée une table possédant trois champs : la lettre le nombre de fois où elle est apparait, et sa fréquence. On la remplit à l'aide du tableau dynamique et de la table des lettres, puis on trie cette table sur la fréquence.
Finalement pour chaque entrée de la table ainsi construite, on effectue une requête sur la même table, jusqu'à obtenir une réponse vide, ou d'avoir épuisé les entrées de la table. Le critère de requête étant la lettre suivie des caractères point point, autant de fois qu'elle apparait.
Algorithme : on recherche tous les mots contenant les lettres AANNNT
1 Alimentation du tableau dynamique
TabLet[A] = 2
TabLet[N] = 3
TabLet[T] = 1
2 Table ":PRIVE :LETTRE.DB"
FRQ LET NBR
11 T 1
6 N 3
2 À 2
3 Requête sur la même table
premier critère : ..T..
second critère : ..N..N..N..
troisième critère : ..A..A..
Requête :
Query
:PRIVE:DICO.db | MOT_MAJ_DICO |
| Check ..~laLet |
EndQuery
Exemple 71 : en voici le code :
method pushButton(var eventInfo Event)
var
RQ Query
NCar string
TabLet DynArray[] smallint
Lettres string
LaLet string
I smallint
Indx string
T table
TCAllLet,
TCMyLet,
TC TCursor
endvar
; nombre de caractères si précisé
nCar = iif(ChNCar.isBlank(),"",string(smallint(ChNCar.value)))
; modèle recherché
lettres = ChLetn.value
; mise en majuscules éventuelle
if not ChAccent.value
then
lettres = lettres.upper()
endif
; alimentation du tableau dynamique
for i from 1 to Lettres.size()
if TabLet.contains(lettres.substr(i))
then
TabLet[lettres.substr(i)] = TabLet[lettres.substr(i)] +1
else
Tablet[lettres.substr(i)] = 1
endif
endFor
; construction de la table
T = create ":PRIVE:LETTRES"
with "FRQ" : "N",
"LET" : "A1",
"NBR" : "S"
endCreate
; alimentation de la table
TCMyLet.open(T)
TCMyLet.edit()
TCAllLet.open("LETTRE")
foreach indx in TabLet
TCAllLet.qLocate(indx)
TCMyLet.insertRecord()
TCMyLet.FRQ = TCAllLet.FREQUENCE_LET
TCMyLet.LET = TCAllLet.LETTRE_LET
TCMyLet.NBR = TabLet[indx]
TCMyLet.postRecord()
endForeach
TCAllLet.close()
TCMyLet.endEdit()
TCMyLet.close()
; tri de la table
sort T
on
"FRQ" D
endSort
TCMyLet.open(T)
T.unAttach()
; première requête : restriction sur le nombre de lettres du mot
; sinon construction de la table en PRIVE, puisque les requêtes
; doivent s'effectuer au sein d'une même table
RQ =
Query
DICO.db | CAR_DICO | MOT_MAJ_DICO |
| ~nCar | Check |
EndQuery
RQ.executeQBE(":PRIVE:DICO")
; boucle de requête
SCAN TCMyLet :
laLet = ""
for i from 1 to TCmyLet.NBR
laLet = laLet+TCMyLet.LET+".."
endFor
RQ =
Query
:PRIVE:DICO.db | MOT_MAJ_DICO |
| Check ..~laLet |
EndQuery
RQ.executeQBE(":PRIVE:DICO")
; test d'arrêt
if isEmpty(":PRIVE:DICO.DB")
then
msgStop("HÉLAS","pas de réponse")
return
endif
ENDSCAN
; fin de la boucle : des réponses ont été trouvées
; on alimente la liste de visualisation
TC.open(":PRIVE:DICO")
ListeMot.list.count = 0
DelayScreenUpdates(yes)
SCAN TC :
ListeMot.List.count = ListeMot.List.count+1
ListeMot.List.selection = ListeMot.List.count
ListeMot.List.value = TC.(1)
ENDSCAN
DelayScreenUpdates(No)
ListeMot.List.selection = 1
endmethod
IX-A-3. Les mots constitués uniquement des lettres…▲
Exemple : AEMRS donnera AMERS, ARMES, RAMES
À un iota près, la technique est la même que précédemment…
En effet il suffit de dire que le nombre de lettres doit être exactement celui du nombre de lettres précisé dans le modèle
If faut donc remplacer la ligne :
nCar = iif(ChNCar.isBlank(),"",string(smallint(ChNCar.value)))
par
nCar = string(Lettres.size())
Et le tour est joué.
IX-B. À la recherche des lignes filles▲
IX-B-1. Recherches dans un sous-ensemble de lignes▲
Il arrive parfois, afin de laisser le champ libre à l'inspiration des utilisateurs, de concevoir des bases de données dotées de champs paramétrables. Comment faire, alors pour rechercher les enregistrements correspondant à plusieurs caractéristiques simultanément ?
Prenons par exemple une table client avec un n° de client et le nom, et une table de caractéristiques des clients dotés des champs n° client, nom caractéristique, valeur caractéristique. Voici un exemple de données de telles tables :
Comment récupérer des clients affublés de différentes caractéristiques ?
Une première requête consiste à faire :
Mais la réponse apparait là, cinglante de vérité : nous avons sélectionné à la fois les clients dont la profession est ENSEIGNANT et ceux dont l'état est AU FOYER… Alors que nous recherchions ceux qui sont à la fois ENSEIGNANT et en même temps AU FOYER !
Exemple 72 : la solution consiste à établir une autojointure !!!
On trouve alors les clients possédant toutes les caractéristiques décrites à la fois.
Le tout peut être mis dans une fiche et la requête construite dans le code en très peu de lignes :
Exemple 73 :
var
QRS string
RQ query
NbLigne longint
I smallint
endVar
TCcar.attach(CARACCLI)
nbLigne = longint(TCcar.nRecords())
QRS = "Query\n\n :TABLE:CARACTER.db | NO_ADH | NOM_CAR | VALEUR_CAR |\n"
i = 0
SCAN TCCar :
QRS = QRS +" | "
+iif(i=nbLigne-1,"_J"+string(i),"_J"+string(i)+", _J"+string(i+1))
+" | \""+TCcar.NOM_CAR+"\" | \""+TCcar.VALEUR_CAR+"\" |\n"
i = i+1
ENDSCAN
QRS = QRS+"\n\n :PRIVE:ADHNO.DB | NO_ADH |\n INSERER | _J0 |\n\nEndQuery"
if not QRS.executeQBEstring()
then
errorShow()
return
endif
IX-B-2. Linéariser un sous-ensemble…▲
Comment faire pour linéariser dans un seul champ, l'ensemble des lignes d'une table fille ?
Reprenons notre base de test avec nos clients et leurs caractéristiques (paragraphe 9.2.1). Nous désirons obtenir en final une table avec 2 champs : NO_CLI et CAR_CLI, ce dernier champ regroupant l'ensemble des caractéristiques de nos clients :
La construction d'une telle réponse paraît simple a priori, mais de nombreux écueils sont à éviter. Par exemple comment faire si le nombre de lignes de la table fille est fortement variable, notamment allant de 0 à n ? 6 requêtes sont nécessaires, dont une répétitive…
Voici l'algorithme que j'utilise pour traiter ce problème :
- Création de la table réponse : les deux champs de la réponse plus un champ de comptage auto-incrémenté ;
- Alimentation de la table réponse avec l'ensemble des lignes de la table fille, mais avec les différents champs concaténés ;
- Calcul du nombre de lignes par client ;
- Récupération du nombre maximum de lignes par client de la table fille ;
- bBoucle sur les n lignes ;
- Ajout, par client, de la ligne d'ordre i+1 des caractéristiques à la ligne 1, si la ligne d'ordre i+1 contient une donnée ;
- Suppression par client, des lignes d'ordre supérieur à 1 ;
- Jointure externe avec la table client pour récupérer les clients qui n'ont aucune caractéristique.
Exemple 74 : qui se traduit en code par
var
RQ query
T Table
TC Tcursor
TCctc Tcursor
TCrep Tcursor
s string
endvar
; création de la table réponse : les deux champs de la réponse plus un champ
; de comptable en auto-incrémenté
T =
create ":PRIVE:REPCTC.DB"
with
"NO_CLI" : "I",
"NO_CAR" : "+",
"CAR_CLI" : "A255"
key "NO_CLI", "NO_CAR"
endCreate
; alimentation de la table réponse avec l'ensemble des lignes de la table fille,
; mais avec les différents champs concaténés
RQ =
Query
OPTIONS: NO AUXILIARY TABLES
:PRIVE:Repctc.db | NO_CLI | CAR_CLI |
INSERER | _jointure1 | _n+" : "+_v |
:TRAVAIL:CARACTER.db | NO_CLI | NOM_CAR | VALEUR_CAR |
| _jointure1 | _n | _v |
EndQuery
RQ.executeQBE()
; calcul du nombre de lignes par client
RQ =
Query
:PRIVE:Repctc.db | NO_CLI |
| Check calc count all |
EndQuery
RQ.executeQBE(":PRIVE:REPCTCn")
; récupération du nombre maximum de lignes par client de la table fille
RQ =
Query
:PRIVE:REPCTCn.DB | Nombre de NO_CLI |
| calc max |
EndQuery
RQ.executeQBE(TCrep)
nLines = longint(TCrep.(1))
TCrep.close()
; boucle sur les n lignes
For i from 1 to nLines-2
RQ =
; ajout, par client, de la ligne d'ordre i+1 des caractéristiques à la ligne 1, si
; la ligne d'ordre i+1 contient une donnée
Query
OPTIONS: NO AUXILIARY TABLES
:PRIVE:Repctc.db | NO_CLI | NO_CAR | CAR_CLI |
| _j1 | _n | _c1, remplacerPar _c1+"\n"+_c2 |
| _j1 | _n+~i | SAUF BLANC, _c2 |
EndQuery
RQ.executeQBE()
EndFor
; suppression par client des lignes d'ordre supérieur à 1 par client
RQ =
Query
OPTIONS: NO AUXILIARY TABLES
:PRIVE:Repctc.db | NO_CLI | NO_CAR |
| _n | _i |
SUPPRIMER | _n | >_i |
EndQuery
RQ.executeQBE()
; jointure externe avec la table client pour récupérer les clients qui n'ont
; aucune caractéristique
RQ =
Query
:TRAVAIL:CLIENT.db | NO_CLI |
| Check _j1! |
:PRIVE:Repctc.db | NO_CLI | CAR_CLI |
| _j1 | Check |
EndQuery
RQ.executeQBE(":PRIVE:RepCtc")
IX-B-3. Linéariser un sous-ensemble en un nombre fini de champs▲
Autre problème, celui de la dénormalisation d'une base de données…
Exemple : nous désirons présenter les ventes de l'année 1998 par trimestre et par clients. Un premier jet nous donne la requête et la présentation suivante :
Mais souvent les utilisateurs désirent obtenir un tableau avec les 4 trimestres en ligne…
Une seconde idée pourrait nous faire penser que la requête suivante est correcte :
Hélas il n'en est rien. À l'exécution vous verrez avec horreur que certains totaux sont multipliés par 2 d'autres par 3 ou 4, et d'autres enfin jusqu'à 8 ! Amusez-vous donc à trouver pourquoi (je vous mets sur la piste : comptez le nombre de lignes analysées dans la table des commandes…)
Essayons autre chose : rajoutons l'opérateur unique dans le calcul des sommes. Ainsi les doublons de montant de factures égales ne seront pas pris en compte n fois…
Cette nouvelle manière semble bien plus prometteuse…
Hélas elle est à nouveau entachée d'une erreur grave : l'un de nos clients peut parfaitement avoir commandé dans le même trimestre pour le même montant de facture… C'est d'ailleurs le cas de notre client n° 5, et par conséquent le total de son trimestre 3 devrait être le double…
Je n'ai en fait pas trouvé comment faire en une seule requête. Peut-être avez-vous un tuyau à me donner ?
Exemple 75 : en fait le plus simple, consiste à le faire en 2 requêtes :
La première (1ComTrim.qbe) produisant une table réponse de nom RepTrim.DB.
IX-C. À la recherche du vide▲
IX-C-1. Les absents, levez le doigt !▲
Comment retrouver les clients absents d'une table ?
Bien entendu nous allons utiliser le principe de la jointure externe que nous avons vu au chapitre 5, paragraphe 5.2…
Exemple 76 : il s'agit de joindre de la table des caractéristiques, les n° de clients, même pour ceux qui n'existent pas et de sélectionner ceux pour lesquels le nombre d'occurrences dans la table client du numéro de client est inférieur à 1 (ou encore égal à zéro…)
IX-C-2. Les présents, avez-vous des absences ?▲
Comment rechercher tous les clients qui n'ont pas une caractéristique commune ?
Il existe différentes techniques pour résoudre ce problème. La première et la plus drastique consiste à effectuer une sélection globale des données puis à supprimer les enregistrements qui contiennent la valeur à rejeter.
Exemple 77 : ne pas sélectionner les clients dont l'état est AU FOYER ?
Est-il possible de faire le travail en une seule requête ?
Exemple 78 : bien entendu, en utilisant une jointure externe associée à un critère de décompte d'occurrences.
Reposons la question autrement : je désire sélectionner les clients dont le nombre de fois où la donnée AU FOYER apparait 0 (zéro) fois dans la réponse…
C'est quand même beaucoup plus élégant… (et plus rapide !)
IX-C-3. Les absents, devenez présents !▲
Et maintenant, poussons le vice un peu plus loin : comment insérer dans une table des enregistrements manquants ?
Dans notre table CARACTER nous avons des n° de clients qui vont jusqu'à 109… Or dans notre table client, les clients sont référencés jusqu'au n° 100. Il manque donc des clients. Comment faire pour les réinsérer dans la table client ?
Exemple 79 : la solution en une seule requête est assez vicieuse. Je vous la livre brute :
Jointure externe plus une ligne d'insertion. Mais pourquoi donc cette coche dans Caracter.db NO_CLI ? Me demandez pas pourquoi. Cela marche. Cependant un petit conseil, cette requête va généré trois tables en plus de modifier la table client ! En effet, vous allez créer une table de modification, une table d'erreur de modification, mais aussi une table du fait du champ coché… Pour éviter de pénaliser le temps de traitement de cette requête, je vous conseille fortement d'activer le mode "requêtes rapide" dans la boîte de dialogue des propriétés de la requête, onglet QBE.
IX-C-4. Rechercher les présences, sauf ce jour-là…▲
Comment rechercher des clients possédant 2 caractéristiques, mais surtout par une troisième ?
A priori, le problème paraît simple. Mais la requête évidente ne marche pas :
En fait le client 38 est patron !
En rajoutant les données de caractéristiques, la réponse se présente déjà mieux…
Exemple 80 : mais de manière générale, la bonne formulation de cette requête est :
IX-D. Problèmes de date▲
IX-D-1. Calculer l'âge▲
Comment calculer l'âge en fonction d'une date de naissance au jour d'aujourd'hui ?
Dans le champ DATE_NAISSANCE :
_dn, calc (CEJOUR-_dn)/(365+1/4) as AGE
Exemple 81 :
IX-D-2. Calculer les anniversaires…▲
Comment trouver les anniversaires à fêter dans les 15 prochains jours ?
Cette requête très complexe en Paradox nécessite l'adjonction d'un champ supplémentaire dans la table originelle de type entier que nous appellerons AGE. Il n'est bien entendu pas question de stocker l'âge des clients puisque nous pouvons le déduire de la date de naissance, mais simplement d'y reporter temporairement le calcul d'âge afin d'obtenir notre réponse. 3 requêtes qui s'enchaînent sont alors nécessaires.
Exemple 82 :
La première requête met à jour l'âge des clients par rapport à la date courante et la stocke sous la forme d'un nombre entier.
La seconde requête calcule le nombre de jours écoulés depuis le dernier anniversaire en le déduisant de l’âge décimal recalculé à l'aide de la même formule et de l'âge entier stocké dans la table.
La dernière permet de sélectionner les personnes qui vont avoir leur anniversaire dans les n prochains jours, ici n ayant la valeur 15…
Mais pour les requêtes sur les dates, il est préférable d'utiliser le SQL et la fonction magique EXTRACT :
Exemple 82 : voici une requête qui extrait les n° des clients qui vont avoir leur anniversaire entre le 5 février et le 22 mars en ne partant que de la date de naissance…
SELECT
NO_CLI
FROM
Client
WHERE
((
35
<
(
EXTRACT
(
MONTH
FROM
DATE_NAISSANCE_CLI)-
1
)*
31
+
EXTRACT
(
DAY
FROM
DATE_NAISSANCE_CLI))
AND
((
EXTRACT
(
MONTH
FROM
DATE_NAISSANCE_CLI)-
1
)*
31
+
EXTRACT
(
DAY
FROM
DATE_NAISSANCE_CLI)
<=
84
))
Exemple 83 : bien entendu vous pouvez rendre la requête SQL ci-dessus parfaitement paramétrable.
Partons d'une date d et d'un intervalle i. Recherchons tous les clients dont les anniversaires vont être compris entre la date d et la date d + i (où i est un nombre de jours). Calculons MinDay et MaxDay de la façon suivante :
MinDay = (mois(d)-1)*31+jour(d)
MaxDay = MinDay + i + 1
Alors la requête s'écrit dorénavant :
SELECT
NO_CLI
FROM
Client
WHERE
((
~
minDay <
(
EXTRACT
(
MONTH
FROM
DATE_NAISSANCE_CLI)-
1
)*
31
+
EXTRACT
(
DAY
FROM
DATE_NAISSANCE_CLI))
AND
((
EXTRACT
(
MONTH
FROM
DATE_NAISSANCE_CLI)-
1
)*
31
+
EXTRACT
(
DAY
FROM
DATE_NAISSANCE_CLI)
<=
~
MaxDay ))
Malheureusement il manque au moteur BDE de Paradox version SQL une fonction permettant de récupérer l'heure courante ou la date courante alors que celle-ci est présente dans le QBE !
X. Annexes▲
X-A. BENCHMARK QBE / SQL sous BDE▲
Que faut-il utiliser avec le BDE sur des tables Paradox ? QBE ou SQL ??? Le match est serré, mais SQL l'emporte…
X-B. APPENDICE A : OPÉRATEURS EN ANGLAIS▲
INSERE |
INSERT |
SUPPRIME |
DELETE |
RemplacerPar |
ChangeTo |
GrouperPar |
GroupBy |
Blanc |
Blank |
Ou |
Or |
Et |
And |
Sauf |
Not |
ENSEMBLE |
SET |
Comme |
Like |
Cejour |
Today |
En |
As |
Toutes |
All |
Calc |
Calc |
Unique |
Unique |
Moyenne |
Average |
Min |
Min |
Max |
Max |
Nombre |
Count |
Somme |
Sum |
RienQue |
Only |
Aucun |
No |
Chacun |
Every |
Identique |
Exactly |
Check |
Check |
CheckDesc |
CheckDesc |
CheckGroup |
CheckGroup |
CheckPlus |
CheckPlus |
NOTA : même dans votre Paradox version française, vous pouvez utiliser les mots clefs anglais. Cela est impératif si vous devez faire tourner vos requêtes dans l'environnement d'un autre pays (US par exemple, ou application internationale).