2009-08-21 14 views
29

Provare per la prima volta Postgresql, proveniente da MySQL. Nella nostra applicazione Rails abbiamo un paio di posizioni con SQL in questo modo:Simulazione di ORDER BY FIELD() di MySQL in Postgresql

SELECT * FROM `currency_codes` ORDER BY FIELD(code, 'GBP', 'EUR', 'BBD', 'AUD', 'CAD', 'USD') DESC, name ASC 

Non ci volle molto per scoprire che questo non è supportato/consentito in PostgreSQL.

Qualcuno sa come simulare questo comportamento in Postgres o dobbiamo estrarre l'ordinamento nel codice?

Grazie

Peer

+3

Potrebbe essere utile spiegare cosa si desidera ottenere. Difficile da immaginare, non tutti conoscono MySQL :) –

+0

Great point depesz! Fondamentalmente un ordinamento personalizzato è ciò che stiamo cercando. La funzione FIELD ti consente di creare un set personalizzato con cui effettuare l'ordinamento. –

risposta

48

Ah, gahooa era così vicino:

SELECT * FROM currency_codes 
    ORDER BY 
    CASE 
    WHEN code='USD' THEN 1 
    WHEN code='CAD' THEN 2 
    WHEN code='AUD' THEN 3 
    WHEN code='BBD' THEN 4 
    WHEN code='EUR' THEN 5 
    WHEN code='GBP' THEN 6 
    ELSE 7 
    END,name; 
+1

Oops !! un po 'di dislessia in tarda serata ... Hai il mio voto! – gahooa

+4

Questo metodo non funziona quando si utilizza DISTINCT. Qualche altra idea per questo scenario? – Corey

+2

Non so perché funzioni, ma ho trovato un'alternativa. Se vuoi ordinare in ordine per j, a, k, e, allora ordina per id = e, id = k, id = a, id = j'. – jakeonrails

1

Si può fare questo ...

SELECT 
    ..., code 
FROM 
    tablename 
ORDER BY 
    CASE 
     WHEN code='GBP' THEN 1 
     WHEN code='EUR' THEN 2 
     WHEN code='BBD' THEN 3 
     ELSE 4 
    END 

Ma perché questi hardcoding nella query - non sarebbe un tavolo di supporto più appropriato?

-

Edit: capovolto intorno come per i commenti

+0

@gahooa, penso che tu abbia invertito il senso del "codice": il codice è l'abbreviazione di tre alfa, che l'OP desidera ordinare in modo non alfa. – pilcrow

+0

esattamente right pilcrow –

+0

Vorrei poter prendere il merito del perché l'SQL è così com'è, stiamo lavorando su un refactoring, ma ammetto che sto cercando la soluzione rapida adesso –

11

aggiornamento, ideare suggestione formidabile per @Tometzky.

Questo dovrebbe dare un MySQL FIELD() -alike funzione in pg 8.4:

-- SELECT FIELD(varnames, 'foo', 'bar', 'baz') 
CREATE FUNCTION field(anyelement, VARIADIC anyarray) RETURNS numeric AS $$ 
    SELECT 
    COALESCE(
    (SELECT i FROM generate_subscripts($2, 1) gs(i) 
     WHERE $2[i] = $1), 
    0); 
$$ LANGUAGE SQL STABLE 

Mea culpa, ma non riesco a verificare l'alto a 8.4 in questo momento; tuttavia, posso lavorare a ritroso a una versione "moralmente" equivalente che funziona nell'istanza 8.1 davanti a me:

-- SELECT FIELD(varname, ARRAY['foo', 'bar', 'baz']) 
CREATE OR REPLACE FUNCTION field(anyelement, anyarray) RETURNS numeric AS $$ 
    SELECT 
    COALESCE((SELECT i 
       FROM generate_series(1, array_upper($2, 1)) gs(i) 
       WHERE $2[i] = $1), 
      0); 
$$ LANGUAGE SQL STABLE 

più goffamente, è ancora possibile portabile utilizzare una (forse derivato) Tabella delle classifiche codice di valuta, in questo modo:

pg=> select cc.* from currency_codes cc 
    left join 
     (select 'GBP' as code, 0 as rank union all 
     select 'EUR', 1 union all 
     select 'BBD', 2 union all 
     select 'AUD', 3 union all 
     select 'CAD', 4 union all 
     select 'USD', 5) cc_weights 
    on cc.code = cc_weights.code 
    order by rank desc, name asc; 
code |   name 
------+--------------------------- 
USD | USA bits 
CAD | Canadian maple tokens 
AUD | Australian diwallarangoos 
BBD | Barbadian tridents 
EUR | Euro chits 
GBP | British haypennies 
(6 rows) 
1

Se si esegue questo spesso, aggiungere una nuova colonna e un/update trigger di pre-inserimento. Quindi imposti il ​​valore nella nuova colonna in base a questo trigger e ordine in questo campo. Puoi persino aggiungere un indice su questo campo.

