2012-04-25 15 views
5

In via di introduzione ...
Ho eseguito in questa domanda: Difference between 2 adjacent fields - Date - PHP MYSQL e stava cercando di raggiungere l'obiettivo, vale a dire scorrere le date e ottenere diff, con puro MySQL.
Un'altra domanda (Subtracting one row of data from another in SQL) mi ha aiutato a capire come creare qualcosa di simile con MySQL. Non ha risolto il problema, poiché le soluzioni sono ancora depandant sia su valori fissi che sull'ordine assunto dei dati, ma mi ha aiutato a capire la metodologia.
C'è un'altra domanda (How to get next/previous record in MySQL?) con le risposte che descrivono come ottenere i valori dalla riga successiva/precedente. Dipende ancora da alcuni valori fissi, ma ho imparato a usare la tecnica.data di MySQL Query diff iterazione - razionalizzare query o ottimizzare la struttura dei dati

Dire che ho questa tabella foo:

CREATE TABLE `foo` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `dateof` date NOT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 
id | dateof 
-----+------------ 
    1 | 2012-01-01 
    2 | 2012-01-02 
    11 | 2012-01-04 
    12 | 2012-01-01 
    13 | 2012-01-02 
    14 | 2012-01-09 
111 | 2012-01-01 
112 | 2012-01-01 
113 | 2012-01-01 

ci sono due ipotesi:

  1. Chiave primaria (id) ordinato ascendente e "buchi" consentiti.
  2. Ogni data nella colonna dateof è valida, nel significato: no NULL s e senza valori predefiniti (0000-00-00). Voglio scorrere ogni riga e calcolare il numero di giorni trascorsi con la voce precedente, di ricevere questo:
id | date  | days_diff 
-----+------------+----------- 
    1 | 2012-01-01 |  0 
    2 | 2012-01-02 |  1 
    11 | 2012-01-04 |  2 
    12 | 2012-01-01 | -3 
    13 | 2012-01-02 |  1 
    14 | 2012-01-09 |  7 
111 | 2012-01-01 | -8 
112 | 2012-01-01 |  0 
113 | 2012-01-01 | 30 

Con tutto quello che ho imparato sono arrivato a questa soluzione (diciamo soluzione 1 , in quanto non v'è un altro):

SELECT 
    f.id, 
    DATE_FORMAT(f.dateof, '%b %e, %Y') AS date, 
    (SELECT DATEDIFF(f.dateof, f2.dateof) 
     FROM foo f2 
     WHERE f2.id = (
      SELECT MAX(f3.id) FROM foo f3 WHERE f3.id < f.id 
     ) 
    ) AS days_diff 
FROM foo f; 

(esempio violino qui: http://sqlfiddle.com/#!2/099fc/3).

Questo funziona come un incantesimo ... finché non ci sono solo un paio di voci in db. C'è di peggio quando più:

EXPLAIN: 
id select_type  table type possible_keys key  key_len ref rows Extra 
1 PRIMARY   f  ALL NULL   NULL NULL NULL 17221 
2 DEPENDENT SUBQUERY f2 eq_ref PRIMARY  PRIMARY 4  func 1  Using where 
3 DEPENDENT SUBQUERY f3 index PRIMARY  PRIMARY 4  NULL 17221 Using where; Using index 

18031 righe: Durata: 8,672 sec. Fetch: 228.515 sec.

ho pensato di aggiungere indice su dateof colonna:

CREATE TABLE `foo` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `dateof` date DEFAULT NULL, 
    PRIMARY KEY (`id`), 
    KEY `dateof` (`dateof`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

... ed ha guadagnato piccolo miglioramento:

EXPLAIN: 
id select_type  table type possible_keys key  key_len ref rows Extra 
1 PRIMARY   f  index NULL   dateof 4  NULL 18369 Using index 
2 DEPENDENT SUBQUERY f2 eq_ref PRIMARY  PRIMARY 4  func 1  Using where 
3 DEPENDENT SUBQUERY f3 index PRIMARY  dateof 4  NULL 18369 Using where; Using index 

18031 righe: Durata: 8,406 sec . Fetch: 219.281 sec.

Ho ricordato di aver letto da qualche parte sui vantaggi di MyISAM su InnoDB, in alcuni casi.Così ho cambiato la a MyISAM:

ALTER TABLE `foo` ENGINE = MyISAM; 

18031 Righe: durata: 5,671 sec. Fetch: 151.610 sec.

Certo che è meglio ma ancora lento.

Ho provato con un altro algoritmo (soluzione 2):

SELECT 
    f.id, 
    DATE_FORMAT(f.dateof, '%b %e, %Y') AS date, 
    (SELECT DATEDIFF(f.dateof, f2.dateof) 
    FROM foo f2 
    WHERE f2.id < f.id 
    ORDER BY f2.id DESC 
    LIMIT 1 
) AS days_diff 
FROM foo f; 

... ma era ancora più lenta:

18031 righe: durata: 15,609 sec. Recupero: 184,656 sec.


Ci sono altri modi per ottimizzare la query o struttura di dati al fine di avere questo compito eseguito più velocemente?

+0

Penso che una diversa struttura di dati possa essere più appropriata alle vostre esigenze. Puoi dire qualcosa in più su come stai cercando di usare questi dati? – eggyal

+0

@eggyal Niente di particolare. Sto solo cercando di imparare qualcosa che potrebbe essere utile :) – bostaf

risposta

5

Non sorprende che i tuoi approcci siano molto lenti anche per un tavolo di dimensioni moderate.

Dovrebbe essere teoricamente possibile calcolare il risultato in tempo O (n) utilizzando la funzione analitica LAG, che purtroppo non è supportata in MySQL. Tuttavia è possibile emulare LAG in MySQL usando le variabili:

SELECT 
    id, 
    DATE_FORMAT(f.dateof, '%b %e, %Y') AS date, 
    DATEDIFF(dateof, @prev) AS days_diff, 
    @prev := dateof 
FROM FOO, (SELECT @prev := NULL) AS vars 
ORDER BY id 

Questo dovrebbe essere di diversi ordini di grandezza più veloce di quello che si sta cercando di fare.

+0

Potrebbe anche aver tratto beneficio da un'istruzione di tipo CROSS APPLY, ma purtroppo non è disponibile in MySQL. Ricordami perché MySQL è così popolare? –

+3

Bella risposta, a proposito. Ecco il link del violino che lo mostra funzionante: http://sqlfiddle.com/#!2/099fc/5 –

+1

Questo trucco è geniale. La query viene eseguita immediatamente con il mio esempio di dati e circa 1 secondo con 2 milioni di righe. Grazie per la soluzione e, soprattutto, per la tecnica - è sicuramente utile. – bostaf

Problemi correlati