Postgresql - LEFT JOIN LATERAL слишком медленный, чем подзапрос
У меня возникли некоторые трудности с использованием функции "LEFT JOIN LATERAL" с postgresql 9.5.
В моей таблице есть три столбца: "ID", "DATE", "CODE". Один человек (ID) имеет несколько строк, как показано ниже. Номер идентификатора 362, а общее количество строк около 2500000.
ID / DATE / CODE
1 / 20020101 / drugA
1 / 20020102 / drugA
1 / 20020103 / drugB
1 / 20020104 / drugA
1 / 20020105 / drugA
1 / 20020106 / drugB
1 / 20020107 / drugA
2 / ... / ...
Мне нужно обобщить информацию о препарате А, использованном в период между первым и последним днем приема препарата В.
В приведенном выше случае для идентификатора (1) должны оставаться только две строки [между 20020103 ~ 20020106; период приема препарата В].
1 / 20020104 / drugA
1 / 20020105 / drugA
Чтобы получить эту работу, я пишу код SQL, используя "LEFT LATERAL JOIN", как показано ниже.
SELECT * FROM (SELECT ID, min(DATE) as start_date, max(DATE) as end_date from MAIN_TABLE WHERE CODE = 'drugA' GROUP BY ID) AA
LEFT JOIN LATERAL (SELECT ID, COUNT(ID) as no_tx, min(DATE) as fday_tx, max(DATE) lday_tx from MAIN_TABLE WHERE CODE = 'drugB' AND DATE > AA.start_date AND DATE < AA.end_date GROUP BY ID) as BB USING(ID);
Есть только 362 человека ID, но этот код postgresql займет около 2 минут.
Это слишком медленно. Поэтому я попробовал другой код SQL, используя подзапрос.
SELECT * FROM (SELECT ID, min(DATE) as start_date, max(DATE) as end_date from MAIN_TABLE WHERE CODE ='drugA' GROUP BY ID) AA
LEFT JOIN (
SELECT ID, COUNT(ID) as no_tx, min(DATE) as fday_tx, max(DATE) lday_tx FROM (SELECT ID, DATE, CODE FROM MAIN_TABLE) BB
LEFT JOIN (SELECT ID, min(DATE) as start_date, max(DATE) as end_date from MAIN_TABLE WHERE CODE ='drugA' GROUP BY ID) CC USING (ID)
WHERE CODE = 'drugB' and DATE > start_date and DATE < end_date GROUP BY ID
) DD USING (ID);
Этот код не простой, но очень быстрый (занимает всего 1,6 секунды).
Когда я сравниваю объяснение двух кодов, второй код использует хеш-соединение, а первый код - нет.
Могу ли я получить подсказку, как более эффективно улучшить первый код с помощью функции LEFT LATERAL JOIN?
1 ответ
Почему бы просто не использовать join
а также group by
?
SELECT AA.ID, COUNT(B.ID) as no_tx, min(B.DATE) as fday_tx, max(B.DATE) as lday_tx,
AA.start_date, AA.end_date
FROM (SELECT ID, min(DATE) as start_date, max(DATE) as end_date
FROM MAIN_TABLE
WHERE CODE = 'drugA'
GROUP BY ID
) AA LEFT JOIN
MAIN_TABLE b
ON b.CODE = 'drugB' AND b.DATE > AA.start_date AND b.DATE < AA.end_date
GROUP BY AA.ID, AA.start_date, AA.end_date;
Или, возможно, более эффективно, оконные функции:
SELECT ID, SUM(CASE WHEN code = 'drugB' THEN 1 ELSE 0 END) as no_tx,
MIN(CASE WHEN code = 'drugB' THEN DATE END) as fday_tx,
MIN(CASE WHEN code = 'drugB' THEN DATE END) as lday_tx,
start_date, end_date
FROM (SELECT t.*,
MIN(CASE WHEN code = 'drugA' THEN date END) as start_date,
MAX(CASE WHEN code = 'drugB' THEN date END) as end_date
FROM MAIN_TABLE t
) t
WHERE code in ('drugA', 'drugB') AND
date between start_date and end_date
GROUP BY t.id;