Присоединяйтесь к оптимизации PostgresSQL

#sql #postgresql

Вопрос:

У меня есть 2 таблицы : Звонки (10 000 строк) , CRM (25 миллионов строк) Я хочу совершать звонки, чтобы присоединиться к CRM.

 select * 
from calls a
left join crm b
on (
(a.customerID = b.customerID)
OR
(a.Number1 in (b.Number_A,b.Number_B))
OR
(a.Number2 in (b.Number_A,b.Number_B))
);
 

Когда я просто соединяю идентификатор клиента, он работает нормально. Но приведенный выше код вызывает тайм-аут, и он выходит из строя.

Комментарии:

1. Вы должны, по крайней мере, добавить вывод ОБЪЯСНЕНИЯ (т. Е. Выполнить EXPLAIN select * from ... ) в свой вопрос и описать имеющиеся у вас индексы. У вас есть индексы, например, для b.number_a и b.number_b?

2. У меня есть индексы по всем столбцам соединения.

3. Ваш запрос не содержит условий фильтрации и извлекает все комбинации строк, соответствующие условию соединения. Вы уверены, что хотите просмотреть всю таблицу целиком?

4. Помогут примеры данных и желаемые результаты.

5. В вашем запросе может быть до 250 миллиардов строк результатов. Сколько вы ожидаете? Если текущий план объяснения ожидает очень много совпадений, и вы знаете, что их будет всего несколько, то установка pg_hint_plan и добавление подсказки строк в запрос может очень помочь.

Ответ №1:

Я бы предложил несколько left join s:

 select c.*, 
       coalesce(cc.col1, c1a.col1, c1b.col1, c2a.col1, c2b.col1)
from calls c left join
     crm cc
     on c.customerID = cc.customerID left join
     crm c1a
     on c.Number1 = c1a.Number_A left join
     crm c1b
     on c.Number1 = c1b.Number_B left join
     crm c2a
     on c.Number2 = c2a.Number_A left join
     crm c2b
     on c.Number2 = c2b.Number_B;
 

Затем это может использовать преимущества индексов на crm(CustomerId) , crm(Number1) , и crm(Number2) .

Комментарии:

1. Не вызовет ли это дупса? У меня есть индексы для всех столбцов соединения

2. Я не думаю, что это сработает. Если у вызова есть одна строка crm, соответствующая идентификатору клиента, и другая строка crm, соответствующая номеру 1, то мы получаем только одну строку результата, и COALESCE только она показывает нам одно из двух значений col1.

3. @SaadAmir . . . Это зависит от того, что вы подразумеваете под «двойниками».

Ответ №2:

Иногда при замене одного запроса, содержащего два условия , OR на два запроса, которые склеиваются вместе UNION , это приводит к улучшению плана выполнения. Я никогда не понимал, почему оптимизаторы СУБД сами не принимают это во внимание. И я не знаю, верно ли это для PostgreSQL или нет. Но, возможно, стоит попробовать.

В вашем случае в запросе есть внешнее соединение. Это усложняет дело. С помощью отдельных запросов мы можем получить как внешние объединенные, так и совпадающие строки crm для вызова и в этом случае должны избавиться от первых.

 select *
from
(
  select * from calls left join crm on crm.customerID = calls.customerID
  union
  select * from calls left join crm on crm.number_a = calls.number1
  union
  select * from calls left join crm on crm.number_a = calls.number2
  union
  select * from calls left join crm on crm.number_b = calls.number1
  union
  select * from calls left join crm on crm.number_b = calls.number2
) data
order by rank() over (partition by calls.id order by case when crm.id is null then 2 else 1 end)
fetch first row with ties;
 

Чтобы это работало быстро, у вас должен быть один индекс на столбец в запросе, т. е. Шесть индексов с одним столбцом.

Будет ли это быстрее, чем ваш исходный запрос, зависит от многих вещей. Главным образом: чем меньше совпадений, тем лучше.

Комментарии:

1. Просто примечание: UNION может быть намного дороже, чем UNION ALL .

2. @Цепеш: Да, я имел это в виду. Но мне пришлось избавиться от повторяющихся совпадений и повторяющихся несоответствий перед подачей заявки RANK . Я не знаю, будет ли иметь значение, если начать с » UNION ALL вместо», а затем применить DISTINCT позже. Может быть, можно попробовать другой вариант.