Sql 什么';什么是最有效的查询?

Sql 什么';什么是最有效的查询?,sql,function,performance,subquery,aggregate,Sql,Function,Performance,Subquery,Aggregate,我有一个名为Projects的表,该表具有以下关系: 他有很多贡献 他有很多钱 在我的结果集中,我需要以下聚合值: 唯一贡献者的数量(贡献表上的DonorID) 出资总额(出资表上的金额总和) 支付总额(付款表上的付款金额之和) 因为有太多的聚合函数和多个联接,所以使用GROUPBY子句中的标准聚合函数会变得很混乱。我还需要能够排序和过滤这些字段。因此,我提出了两种选择: 使用子查询: SELECT Project.ID AS PROJECT_ID, (SELECT SUM(PaymentA

我有一个名为Projects的表,该表具有以下关系:

他有很多贡献 他有很多钱

在我的结果集中,我需要以下聚合值:

  • 唯一贡献者的数量(贡献表上的DonorID)
  • 出资总额(出资表上的金额总和)
  • 支付总额(付款表上的付款金额之和)
因为有太多的聚合函数和多个联接,所以使用GROUPBY子句中的标准聚合函数会变得很混乱。我还需要能够排序和过滤这些字段。因此,我提出了两种选择:

使用子查询:

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行。但我非常关心可伸缩性,我不想随着表的增长而重新设计所有内容。最好的方法是什么?

知道每1K行的计时很好,但真正的问题是如何使用它们

您是否计划将所有这些内容发送回UI?谷歌每页发布25条搜索结果;也许你也应该

你打算在中间层做计算吗?也许你可以在数据库上做这些计算,然后把所有的字节都放到网络上保存下来

我的观点是,如果仔细考虑如何处理1000行或100万行,您可能永远不需要处理它们


您可以解释计划,看看这两个查询之间的区别。

知道每1K行的计时很好,但真正的问题是如何使用它们

您是否计划将所有这些内容发送回UI?谷歌每页发布25条搜索结果;也许你也应该

你打算在中间层做计算吗?也许你可以在数据库上做这些计算,然后把所有的字节都放到网络上保存下来

我的观点是,如果仔细考虑如何处理1000行或100万行,您可能永远不需要处理它们


您可以解释计划,看看两个查询之间的区别。

我同意第一种方法。您允许RDBMS完成它的工作,而不是试图为它完成它的工作

通过创建临时表,您将始终为每个查询创建完整的表。如果您只需要一个项目的数据,那么最终仍然会创建完整的表(除非您相应地限制每个INSERT语句)。当然,您可以对它进行编码,但它已经成为一个相当数量的代码,并且对于一个小的性能增益来说,它已经变得相当复杂

通过选择,数据库可以获取适当数量的数据,从而基于上下文优化整个查询。如果其他用户查询了相同的数据,甚至可能会对其进行缓存(查询,可能还有数据,具体取决于您的数据库)。如果性能真的是一个问题,您可以考虑使用索引/物化视图,或者在INSERT/UPDATE/DELATE触发器上生成表。通过向外扩展,您可以使用服务器集群和分区视图——我认为如果您要创建临时表,这将很困难


编辑:尽管OP补充说mysql是目标数据库,但上面的内容并没有考虑任何特定的rdbms。

我会选择第一种方法。您允许RDBMS完成它的工作,而不是试图为它完成它的工作

通过创建临时表,您将始终为每个查询创建完整的表。如果您只需要一个项目的数据,那么最终仍然会创建完整的表(除非您相应地限制每个INSERT语句)。当然,您可以对它进行编码,但它已经成为一个相当数量的代码,并且对于一个小的性能增益来说,它已经变得相当复杂

通过选择,数据库可以获取适当数量的数据,从而基于上下文优化整个查询。如果其他用户查询了相同的数据,甚至可能会对其进行缓存(查询,可能还有数据,具体取决于您的数据库)。如果性能真的是一个问题,您可以考虑使用索引/物化视图,或者在INSERT/UPDATE/DELATE触发器上生成表。通过向外扩展,您可以使用服务器集群和分区视图——我认为如果您要创建临时表,这将很困难


编辑:尽管OP补充说mysql是目标数据库,但上面的内容没有考虑任何特定的rdbms。

还有第三个选项是派生表:

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

我不确定它是否会表现得更好,但您可以试一试。

还有第三个选项是派生表:

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
我不确定它是否会表现得更好,但你可以试一试。

一些想法:

  • 派生表的想法在其他平台上也不错,但是MySQL对于派生表和视图有相同的问题:它们没有索引。这意味着MySQL将在应用WHERE子句之前执行派生表的全部内容,WHERE子句根本不可伸缩

  • 选项1有利于紧凑,但当您想开始将派生表达式放入WHERE子句时,语法可能会变得棘手

  • 物化视图的建议很好,但不幸的是MySQL不支持它们。我喜欢使用触发器的想法。您可以将该临时表转换为持续存在的实际表,然后在付款和供款表上使用INSERT/UPDATE/DELETE触发器来更新项目统计表

  • 最后,如果您不想弄乱触发器,如果您不太关心新鲜度,您可以始终使用单独的stats表并脱机更新它,让cron作业每隔几分钟运行一次,完成您在上面的查询#2中指定的工作,但在实际表上除外。根据应用程序的细微差别,用户可能会接受或不接受更新统计数据的这种轻微延迟

    • 一些想法:

      • 派生表的概念是g