Аналог Postgres для CROSS APPLY в SQL Server
Мне нужно перенести SQL-запросы, написанные для MS SQL Server 2005, в Postgres 9.1.
Каков наилучший способ заменить CROSS APPLY
в этом запросе?
SELECT *
FROM V_CitizenVersions
CROSS APPLY
dbo.GetCitizenRecModified(Citizen, LastName, FirstName, MiddleName,
BirthYear, BirthMonth, BirthDay, ..... ) -- lots of params
Функция
GetCitizenRecModified()
- это функция с табличной оценкой. Я не могу поместить код этой функции, потому что она действительно огромна, она делает некоторые сложные вычисления, и я не могу отказаться от нее.
Ответы
Ответ 1
В Postgres 9.3 или позже используйте соединение LATERAL
:
SELECT v.col_a, v.col_b, f.* -- no parentheses here, f is a table alias
FROM v_citizenversions v
LEFT JOIN LATERAL f_citizen_rec_modified(v.col1, v.col2) f ON true
WHERE f.col_c = _col_c;
Почему LEFT JOIN LATERAL ... ON true
?
Для более старых версий существует очень простой способ выполнить то, что, как я думаю, вы пытаетесь выполнить с помощью функции set-return ( RETURNS TABLE
или RETURNS SETOF record
ИЛИ RETURNS record
):
SELECT *, (f_citizen_rec_modified(col1, col2)).*
FROM v_citizenversions v
Функция вычисляет значения один раз для каждой строки внешнего запроса. Если функция возвращает несколько строк, результирующие строки умножаются соответственно. Все круглые скобки синтаксически необходимы для разложения типа строки. Функция таблицы может выглядеть примерно так:
CREATE OR REPLACE FUNCTION f_citizen_rec_modified(_col1 int, _col2 text)
RETURNS TABLE(col_c integer, col_d text) AS
$func$
SELECT s.col_c, s.col_d
FROM some_tbl s
WHERE s.col_a = $1
AND s.col_b = $2
$func$ LANGUAGE sql;
Вам нужно обернуть это в подзапрос или CTE, если вы хотите применить предложение WHERE
, потому что столбцы не видны на одном уровне. (И это лучше для производительности в любом случае, потому что вы предотвращаете повторную оценку для каждого выходного столбца функции):
SELECT col_a, col_b, (f_row).*
FROM (
SELECT col_a, col_b, f_citizen_rec_modified(col1, col2) AS f_row
FROM v_citizenversions v
) x
WHERE (f_row).col_c = _col_c;
Есть несколько других способов сделать это или что-то подобное. Все зависит от того, что вы хотите точно.
Ответ 2
Некроминанты:
Новое в PostgreSQL 9.3:
Ключевое слово LATERAL
слева | право | внутренний JOIN LATERAL
INNER JOIN LATERAL
совпадает с CROSS APPLY
и LEFT JOIN LATERAL
совпадает с OUTER APPLY
Пример использования:
SELECT * FROM T_Contacts
--LEFT JOIN T_MAP_Contacts_Ref_OrganisationalUnit ON MAP_CTCOU_CT_UID = T_Contacts.CT_UID AND MAP_CTCOU_SoftDeleteStatus = 1
--WHERE T_MAP_Contacts_Ref_OrganisationalUnit.MAP_CTCOU_UID IS NULL -- 989
LEFT JOIN LATERAL
(
SELECT
--MAP_CTCOU_UID
MAP_CTCOU_CT_UID
,MAP_CTCOU_COU_UID
,MAP_CTCOU_DateFrom
,MAP_CTCOU_DateTo
FROM T_MAP_Contacts_Ref_OrganisationalUnit
WHERE MAP_CTCOU_SoftDeleteStatus = 1
AND MAP_CTCOU_CT_UID = T_Contacts.CT_UID
/*
AND
(
(__in_DateFrom <= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateTo)
AND
(__in_DateTo >= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateFrom)
)
*/
ORDER BY MAP_CTCOU_DateFrom
LIMIT 1
) AS FirstOE
Ответ 3
Эта ссылка показывает, как это сделать в Postgres 9.0 +:
PostgreSQL: параметризация рекурсивного CTE
Далее он находится на странице в разделе "Эмуляция CROSS APPLY с функциями набора номера". Обязательно обратите внимание на список ограничений после примера.
Ответ 4
Мне нравится Erwin Brandstetter, однако, я обнаружил проблему с производительностью:
при запуске
SELECT *, (f_citizen_rec_modified(col1, col2)).*
FROM v_citizenversions v
Функция f_citizen_rec_modified будет запускаться 1 раз для каждого возвращаемого столбца (умножается на каждую строку в v_citizenversions). Я не нашел документацию для этого эффекта, но смог ее вывести путем отладки. Теперь возникает вопрос, как мы можем получить этот эффект (до 9.3, где доступны боковые соединения) без этого эффекта, ограждающего побочный эффект?
Обновление: Кажется, я нашел ответ. Перепишите запрос следующим образом:
select x.col1, x.col2, x.col3, (x.func).*
FROM (select SELECT v.col1, v.col2, v.col3, f_citizen_rec_modified(col1, col2) func
FROM v_citizenversions v) x
Ключевое различие заключается в том, что сначала получает исходную функцию (внутренний подзапрос), а затем обертывает ее в другой выбор, который распаковывает эти результаты в столбцы. Это было проверено на PG 9.2