2016-07-04 23 views
7

Ich machte ein bisschen ein Idiot früher heute auf this question. Die Frage war, SQL Server zu verwenden, und die richtige Antwort beinhaltete das Hinzufügen einer HAVING-Klausel. Der erste Fehler, den ich gemacht habe, war zu denken, dass ein Alias ​​in der SELECT-Anweisung in der HAVING-Klausel verwendet werden könnte, die in SQL Server nicht zulässig ist. Ich machte diesen Fehler, weil ich annahm, dass SQL Server die gleichen Regeln wie MySQL hatte, die es erlauben, einen Alias ​​in der HAVING-Klausel zu verwenden. DieseLeistung Auswirkungen von Alias ​​in HAVING Klausel verwendet werden

hat mich neugierig, und ich stocherte auf Stack-Überlauf und anderswo, eine Reihe von Material zu finden, zu erklären, warum diese Regeln auf den beiden jeweiligen RDBMS durchgesetzt werden. Aber nirgendwo fand ich eine Erklärung dafür, was die Performance Implikationen wären, einen Alias ​​in der HAVING Klausel zu erlauben/zu verbieten.

ein konkretes Beispiel zu geben, werde ich die Abfrage duplizieren, die in der oben genannten Frage aufgetreten:

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 

Was sind die Auswirkungen auf die Leistung der Verwendung eines Alias ​​in der HAVING Klausel statt re Angabe wäre die COUNT? Diese Frage kann direkt in MySQL beantwortet werden, und hoffentlich könnte jemand einen Einblick geben, was in SQL passieren würde, wenn es den Alias ​​in der HAVING-Klausel unterstützen würde.

Dies ist ein seltener Fall, bei dem es in Ordnung sein kann, eine SQL-Frage sowohl mit MySQL als auch mit SQL Server zu versehen, also genießen Sie diesen Moment in der Sonne.

+1

ich einen Narren aus mir die ganze Zeit machen. – Drew

+1

