2012-04-25 18 views
5

A modo de introducción ...
He encontrado esta pregunta: Difference between 2 adjacent fields - Date - PHP MYSQL y estaba tratando de lograr el objetivo, es decir, repetir fechas y obtener diff, con puro MySQL.
Otra pregunta (Subtracting one row of data from another in SQL) me ayudó a entender cómo hacer algo similar con MySQL. No resolvió el problema, ya que las soluciones siguen siendo depandant en valores fijos o en el orden asumido de datos, pero me ayudó a entender la metodología.
Hay otra pregunta (How to get next/previous record in MySQL?) con respuestas que describen cómo obtener valores de la fila siguiente/anterior. Todavía depende y en algunos valores fijos, pero aprendí a usar la técnica.MySQL date diff iteration query - optimizar la consulta u optimizar la estructura de datos

Decir que tengo esta tabla 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 

Hay dos supuestos:

  1. clave primaria (id) ordenó ascendente y "agujeros" permitidas.
  2. Cada fecha en la columna dateof es válida, en el significado: no NULL sy no hay valores predeterminados (0000-00-00). Quiero recorrer cada fila y calcular el número de días transcurridos con la entrada anterior, para recibir este:
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 todo lo que he aprendido que vine a esta solución (por ejemplo solución al 1 , ya que hay otro):

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; 

(violín ejemplo aquí: http://sqlfiddle.com/#!2/099fc/3).

Esto funciona como un encanto ... hasta que haya solo un par de entradas en db. Se pone peor cuando hay más:

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 filas: duración: 8.672 seg. Buscar: 228.515 seg.

pensé en añadir índice en la columna dateof:

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; 

... y ganaron pequeña mejora:

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 filas: duración: 8.406 seg . Buscar: 219.281 seg.

Recordé leer en algún lugar acerca de las ventajas de MyISAM sobre InnoDB, en algunos casos.Así que cambié el que MyISAM:

ALTER TABLE `foo` ENGINE = MyISAM; 

18031 filas: duración: 5.671 seg. Buscar: 151.610 seg.

Claro que es mejor, pero aún lento.

he intentado con otro algoritmo (solución al 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; 

... pero era aún más lento:

18031 filas: duración: 15.609 seg. Buscar: 184.656 seg.


¿Hay otras maneras de optimizar esta consulta o estructura de datos con el fin de que esta tarea sea realizada más rápido?

+0

Creo que una estructura de datos diferente puede ser más apropiada para sus necesidades. ¿Puedes decir un poco más sobre cómo estás tratando de usar esta información? – eggyal

+0

@eggyal Nada en particular. Estoy tratando de aprender algo que puede ser útil :) – bostaf

Respuesta

5

No sorprende que sus métodos sean muy lentos incluso para una mesa de tamaño moderado.

En teoría, es posible calcular el resultado en tiempo O (n) utilizando la función analítica LAG, que desafortunadamente no es compatible con MySQL. Sin embargo, usted puede emular LAG en MySQL utilizando variables:

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 

Esto debería ser varios órdenes de magnitud más rápido que lo que está tratando de hacer.

+0

También podría haberse beneficiado de una declaración de tipo CROSS APPLY, pero lamentablemente tampoco está disponible en MySQL. ¿Me recuerdas por qué MySQL es tan popular? –

+3

Buena respuesta, por cierto. Aquí está el enlace del violín que lo muestra funcionando - http://sqlfiddle.com/#!2/099fc/5 –

+1

Este truco es brillante. La consulta se ejecuta instantáneamente con mi conjunto de datos de ejemplo, y alrededor de 1 segundo con 2 millones de filas. Gracias por la solución y, especialmente, por la técnica, es muy útil. – bostaf

Cuestiones relacionadas