2016-07-04 59 views
7

Mi sono reso un po 'pazzo da me stesso oggi in data this question. La domanda stava usando SQL Server e la risposta corretta prevedeva l'aggiunta di una clausola HAVING. L'errore iniziale che ho fatto è stato pensare che un alias nell'istruzione SELECT potesse essere utilizzato nella clausola HAVING, che non è consentita in SQL Server. Ho fatto questo errore perché ritenevo che SQL Server avesse le stesse regole di MySQL, il che consente di utilizzare un alias nella clausola HAVING.Implicazioni sulle prestazioni di consentire l'utilizzo di alias nella clausola HAVING

Questo mi ha fatto curioso, e io curiosato su Stack Overflow e altrove, trovando un po 'di materiale che spiega il motivo per cui queste regole vengono applicate sui due rispettivi RDBMS. Ma da nessuna parte ho trovato una spiegazione di ciò che le prestazioni prestazioni sarebbero di consentire/non consentire un alias nella clausola HAVING.

Per fare un esempio concreto, io copiare la query che si è verificato nella suddetta domanda:

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 

Quali sarebbero le implicazioni sulle prestazioni di utilizzare un alias nella clausola HAVING invece di ri specificare il COUNT? Questa domanda può rispondere direttamente a MySQL, e si spera che qualcuno possa dare una visione di ciò che accadrebbe in SQL se dovesse supportare l'alias nella clausola HAVING.

Questo è un raro caso in cui potrebbe essere corretto taggare una domanda SQL con MySQL e SQL Server, quindi godetevi questo momento al sole.

+1

Faccio un pazzo fuori di me tutto il tempo. – Drew

+1

Possibile duplicato di [Parametrizzare una clausola SQL IN] (http://stackoverflow.com/questions/337704/parameterize-an-sql-in-clause) –

risposta

3

Strettamente focalizzato solo su quella particolare query e con i dati di esempio caricati di seguito. Questo risolve alcune altre domande come la count(distinct ...) menzionata da altri.

Il alias in the HAVING sembra leggermente sovraperformare o leggermente superiore alla sua alternativa (a seconda della query).

Questo utilizza una tabella preesistente con circa 5 milioni di righe in esso create rapidamente tramite questo answer del mio che richiede da 3 a 5 minuti.

struttura risultante:

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; 

Ma usando INNODB invece. Crea l'anomalia di gap INNODB prevista a causa degli inserimenti di prenotazione range. Sto solo dicendo, ma non fa differenza. 4,7 milioni di righe.

Modificare la tabella per avvicinarsi allo schema assunto da Tim.

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 

Quanto segue richiederà un po 'di tempo. Eseguilo ancora e ancora in blocchi, altrimenti la tua connessione potrebbe scadere. Il timeout è dovuto a 5 milioni di righe senza una clausola LIMIT nell'istruzione di aggiornamento. Nota, abbiamo do avere una clausola LIMIT.

Quindi lo stiamo facendo in mezzo milione di iterazioni di righe. Imposta una colonna a un numero casuale tra 1 e 20

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

Continua a correre il precedente finché non camId è nullo.

ho fatto funzionare come 10 volte (il tutto prende 7 a 10 minuti)

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 

Creare un indice utile (dopo gli inserti ovviamente).

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 

Creare la tabella del campus.

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 

Eseguire le due query:

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 

e

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 

Quindi i tempi sono identici. Esegui ciascuna una dozzina di volte.

Il EXPLAIN uscita è la stessa per entrambi

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

Utilizzando la funzione AVG(), sto ottenendo circa un aumento del 12% in termini di prestazioni con l'alias nella having (con identico EXPLAIN uscita) dal seguendo due domande.

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 

E, infine, il 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 

L'alias nel dover corre costantemente più veloceil 35% con la stessa EXPLAIN uscita. Visto di seguito Quindi lo stesso output di Explain è stato mostrato due volte per non dare la stessa performance, ma come un indizio generale.

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

L'Optimizer sembra favorire l'alias nel avendo in questo momento, soprattutto per il DISTINCT.

2

Questo è troppo lungo per un commento.

Non penso ci siano davvero implicazioni di prestazioni, a meno che l'espressione nella clausola having contenga elaborazione complessa (ad esempio, count(distinct) o una funzione complessa, come l'elaborazione di stringhe su una stringa lunga).

