在Postgresql中模拟MySQL的ORDER BY FIELD()

只是第一次尝试Postgresql,来自MySQL。 在我们的Rails应用程序中,我们有一些像SQL这样的位置:

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

Postgresql不支持/不允许发现这种情况并不需要很长时间。

有没有人知道如何在Postgres中模拟这种行为,还是我们不得不拉动整理代码?

谢谢

窥视

啊,gahooa是如此接近:

 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; 

在mysql中sorting:

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

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

更新 ,通过@Tometzky充实了很棒的build议。

这应该给你一个在8.4下的MySQL FIELD()类似的函数:

 -- 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 ,但现在我无法validation8.4上面的内容; 然而,我可以倒退到一个在我面前的8.1实例上的“道德上”相当的版本:

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

更尴尬的是,你仍然可以轻易地使用(可能派生的)货币代码排名表,如下所示:

 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) 

这是我认为最简单的方法:

 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) 

在PostgreSQL 8.4中,你也可以使用带有可变数量的参数 (可变参数函数)的函数来移植field函数。

其实postgres 8.1的版本是另一个优点。

当调用一个postgres函数时,你不能传递超过100个参数,所以你的sorting最多可以在99个元素上完成。

使用函数使用数组作为第二个参数,而不是有一个可变参数只是删除这个限制。

只需定义FIELDfunction并使用它。 这很容易实现。 以下应该在8.4中工作,因为它具有unnestrow_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; 

您也可以使用签名定义另一个副本:

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

如果你想支持任何数据types的field() ,那么它是同一个主体。

用这个函数创build一个迁移

 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; 

那就这样做吧

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

瞧!

你可以这样做…

 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 

但是,为什么你把这些问题硬编码到查询中 – 支持表是不是更合适?

编辑:按照评论翻转它

如果你经常运行,添加一个新的列和一个预插入/更新触发器。 然后,根据此触发器在新列中设置值,并按此字段sorting。 你甚至可以在这个字段上添加一个索引。

正如我在这里回答的,我刚刚发布了一个gem( order_as_specified ),它允许你像这样做原生的SQL命令:

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

它返回一个ActiveRecord关系,因此可以与其他方法链接,并且与我testing过的每个RDBMS一起工作。