Параметризованный в PostgreSQL Order By / Limit в табличной функции - PullRequest
15 голосов
/ 15 ноября 2011

У меня есть функция sql, которая выполняет простое выражение выбора sql:

CREATE OR REPLACE FUNCTION getStuff(param character varying)
  RETURNS SETOF stuff AS
$BODY$
    select *
    from stuff
    where col = $1
$BODY$
  LANGUAGE sql;

Сейчас я вызываю эту функцию следующим образом:

select * from getStuff('hello');

Какие у меня варианты, если янужно упорядочить и ограничить результаты с помощью предложений order by и limit?

Я предполагаю, что такой запрос:

select * from getStuff('hello') order by col2 limit 100;

не будет очень эффективным, поскольку все строки таблицыstuff будет возвращено функцией getStuff и только затем упорядочено и разрезано по лимиту.

Но даже если я прав, нет простого способа передать порядок по аргументу функции языка SQL.,Могут быть переданы только значения, а не части оператора sql.

Другой вариант - создать функцию на языке plpgsql, где можно построить запрос и выполнить его с помощью EXECUTE.Но это тоже не очень хороший подход.

Итак, есть ли другой способ добиться этого?Или какой вариант вы бы выбрали?Порядок / ограничение вне функции, или plpgsql?

Я использую postgresql 9.1.

Edit

Я изменил оператор CREATE FUNCTION так:

CREATE OR REPLACE FUNCTION getStuff(param character varying, orderby character varying)
  RETURNS SETOF stuff AS
$BODY$
    select t.*
    from stuff t
    where col = $1
    ORDER BY
        CASE WHEN $2 = 'parent' THEN t.parent END,
        CASE WHEN $2 = 'type' THEN t."type" END, 
        CASE WHEN $2 = 'title' THEN t.title END

$BODY$
  LANGUAGE sql;

Это выдает:

ОШИБКА: символы типа CASE меняются и целое число не может быть сопоставлено ŘÁDKA 13: WHEN $ 1 = 'parent' THEN t.parent

Таблица stuff выглядит следующим образом:

CREATE TABLE stuff
    (
      id integer serial,
      "type" integer NOT NULL,
      parent integer,
      title character varying(100) NOT NULL,
      description text,
      CONSTRAINT "pkId" PRIMARY KEY (id),
    )

Edit2

Я плохо прочитал код Dems.Я исправил это к вопросу.Этот код работает для меня.

Ответы [ 4 ]

26 голосов
/ 16 ноября 2011

В функции plpgsql нет ничего плохого. Это самое элегантное и быстрое решение для чего-то более сложного. Единственная ситуация, когда производительность может пострадать, это когда вложена функция plpgsql, потому что планировщик запросов не может дополнительно оптимизировать содержащийся код в контексте внешнего запроса, что может замедлять или не замедлять его работу.
Более подробно в этом последующем ответе:

В этом случае это намного проще, чем множество CASE предложений в запросе:

CREATE OR REPLACE FUNCTION get_stuff(_param text, _orderby text, _limit int)
  RETURNS SETOF stuff AS
$func$
BEGIN
   RETURN QUERY EXECUTE '
      SELECT *
      FROM   stuff
      WHERE  col = $1
      ORDER  BY ' || quote_ident(_orderby) || ' ASC
      LIMIT  $2'
   USING _param, _limit;
END
$func$  LANGUAGE plpgsql;

Звоните:

SELECT * FROM get_stuff('hello', 'col2', 100);

Примечания

  • Используйте RETURN QUERY EXECUTE, чтобы вернуть результаты запроса за один раз.
  • Используйте quote_ident() для идентификаторов для защиты от SQLi. Или format() для чего-то более сложного. Связанные с:
  • Передайте значения параметров с предложением USING, чтобы избежать повторного приведения, цитирования и SQLi.
  • Будьте осторожны, чтобы не создавать конфликты имен между параметрами и именами столбцов. В этом примере имена параметров начинаются с подчеркивания (_). Просто мои личные предпочтения.