Sono quasi certo che MySQL eseguirà la funzione di aggregazione due volte se viene citata due volte nella query. Non sono sicuro che SQL Server ottimizzerà il secondo riferimento, ma suppongo di no (SQL Server ha un buon ottimizzatore, ma non è una buona eliminazione di espressioni comuni).

La domanda è quindi la complessità dell'espressione. Le espressioni semplici come count() e sum() non comportano alcun sovraccarico aggiuntivo, una volta che l'aggregazione è già stata eseguita. Le espressioni complesse potrebbero iniziare a diventare costose.

Se si dispone di un'espressione complessa in SQL Server, è necessario essere in grado di garantire che venga valutata una sola volta utilizzando una sottoquery.

+0

Quindi sarebbe corretto dire che in genere è preferibile in MySQL _use_ un alias nella clausola 'HAVING', assumendo che sia possibile farlo? –

+0

Sto cercando di valutare se il sovraccarico del calcolo di un alias supera di nuovo la necessità di rivalutare nuovamente l'espressione nella clausola 'HAVING'. In altre parole, speravo che potessi farci sapere quale dovrebbe essere la migliore pratica riguardo alla clausola "HAVING". –

+0

@TimBiegeleisen. . . Si, sono d'accordo. Sono piuttosto sicuro che lo scopo di consentire l'alias nella clausola 'having' sia parzialmente basato sul fatto che MySQL materializza le sottoquery. Quindi, una sottoquery non è un buon modo per definire un calcolo complesso (come la distanza) quando si vuole usarlo per il filtraggio. –

1

mi aspettavo lo SQL per procedere in ordine di FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY

io non sono un esperto di MYSQL, ma trovato questo motivo nel MYSQL Documentation sul perché è legale .

MySQL estende l'utilizzo SQL standard di GROUP BY in modo che l'elenco di selezione possa fare riferimento a colonne non aggregate non denominate nella clausola GROUP BY. Ciò significa che la query precedente è legale in MySQL. È possibile utilizzare questa funzione per ottenere prestazioni migliori evitando l'ordinamento e il raggruppamento non necessari delle colonne. Tuttavia, ciò è utile soprattutto quando tutti i valori in ogni colonna non aggregata non nominata in GROUP BY sono uguali per ciascun gruppo. Il server è libero di scegliere qualsiasi valore da ciascun gruppo, quindi a meno che non siano gli stessi, i valori scelti sono indeterminati. Inoltre, la selezione dei valori di ciascun gruppo non può essere influenzata dall'aggiunta di una clausola ORDER BY. L'ordinamento dei set di risultati si verifica dopo aver scelto i valori e ORDER BY non influenza i valori all'interno di ciascun gruppo scelto dal server.

Un'estensione MySQL simile si applica alla clausola HAVING. Nell'SQL standard, una query non può fare riferimento a colonne non aggregate nella clausola HAVING che non sono denominate nella clausola GROUP BY. Per semplificare i calcoli, un'estensione MySQL consente i riferimenti a tali colonne. Questa estensione presuppone che le colonne non raggruppate abbiano gli stessi valori di gruppo. Altrimenti, il risultato è indeterminato.

Per quanto riguarda l'impatto sulle prestazioni, suppongo che l'aliasing abbia una durata inferiore a quella non allineata poiché il filtro deve essere applicato dopo tutta l'esecuzione. Aspetterò che gli esperti commentino.

Problemi correlati