在Oracle 11g数据库中,假设我们有表CUSTOMER
和PAYMENT
,如下所示
CUSTOMER_ID | CUSTOMER_NAME | CUSTOMER_AGE | CUSTOMER_CREATION_DATE
--------------------------------------------------------------------
001 John 30 1 Jan 2017
002 Jack 10 2 Jan 2017
003 Jim 50 3 Jan 2017
CUSTOMER_ID | PAYMENT_ID | PAYMENT_AMOUNT |
-------------------------------------------
001 900 100.00
001 901 200.00
001 902 300.00
003 903 999.00
我们想编写一个SQL来获取表CUSTOMER
中的所有列以及每个客户的所有付款总和。有很多可能的方法可以做到这一点,但我想问下列哪一个更好。
SELECT C.CUSTOMER_ID
, MAX(C.CUSTOMER_NAME) CUSTOMER_NAME
, MAX(C.CUSTOMER_AGE) CUSTOMER_AGE
, MAX(C.CUSTOMER_CREATION_DATE) CUSTOMER_CREATION_DATE
, SUM(P.PAYMENT_AMOUNT) TOTAL_PAYMENT_AMOUNT
FROM CUSTOMER C
JOIN PAYMENT P ON (P.CUSTOMER_ID = C.CUSTOMER_ID)
GROUP BY C.CUSTOMER_ID;
SELECT C.CUSTOMER_ID
, C.CUSTOMER_NAME
, C.CUSTOMER_AGE
, C.CUSTOMER_CREATION_DATE
, SUM(P.PAYMENT_AMOUNT) PAYMENT_AMOUNT
FROM CUSTOMER C
JOIN PAYMENT P ON (P.CUSTOMER_ID = C.CUSTOMER_ID)
GROUP BY C.CUSTOMER_ID, C.CUSTOMER_NAME, C.CUSTOMER_AGE, C.CUSTOMER_CREATION_DATE
请注意解决方案1 我使用MAX
并不是因为我真的想要最高结果,但我因为我想要" ONE"对于具有相同CUSTOMER_ID
在解决方案2 中,我通过将列放在MAX
部分而避免在SELECT
部分中误导GROUP BY
。
根据我目前的知识,我更喜欢解决方案1 ,因为理解GROUP BY
部分中的逻辑比理解SELECT
部分更重要。我只会放置一组唯一键来表达查询的意图,因此应用程序可以推断出预期的行数。但我不了解表现。
我问这个问题是因为我正在审查在GROUP BY
子句中放置50列的大型SQL的代码更改,因为编辑器想要避免MAX
部分中的SELECT
函数。我知道我们可以在某种程度上重构查询以避免在GROUP BY
和SELECT
部分中放置不相关的列,但请放弃该选项,因为它会影响应用程序逻辑并需要更多时间来进行测试
我刚刚在所有人建议的两个版本中对我的大查询进行了测试。查询很复杂,有69行涉及20多个表,执行计划超过190行,所以我认为这不是展示它的地方。
我的生产数据现在很小,它有大约4000个客户,并且查询是针对整个数据库运行的。只有表CUSTOMER
和一些参考表在执行计划中有TABLE ACCESS FULL
,其他表可以通过索引访问。两个版本的执行计划在某些方面的连接算法(HASH GROUP BY
vs SORT AGGREGATE
)略有不同。
两个版本使用大约13分钟,没有显着差异。
我也对类似于问题中的SQL的简化版本进行了测试。两个版本具有完全相同的执行计划和经过时间。
根据当前的信息,我认为最合理的答案是它是不可预测的,除非测试决定两个版本的质量,因为优化器将完成这项工作。如果有人能提供任何信息来说服或拒绝这个想法,我将非常感激。
答案 0 :(得分:3)
另一种选择是
SELECT C.CUSTOMER_ID
, C.CUSTOMER_NAME
, C.CUSTOMER_AGE
, C.CUSTOMER_CREATION_DATE
, P.PAYMENT_AMOUNT
FROM CUSTOMER C
JOIN (
SELECT CUSTOMER_ID, SUM(PAYMENT_AMOUNT) PAYMENT_AMOUNT
FROM PAYMENT
GROUP BY CUSTOMER_ID
) P ON (P.CUSTOMER_ID = C.CUSTOMER_ID)
要确定哪三个更好,只需测试它们并查看执行计划。
答案 1 :(得分:1)
都不是。支付金额,然后加入结果。
select C.*, p.total_payment -- c.* gets all columns from table alias c without typing them all out
from Customer C
left join -- I've used left in case you want to include customers with no orders
(
select customer_id, sum(payment_amount) as total_payment
from Payment
group by customer_id
) p
on p.customer_id = c.customer_id
答案 2 :(得分:1)
解决方案1 成本高昂。
即使优化器可以避免不必要的排序, 在某些时候,您将被迫添加索引/约束 通过不相关的列来提高性能。 从长远来看,这不是一个好习惯。
解决方案2 是Oracle方式。
Oracle文档声明:
GROUP BY子句必须只包含聚合或分组列
Oracle工程师有正当理由这样做,
但这并不适用于您所在的其他RDBMS
可以简单地放GROUP BY c.customerID
,一切都会好的。
为了代码可读性,--comment
会更便宜。
一般而言,不接受任何平台原则会产生成本: 更多代码,奇怪的代码,内存,磁盘空间,性能等。
答案 3 :(得分:0)
在解决方案1中,查询将为每列重复MAX功能。我不确切知道MAX函数是如何工作的,但我认为它对列上的所有元素进行排序而不是选择第一个(最佳情况)。这是一种定时炸弹,当你的桌子变大时,这个查询会变得非常快。因此,如果您对性能有所了解,那么您应该选择解决方案2.它看起来更混乱,但对应用程序来说会更好。