Ваша вторая функция после редактирования не может работать, потому что вы возвращаете parent только тогда, когда тип возврата объявлен SETOF stuff. Вы можете объявить любой возвращаемый тип, который вам нравится, но фактические возвращаемые значения должны соответствовать объявлению. Вы можете использовать для этого RETURNS TABLE.

2 голосов
/ 27 октября 2015

Если ваша функция стабильна (не изменяет базу данных), планировщик запросов обычно встроенный это.Следовательно, выполнение SELECT * FROM getStuff('x') LIMIT 10 приведет к тому же плану запроса, как если бы предел был внутри getStuff().

Однако вы должны сказать PG, что ваша функция стабильна, объявив ее так:

CREATE OR REPLACE FUNCTION getStuff(param varchar)
RETURNS setof STUFF
LANGUAGE SQL
STABLE
AS $$ ... $$;

Теперь выполнение EXPLAIN SELECT * FROM getStuff('x') LIMIT 1 должно привести к тому же плану запроса, что и при записи эквивалентного запроса.

Встраивание также должно работать для предложений ORDER BY вне функции.Но если вы хотите параметризовать функцию для определения порядка, вы можете сделать это следующим образом, чтобы контролировать направление сортировки:

CREATE FUNCTION sort_stuff(sort_col TEXT, sort_dir TEXT DEFAULT 'asc')
RETURNS SETOF stuff
LANGUAGE SQL
STABLE
AS $$
    SELECT *
    FROM stuff
    ORDER BY
      -- Simplified to NULL if not sorting in ascending order.
      CASE WHEN sort_dir = 'asc' THEN
          CASE sort_col
              -- Check for each possible value of sort_col.
              WHEN 'col1' THEN col1
              WHEN 'col2' THEN col2
              WHEN 'col3' THEN col3
              --- etc.
              ELSE NULL
          END
      ELSE
          NULL
      END
      ASC,

      -- Same as before, but for sort_dir = 'desc'
      CASE WHEN sort_dir = 'desc' THEN
          CASE sort_col
              WHEN 'col1' THEN col1
              WHEN 'col2' THEN col2
              WHEN 'col3' THEN col3
              ELSE NULL
          END
      ELSE
          NULL
      END
      DESC
$$;

До тех пор, пока sort_col и sort_dir постоянны в пределахзапрос, планировщик запросов должен иметь возможность упростить подробный запрос до

SELECT *
FROM stuff
ORDER BY <sort_col> <sort_dir>

, который можно проверить с помощью EXPLAIN.

1 голос
/ 15 ноября 2011

Что касается ORDER BY, вы можете попробовать что-то вроде этого:

SELECT
    <column list>
FROM
    Stuff
WHERE
    col1 = $1
ORDER BY
    CASE $2
        WHEN 'col1' THEN col1
        WHEN 'col2' THEN col2
        WHEN 'col3' THEN col3
        ELSE col1  -- Or whatever your default should be
    END

Возможно, вам придется выполнить некоторые преобразования типов данных, чтобы все типы данных в результате CASE совпадали.Просто будьте осторожны при преобразовании чисел в строки - вам нужно будет добавить нули, чтобы правильно их упорядочить.То же самое относится к значениям даты / времени.Порядок в формате, в котором год, затем месяц, затем день и т. Д.

Я сделал это в SQL Server, но никогда в PostgreSQL, и у меня нет копии PostgreSQL на этом компьютеретак что это не проверено.

0 голосов
/ 15 ноября 2011

Вы можете передать предельное значение в качестве аргумента функции без каких-либо проблем.Что касается заказа, вы можете использовать ODER BY в сочетании с оператором CASE.К сожалению, это не будет работать для чего-то вроде

ORDER BY CASE condition_variable
WHEN 'asc' THEN column_name ASC
ELSE column_name DESC
END;
...