我有一个名为Projects的表,它具有以下关系:
有很多贡献 有很多付款
在我的结果集中,我需要以下聚合值:
因为有很多聚合函数和多个连接,所以使用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秒范围内有1,000行。但我真的很关心可扩展性,而且我不想在桌面增长时重新设计所有内容。什么是最好的方法?
答案 0 :(得分:2)
知道每个1K行的时间是好的,但真正的问题是如何使用它们。
您打算将所有这些发送回用户界面吗?谷歌每页发布25个结果;也许你也应该。
您打算在中间层进行计算吗?也许你可以在数据库上进行那些计算,并保存自己将所有这些字节带到网上。
我的观点是,如果您仔细考虑使用它们,您可能永远不需要使用1,000或100万行。
您可以使用EXPLAIN PLAN查看两个查询之间的区别。
答案 1 :(得分:1)
我会采用第一种方法。您允许RDBMS完成它的工作,而不是试图为它完成它的工作。
通过创建临时表,您将始终为每个查询创建完整表。如果您只想要一个项目的数据,您仍然最终会创建完整的表(除非您相应地限制每个INSERT语句。)当然,您可以对其进行编码,但它已经成为一个公平的代码和复杂性,以获得小的性能增益。
使用SELECT,db可以获取适当数量的数据,根据上下文优化整个查询。如果其他用户查询了相同的数据,它甚至可能被缓存(查询,可能还有数据,具体取决于您的数据库)。如果性能确实是一个问题,您可以考虑使用索引/物化视图,或在INSERT / UPDATE / DELETE触发器上生成表。向外扩展,您可以使用服务器群集和分区视图 - 如果您要创建临时表,我认为这将很困难。
编辑:上面写的没有任何特定的rdbms,虽然OP补充说mysql是目标数据库。答案 2 :(得分:1)
第三个选项是派生表:
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
我不确定它是否会表现更好,但你可能会给它拍摄。
答案 3 :(得分:1)
一些想法:
派生表的想法在其他平台上会很好,但是MySQL与派生表有相同的问题:它们没有被编入索引。这意味着MySQL将在应用WHERE子句之前执行派生表的完整内容,而WHERE子句根本不会扩展。
选项1适用于紧凑,但是当您想要开始将派生表达式放在WHERE子句中时,语法可能会变得棘手。
物化视图的建议很好,但遗憾的是MySQL并不支持它们。我喜欢使用触发器的想法。您可以将该临时表转换为持久存在的实际表,然后在付款和贡献表上使用INSERT / UPDATE / DELETE触发器来更新Project Stats表。
最后,如果您不想搞乱触发器,并且如果您不太关心新鲜度,那么您可以始终拥有单独的stats表并将其更新为脱机,具有每个运行的cron作业几分钟完成您在上面的查询#2中指定的工作,除了在真实表上。根据您的申请的细微差别,更新统计数据的这种轻微延迟可能会或可能不会被您的用户接受。