PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

ORDER BY ... Clause USING dans PostgreSQL

Un exemple très simple serait :

> SELECT * FROM tab ORDER BY col USING <

Mais c'est ennuyeux, car ce n'est rien que vous ne puissiez obtenir avec le traditionnel ORDER BY col ASC .

De plus, le catalogue standard ne mentionne rien d'excitant sur les fonctions/opérateurs de comparaison étranges. Vous pouvez en obtenir une liste :

    > SELECT amoplefttype::regtype, amoprighttype::regtype, amopopr::regoper 
      FROM pg_am JOIN pg_amop ON pg_am.oid = pg_amop.amopmethod 
      WHERE amname = 'btree' AND amopstrategy IN (1,5);

Vous remarquerez qu'il y a surtout < et > fonctions pour les types primitifs comme integer , date etc et encore plus pour les tableaux et les vecteurs et ainsi de suite. Aucun de ces opérateurs ne vous aidera à obtenir une commande personnalisée.

Dans la plupart les cas où une commande personnalisée est requise, vous pouvez vous en sortir en utilisant quelque chose comme ... ORDER BY somefunc(tablecolumn) ...somefunc mappe les valeurs de manière appropriée. Parce que cela fonctionne avec toutes les bases de données, c'est aussi le moyen le plus courant. Pour des choses simples, vous pouvez même écrire une expression au lieu d'une fonction personnalisée.

Changer de vitesse

ORDER BY ... USING a du sens dans plusieurs cas :

  • La commande est si rare que le somefunc l'astuce ne marche pas.
  • Vous travaillez avec un type non primitif (comme point , circle ou des nombres imaginaires) et vous ne voulez pas vous répéter dans vos requêtes avec des calculs étranges.
  • L'ensemble de données que vous souhaitez trier est si volumineux que la prise en charge par un index est souhaitée, voire requise.

Je vais me concentrer sur les types de données complexes :il existe souvent plusieurs façons de les trier de manière raisonnable. Un bon exemple est point :Vous pouvez les "ordonner" par la distance à (0,0), ou par x d'abord, puis par y ou juste par y ou tout ce que vous voulez.

Bien sûr, PostgreSQL a opérateurs prédéfinis pour point :

    > CREATE TABLE p ( p point );
    > SELECT p <-> point(0,0) FROM p;

Mais aucun d'entre eux est déclaré utilisable pour ORDER BY par défaut (voir ci-dessus) :

    > SELECT * FROM p ORDER BY p;
    ERROR:  could not identify an ordering operator for type point
    TIP:  Use an explicit ordering operator or modify the query.

Opérateurs simples pour point sont les opérateurs "inférieur" et "au-dessus" <^ et >^ . Ils comparent simplement les y partie de la pointe. Mais :

    >  SELECT * FROM p ORDER BY p USING >^;
    ERROR: operator > is not a valid ordering operator
    TIP: Ordering operators must be "<" or ">" members of __btree__ operator families.

ORDER BY USING nécessite un opérateur avec une sémantique définie :évidemment, il doit s'agir d'un opérateur binaire, il doit accepter le même type d'arguments et il doit renvoyer un booléen. Je pense qu'il doit aussi être transitif (si a btree -ordre d'index. Ceci explique les messages d'erreur étranges contenant la référence à btree .

ORDER BY USING ne nécessite pas seulement un seul opérateur à définir mais une classe d'opérateur et une famille d'opérateurs . Alors qu'on pourrait mettre en œuvre le tri avec un seul opérateur, PostgreSQL essaie de trier efficacement et de minimiser les comparaisons. Par conséquent, plusieurs opérateurs sont utilisés même lorsque vous n'en spécifiez qu'un - les autres doivent respecter certaines contraintes mathématiques - j'ai déjà mentionné la transitivité, mais il y en a d'autres.

Changer de vitesse

Définissons quelque chose d'approprié :un opérateur pour les points qui ne compare que le y partie.

La première étape consiste à créer une famille d'opérateurs personnalisée qui peut être utilisée par le btree méthode d'accès à l'index. voir

    > CREATE OPERATOR FAMILY xyzfam USING btree;   -- superuser access required!
    CREATE OPERATOR FAMILY

Ensuite, nous devons fournir une fonction de comparaison qui renvoie -1, 0, +1 lors de la comparaison de deux points. Cette fonction VA être appelé en interne !

    > CREATE FUNCTION xyz_v_cmp(p1 point, p2 point) RETURNS int 
      AS $$BEGIN RETURN btfloat8cmp(p1[1],p2[1]); END $$ LANGUAGE plpgsql;
    CREATE FUNCTION

Ensuite, nous définissons la classe d'opérateurs pour la famille. Voir le manuel pour une explication des nombres.

    > CREATE OPERATOR CLASS xyz_ops FOR TYPE point USING btree FAMILY xyzfam AS 
        OPERATOR 1 <^ ,
        OPERATOR 3 ?- ,
        OPERATOR 5 >^ ,
        FUNCTION 1 xyz_v_cmp(point, point) ;
    CREATE OPERATOR CLASS

Cette étape combine plusieurs opérateurs et fonctions et définit également leur relation et leur signification. Par exemple OPERATOR 1 signifie :il s'agit de l'opérateur pour less-than tests.

Maintenant les opérateurs <^ et >^ peut être utilisé dans ORDER BY USING :

> INSERT INTO p SELECT point(floor(random()*100), floor(random()*100)) FROM generate_series(1, 5);
INSERT 0 5
> SELECT * FROM p ORDER BY p USING >^;
    p    
---------
 (17,8)
 (74,57)
 (59,65)
 (0,87)
 (58,91)

Voilà - trié par y .

Pour résumer : ORDER BY ... USING est un regard intéressant sous le capot de PostgreSQL. Mais rien dont vous aurez besoin de sitôt à moins que vous ne travailliez dans très domaines spécifiques de la technologie des bases de données.

Un autre exemple peut être trouvé dans la documentation Postgres. avec le code source pour l'exemple ici et ici. Cet exemple montre également comment créer les opérateurs.