PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

ORDER BY ... USING clausola in PostgreSQL

Un esempio molto semplice potrebbe essere:

> SELECT * FROM tab ORDER BY col USING <

Ma questo è noioso, perché non è niente che non puoi ottenere con il tradizionale ORDER BY col ASC .

Anche il catalogo standard non menziona nulla di eccitante su strane funzioni/operatori di confronto. Puoi ottenerne un elenco:

    > 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);

Noterai che ci sono principalmente < e > funzioni per tipi primitivi come integer , date ecc e altri ancora per array e vettori e così via. Nessuno di questi operatori ti aiuterà a ottenere un ordine personalizzato.

Nella maggior parte nei casi in cui è richiesto un ordine personalizzato puoi cavartela usando qualcosa come ... ORDER BY somefunc(tablecolumn) ... dove somefunc mappa i valori in modo appropriato. Poiché funziona con ogni database, questo è anche il modo più comune. Per cose semplici puoi persino scrivere un'espressione invece di una funzione personalizzata.

Cambio di marcia

ORDER BY ... USING ha senso in diversi casi:

  • L'ordine è così raro che somefunc il trucco non funziona.
  • Lavori con un tipo non primitivo (come point , circle o numeri immaginari) e non vuoi ripeterti nelle tue domande con strani calcoli.
  • Il set di dati che vuoi ordinare è così grande che il supporto di un indice è desiderato o addirittura richiesto.

Mi concentrerò sui tipi di dati complessi:spesso c'è più di un modo per ordinarli in modo ragionevole. Un buon esempio è point :puoi "ordinarli" in base alla distanza fino a (0,0) o in base a x prima, poi da y o semplicemente da y o qualsiasi altra cosa tu voglia.

Naturalmente, PostgreSQL ha operatori predefiniti per point :

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

Ma nessuno di essi è dichiarato utilizzabile per ORDER BY per impostazione predefinita (vedi sopra):

    > 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.

Operatori semplici per point sono gli operatori "sotto" e "sopra" <^ e >^ . Confrontano semplicemente il y parte del punto. Ma:

    >  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 richiede un operatore con semantica definita:Ovviamente deve essere un operatore binario, deve accettare come argomenti lo stesso tipo e deve restituire booleano. Penso che debba essere anche transitivo (se a btree -ordinamento dell'indice. Questo spiega gli strani messaggi di errore che contengono il riferimento a btree .

ORDER BY USING richiede inoltre non solo un operatore da definire ma una classe operatore e una famiglia di operatori . Mentre uno potrebbe implementa l'ordinamento con un solo operatore, PostgreSQL cerca di ordinare in modo efficiente e ridurre al minimo i confronti. Pertanto, vengono utilizzati più operatori anche quando ne specifichi uno solo - gli altri devono rispettare determinati vincoli matematici - ho già menzionato la transitività, ma ce ne sono di più.

Cambio di marcia

Definiamo qualcosa di adatto:un operatore per punti che confronta solo il y parte.

Il primo passaggio consiste nel creare una famiglia di operatori personalizzata che può essere utilizzata da btree metodo di accesso all'indice. vedi

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

Successivamente dobbiamo fornire una funzione di confronto che restituisce -1, 0, +1 quando si confrontano due punti. Questa funzione SARA' essere chiamato internamente!

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

Quindi definiamo la classe dell'operatore per la famiglia. Vedere il manuale per una spiegazione dei numeri.

    > 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

Questo passaggio combina diversi operatori e funzioni e ne definisce anche la relazione e il significato. Ad esempio OPERATOR 1 significa:Questo è l'operatore per less-than test.

Ora gli operatori <^ e >^ può essere utilizzato in 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à - ordinato per y .

Per riassumere: ORDER BY ... USING è uno sguardo interessante sotto il cofano di PostgreSQL. Ma nulla di cui avrai bisogno a breve a meno che tu non lavori in molto aree specifiche della tecnologia dei database.

Un altro esempio può essere trovato nei documenti di Postgres. con il codice sorgente per l'esempio qui e qui. Questo esempio mostra anche come creare gli operatori.