Предложение ORDER BY ... USING в PostgreSQL - PullRequest
38 голосов
/ 26 августа 2011

Предложение ORDER BY описывается в документации PostgreSQL как:

ORDER BY expression [ ASC | DESC | USING operator ] [ NULLS { FIRST | LAST } ] [, ...]

Может кто-нибудь дать мне несколько примеров, как использовать USING operator?Можно ли получить чередующийся порядок результатов?

Ответы [ 4 ]

43 голосов
/ 18 сентября 2011

Очень простой пример:

> SELECT * FROM tab ORDER BY col USING <

Но это скучно, потому что вы ничего не можете получить с традиционным ORDER BY col ASC.

Также в стандартном каталоге не упоминается ничего интересного о странных функциях / операторах сравнения. Вы можете получить их список:

    > SELECT amoplefttype::regtype, amoprighttype::regtype, amopopr::regoper 
      FROM pg_am JOIN pg_amop ON pg_am.oid = pg_amop.amopmethod 
      WHERE amname = 'btree' AND amopstrategy IN (1,5);

Вы заметите, что в основном существуют функции < и > для примитивных типов, таких как integer, date и т. Д., И некоторые другие для массивов и векторов и так далее. Ни один из этих операторов не поможет вам получить индивидуальный заказ.

В большинстве случаев, когда требуется пользовательский порядок, вы можете уйти, используя что-то вроде ... ORDER BY somefunc(tablecolumn) ..., где somefunc отображает значения соответствующим образом. Поскольку это работает с каждой базой данных, это также самый распространенный способ. Для простых вещей вы даже можете написать выражение вместо пользовательской функции.

Переключение вверх

ORDER BY ... USING имеет смысл в нескольких случаях:

  • Порядок настолько необычен, что трюк somefunc не работает.
  • Вы работаете с не примитивным типом (например, point, circle или воображаемыми числами) и не хотите повторять себя в своих запросах со странными вычислениями.
  • Набор данных, который вы хотите отсортировать, настолько велик, что поддержка по индексу желательна или даже необходима.

Я остановлюсь на сложных типах данных: часто существует более одного способа их разумной сортировки. Хорошим примером является point: вы можете «упорядочить» их по расстоянию до (0,0) или сначала по x , затем по y или просто по у или все, что вы хотите.

Конечно, PostgreSQL имеет предопределенных операторов для point:

    > CREATE TABLE p ( p point );
    > SELECT p <-> point(0,0) FROM p;

Но нет из них объявлены пригодными для ORDER BY по умолчанию (см. Выше):

    > SELECT * FROM p ORDER BY p;
    ERROR:  could not identify an ordering operator for type point
    TIP:  Use an explicit ordering operator or modify the query.

Простыми операторами для point являются операторы «внизу» и «выше» <^ и >^. Они сравнивают просто часть y. Но:

    >  SELECT * FROM p ORDER BY p USING >^;
    ERROR: operator > is not a valid ordering operator
    TIP: Ordering operators must be "<" or ">" members of __btree__ operator families.

