2016-07-04 44 views
7

我今天早些時候在this question上做了一些傻瓜。問題在於使用SQL Server,並且正確答案涉及添加HAVING子句。我犯的最初錯誤是認爲SELECT語句中的別名可以在HAVING子句中使用,這在SQL Server中是不允許的。我犯了這個錯誤,因爲我認爲SQL Server與MySQL有相同的規則,它允許在HAVING子句中使用別名。允許別名在HAVING子句中使用的性能影響

這讓我很好奇,我在堆棧溢出和其他地方探索,找到一堆材料,解釋爲什麼這些規則在兩個相應的RDBMS上執行。但是我找不到解釋HAVING條款中允許/禁止別名的含義。

舉一個具體的例子,我將重複發生在上述討論中的查詢:

SELECT students.camID, campus.camName, COUNT(students.stuID) as studentCount 
FROM students 
JOIN campus 
    ON campus.camID = students.camID 
GROUP BY students.camID, campus.camName 
HAVING COUNT(students.stuID) > 3 
ORDER BY studentCount 

會是什麼HAVING子句中使用別名而不是重新指定的性能影響COUNT?這個問題可以直接在MySQL中回答,並且希望有人能夠深入瞭解SQL中會發生什麼,如果它支持HAVING子句中的別名。

這是一個罕見的例子,它可以用MySQL和SQL Server標記SQL問題,所以在陽光下享受這一刻。

+1

我出醜出自己所有的時間。 – Drew

+1

