在Oracle SQL

时间:2017-08-15 13:13:14

标签: sql oracle oracle11g group-by

在Oracle 11g数据库中,假设我们有表CUSTOMERPAYMENT,如下所示

客户

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中的所有列以及每个客户的所有付款总和。有很多可能的方法可以做到这一点,但我想问下列哪一个更好。

解决方案1 ​​

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;

解决方案2

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 BYSELECT部分中放置不相关的列,但请放弃该选项,因为它会影响应用程序逻辑并需要更多时间来进行测试

更新

我刚刚在所有人建议的两个版本中对我的大查询进行了测试。查询很复杂,有69行涉及20多个表,执行计划超过190行,所以我认为这不是展示它的地方。

我的生产数据现在很小,它有大约4000个客户,并且查询是针对整个数据库运行的。只有表CUSTOMER和一些参考表在执行计划中有TABLE ACCESS FULL,其他表可以通过索引访问。两个版本的执行计划在某些方面的连接算法(HASH GROUP BY vs SORT AGGREGATE)略有不同。

两个版本使用大约13分钟,没有显着差异。

我也对类似于问题中的SQL的简化版本进行了测试。两个版本具有完全相同的执行计划和经过时间。

根据当前的信息,我认为最合理的答案是它是不可预测的,除非测试决定两个版本的质量,因为优化器将完成这项工作。如果有人能提供任何信息来说服或拒绝这个想法,我将非常感激。

4 个答案:

答案 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.它看起来更混乱,但对应用程序来说会更好。