300.000 righe non sono una tabella enorme. Vediamo spesso 300 milioni di tabelle di righe.
Il problema più grande con la tua query è che stai utilizzando una sottoquery correlata, quindi deve rieseguire la sottoquery per ogni riga nella query esterna.
Capita spesso che non sia necessario fare tutto il tuo lavoro in un'unica istruzione SQL. Ci sono vantaggi nel suddividerlo in diverse istruzioni SQL più semplici:
- Più facile da codificare.
- Più facile da ottimizzare.
- Facile da eseguire il debug.
- Più facile da leggere.
- Più facile da mantenere se/quando devi implementare nuovi requisiti.
Numero di acquisti
SELECT customer, COUNT(sale) AS number_of_purchases
FROM sales
GROUP BY customer;
Un indice sulle vendite (cliente, vendita) sarebbe il migliore per questa query.
Ultimo valore di acquisto
Questo è il greatest-n-per-group problema che si presenta frequentemente.
SELECT a.customer, a.sale as max_sale
FROM sales a
LEFT OUTER JOIN sales b
ON a.customer=b.customer AND a.dates < b.dates
WHERE b.customer IS NULL;
In altre parole, prova ad abbinare la riga a
a un'ipotetica riga b
che ha lo stesso cliente e una data maggiore. Se non viene trovata una tale riga, allora a
deve avere la data più grande per quel cliente.
Un indice sulle vendite (cliente, date, vendita) sarebbe il migliore per questa query.
Se potresti avere più di una vendita per un cliente in quella data massima, questa query restituirà più di una riga per cliente. Dovresti trovare un'altra colonna per rompere il pareggio. Se utilizzi una chiave primaria a incremento automatico, è adatta come spareggio perché è garantita per essere unica e tende ad aumentare cronologicamente.
SELECT a.customer, a.sale as max_sale
FROM sales a
LEFT OUTER JOIN sales b
ON a.customer=b.customer AND (a.dates < b.dates OR a.dates = b.dates and a.id < b.id)
WHERE b.customer IS NULL;
Importo totale degli acquisti, quando ha un valore positivo
SELECT customer, SUM(sale) AS total_purchases
FROM sales
WHERE sale > 0
GROUP BY customer;
Un indice sulle vendite (cliente, vendita) sarebbe il migliore per questa query.
Dovresti considerare l'utilizzo di NULL per indicare un valore di vendita mancante anziché -1. Le funzioni aggregate come SUM() e COUNT() ignorano i NULL, quindi non è necessario utilizzare una clausola WHERE per escludere le righe con vendita <0.
Re:il tuo commento
I primi cinque clienti per il quarto trimestre 2012
SELECT customer, SUM(sale) AS total_purchases
FROM sales
WHERE (year, quarter) = (2012, 4) AND sale > 0
GROUP BY customer
ORDER BY total_purchases DESC
LIMIT 5;
Vorrei testarlo con dati reali, ma credo che un indice sulle vendite (anno, trimestre, cliente, vendita) sarebbe il migliore per questa query.
Ultimo acquisto per clienti con acquisti totali> 5
SELECT a.customer, a.sale as max_sale
FROM sales a
INNER JOIN sales c ON a.customer=c.customer
LEFT OUTER JOIN sales b
ON a.customer=b.customer AND (a.dates < b.dates OR a.dates = b.dates and a.id < b.id)
WHERE b.customer IS NULL
GROUP BY a.id
HAVING COUNT(*) > 5;
Come nell'altra query più grande-n-per-gruppo sopra, un indice sulle vendite (cliente, date, vendita) sarebbe la cosa migliore per questa query. Probabilmente non può ottimizzare sia il join che il group by, quindi ciò comporterà una tabella temporanea. Ma almeno farà solo una tabella temporanea invece di molte.
Queste query sono abbastanza complesse. Non dovresti provare a scrivere una singola query SQL che può fornire tutto di questi risultati. Ricorda la classica citazione di Brian Kernighan: