2015-03-17 4 views
-1

У меня есть функция, чтобы сотрудник работал в статусе «Создать».Почему postgres работают так медленно, но один запрос выполняется быстро?

CREATE OR REPLACE FUNCTION get_probation_contract(AccountOrEmpcode TEXT, FromDate DATE, 
                ToDate   DATE) 
    RETURNS TABLE("EmpId" INTEGER, "EmpCode" CHARACTER VARYING, 
    "DomainAccount" CHARACTER VARYING, "JoinDate" DATE, 
    "ContractTypeCode" CHARACTER VARYING, "ContractTypeName" CHARACTER VARYING, 
    "ContractFrom" DATE, "ContractTo" DATE, "ContractType" CHARACTER VARYING, 
    "Signal" CHARACTER VARYING) AS $$ 
BEGIN 
    RETURN QUERY 
    EXECUTE 'SELECT 
    he.id                  "EmpId", 
    rr.code                  "EmpCode", 
    he.login                 "DomainAccount", 
    he.join_date                "JoinDate", 
    contract_type.code               "ContractTypeCode", 
    contract_type.name               "ContractTypeName", 
    contract.date_start               "ContractFrom", 
    contract.date_end               "ContractTo", 
    CASE WHEN contract_group.code = ''1'' THEN ''Probation'' 
    WHEN contract_group.code IN (''3'', ''4'', ''5'') THEN ''Official'' 
    WHEN contract_group.code = ''2'' THEN ''Collaborator'' END :: CHARACTER VARYING "ContractType", 
    ''CREATE'' :: CHARACTER VARYING            "Signal" 
    FROM 
    hr_employee he 
    INNER JOIN resource_resource rr 
     ON rr.id = he.resource_id 
    INNER JOIN hr_contract contract 
     ON contract.employee_id = he.id AND contract.date_start = (
     SELECT max(date_start) "date_start" 
     FROM hr_contract cc 
     WHERE cc.employee_id = contract.employee_id 
    ) 
    INNER JOIN hr_contract_type contract_type 
     ON contract_type.id = contract.type_id 
    INNER JOIN hr_contract_type_group contract_group 
     ON contract_group.id = contract_type.contract_type_group_id 
    WHERE 
    contract_group.code = ''1'' 


    AND 
    ($1 IS NULL OR $1 = '''' OR rr.code = $1 OR 
    he.login = $1) 
    AND (
     (he.join_date BETWEEN $2 AND $3) 
     OR (he.join_date IS NOT NULL AND (contract.date_start BETWEEN $2 AND $3)) 
     OR (he.create_date BETWEEN $2 AND $3 AND he.create_date > he.join_date) 
    ) 
    AND rr.active = TRUE 
'using AccountOrEmpcode, FromDate, ToDate ; 
END; 
$$ LANGUAGE plpgsql; 

потребовалось 37 секунд, чтобы выполнить

SELECT * 
FROM get_probation_contract('', '2014-01-01', '2014-06-01'); 

Когда я использую один запрос

SELECT 
     he.id                  "EmpId", 
     rr.code                  "EmpCode", 
     he.login                 "DomainAccount", 
     he.join_date                "JoinDate", 
     contract_type.code               "ContractTypeCode", 
     contract_type.name               "ContractTypeName", 
     contract.date_start               "ContractFrom", 
     contract.date_end               "ContractTo", 
     CASE WHEN contract_group.code = '1' THEN 'Probation' 
     WHEN contract_group.code IN ('3', '4', '5') THEN 'Official' 
     WHEN contract_group.code = '2' THEN 'Collaborator' END :: CHARACTER VARYING "ContractType", 
     'CREATE' :: CHARACTER VARYING            "Signal" 
    FROM 
     hr_employee he 
     INNER JOIN resource_resource rr 
     ON rr.id = he.resource_id 
     INNER JOIN hr_contract contract 
     ON contract.employee_id = he.id AND contract.date_start = (
     SELECT max(date_start) "date_start" 
     FROM hr_contract 
     WHERE employee_id = he.id 
    ) 
     INNER JOIN hr_contract_type contract_type 
     ON contract_type.id = contract.type_id 
     INNER JOIN hr_contract_type_group contract_group 
     ON contract_group.id = contract_type.contract_type_group_id 
    WHERE 
     contract_group.code = '1' 
AND (
     (he.join_date BETWEEN '2014-01-01' AND '2014-06-01') 
     OR (he.join_date IS NOT NULL AND (contract.date_start BETWEEN '2014-01-01' AND '2014-01-06')) 
     OR (he.create_date BETWEEN '2014-01-01' AND '2014-01-06' AND he.create_date > he.join_date) 
    ) 
    AND rr.active = TRUE 

Это займет 5 секунд, чтобы завершить

Как оптимизировать функцию выше. и почему функция медленна, чем одиночный запрос, даже если я использую функцию «select ...» в функции.

Индексирование в поле идентификатора каждой таблицы.

+1

Возможно, вы пропустили часть своего теста или в пасте запроса: после того как contract_group.code = '1' у вас есть И ETC в функции. Также укажите вывод плана, указатели на месте, чтобы увидеть, есть ли там намек. –

+0

спасибо за ваш комментарий. Я добавил информацию о запросе и индексировании. – giaosudau

+0

К сожалению, в вашем тестовом запросе отсутствует какая-либо часть: И ($ 1 IS NULL ИЛИ $ 1 = '' '' OR rr.code = $ 1 ИЛИ he.login = $ 1) Плюс ваш намек «использование AccountOrEmpcode, FromDate, ToDate», кажется, отсутствует в вашем тесте. –

ответ

2

Возможная причина - слепая оптимизация для подготовленных операторов (встроенный SQL). Это немного лучше в новых выпусках PostgreSQL, хотя это может быть и проблема. План выполнения во встроенном SQL в PL/pgSQL повторно используется для большего количества вызовов - и он оптимизирован для более частого значения (не для действительно используемого значения). Иногда эта разница может привести к значительному замедлению.

Затем вы можете использовать динамический SQL-оператор EXECUTE. Динамический SQL использует только один раз выполненные планы и использует реальные параметры. Это должно решить эту проблему.

Пример встроенного SQL с повторно используемыми подготовленными планами.

CREATE OR REPLACE FUNCTION fx1(_surname text) 
RETURNS int AS $$ 
BEGIN 
    RETURN (SELECT count(*) FROM people WHERE surname = _surname) 
END; 

Пример с динамическим SQL:

CREATE OR REPLACE FUNCTION fx2(_surname text) 
RETURNS int AS $$ 
DECLARE result int; 
BEGIN 
    EXECUTE 'SELECT count(*) FROM people WHERE surname = $1' INTO result 
     USING _surname; 
    RETURN result; 
END; 
$$ LANGUAGE plpgsql; 

Вторая функция может быть быстрее, если ваш набор данных содержит какую-то страшную часто фамилию - то общий план будет seq scan, но много времени вы будете просить некоторую другую фамилию , и вы захотите использовать index scan. Параметр параметризации динамического запроса (например, ($1 IS NULL OR $1 = '''' OR rr.code = $1 OR) имеет тот же эффект.

+0

Это, кажется, не складывается. OP уже использует 'EXECUTE'. –

1

Ваши запросы: не то же самое.

Первый имеет

WHERE cc.employee_id = contract.employee_id 

где второй из них имеет:

WHERE employee_id = he.id 

А также:

($1 IS NULL OR $1 = '''' OR rr.code = $1 OR 
he.login = $1) 

Пожалуйста, проверьте снова одинаковых запросов и идентичных значений.