Ответ 1
Вы можете обернуть его в подзапрос, но не гарантированный сейф без взлома OFFSET 0
. В 9.3 используйте LATERAL
. Проблема связана с тем, что парсер эффективно макрорасширяет *
в список столбцов.
Обход
Где:
SELECT (my_func(x)).* FROM some_table;
будет оценивать my_func
n
раз за n
столбцы результата от функции, эта формулировка:
SELECT (mf).* FROM (
SELECT my_func(x) AS mf FROM some_table
) sub;
обычно не будет и не будет добавлять дополнительное сканирование во время выполнения. Чтобы гарантировать, что множественная оценка не будет выполнена, вы можете использовать OFFSET 0
взломать или злоупотреблять PostgreSQL, чтобы оптимизировать границы CTE:
SELECT (mf).* FROM (
SELECT my_func(x) AS mf FROM some_table OFFSET 0
) sub;
или
WITH tmp(mf) AS (
SELECT my_func(x) FROM some_table
)
SELECT (mf).* FROM tmp;
В PostgreSQL 9.3 вы можете использовать LATERAL
, чтобы получить более здравое поведение:
SELECT mf.*
FROM some_table
LEFT JOIN LATERAL my_func(some_table.x) AS mf ON true;
LEFT JOIN LATERAL ... ON true
сохраняет все строки, такие как исходный запрос, даже если вызов функции не возвращает строку.
Demo
Создайте функцию, которая не является встроенной в качестве демонстрации:
CREATE OR REPLACE FUNCTION my_func(integer)
RETURNS TABLE(a integer, b integer, c integer) AS $$
BEGIN
RAISE NOTICE 'my_func(%)',$1;
RETURN QUERY SELECT $1, $1, $1;
END;
$$ LANGUAGE plpgsql;
и таблицу фиктивных данных:
CREATE TABLE some_table AS SELECT x FROM generate_series(1,10) x;
затем попробуйте приведенные выше версии. Вы увидите, что первый вызывает три уведомления за вызов; последний только поднимает один.
Почему?
Хороший вопрос. Это ужасно.Похоже:
(func(x)).*
расширяется как:
(my_func(x)).i, (func(x)).j, (func(x)).k, (func(x)).l
при разборе, согласно взглядам debug_print_parse
, debug_print_rewritten
и debug_print_plan
. Дерево разбора (обрезанное) выглядит следующим образом:
:targetList (
{TARGETENTRY
:expr
{FIELDSELECT
:arg
{FUNCEXPR
:funcid 57168
...
}
:fieldnum 1
:resulttype 23
:resulttypmod -1
:resultcollid 0
}
:resno 1
:resname i
...
}
{TARGETENTRY
:expr
{FIELDSELECT
:arg
{FUNCEXPR
:funcid 57168
...
}
:fieldnum 2
:resulttype 20
:resulttypmod -1
:resultcollid 0
}
:resno 2
:resname j
...
}
{TARGETENTRY
:expr
{FIELDSELECT
:arg
{FUNCEXPR
:funcid 57168
...
}
:fieldnum 3
:...
}
:resno 3
:resname k
...
}
{TARGETENTRY
:expr
{FIELDSELECT
:arg
{FUNCEXPR
:funcid 57168
...
}
:fieldnum 4
...
}
:resno 4
:resname l
...
}
)
Итак, в основном, мы используем немой синтаксический анализатор для расширения подстановочных знаков путем клонирования узлов.