+0

I trigger sono cattivi, mmkay? Evitare se possibile! –

+0

@WillSheppard Questo è sbagliato. Non possono essere usati senza cervello e spesso, ma semplicemente non si può dire che "I trigger sono cattivi" – jirigracik

10

Questo è penso che il modo più semplice:

create temporary table test (id serial, field text); 
insert into test(field) values 
    ('GBP'), ('EUR'), ('BBD'), ('AUD'), ('CAD'), ('USD'), 
    ('GBP'), ('EUR'), ('BBD'), ('AUD'), ('CAD'), ('USD'); 
select * from test 
order by field!='GBP', field!='EUR', field!='BBD', 
    field!='AUD', field!='CAD', field!='USD'; 
id | field 
----+------- 
    1 | GBP 
    7 | GBP 
    2 | EUR 
    8 | EUR 
    3 | BBD 
    9 | BBD 
    4 | AUD 
10 | AUD 
    5 | CAD 
11 | CAD 
    6 | USD 
12 | USD 
(12 rows) 

In PostgreSQL 8.4 è anche possibile utilizzare una (Funzione Variadica) function with variable number of arguments alla porta la funzione field.

+0

+1 per ordinare-bang e per il suggerimento 'VARIADIC', che cercherò di implementare. – pilcrow

3

In realtà la versione per Postgres 8.1 è un altro vantaggio.

Quando si chiama una funzione postgres non è possibile passare più di 100 parametri, quindi l'ordine può essere eseguito al massimo su 99 elementi.

Utilizzando la funzione utilizzando un array come secondo argomento invece di avere un argomento variadico basta rimuovere questo limite.

2

Basta definire la funzione FIELD e utilizzarla. È abbastanza facile da implementare. Il seguente dovrebbe funzionare in 8.4, in quanto ha unnest e finestre funzioni come row_number:

CREATE OR REPLACE FUNCTION field(text, VARIADIC text[]) RETURNS bigint AS $$ 
SELECT n FROM (
    SELECT row_number() OVER() AS n, x FROM unnest($2) x 
) numbered WHERE numbered.x = $1; 
$$ LANGUAGE 'SQL' IMMUTABLE STRICT; 

è anche possibile definire un'altra copia con la firma:

CREATE OR REPLACE FUNCTION field(anyelement, VARIADIC anyarray) RETURNS bigint AS $$ 

e lo stesso corpo, se si desidera supportare field() per qualsiasi tipo di dati.

+0

Penso che questa sia la migliore risposta, ma quando provo a creare la funzione, pgadmin dice: "il tipo di corrispondenza mancata nella funzione dichiarata per restituire bigint, l'istruzione finale della funzione deve essere selezionata/inserita" idee? – chrismarx

+0

@chrismarx versione Pg? 'Seleziona versione()' –

+0

anche non più non sembra essere una funzione registrata, "PostgreSQL 9.3.4 su x86_64-apple-darwin13.1.0, compilato da Apple LLVM versione 5.1 (clang-503.0.38) (basato su LLVM 3.4svn), 64-bit " – chrismarx

16

quelli in mysql:

> ids = [11,31,29] 
=> [11, 31, 29] 
> User.where(id: ids).order("field(id, #{ids.join(',')})") 

in Postgres:

def self.order_by_ids(ids) 
    order_by = ["CASE"] 
    ids.each_with_index do |id, index| 
    order_by << "WHEN id='#{id}' THEN #{index}" 
    end 
    order_by << "END" 
    order(order_by.join(" ")) 
end 

User.where(id: [3,2,1]).order_by_ids([3,2,1]).map(&:id) 
#=> [3,2,1] 
+1

ama questa idea! – mklb

+1

Good Idea @ilgam –

+0

Funziona alla grande per me! – armchairdj

0

Come ho risposto here, ho appena pubblicato una gemma (order_as_specified) che ti permette di fare l'ordinazione SQL nativo in questo modo:

CurrencyCode.order_as_specified(code: ['GBP', 'EUR', 'BBD', 'AUD', 'CAD', 'USD']) 

Restituisce una relazione ActiveRecord e quindi può essere concatenato con h altri metodi, e ha funzionato con ogni RDBMS che ho testato.

2

Creare una migrazione con questa funzione

CREATE OR REPLACE FUNCTION field(anyelement, VARIADIC anyarray) RETURNS bigint AS $$ 
    SELECT n FROM (
    SELECT row_number() OVER() AS n, x FROM unnest($2) x) 
     numbered WHERE numbered.x = $1; 
$$ LANGUAGE SQL IMMUTABLE STRICT; 

Poi basta fare questo

sequence = [2,4,1,5] 
Model.order("field(id,#{sequence.join(',')})") 

voilà!