2011-01-13 13 views
3

der Hoffnung, etwas Hilfe mit dieser Abfrage zu bekommen, habe ich jetzt bei ihm für eine Weile gearbeitet und kann es nicht schneller jeder:MySQL linken äußeren Join langsam

SELECT date, count(id) as 'visits' FROM dates 
LEFT OUTER JOIN visits 
ON (dates.date = DATE(visits.start) and account_id = 40) 
WHERE date >= '2010-12-13' AND date <= '2011-1-13' 
GROUP BY date ORDER BY date ASC 

Diese Abfrage etwa 8 Sekunden dauert zu rennen. Ich habe Indizes für dates.date, visits.start, visits.account_id und visits.start + visits.account_id hinzugefügt und kann sie nicht schneller ausführen.

Tabellenstruktur (nur relevante Spalten in Besuch Tabelle angezeigt):

create table visits (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `account_id` int(11) NOT NULL, 
    `start` DATETIME NOT NULL, 
    `end` DATETIME NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 

CREATE TABLE `dates` (
    `date` date NOT NULL, 
    PRIMARY KEY (`date`) 
) ENGINE=MyISAM DEFAULT CHARSET=latin1; 

Daten Tabelle enthält alle Tage von 2010.01.01 bis 2020.01.01 (~ 3k Zeilen). Besuche Tabelle enthält etwa 400k Zeilen von 2010-6-1 bis gestern. Ich verwende die Datumstabelle, so dass der Join 0 Besuche für Tage zurückgibt, an denen keine Besuche stattfanden.

Ergebnisse I Referenz will:

+------------+--------+ 
| date  | visits | 
+------------+--------+ 
| 2010-12-13 | 301 | 
| 2010-12-14 | 356 | 
| 2010-12-15 | 423 | 
| 2010-12-16 | 332 | 
| 2010-12-17 | 346 | 
| 2010-12-18 | 226 | 
| 2010-12-19 | 213 | 
| 2010-12-20 | 311 | 
| 2010-12-21 | 273 | 
| 2010-12-22 | 286 | 
| 2010-12-23 | 241 | 
| 2010-12-24 | 149 | 
| 2010-12-25 | 102 | 
| 2010-12-26 | 174 | 
| 2010-12-27 | 258 | 
| 2010-12-28 | 348 | 
| 2010-12-29 | 392 | 
| 2010-12-30 | 395 | 
| 2010-12-31 | 278 | 
| 2011-01-01 | 241 | 
| 2011-01-02 | 295 | 
| 2011-01-03 | 369 | 
| 2011-01-04 | 438 | 
| 2011-01-05 | 393 | 
| 2011-01-06 | 368 | 
| 2011-01-07 | 435 | 
| 2011-01-08 | 313 | 
| 2011-01-09 | 250 | 
| 2011-01-10 | 345 | 
| 2011-01-11 | 387 | 
| 2011-01-12 |  0 | 
| 2011-01-13 |  0 | 
+------------+--------+ 

Vielen Dank im Voraus für jede Hilfe!

+0

nachschlagen 'explain' und' erklären extended' im MySQL-Handbuch – goat

Antwort

4

Ihr Problem ist hier:

ON (dates.date = DATE(visits.start) and account_id = 40) 

Da Sie die DATE Funktion auf visits.start verwenden, MySQL nicht in der Lage ist, einen Index für die Verbindung zu verwenden.

Wahrscheinlich wäre die beste Lösung, eine start_date und end_date Spalte zur Tabelle hinzuzufügen und diese Spalten zu indizieren. Für eine Zeile mit dem Datum 2011-01-01 wäre das Startdatum 2011-01-01 00:00:00 und das Enddatum 2011-01-01 23:59:59.

Dann können Sie wie so direkt an die Termine Tisch sitzen:

SELECT date, count(id) as 'visits' FROM dates 
LEFT OUTER JOIN visits 
ON (visits.start BETWEEN dates.start_date AND dates.end_date and account_id = 40) 
WHERE date >= '2010-12-13' AND date <= '2011-1-13' 
GROUP BY date ORDER BY date ASC 

Eine weitere Option für Datum und Uhrzeit Teile separat auf der Besuche Tabelle zu speichern wäre, und kommen nur den Datumsteil verwenden.

+0

Danke, das hat die Trick. Ich habe der Besuchertabelle eine 'start_date'-Spalte und einen Index hinzugefügt. Bis zu 300ms! –

0

Ich denke, es ist hauptsächlich langsam wegen der Funktion DATE(). Sie könnten den Visits eine Datumsspalte hinzufügen, die das gesamte Datum speichert und einen Trigger schreibt, um sie automatisch zu aktualisieren, wenn ein Besuch eingefügt oder seine Datumszeit aktualisiert wird. Dadurch kann MySQL die im Join verwendeten Indizes besser nutzen.

0

Wie wäre es mit so etwas: Outer Join auf das Ergebnis der Auswahl von Eumiro?

SELECT date, v.visits as 'visits' FROM dates 
LEFT OUTER JOIN (SELECT DATE(start) as dt, count(id) as 'visits' 
FROM visits 
WHERE account_id = 40 
AND date BETWEEN '2010-12-13' AND '2011-01-13' 
GROUP BY DATE(start) 
ORDER BY 1) 
v 
ON (dates.date = v.dt) 
WHERE date >= '2010-12-13' AND date <= '2011-1-13' 

Edit: bearbeitet SQL Edit: eine weitere Option - inline wählen, so etwas wie das:

SELECT date, (select count(*) as 'visits' 
FROM from visits 
where date = DATE(visits.start) and account_id = 40) 
) from dates 
WHERE date >= '2010-12-13' AND date <= '2011-1-13' 
ORDER BY date ASC 
Verwandte Themen