Какой самый эффективный запрос? - PullRequest
1 голос
/ 15 мая 2010

У меня есть таблица с именем Projects, которая имеет следующие отношения:

имеет много вкладов имеет много платежей

В моем наборе результатов мне нужны следующие совокупные значения:

  • Количество уникальных участников (DonorID в таблице взносов)
  • Всего внесено (Сумма суммы в таблице взносов)
  • Всего оплачено (Сумма платежаСумма на платёжной таблице)

Поскольку существует так много агрегатных функций и множественных объединений, становится грязно, поэтому используйте стандартные агрегатные функции в предложении GROUP BY. Мне также нужна возможность сортировать и фильтровать эти поля. Итак, я предложил два варианта:

Использование подзапросов:

SELECT Project.ID AS PROJECT_ID,
(SELECT SUM(PaymentAmount) FROM Payment WHERE ProjectID = PROJECT_ID) AS TotalPaidBack,
(SELECT COUNT(DISTINCT DonorID) FROM Contribution WHERE RecipientID = PROJECT_ID) AS ContributorCount,
(SELECT SUM(Amount) FROM Contribution WHERE RecipientID = PROJECT_ID) AS TotalReceived
FROM Project;

Использование временной таблицы:

DROP TABLE IF EXISTS Project_Temp;
CREATE TEMPORARY TABLE Project_Temp (project_id INT NOT NULL, total_payments INT, total_donors INT, total_received INT, PRIMARY KEY(project_id)) ENGINE=MEMORY;
INSERT INTO Project_Temp (project_id,total_payments)
 SELECT `Project`.ID, IFNULL(SUM(PaymentAmount),0) FROM `Project` LEFT JOIN `Payment` ON ProjectID = `Project`.ID GROUP BY 1;
INSERT INTO Project_Temp (project_id,total_donors,total_received)
 SELECT `Project`.ID, IFNULL(COUNT(DISTINCT DonorID),0), IFNULL(SUM(Amount),0) FROM `Project` LEFT JOIN `Contribution` ON RecipientID = `Project`.ID  GROUP BY 1
 ON DUPLICATE KEY UPDATE total_donors = VALUES(total_donors), total_received = VALUES(total_received);

SELECT * FROM Project_Temp;

Тесты для обоих довольно сопоставимы, в диапазоне 0,7 - 0,8 секунд с 1000 строк. Но я действительно обеспокоен масштабируемостью, и я не хочу перестраивать все по мере роста моих таблиц. Какой лучший подход?

Ответы [ 4 ]

2 голосов
/ 15 мая 2010

Хорошо знать время для каждой строки 1К, но реальный вопрос в том, как они будут использоваться.

Планируете ли вы отправить все это обратно в интерфейс? Google выдает результаты 25 на страницу; может тебе тоже стоит.

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

Суть в том, что вам никогда не понадобится работать с 1000 или миллионами строк, если вы тщательно продумаете, что вы делаете с ними.

Вы можете ОБЪЯСНИТЬ ПЛАН, чтобы увидеть разницу между этими двумя запросами.

1 голос
/ 15 мая 2010

Несколько мыслей:

  • Идея производной таблицы была бы полезна на других платформах, но MySQL имеет ту же проблему с производными таблицами, что и с представлениями: они не индексируются. Это означает, что MySQL будет выполнять все содержимое производной таблицы перед применением предложения WHERE, которое не масштабируется вообще.

  • Вариант 1 хорош для компактности, но синтаксис может оказаться сложным, если вы хотите начать помещать производные выражения в предложение WHERE.

  • Предложение материализованных представлений является хорошим, но, к сожалению, MySQL их не поддерживает. Мне нравится идея использования триггеров. Вы можете преобразовать эту временную таблицу в реальную таблицу, которая сохраняется, а затем использовать триггеры INSERT / UPDATE / DELETE в таблицах Payments и Contribution для обновления таблицы Project Stats.

  • Наконец, если вы не хотите связываться с триггерами и если вас не слишком волнует свежесть, вы всегда можете иметь отдельную таблицу статистики и обновлять ее в автономном режиме, имея задание cron, которое запускается каждый раз. несколько минут, которые выполняют ту работу, которую вы указали в Query # 2 выше, за исключением реальной таблицы. В зависимости от нюансов вашего приложения, эта небольшая задержка в обновлении статистики может или не может быть приемлемой для ваших пользователей.

1 голос
/ 15 мая 2010

Существует третий вариант, который является производным таблиц:

Select Project.ID AS PROJECT_ID
    , Payments.Total AS TotalPaidBack
    , Coalesce(ContributionStats.DonarCount, 0) As ContributorCount
    , ContributionStats.Total As TotalReceived
From Project
    Left Join   (
                Select C1.RecipientId, Sum(C1.Amount) As Total, Count(Distinct C1.DonarId) ContributorCount
                From Contribution As C1
                Group By C1.RecipientId
                ) As ContributionStats
        On ContributionStats.RecipientId = Project.Project_Id
    Left Join   (
                Select P1.ProjectID, Sum(P1.PaymentAmount) As Total
                From Payment As P1
                Group By P1.RecipientId
                ) As Payments
        On Payments.ProjectId = Project.Project_Id

Я не уверен, что он будет работать лучше, но вы можете дать ему шанс.

1 голос
/ 15 мая 2010

Я бы пошел с первым подходом. Вы позволяете СУБД выполнять свою работу, а не пытаетесь выполнять свою работу за нее.

Создав временную таблицу, вы всегда будете создавать полную таблицу для каждого запроса. Если вам нужны данные только для одного проекта, вы все равно в конечном итоге создаете полную таблицу (если только вы не ограничиваете каждый оператор INSERT соответствующим образом.) Конечно, вы можете кодировать ее, но она уже становится достаточным количеством кода и усложняется для небольшого прироста производительности.

С помощью SELECT БД может извлекать соответствующее количество данных, оптимизируя весь запрос на основе контекста. Если другие пользователи запрашивают те же данные, они могут даже кэшироваться (запрос и, возможно, данные, в зависимости от вашей базы данных). Если производительность действительно вызывает озабоченность, вы можете рассмотреть возможность использования индексированных / материализованных представлений или создания таблицы по триггеру INSERT / UPDATE / DELETE. Масштабируя, вы можете использовать кластеры серверов и разделенные представления - я полагаю, что это будет сложно, если вы создаете временные таблицы.

РЕДАКТИРОВАТЬ: выше написано без каких-либо конкретных rdbms, хотя ОП добавил, что MySQL является целевым дБ.

...