[參數化SQL IN子句]的可能重複(http://stackoverflow.com/questions/337704/parameterize-an-sql-in-clause) –

回答

3

只單純只是特定的查詢,並與樣本數據下面加載。這確實解決了其他一些查詢,例如其他人提到的count(distinct ...)

alias in the HAVING似乎略勝一籌或相當多的表現勝過其替代(取決於查詢)。

這使用一個約有500萬行的預先存在的表格,通過這個answer快速創建,需要3到5分鐘。

產生的結構:

CREATE TABLE `ratings` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `thing` int(11) NOT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB AUTO_INCREMENT=5046214 DEFAULT CHARSET=utf8; 

但使用的是InnoDB代替。由於範圍保留插入,創建期望的INNODB間隙異常。只是說,但沒有區別。 4.7百萬行。

修改表,要接近蒂姆的假設模式。

rename table ratings to students; -- not exactly instanteous (a COPY) 
alter table students add column camId int; -- get it near Tim's schema 
-- don't add the `camId` index yet 

以下將花費一段時間。一次又一次以塊運行,否則您的連接可能會超時。超時是由於更新語句中有500萬行沒有LIMIT子句。請注意,我們有個限度條款。

所以我們正在做50萬行迭代。集1和20

update students set camId=floor(rand()*20+1) where camId is null limit 500000; -- well that took a while (no surprise) 

之間的 隨機數列保持運行以上直到沒有camId爲空。

我跑它像10倍(整個事情需要7到10分鐘)

select camId,count(*) from students 
group by camId order by 1 ; 

1 235641 
2 236060 
3 236249 
4 235736 
5 236333 
6 235540 
7 235870 
8 236815 
9 235950 
10 235594 
11 236504 
12 236483 
13 235656 
14 236264 
15 236050 
16 236176 
17 236097 
18 235239 
19 235556 
20 234779 

select count(*) from students; 
-- 4.7 Million rows 

創建有用的索引(當然插入後)。

create index `ix_stu_cam` on students(camId); -- takes 45 seconds 

ANALYZE TABLE students; -- update the stats: http://dev.mysql.com/doc/refman/5.7/en/analyze-table.html 
-- the above is fine, takes 1 second 

創建校園表。

create table campus 
( camID int auto_increment primary key, 
    camName varchar(100) not null 
); 
insert campus(camName) values 
('one'),('2'),('3'),('4'),('5'), 
('6'),('7'),('8'),('9'),('ten'), 
('etc'),('etc'),('etc'),('etc'),('etc'), 
('etc'),('etc'),('etc'),('etc'),('twenty'); 
-- ok 20 of them 

運行兩個查詢:

SELECT students.camID, campus.camName, COUNT(students.id) as studentCount 
FROM students 
JOIN campus 
    ON campus.camID = students.camID 
GROUP BY students.camID, campus.camName 
HAVING COUNT(students.id) > 3 
ORDER BY studentCount; 
-- run it many many times, back to back, 5.50 seconds, 20 rows of output 

SELECT students.camID, campus.camName, COUNT(students.id) as studentCount 
FROM students 
JOIN campus 
    ON campus.camID = students.camID 
GROUP BY students.camID, campus.camName 
HAVING studentCount > 3 
ORDER BY studentCount; 
-- run it many many times, back to back, 5.50 seconds, 20 rows of output 

所以時間是相同的。每跑十幾次。

EXPLAIN輸出對於兩個

+----+-------------+----------+------+---------------+------------+---------+----------------------+--------+---------------------------------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref     | rows | Extra       | 
+----+-------------+----------+------+---------------+------------+---------+----------------------+--------+---------------------------------+ 
| 1 | SIMPLE  | campus | ALL | PRIMARY  | NULL  | NULL | NULL     |  20 | Using temporary; Using filesort | 
| 1 | SIMPLE  | students | ref | ix_stu_cam | ix_stu_cam | 5  | bigtest.campus.camID | 123766 | Using index      | 
+----+-------------+----------+------+---------------+------------+---------+----------------------+--------+---------------------------------+ 

使用AVG()函數從所述,我得到約增加了12%的性能與別名在having(具有相同EXPLAIN輸出)相同的以下兩個查詢。

SELECT students.camID, campus.camName, avg(students.id) as studentAvg 
FROM students 
JOIN campus 
    ON campus.camID = students.camID 
GROUP BY students.camID, campus.camName 
HAVING avg(students.id) > 2200000 
ORDER BY students.camID; 
-- avg time 7.5 

explain 

SELECT students.camID, campus.camName, avg(students.id) as studentAvg 
FROM students 
JOIN campus 
    ON campus.camID = students.camID 
GROUP BY students.camID, campus.camName 
HAVING studentAvg > 2200000 
ORDER BY students.camID; 
-- avg time 6.5 

最後,所述DISTINCT

SELECT students.camID, count(distinct students.id) as studentDistinct 
FROM students 
JOIN campus 
    ON campus.camID = students.camID 
GROUP BY students.camID 
HAVING count(distinct students.id) > 1000000 
ORDER BY students.camID; -- 10.6 10.84 12.1 11.49 10.1 9.97 10.27 11.53 9.84 9.98 
-- 9.9 

SELECT students.camID, count(distinct students.id) as studentDistinct 
FROM students 
JOIN campus 
    ON campus.camID = students.camID 
GROUP BY students.camID 
HAVING studentDistinct > 1000000 
ORDER BY students.camID; -- 6.81 6.55 6.75 6.31 7.11 6.36 6.55 
-- 6.45 

別名在具有持續運行速度更快具有相同EXPLAIN輸出35%。見下文。因此,相同的解釋輸出已經被顯示兩次,不會產生相同的性能,但是作爲一般線索。

+----+-------------+----------+-------+---------------+------------+---------+----------------------+--------+----------------------------------------------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref     | rows | Extra          | 
+----+-------------+----------+-------+---------------+------------+---------+----------------------+--------+----------------------------------------------+ 
| 1 | SIMPLE  | campus | index | PRIMARY  | PRIMARY | 4  | NULL     |  20 | Using index; Using temporary; Using filesort | 
| 1 | SIMPLE  | students | ref | ix_stu_cam | ix_stu_cam | 5  | bigtest.campus.camID | 123766 | Using index         | 
+----+-------------+----------+-------+---------------+------------+---------+----------------------+--------+----------------------------------------------+ 

的優化似乎有利於在此刻有別名,尤其是對DISTINCT.

2

這是太長的評論。

我不認爲真的有任何性能影響,除非having子句中的表達式包含複雜的處理(例如,count(distinct)或複雜函數,如長字符串上的字符串處理)。

我幾乎可以肯定,如果在查詢中提到兩次,MySQL將執行兩次聚合函數。我不確定SQL Server是否會優化掉第二個引用,但我猜不到(SQL Server有一個好的優化器,但它不是很好的常見表達式消除)。

接下來的問題是表達式的複雜性。簡單的表達式如count()sum()確實不會產生太多的額外開銷 - 一旦聚合已經完成。複雜的表達式可能開始變得昂貴。

如果您在SQL Server中有一個複雜的表達式,您應該能夠保證使用子查詢僅評估一次。

+0

因此,可以肯定地說,在MySQL中通常更喜歡在'HAVING'子句中使用別名,假設可以這樣做? –

+0

我想測量計算別名的開銷是否超過必須在'HAVING'子句中重新評估表達式。換句話說,我希望你可以讓我們知道'HAVING'條款的最佳做法。 –

+0

@TimBiegeleisen。 。 。是的我同意。我很確定在'having'子句中允許別名的目的部分基於MySQL實現子查詢的事實。因此,當您想要使用它進行過濾時,子查詢不是定義複雜計算(例如距離)的好方法。 –

1

我期待SQL中的FROMWHEREGROUP BYHAVINGSELECT的順序進行,ORDER BY

我不是MYSQL專家,但發現這一點的原因在MYSQL Documentation爲什麼它是合法的。

MySQL擴展了GROUP BY的標準SQL使用,以便選擇列表可以引用未在GROUP BY子句中指定的非聚集列。這意味着前面的查詢在MySQL中是合法的。 您可以使用此功能通過避免不必要的列排序和分組來獲得更好的性能。然而,主要當在GROUP BY未命名每個非聚集列中的所有值都爲每個組相同的,這是有用的。服務器可以自由選擇每個組中的任何值,因此除非它們相同,否則所選值是不確定的。此外,每個組的值的選擇不能通過添加ORDER BY子句來影響。結果集排序在選擇值後發生,並且ORDER BY不會影響服務器選擇的每個組中的哪些值。

類似MySQL擴展適用於HAVING子句。在標準SQL中,查詢無法引用未在GROUP BY子句中命名的HAVING子句中的非聚合列。爲了簡化計算,MySQL擴展允許引用這樣的列。此擴展假定非分組列具有相同的分組值。否則,結果是不確定的。

在性能影響上,我假設別名會比不混淆的具有更慢,因爲過濾器必須在所有執行後應用。我會等待專家發表評論。