在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个元素上完成。
使用函数使用数组作为第二个参数,而不是有一个可变参数只是删除这个限制。
只需定义FIELD
function并使用它。 这很容易实现。 以下应该在8.4中工作,因为它具有unnest
和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;
您也可以使用签名定义另一个副本:
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一起工作。