Присоединяйтесь к группе после выступления - PullRequest
1 голос
/ 06 мая 2020

Объединить таблицы, а затем сгруппировать по нескольким столбцам (например, заголовок) или сгруппировать строки в подзапросе, а затем присоединиться к другим таблицам? Второй метод медленный из-за отсутствия индексов после группировки? Следует ли мне вручную заказывать строки для второго метода, чтобы инициировать объединение слияния вместо вложенного l oop? Как это сделать правильно?

Это первый способ. Стало довольно неприятным, потому что Contragent_title и product_title должны быть в группе для строгого режима. И работаю только со строгой группировкой по режиму.

SELECT
    s.contragent_id,
    s.contragent_title,
    s.product_id AS sort_id,
    s.product_title AS sort_title,
    COALESCE(SUM(s.amount), 0) AS amount,
    COALESCE(SUM(s.price), 0) AS price,
    COALESCE(SUM(s.discount), 0) AS discount,
    COUNT(DISTINCT s.product_id) AS sorts_count,
    COUNT(DISTINCT s.contragent_id) AS contragents_count,
    dd.date,
    ~grouping(dd.date, s.contragent_id, s.product_id) :: bit(3) AS mask
FROM date_dimension dd
LEFT JOIN (
    SELECT 
        s.id, 
        s.created_at,
        s.contragent_id, 
        ca.title AS contragent_title,
        p.id AS product_id, 
        p.title AS product_title,
        sp.amount, 
        sp.price, 
        sp.discount
    FROM sales s
    LEFT JOIN sold_products sp 
        ON s.id = sp.sale_id
    LEFT JOIN products p 
        ON sp.product_id = p.id
    LEFT JOIN contragents ca 
        ON s.contragent_id = ca.id
    WHERE s.created_at BETWEEN :caf AND :cat
        AND s.plant_id = :plant_id
        AND (s.is_cache = :is_cache OR :is_cache IS NULL)
        AND (sp.product_id = :sort_id OR :sort_id IS NULL)
) s ON dd.date = date(s.created_at)                
WHERE (dd.date BETWEEN :caf AND :cat)
GROUP BY GROUPING SETS (
    (dd.date, s.contragent_id, s.contragent_title, s.product_id, s.product_title),
    (dd.date, s.contragent_id, s.contragent_title),
    (dd.date)
)

1 Ответ

1 голос
/ 06 мая 2020

Это пример того, о чем вы говорите:

Присоединение, затем агрегирование:

select d.name, count(e.employee_id) as number_of_johns
from departments d
left join employees e on e.department_id = e.department_id
where e.first_name = 'John'
group by d.department_id;

Агрегирование, затем присоединение:

select d.name, coalesce(number_of_johns, 0) as number_of_johns
from departments d
left join
(
  select department_id, count(*) as number_of_johns
  from employees
  where first_name = 'John'
  group by department_id
) e on e.department_id = e.department_id;

Вопрос

Вы хотите знать, быстрее ли один из них, чем другой, предполагая, что последний может быть медленнее из-за потери прямых ссылок на таблицы через идентификаторы. (Хотя каждый результат запроса представляет собой таблицу, а следовательно, и результат подзапроса, это не физическая таблица, хранящаяся в базе данных и, следовательно, не имеющая индексов.) что делают запросы:

  1. Первый запрос должен объединить все отделы и сотрудников и оставить только Джонсов. Как он это сделает? Вероятно, сначала он найдет всех Джонов. Если есть индекс для employees(first_name), он, вероятно, будет его использовать, иначе он прочитает полную таблицу. Затем найдите количество по Department_id. Если бы индекс, о котором я говорил, даже содержал отдел (индекс на employees(first_name, department_id), СУБД теперь имела бы предварительно отсортированные Джонсы и могла бы просто подсчитывать. Если это не так, СУБД может упорядочить строки сотрудников сейчас и подсчитать их, либо использовать другие метод подсчета. И если бы мы искали два имени вместо одного, составной индекс не принес бы или не принес бы никакой пользы по сравнению с простым индексом для first_name. Наконец, СУБД прочитает все отделы и присоединится к найденным подсчетам. Но наши строки результатов подсчета не являются таблицей, поэтому нет индекса, который мы могли бы использовать. В любом случае СУБД будет либо просто l oop над результатами, либо их все равно будет отсортировано, так что объединение будет простым и легким. Я думаю, что СУБД подойдет. В моих предположениях много «если», и СУБД может по-прежнему иметь другие методы на выбор или вообще не будет использовать индекс, потому что таблицы в любом случае такие маленькие, или что угодно.
  2. Второй запрос, ну, то же самое.

Ответ

Видите ли, мы можно только догадываться, как СУБД подойдет к объединениям с агрегатами. Он может предложить или не предложить один и тот же план выполнения для двух запросов. Идеальная СУБД создаст один и тот же план, поскольку два запроса делают одно и то же. Не очень совершенная СУБД может создавать разные планы, но что лучше, мы вряд ли можем догадаться. Давайте просто положимся на СУБД, которая хорошо справится с этим.

Я использую в основном Oracle и просто пробовал примерно то же, что показано с двумя моими таблицами. Он показывает точно такой же план выполнения для обоих запросов. PostgreSQL - тоже отличная СУБД. Не о чем беспокоиться, я бы сказал: -)

Лучше сосредоточиться на написании удобочитаемых, обслуживаемых запросов. С этими небольшими запросами нет большой разницы; первый немного компактен и удобен в обращении, второй немного сложнее.

Лично я предпочитаю второй запрос. Агрегирование перед объединением является хорошим стилем, и такие запросы можно легко расширить с помощью дополнительных агрегатов, что может быть намного сложнее с первым. Только если бы у меня возникли проблемы с производительностью, я бы попробовал другой подход.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...