2009-08-21 7 views
29

Nur ausprobieren, PostgreSQL zum ersten Mal, von MySQL. In unserer Rails-Anwendung haben wir ein paar Stellen mit SQL wie folgt:Simulieren von MySQL ORDER BY FIELD() in Postgresql

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

Es dauerte nicht lange, zu entdecken, dass diese in Postgresql unterstützt/nicht erlaubt ist.

Weiß jemand, wie man dieses Verhalten in Postgres simuliert, oder müssen wir zum Aussortieren in den Code ziehen?

Dank

Peer

+3

Es könnte nützlich sein zu erklären, was Sie erreichen möchten. So schwer vorzustellen, wie es ist - nicht jeder kennt MySQL :) –

+0

Toller Punkt depesz! Grundsätzlich ist eine benutzerdefinierte Sortierreihenfolge das, wonach wir suchen. Mit der FIELD-Funktion können Sie benutzerdefinierte Sets erstellen, mit denen Sie sortieren können. –

Antwort

48

Ah, war gahooa so nah:

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

Hoppla! ein bisschen spät abends Dyslexie ... Du hast meine Stimme! – gahooa

+4

Diese Methode funktioniert nicht, wenn Sie DISTINCT verwenden. Irgendwelche anderen Ideen für dieses Szenario? – Corey

+2

Ich bin mir nicht sicher, warum das funktioniert, aber ich habe eine Alternative gefunden. Wenn Sie Ergebnisse in der Reihenfolge j, a, k, e wollen, dann ordnen Sie nach id = e, id = k, id = a, id = j. – jakeonrails

1

Sie dies tun können ...

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 

Aber warum Sie diese in die Abfrage zu - wäre kein Auflagetisch besser geeignet sein?

-

Edit: blätterte es um so pro Kommentare

+0

@gahooa, ich denke, du hast das Gefühl von "Code" umgekehrt - der Code ist die Drei-Alpha-Abkürzung, die das OP in einer Nicht-Alpha-Art sortieren möchte. – pilcrow

+0

genau richtig pilcrow –

+0

Ich wünschte, ich könnte Kredit nehmen, warum die SQL ist so, wie es ist, wir arbeiten an einem Refactor, aber ich gebe zu, ich bin auf der Suche nach der schnellen Lösung im Moment –

11

aktualisieren, grandios Vorschlag von @Tometzky konkretisieren.

Dies sollte Ihnen eine MySQL FIELD() -alike Funktion unter pg geben 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, aber ich kann die oben auf 8.4 jetzt nicht überprüfen; jedoch kann ich arbeite nach hinten zu einer „moralisch“ gleichwertigen Version, die auf der 8.1-Instanz vor mir funktioniert:

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

Mehr ungeschickt, noch kann man portably eine (möglicherweise abgeleitet) Tabelle des Währungscode Rankings verwenden, so wie:

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

Wenn Sie dies oft ausgeführt werden, um eine neue Spalte hinzufügen und einen Pre-insert/update-Trigger. Dann setzen Sie den Wert in der neuen Spalte basierend auf diesem Trigger und der Reihenfolge nach diesem Feld. Sie können sogar einen Index für dieses Feld hinzufügen.

+0

Trigger sind schlecht, mmkay? Vermeide es wenn überhaupt möglich! –

+0

@WillSheppard Das ist falsch. Sie können nicht hirnlos und oft verwendet werden, aber Sie können einfach nicht sagen "Trigger sind schlecht" – jirigracik

10

Dies ist Ich denke, die einfachste Art und Weise:

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 Sie auch eine function with variable number of arguments (variadische Funktion) Port field Funktion verwenden können.

+0

+1 für Ordnung-für-Knall, und für 'VARIADIC' Vorschlag, den ich versuchen werde zu implementieren. – pilcrow

3

Eigentlich ist die Version für Postgres 8.1 ein weiterer Vorteil.

Wenn Sie eine Postgres-Funktion aufrufen, können Sie nicht mehr als 100 Parameter an sie übergeben, so dass Ihre Bestellung maximal bei 99 Elementen erfolgen kann.

Verwenden Sie die Funktion, die ein Array als zweites Argument anstelle eines variadischen Arguments verwendet, entfernen Sie dieses Limit.

2

Definieren Sie einfach die FIELD Funktion und verwenden Sie sie. Es ist einfach genug zu implementieren. Folgendes sollte in 8.4 funktionieren, da es unnest und Fensterfunktionen wie 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; 

Sie können auch ein anderes Exemplar mit der Signatur definieren:

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

und demselben Körper, wenn Sie unterstützen möchten field() für jeden Datentyp.

+0

Ich denke, das ist die beste Antwort, aber wenn ich versuche, die Funktion zu erstellen, sagt pgadmin - "Rückgabetyp Mismatch in Funktion deklariert, um Bigint zurückzugeben, muss die letzte Anweisung Function auswählen/einfügen" Ideen? – chrismarx

+0

@chrismarx Pg version? 'Select-Version()' –

+0

auch unnest scheint keine registrierte Funktion zu sein, "PostgreSQL 9.3.4 auf x86_64-Apple-Darwin13.1.0, von Apple LLVM Version 5.1 (clang-503.0.38) kompiliert (basierend auf LLVM 3.4svn), 64-bit " – chrismarx

16

Art 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

liebe diese Idee! – mklb

+1

Gute Idee @ilgam –

+0

Das funktioniert gut für mich! – armchairdj

0

Wie ich here antwortete, Ich habe gerade ein Juwel (order_as_specified), die Sie nativen SQL-Bestellung wie dies zu tun erlaubt:

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

Es gibt eine ActiveRecord-Beziehung zurück und kann daher mit wit verkettet werden h andere Methoden, und es hat mit jedem RDBMS funktioniert, das ich getestet habe.

2

erstellen Migration mit dieser Funktion

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; 

Dann diese gerade tun

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

voila!