Documentation PostgreSQL 7.4.29 | ||||
---|---|---|---|---|
Précédent | Arrière rapide | Chapitre 33. Extension de SQL | Avance rapide | Suivant |
Une définition d'opérateur PostgreSQL peut inclure plusieurs clauses optionnelles qui donnent au système des informations utiles sur le comportement de l'opérateur. Ces clauses devraient être fournies chaque fois que c'est utile car elles peuvent considérablement accélérer l'exécution des requêtes utilisant cet opérateur. Mais si vous le faites, vous devez être sûr de leur justesse ! L'usage incorrect d'une clause d'optimisation peut entraîner un arrêt brutal du processus serveur, des sorties subtilement fausses ou d'autres effets pervers. Vous pouvez toujours abandonner une clause d'optimisation si vous n'êtes pas sûr d'elle ; la seule conséquence est un possible ralentissement des requêtes.
Des clauses additionnelles d'optimisation pourront être ajoutées dans les futures versions de PostgreSQL. Celles décrites ici sont toutes celles que cette version comprend.
Si elle est fournie, la clause COMMUTATOR désigne un opérateur qui est le commutateur de l'opérateur en cours de définition. Nous disons qu'un opérateur A est le commutateur de l'opérateur B si (x A y) est égal à (y B x) pour toute valeur possible de x, y. Notez que B est aussi le commutateur de A. Par exemple, les opérateurs < et > pour un type particulier de données sont habituellement des commutateurs l'un pour l'autre, et l'opérateur + est habituellement commutatif avec lui-même. Mais l'opérateur - n'est habituellement commutatif avec rien.
Le type de l'opérande gauche d'un opérateur commuté est le même que l'opérande droit de son commutateur, et vice versa. Aussi PostgreSQL n'a besoin que du nom de l'opérateur commutateur pour consulter le commutateur, et c'est tout ce qui doit être fourni à la clause COMMUTATOR .
Vous avez juste à définir un opérateur auto-commutateur. Mais les choses sont un peu plus compliquées quand vous définissez une paire de commutateurs : comment peut-on définir la référence du premier au second alors que ce dernier n'est pas encore défini ? Il y a deux solutions à ce problème :
Une façon d'opérer est d'omettre la clause COMMUTATOR dans le premier opérateur que vous définissez et ensuite d'en insérer une dans la définition du second opérateur. Puisque PostgreSQL sait que les opérateurs commutatifs vont par paire, quand il voit la seconde définition, il retourne instantanément remplir la clause COMMUTATOR manquante dans la première définition.
L'autre façon, plus directe, est de simplement inclure les clauses COMMUTATOR dans les deux définitions. Quand PostgreSQL traite la première définition et réalise que la clause COMMUTATOR se réfère à un opérateur inexistant, le système va créer une entrée provisoire pour cet opérateur dans le catalogue système. Cette entrée sera pourvue seulement de données valides pour le nom de l'opérateur, les types d'opérande droit et gauche et le type du résultat, puisque c'est tout ce que PostgreSQL peut déduire à ce point. La première entrée du catalogue pour l'opérateur sera liée à cette entrée provisoire. Plus tard, quand vous définirez le second opérateur, le système mettra à jour l'entrée provisoire avec les informations additionnelles fournies par la seconde définition. Si vous essayez d'utiliser l'opérateur provisoire avant qu'il ne soit complété, vous aurez juste un message d'erreur.
La clause NEGATOR dénomme un opérateur qui est l'opérateur de négation de l'opérateur en cours de définition. Nous disons qu'un opérateur A est l'opérateur de négation de l'opérateur B si tous les deux renvoient des résultats booléens et si (x A y) est égal à NOT (x B y) pour toutes les entrées possible x, y. Notez que B est aussi l'opérateur de négation de A. Par exemple, < et >= forment une paire d'opérateurs de négation pour la plupart des types de données. Un opérateur ne peut jamais être validé comme son propre opérateur de négation .
Au contraire des commutateurs, une paire d'opérateurs unaires peut être validée comme une paire d'opérateurs de négation réciproques ; ce qui signifie que (A x) est égal à NOT (B x) pour tout x ou l'équivalent pour les opérateurs unaires à droite.
L'opérateur de négation d'un opérateur doit avoir les mêmes types d'opérandes gauche et/ou droit que l'opérateur à définir comme avec COMMUTATOR. Seul le nom de l'opérateur doit être donné dans la clause NEGATOR.
Définir un opérateur de négation est très utile pour l'optimiseur de requêtes car il permet de simplifier des expressions telles que NOT (x = y) en x <> y. Ceci arrive souvent parce que les opérations NOT peuvent être insérées à la suite d'autres réarrangements.
Des paires d'opérateurs de négation peuvent être définies en utilisant la même méthode que pour les commutateurs.
La clause RESTRICT, si elle est invoquée, nomme une fonction d'estimation de sélectivité de restriction pour cet opérateur (notez que c'est un nom de fonction, et non pas un nom d'opérateur). Les clauses RESTRICT n'ont de sens que pour les opérateurs binaires qui renvoient un type boolean. Un estimateur de sélectivité de restriction repose sur l'idée de prévoir quelle fraction des lignes dans une table satisfera une condition de clause WHERE de la forme
colonne OP constante
pour l'opérateur courant et une valeur constante particulière. Ceci aide l'optimiseur en lui donnant une idée du nombre de lignes qui sera éliminé par les clauses WHERE qui ont cette forme (vous pouvez vous demander, qu'arrivera-t-il si la constante est à gauche ? hé bien, c'est une des choses à laquelle sert le COMMUTATOR...).
L'écriture de nouvelles fonctions d'estimation de restriction de sélectivité est éloignée des objectifs de ce chapitre mais, heureusement, vous pouvez habituellement utiliser un des estimateurs standards du système pour beaucoup de vos propres opérateurs. Voici les estimateurs standards de restriction :
eqsel pour = |
neqsel pour <> |
scalarltsel pour < ou <= |
scalargtsel pour > ou >= |
Vous pouvez fréquemment vous en sortir à bon compte en utilisant soit
eqsel
ou neqsel
pour des
opérateurs qui ont une très grande ou une très faible sélectivité, même
s'ils ne sont pas réellement égalité ou inégalité. Par exemple, les
opérateurs géométriques d'égalité approchée utilisent
eqsel
en supposant habituellement qu'ils ne
correspondent qu'à une petite fraction des entrées dans une table.
Vous pouvez utiliser scalarltsel
et scalargtsel
pour des comparaisons de types de données qui possèdent un moyen de
conversion en scalaires numériques pour les comparaisons de rang. Si
possible, ajoutez le type de données à ceux acceptés par la fonction
convert_to_scalar()
dans
src/backend/utils/adt/selfuncs.c (finalement, cette
fonction devrait être remplacée par des fonctions pour chaque type de
données identifié grâce à une colonne du catalogue système
pg_type
; mais cela n'a pas encore été fait). Si vous
ne faites pas ceci, les choses fonctionneront mais les estimations de
l'optimiseur ne seront pas aussi bonnes qu'elles pourraient l'être.
D'autres fonctions d'estimation de sélectivité conçues pour les opérateurs
géométriques sont placées dans
src/backend/utils/adt/geo_selfuncs.c :
areasel
, positionsel
et
contsel
. Lors de cette rédaction, ce sont seulement
des fragments mais vous pouvez vouloir les utiliser (ou mieux les
améliorer).
La clause JOIN, si elle est invoquée, nomme une fonction d'estimation de sélectivité de jointure pour l'opérateur (notez que c'est un nom de fonction, et non pas un nom d'opérateur). Les clauses JOIN n'ont de sens que pour les opérateurs binaires qui renvoient un type boolean. Un estimateur de sélectivité de jointure repose sur l'idée de prévoir quelle fraction des lignes dans une paire de tables satisfera une condition de clause WHERE de la forme
table1.colonne1 OP table2.colonne2
pour l'opérateur courant. Comme pour la clause RESTRICT, ceci aide considérablement l'optimiseur en lui indiquant parmi plusieurs séquences de jointure possibles laquelle prendra vraisemblablement le moins de travail.
Comme précédemment, ce chapitre n'essaiera pas d'expliquer comment écrire une fonction d'estimation de sélectivité de jointure mais suggérera simplement d'utiliser un des estimateurs standard s'il est applicable :
eqjoinsel pour = |
neqjoinsel pour <> |
scalarltjoinsel pour < ou <= |
scalargtjoinsel pour > ou >= |
areajoinsel pour des comparaisons basées sur une aire 2D |
positionjoinsel pour des comparaisons basées sur une position 2D |
contjoinsel pour des comparaisons basées sur un appartenance 2D |
La clause HASHES indique au système qu'il est permis d'utiliser la méthode de jointure-découpage pour une jointure basée sur cet opérateur. HASHES n'a de sens que pour un opérateur binaire qui renvoie un boolean et en pratique l'opérateur égalité serait mieux approprié pour certains types de données
La jointure-découpage repose sur l'hypothèse que l'opérateur de jointure peut seulement renvoyer la valeur vrai pour des paires de valeurs droite et gauche qui correspondent au même code de découpage. Si deux valeurs sont placées dans deux différents paquets (<< buckets >>), la jointure ne pourra jamais les comparer avec la supposition implicite que le résultat de l'opérateur de jointure doit être faux. Ainsi, il n'y a aucun sens à spécifier HASHES pour des opérateurs qui ne représentent pas l'égalité.
Pour être marqué HASHES, l'opérateur de jointure doit apparaître dans une classe d'opérateurs d'index de découpage. Ceci n'est pas rendu obligatoire quand vous créez l'opérateur, puisque évidemment la classe référençant l'opérateur peut ne pas encore exister. Mais les tentatives d'utilisation de l'opérateur dans les jointure-découpage échoueront à l'exécution si une telle classe d'opérateur n'existe pas. Le système a besoin de la classe d'opérateur pour définir la fonction de découpage spécifique au type de données d'entrée de l'opérateur. Bien sûr, vous devez également fournir une fonction de découpage appropriée avant de pouvoir créer la classe d'opérateur.
On doit apporter une grande attention à la préparation des fonctions de
découpage parce qu'il y a des processus dépendants de la machine qui
peuvent ne pas faire les choses correctement. Par exemple, si votre type de
données est une structure dans laquelle peuvent se trouver des bits de
remplissage sans intérêt, vous ne pouvez pas simplement passer la structure
complète à la fonction hash_any
(à moins d'écrire vos autres
opérateurs et fonctions de façon à s'assurer que les bits inutilisés sont
toujours zéro, ce qui est la stratégie recommandée). Un autre exemple est
fourni sur les machines qui respectent le standard de virgule-flottante
IEEE, le zéro négatif et le zéro positif sont des valeurs
différentes (les motifs de bit sont différents) mais ils sont définis pour
être égaux. Si une valeur flottante peut contenir un zéro négatif, alors
une étape supplémentaire est nécessaire pour s'assurer qu'elle génère la
même valeur de découpage qu'un zéro positif.
Note : La fonction sous-jacente à un opérateur de jointure-découpage doit être marquée immuable ou stable. Si elle est volatile, le système n'essaiera jamais d'utiliser l'opérateur pour une jointure hachage.
Note : Si un opérateur de jointure-hachage a une fonction sous-jacente marquée stricte, la fonction doit également être complète : cela signifie qu'elle doit renvoyer TRUE ou FALSE, jamais NULL, pour n'importe quelle double entrée non nulle. Si cette règle n'est pas respectée, l'optimisation de découpage des opérations IN peut générer des résultats faux (spécifiquement, IN devrait renvoyer FALSE quand la réponse correcte devrait être NULL ; ou bien il devrait renvoyer une erreur indiquant qu'il ne s'attendait pas à un résultat NULL).
La clause MERGES, si elle est présente, indique au système qu'il est permis d'utiliser la méthode de jointure-union pour une jointure basée sur cet opérateur. MERGES n'a de sens que pour un opérateur binaire qui renvoie un boolean, et en pratique, cet opérateur doit représenter l'égalité pour des types de données ou des paires de types de données.
La jointure-union est fondée sur le principe d'ordonner les tables gauche et droite et ensuite de les comparer en parallèle. Ainsi, les deux types de donnée doivent être capable d'être pleinement ordonnées, et l'opérateur de jointure doit pouvoir réussir seulement pour des paires de valeur tombant à la << même place >> dans l'ordre de tri. En pratique, cela signifie que l'opérateur de jointure doit se comporter comme l'opérateur égalité. Mais contrairement à la jointure-hachage, où il vaut mieux que les types de donnée droite et gauche soit les mêmes (ou au moins soient bitwise équivalent), il est possible de faire une jointure-union sur deux types de données distincts, tant qu'ils sont logiquement compatibles. Par exemple, l'opérateur d'égalité smallint-contre-integer est susceptible d'opérer une jointure-union. Nous avons seulement besoin d'opérateurs de tri qui organisent les deux types de données en séquences logiquement comparables.
L'exécution d'une jointure-union exige que le système soit capable d'identifier quatre opérateurs rattachés à l'opérateur de jointure-union : la comparaison less-than pour le type de donnée de l'opérande gauche, la comparaison less-than pour le type de donnée de l'opérande droit, la comparaison less-than entre les deux types de donnée et la comparaison greater-than entre les deux types de donnée (il y a en fait quatre opérateurs distincts si l'opérateur de jointure-union a deux types de données d'opérande différents ; mais quand les types d'opérande sont les mêmes, les trois opérateurs less-than sont tous le même opérateur). Il est possible de spécifier ces opérateurs individuellement par leur nom, comme les options respectives SORT1, SORT2, LTCMP et GTCMP. Le système remplira respectivement par défaut les noms <, <, <, > si n'importe lequel d'entre eux est omis quand MERGES est spécifié. De même, MERGES sera supposé être indiqué si n'importe laquelle de ces quatre options apparaît, il est donc possible de seulement spécifier quelques-unes de ces options et de laisser le système compléter le reste.
Les types de données des opérandes des quatre opérateurs de comparaison peuvent être déduits des types d'opérandes de l'opérateur de jointure-union, aussi, exactement comme avec COMMUTATOR, seuls les noms d'opérateurs ont besoin d'être donnés dans ces clauses. À moins que vous ne fassiez des choix particuliers de noms d'opérateurs, il suffit d'écrire MERGES et laisser le système remplir les détails (comme avec COMMUTATOR et NEGATOR, le système est capable de faire des entrées d'opérateur fictives si il vous arrive de définir l'opérateur égalité avant les autres).
Il existe des restrictions additionnelles sur les opérateurs que vous marquez comme jointure-union. Ces restrictions ne sont pas actuellement contrôlées par la commande CREATE OPERATOR mais des erreurs peuvent intervenir lors de l'utilisation de l'opérateur si un des points suivants n'est pas vérifié :
Un opérateur d'égalité capable de jointure-union doit avoir un commutateur capable de jointure-union (qui peut être lui-même si les deux types de donnée d'opérande sont les mêmes, ou un opérateur d'égalité apparenté si ils sont différents).
S'il existe un opérateur capable de jointure-union reliant deux types de données A et B, et un autre opérateur capable de jointure-union reliant B à un troisième type de donnée C, alors A et C doivent aussi avoir un opérateur capable de jointure-union ; en d'autres mots, avoir un opérateur de jointure-union doit être une propriété transitive.
Des résultats bizarre apparaîtront lors de l'exécution si les quatre opérateurs de comparaison que vous nommez ne trient pas les valeurs de façon compatible.
Note : La fonction sous jacente à un opérateur de jointure-union doit être marquée immuable ou stable. Si elle est volatile, le système n'essaiera jamais d'utiliser l'opérateur pour une jointure union.
Note : Dans les versions de PostgreSQL antérieure à 7.3, MERGES n'était pas disponible : pour faire un opérateur de jointure union, on devait explicitement écrire SORT1 et SORT2. De plus, les options LTCMP et GTCMPn'existaient pas ; les noms de ces opérateurs ont été rattachés respectivement à < et >.
Précédent | Sommaire | Suivant |
Opérateurs définis par l'utilisateur | Niveau supérieur | Interfacer des extensions d'index |