2017-08-15 55 views
2

在Oracle 11g數據庫,假設我們有桌子,CUSTOMERPAYMENT如下用Oracle SQL把很多列在GROUP BY子句

客戶

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,我避免把SELECT部分中誤導MAX通過將GROUP BY部分而不是列。

從我目前的知識,我更喜歡解決方案1 ​​,因爲它理解的邏輯GROUP BY部分比SELECT部分更重要。我只會放一組唯一的鍵來表示查詢的意圖,所以應用程序可以推斷出預期的行數。但我不知道表現。

,因爲編輯器要避免SELECT部分MAX功能我問這個問題,因爲我將回顧一個大的SQL投入50列GROUP BY子句中的代碼更改。我知道我們可以在某種程度上重構查詢以避免將不相關的列放在GROUP BYSELECT部分,但是請放棄該選項,因爲它會影響應用程序邏輯並需要更多時間來執行測試。


更新

我剛纔做了測試上我的大查詢兩個版本中,每個人都建議。查詢是複雜的,它有69行涉及超過20個表,執行計劃超過190行,所以我認爲這不是顯示它的地方。

我的生產數據現在很小,它有大約4000個客戶,並且查詢是針對整個數據庫運行的。在執行計劃中只有表CUSTOMER和幾個參考表有TABLE ACCESS FULL,其他表有索引訪問。兩個版本的執行計劃在某些部分中的加入算法(HASH GROUP BYSORT AGGREGATE)有一點點差異。

兩個版本使用約13分鐘,無顯着差異。

我也對與問題中的SQL類似的簡化版本進行了測試。兩個版本的執行計劃和流逝時間完全相同。

根據目前的信息,我認爲最合理的答案是它是不可預測的,除非測試決定優化器將執行這項工作時兩個版本的質量。如果有人能夠提供任何信息來說服或拒絕這個想法,我將非常感激。

+0

請有資格你所說的「更好」是什麼意思? –

+0

這個問題暗示着SQL語法的冗餘。 'select'子句**中的每個非聚合列必須出現在'group by'子句中,以使SQL有效,並且'group by'子句中的每一列都應該是**在'select'子句中聚集列,否則結果可能不明確。所以在我看來,應該不需要一個「group by」子句! –

+0

@Caius Jard,如果有任何方面從未想過,我想留下來,但我最關心的是性能。 – asinkxcoswt

回答

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 
+0

爲什麼**離開**加入? –

+1

@TonyAndrews查看編輯 – JohnHC

+0

謝謝,但請假設我們不能這樣做。我想知道將列放在'group by'中與將'MAX'放在'select'部分 – asinkxcoswt

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) 

要決定其中三個是一個更好的只是測試,看看他們的執行計劃。

+0

謝謝,但請假設我們可以不要這樣做。我想知道將列放在'group by'中與將'MAX'放在'select'部分 – asinkxcoswt

+0

之間的差異請在寫入JOIN關鍵字時指定INNER,LEFT,RIGHT或FULL OUTER –

+1

優化器通常是明智的足以爲您的解決方案制定好計劃。人們可能會期望在解決方案2中進行過多的排序,但完全取決於當前的優化器版本,並且可能會發生不可預測的變化。 – Serg

0

在解決方案1中,查詢將爲每列重複MAX函數。我不知道MAX函數是如何工作的,但我認爲它會對列中的所有元素進行排序,而不是選擇第一個元素(最好的情況)。這是一種定時炸彈,當你的桌子變得更大時,這個查詢會變得非常快。所以,如果你關注性能,你應該選擇解決方案2.它看起來更混亂,但會更好的應用程序。

1

解決方案1 ​​是昂貴的。

儘管優化器可以避免不必要的排序,但在某些時候,您將被迫在不相關列上添加索引/約束 以提高性能。 從長遠來看,這不是一個好的做法。

解決方案2是Oracle的方式。

Oracle文檔指出:

GROUP BY子句中只能包含聚集或分組列

甲骨文工程師們正當的理由這樣做, 然而這並不適用於其他RDBMS哪裏你可以簡單地把GROUP BY c.customerID和所有將罰款。

爲了代碼可讀性,--comment會更便宜。

在一般情況下,沒有任何擁抱平臺原則將有代價的: 更多的代碼,代碼怪異,內存,磁盤空間,性能等