ORDER BY USING требует оператора с определенной семантикой: очевидно, это должен быть двоичный оператор, он должен принимать тот же тип, что и аргументы, и он должен возвращать логическое значение. Я думаю, что он также должен быть транзитивным (если a btree -индексного заказа. Это объясняет странные сообщения об ошибках, содержащие ссылку на btree .

ORDER BY USING также требует не только одного оператора , но и класса операторов и семейства операторов . В то время как один может реализовать сортировку только с одним оператором, PostgreSQL пытается эффективно сортировать и минимизировать сравнения. Поэтому несколько операторов используются, даже когда вы указываете только один - другие должны придерживаться определенных математических ограничений - я уже упоминал о транзитивности, но есть и другие.

Переключение передач

Давайте определим что-то подходящее: оператор для точек, который сравнивает только часть y.

Первым шагом является создание пользовательского семейства операторов, которое можно использовать методом доступа к индексу btree . см

    > CREATE OPERATOR FAMILY xyzfam USING btree;   -- superuser access required!
    CREATE OPERATOR FAMILY

Далее мы должны предоставить функцию сравнения, которая возвращает -1, 0, +1 при сравнении двух точек. Эта функция БУДЕТ вызываться изнутри!

    > CREATE FUNCTION xyz_v_cmp(p1 point, p2 point) RETURNS int 
      AS $$BEGIN RETURN btfloat8cmp(p1[1],p2[1]); END $$ LANGUAGE plpgsql;
    CREATE FUNCTION

Далее мы определяем класс операторов для семейства. См. Руководство для объяснения чисел.

    > CREATE OPERATOR CLASS xyz_ops FOR TYPE point USING btree FAMILY xyzfam AS 
        OPERATOR 1 <^ ,
        OPERATOR 3 ?- ,
        OPERATOR 5 >^ ,
        FUNCTION 1 xyz_v_cmp(point, point) ;
    CREATE OPERATOR CLASS

Этот шаг объединяет несколько операторов и функций, а также определяет их отношения и значение. Например, OPERATOR 1 означает: это оператор для less-than испытаний.

Теперь операторы <^ и >^ могут использоваться в ORDER BY USING:

> INSERT INTO p SELECT point(floor(random()*100), floor(random()*100)) FROM generate_series(1, 5);
INSERT 0 5
> SELECT * FROM p ORDER BY p USING >^;
    p    
---------
 (17,8)
 (74,57)
 (59,65)
 (0,87)
 (58,91)

Вуаля - отсортировано по y .

Подводя итог: ORDER BY ... USING - интересный взгляд под капотом PostgreSQL. Но ничего вам не потребуется в ближайшее время, если вы не будете работать в очень определенных областях технологии баз данных.

Другой пример можно найти в документации Postgres. с исходным кодом для примера здесь и здесь . В этом примере также показано, как создавать операторы.

4 голосов
/ 26 августа 2011

Примеры:

CREATE TABLE test
(
  id serial NOT NULL,
  "number" integer,
  CONSTRAINT test_pkey PRIMARY KEY (id)
)

insert into test("number") values (1),(2),(3),(0),(-1);

select * from test order by number USING > //gives 3=>2=>1=>0=>-1

select * from test order by number USING < //gives -1=>0=>1=>2=>3

Итак, это эквивалентно desc и asc. Но вы можете использовать свой собственный оператор, это основная функция USING

1 голос
/ 27 ноября 2017

Хорошие ответы, но они не упомянули один настоящий ценный случай для ИСПОЛЬЗОВАНИЯ.

Когда вы создаете индекс с семейством операторов не по умолчанию, например, varchar_pattern_ops (~> ~, ~ <~, ~> = ~ ...) вместо <,>,> = ... тогда, если вы ищете на индекс, и вы хотите использовать индекс в порядке по предложению, вы должны указать ИСПОЛЬЗОВАНИЕ с соответствующим оператором.

Это можно проиллюстрировать на следующем примере:

CREATE INDEX index_words_word ON words(word text_pattern_ops); 

Давайте сравним эти два запроса:

SELECT * FROM words WHERE word LIKE 'o%' LIMIT 10;

и

SELECT * FROM words WHERE word LIKE 'o%' ORDER BY word LIMIT 10;

Разница между их исполнениями составляет почти 100 раз при 500 000 слов БД! А также результаты могут быть неверными в не-C локали.

Как это могло произойти?

Когда вы выполняете поиск с помощью предложений LIKE и ORDER BY, вы фактически делаете этот вызов:

SELECT * FROM words WHERE word ~>=~ 'o' AND word ~<~'p' ORDER BY word USING < LIMIT 10;

Ваш индекс создан с учетом оператора ~ <~, поэтому PG не может использовать данный индекс в данном предложении ORDER BY. Чтобы все было сделано правильно, запрос должен быть переписан в эту форму: </p>

SELECT * FROM words WHERE word ~>=~ 'o' AND word ~<~'p' ORDER BY word USING ~<~ LIMIT 10;

или

SELECT * FROM words WHERE word LIKE 'o%' ORDER BY word USING ~<~ LIMIT 10;
0 голосов
/ 26 августа 2011

При желании можно добавить ключевое слово ASC (по возрастанию) или DESC. (по убыванию) после любого выражения в предложении ORDER BY. Если не указано, ASC предполагается по умолчанию. Альтернативно, конкретный имя оператора заказа может быть указано в предложении USING. оператор заказа должен быть членом, меньшим или большим, чем некоторые Семейство операторов B-дерева. ASC обычно эквивалентно USING <и DESC обычно эквивалентно USING>.

PostgreSQL 9.0

Я думаю, это может выглядеть примерно так (у меня нет postgres, чтобы проверить это прямо сейчас, но проверю позже)

SELECT Name FROM Person
ORDER BY NameId USING >
...