Имитация MySQL ORDER BY FIELD () в Postgresql - PullRequest
37 голосов
/ 21 августа 2009

Просто пробую PostgreSQL в первый раз, исходя из MySQL. В нашем приложении Rails у нас есть пара мест с SQL, например:

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

Вскоре выяснилось, что это не поддерживается / не разрешено в PostgreSQL.

Кто-нибудь знает, как смоделировать это поведение в PostgreSQL, или мы должны выполнить сортировку в коде?

Ответы [ 10 ]

57 голосов
/ 21 августа 2009

Ах, гауаа был так близко:

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;
24 голосов
/ 06 ноября 2014

сортировка в mysql:

> 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]
12 голосов
/ 21 августа 2009

Обновление , воплощение потрясающего предложения @ Tometzky.

Это должно дать вам MySQL FIELD() -подобную функцию в 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 , но я не могу проверить вышеизложенное на 8.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)
11 голосов
/ 21 августа 2009

Это, я думаю, самый простой способ:

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 функции.

4 голосов
/ 13 апреля 2010

На самом деле версия для postgres 8.1 является еще одним преимуществом.

При вызове функции postgres вы не можете передать ей более 100 параметров, поэтому вы можете сделать заказ максимально на 99 элементах.

Использование функции с использованием массива в качестве второго аргумента вместо аргумента с переменным значением просто снимает этот предел.

2 голосов
/ 17 августа 2015

Создать миграцию с помощью этой функции

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(',')})")

вуаля!

2 голосов
/ 11 сентября 2012

Просто определите функцию FIELD и используйте ее. Это достаточно легко реализовать. Следующее должно работать в 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 $$

и то же тело, если вы хотите поддерживать field() для любого типа данных.

1 голос
/ 21 августа 2009

Если вы будете запускать это часто, добавьте новый столбец и триггер предварительной вставки / обновления. Затем вы устанавливаете значение в новом столбце на основе этого триггера и упорядочиваете по этому полю. Вы даже можете добавить индекс в этом поле.

1 голос
/ 21 августа 2009

Вы можете сделать это ...

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

Но почему вы жестко запрограммировали их в запросе - не будет ли более подходящей вспомогательная таблица?

-

Редактировать: перевернул в соответствии с комментариями

0 голосов
/ 13 марта 2015

Когда я ответил здесь , я только что выпустил гем ( order_as_specified ), который позволяет вам выполнять упорядочивание собственного SQL следующим образом:

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

Он возвращает отношение ActiveRecord и, следовательно, может быть связан с другими методами, и он работает с каждой протестированной СУБД.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...