Mögliches Duplikat von [Eine SQL IN-Klausel parametrieren] (http://stackoverflow.com/questions/337704/parameterize-an-sql-in-clause) –

Antwort

3

Nur auf diese bestimmte Abfrage konzentriert, und mit Beispieldaten unten geladen. Dies adressiert einige andere Abfragen wie die von anderen erwähnte count(distinct ...).

Die alias in the HAVING scheint entweder ihre Leistung etwas zu übertreffen oder ihre Leistung (je nach Abfrage) deutlich zu übertreffen.

Dies verwendet eine bereits existierende Tabelle mit etwa 5 Millionen Zeilen darin erstellt schnell über diese answer von mir, die 3 bis 5 Minuten dauert.

Resultierende Struktur:

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; 

Aber statt mit INNODB. Erstellt die erwartete INNODB-Lückenanomalie aufgrund der Bereichsreservierungseinfügungen. Nur zu sagen, macht aber keinen Unterschied. 4,7 Millionen Zeilen.

Ändern Sie die Tabelle, um Tims angenommenem Schema zu nähern.

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 

Das folgende wird eine Weile dauern. Führen Sie es immer wieder in Chunks aus, sonst könnte die Verbindung zu einem Timeout führen. Die Zeitüberschreitung ist auf 5 Millionen Zeilen ohne eine LIMIT-Klausel in der Update-Anweisung zurückzuführen. Beachten Sie, wir do haben eine LIMIT-Klausel.

Wir machen es also in einer halben Million Iterationen. Setzt eine Spalte auf eine Zufallszahl zwischen 1 und 20

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

Halten Sie die oben laufen, bis keine camId null ist.

Ich lief es wie 10-mal (das Ganze dauert 7 bis 10 Minuten)

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 

erstellen nützlichen Index (nach den Einsätzen natürlich).

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 

Erstellen Sie die Campus-Tabelle.

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 

Führen Sie die zwei Abfragen:

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 

und

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 

So sind die Zeiten identisch. Lief jedes ein Dutzend Mal.

Der EXPLAIN Ausgang ist das gleiche für beide

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

Verwenden der AVG() Funktion, ich bin immer um eine 12% ige Erhöhung der Leistung mit der Alias ​​in den having (mit identischem EXPLAIN Ausgang) aus dem nach zwei Abfragen.

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 

Und schließlich die 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 

Die Alias ​​in dem having läuft konsequent 35% schneller mit dem gleichen EXPLAIN Ausgang. Unten gesehen. Der gleiche Explain-Ausgang wurde zweimal angezeigt, um nicht die gleiche Leistung zu erbringen, sondern als allgemeiner Hinweis.

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

Der Optimizer erscheint der Alias ​​in der mit im Moment zu begünstigen, vor allem für die DISTINCT.

2

Dies ist zu lang für einen Kommentar.

Ich glaube nicht, es gibt wirklich irgendwelche Auswirkungen auf die Leistung, es sei denn, der Ausdruck in der having Klausel enthält komplizierte Verarbeitung (z. B. count(distinct) oder eine komplexe Funktion, wie String-Verarbeitung auf einer langen Zeichenfolge).

Ich bin fast sicher, dass MySQL die Aggregation Funktion zweimal durchführen wird, wenn es zweimal in der Abfrage erwähnt wird. Ich bin mir nicht sicher, ob SQL Server die zweite Referenz optimieren wird, aber ich würde nicht raten (SQL Server hat einen guten Optimierer, aber es ist nicht so gut eine häufige Eliminierung von Ausdruck).

Die Frage ist dann die Komplexität des Ausdrucks. Einfache Ausdrücke wie count() und sum() verursachen wirklich keinen zusätzlichen Aufwand - wenn die Aggregation bereits durchgeführt wird. Komplexe Ausdrücke werden möglicherweise teuer.

Wenn Sie einen komplexen Ausdruck in SQL Server haben, sollten Sie sicherstellen können, dass er nur einmal mithilfe einer Unterabfrage ausgewertet wird.

+0

Es wäre also sicher zu sagen, dass es in MySQL generell vorzuziehen ist _use_ ein Alias ​​in der 'HAVING'-Klausel, vorausgesetzt, dass dies möglich ist? –

+0

Ich versuche zu messen, ob der Overhead beim Berechnen eines Alias ​​die Neubewertung des Ausdrucks in der "HAVING" -Klausel überwiegt. Mit anderen Worten, ich hatte gehofft, Sie könnten uns wissen lassen, was die beste Praxis in Bezug auf die "HAVING" -Klausel sein sollte. –

+0

@TimBiegeleisen. . . Ja, ich stimme zu. Ich bin mir ziemlich sicher, dass der Zweck, den Alias ​​in der 'having'-Klausel zuzulassen, teilweise auf der Tatsache beruht, dass MySQL Unterabfragen materialisiert. Daher ist eine Unterabfrage keine gute Möglichkeit, eine komplexe Berechnung (z. B. Entfernung) zu definieren, wenn Sie sie zum Filtern verwenden möchten. –

1

ich die SQL erwartete in der Größenordnung von FROM, WHERE, GROUP BY, HAVING, SELECT, um fortzufahren ORDER BY

Ich bin kein MYSQL-Experte, aber aus diesem Grund fand heraus, in dem MYSQL Documentation auf, warum es legal ist, .

MySQL erweitert die Standard-SQL-Verwendung von GROUP BY, sodass die Auswahlliste auf nicht aggregierte Spalten verweisen kann, die nicht in der GROUP BY-Klausel benannt sind. Dies bedeutet, dass die vorhergehende Abfrage in MySQL zulässig ist. Sie können diese Funktion verwenden, um eine bessere Leistung zu erzielen, indem Sie unnötige Spaltensortierung und Gruppierung vermeiden. Dies ist jedoch in erster Linie nützlich, wenn alle Werte in jeder nichtaggregierten Spalte, die nicht in GROUP BY benannt sind, für jede Gruppe gleich sind. Der Server kann aus jeder Gruppe einen beliebigen Wert auswählen. Wenn sie nicht identisch sind, sind die ausgewählten Werte unbestimmt. Außerdem kann die Auswahl von Werten aus jeder Gruppe nicht durch Hinzufügen einer ORDER BY-Klausel beeinflusst werden. Die Sortierung der Ergebnismenge erfolgt nach der Auswahl der Werte, und ORDER BY hat keinen Einfluss darauf, welche Werte innerhalb jeder Gruppe der Server auswählt.

Eine ähnliche MySQL-Erweiterung gilt für die HAVING-Klausel. In Standard-SQL kann eine Abfrage nicht auf nicht aggregierte Spalten in der HAVING-Klausel verweisen, die nicht in der GROUP BY-Klausel benannt sind. Um die Berechnungen zu vereinfachen, erlaubt eine MySQL-Erweiterung Verweise auf solche Spalten. Diese Erweiterung setzt voraus, dass die nicht gruppierten Spalten die gleichen gruppenweisen Werte haben. Ansonsten ist das Ergebnis unbestimmt.

Auf die Leistung Auswirkungen, ich gehe davon aus, dass die Aliasing wird langsamer als die unaliased haben, da der Filter nach der ganzen Ausführung angewendet werden muss. Ich werde auf die Kommentare der Experten warten.