2011-02-23 17 views
8

Estoy intentando realizar una consulta que requiere una columna calculada mediante una subconsulta que pasa la referencia de fecha a través de una variable. No estoy seguro si no estoy "haciendo las cosas bien", pero esencialmente la consulta nunca termina y gira durante minutos. Esta es mi consulta:Subconsulta de MySQL con variables definidas por el usuario

select @groupdate:=date_format(order_date,'%Y-%m'), count(distinct customer_email) as num_cust, 
(
    select count(distinct cev.customer_email) as num_prev 
    from _pj_cust_email_view cev 
    inner join _pj_cust_email_view as prev_purch on (prev_purch.order_date < @groupdate) and (cev.customer_email=prev_purch.customer_email) 
    where cev.order_date > @groupdate 
) as prev_cust_count 
from _pj_cust_email_view 
group by @groupdate; 

subconsulta tiene una inner join realiza la autocombinación que sólo me da el número de los que han adquirido antes de la fecha en @groupdate. El EXPLAIN es a continuación:

+----+----------------------+---------------------+------+---------------+-----------+---------+---------------------------+--------+---------------------------------+ 
| id | select_type   | table    | type | possible_keys | key  | key_len | ref      | rows | Extra       | 
+----+----------------------+---------------------+------+---------------+-----------+---------+---------------------------+--------+---------------------------------+ 
| 1 | PRIMARY    | _pj_cust_email_view | ALL | NULL   | NULL  | NULL | NULL      | 140147 | Using temporary; Using filesort | 
| 2 | UNCACHEABLE SUBQUERY | cev     | ALL | IDX_EMAIL  | NULL  | NULL | NULL      | 140147 | Using where      | 
| 2 | UNCACHEABLE SUBQUERY | prev_purch   | ref | IDX_EMAIL  | IDX_EMAIL | 768  | cart_A.cev.customer_email |  1 | Using where      | 
+----+----------------------+---------------------+------+---------------+-----------+---------+---------------------------+--------+---------------------------------+ 

y la estructura de la tabla _pj_cust_email_view es como tal:

'_pj_cust_email_view', 'CREATE TABLE `_pj_cust_email_view` (
    `order_date` varchar(10) CHARACTER SET utf8 DEFAULT NULL, 
    `customer_email` varchar(255) CHARACTER SET utf8 DEFAULT NULL, 
    KEY `IDX_EMAIL` (`customer_email`), 
    KEY `IDX_ORDERDATE` (`order_date`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1' 

Una vez más, como he dicho antes, no estoy realmente seguro de que esta es la mejor manera de lograr esto Cualquier crítica, dirección es apreciada!

actualización

que he hecho un pequeño progreso, y ahora estoy haciendo lo anterior procesalmente por iteración a través de todos los meses conocidas en lugar de meses en la base de datos y el establecimiento de los VARs antes de tiempo. No me gusta esto todavía. Esto es lo que tengo ahora:

establece el usuario definido vars

set @startdate:='2010-08', @enddate:='2010-09'; 

Obtiene mensajes de correo electrónico distintas totales en el rango dado

select count(distinct customer_email) as num_cust 
from _pj_cust_email_view 
where order_date between @startdate and @enddate; 

Obtiene el recuento total de clientes que compraron antes del rango dado

select count(distinct cev.customer_email) as num_prev 
    from _pj_cust_email_view cev 
    inner join _pj_cust_email_view as prev_purch on (prev_purch.order_date < @startdate) and (cev.customer_email=prev_purch.customer_email) 
    where cev.order_date between @startdate and @enddate; 

Donde @startdate está configurado al principio del mes y @enddate indica el final del rango de ese mes.

Realmente siento que esto se puede hacer en una consulta completa.

+0

véase la actualización anterior para la solución actual que devuelve lo que estoy buscando, pero de manera iterativa y me gustaría que estuviera en una consulta completa. – philwinkle

+0

Ayudaría si proporcionó algunos datos de entrada de muestra y algunos resultados esperados. – Thomas

Respuesta

8

No creo que deba usar subconsultas en absoluto, ni necesita iterar durante meses.

En su lugar, le recomiendo que cree una tabla para almacenar todos los meses. Incluso si lo prepobla con 100 años de meses, solo tendría 1200 filas, lo cual es trivial.

CREATE TABLE Months (
    start_date DATE, 
    end_date DATE, 
    PRIMARY KEY (start_date, end_date) 
); 
INSERT INTO Months (start_date, end_date) 
VALUES ('2011-03-01', '2011-03-31'); 

Almacena las fechas reales de inicio y finalización, por lo que puede usar el tipo de datos FECHA e indexar las dos columnas correctamente.

editar: Creo que entiendo su requisito un poco mejor, y he limpiado esta respuesta.La siguiente consulta puede ser adecuado para usted:

SELECT DATE_FORMAT(m.start_date, '%Y-%m') AS month, 
    COUNT(DISTINCT cev.customer_email) AS current, 
    GROUP_CONCAT(DISTINCT cev.customer_email) AS current_email, 
    COUNT(DISTINCT prev.customer_email) AS earlier, 
    GROUP_CONCAT(DISTINCT prev.customer_email) AS earlier_email 
FROM Months AS m 
LEFT OUTER JOIN _pj_cust_email_view AS cev 
    ON cev.order_date BETWEEN m.start_date AND m.end_date 
INNER JOIN Months AS mprev 
    ON mprev.start_date <= m.start_date 
LEFT OUTER JOIN _pj_cust_email_view AS prev 
    ON prev.order_date BETWEEN mprev.start_date AND mprev.end_date 
GROUP BY month; 

Si crea el siguiente índice compuesto en su mesa:

CREATE INDEX order_email on _pj_cust_email_view (order_date, customer_email); 

A continuación, la consulta tiene la mejor oportunidad de ser una consulta de sólo índice, y correrá mucho más rápido.

A continuación se muestra el informe de optimización de EXPLAIN de esta consulta. Nota type: index para cada tabla.

*************************** 1. row *************************** 
      id: 1 
    select_type: SIMPLE 
     table: m 
     type: index 
possible_keys: PRIMARY 
      key: PRIMARY 
     key_len: 6 
      ref: NULL 
     rows: 4 
     Extra: Using index; Using temporary; Using filesort 
*************************** 2. row *************************** 
      id: 1 
    select_type: SIMPLE 
     table: mprev 
     type: index 
possible_keys: PRIMARY 
      key: PRIMARY 
     key_len: 6 
      ref: NULL 
     rows: 4 
     Extra: Using where; Using index; Using join buffer 
*************************** 3. row *************************** 
      id: 1 
    select_type: SIMPLE 
     table: cev 
     type: index 
possible_keys: order_email 
      key: order_email 
     key_len: 17 
      ref: NULL 
     rows: 10 
     Extra: Using index 
*************************** 4. row *************************** 
      id: 1 
    select_type: SIMPLE 
     table: prev 
     type: index 
possible_keys: order_email 
      key: order_email 
     key_len: 17 
      ref: NULL 
     rows: 10 
     Extra: Using index 

He aquí algunos datos de prueba:

INSERT INTO Months (start_date, end_date) VALUES 
('2011-03-01', '2011-03-31'), 
('2011-02-01', '2011-02-28'), 
('2011-01-01', '2011-01-31'), 
('2010-12-01', '2010-12-31'); 

INSERT INTO _pj_cust_email_view VALUES 
('ron', '2011-03-10'), 
('hermione', '2011-03-15'), 
('hermione', '2011-02-15'), 
('hermione', '2011-01-15'), 
('hermione', '2010-12-15'), 
('neville', '2011-01-10'), 
('harry', '2011-03-19'), 
('harry', '2011-02-10'), 
('molly', '2011-03-25'), 
('molly', '2011-01-10'); 

Aquí está el resultado teniendo en cuenta que los datos, incluyendo la lista concatenada de mensajes de correo electrónico para que sea más fácil de ver.

+---------+---------+--------------------------+---------+----------------------------------+ 
| month | current | current_email   | earlier | earlier_email     | 
+---------+---------+--------------------------+---------+----------------------------------+ 
| 2010-12 |  1 | hermione     |  1 | hermione       | 
| 2011-01 |  3 | neville,hermione,molly |  3 | hermione,molly,neville   | 
| 2011-02 |  2 | hermione,harry   |  4 | harry,hermione,molly,neville  | 
| 2011-03 |  4 | molly,ron,harry,hermione |  5 | molly,ron,hermione,neville,harry | 
+---------+---------+--------------------------+---------+----------------------------------+ 
+0

Creo que uno de los problemas es recabar la lógica deseada para pedidos anteriores. Por ejemplo, ¿es el recuento total de clientes distintos con fechas de pedido anteriores a la fecha de inicio actual? Si ese fuera el caso según lo sugerido por el OP, entonces, usando sus datos de prueba, la columna de orden anterior para 2011-03 debería ser 4 (hermione, neville, harry y molly tienen un pedido anterior a 2011-03-01) y para 2011-02 debería ser 2 (hermione, neville). – Thomas

+0

@Thomas: Estoy de acuerdo, no está claro por la pregunta del OP cuál es el comportamiento deseado. –

+0

Perdón por la ausencia: ha sido una semana difícil. La intención es exactamente como Thomas ha descrito aquí, que es un recuento de todos los distintivos en todos los rangos anteriores al rango actual, incluido el correo electrónico distinto actual. Estoy tratando de encontrar una manera de describirlo mejor, y puedo enmendar la pregunta anterior una vez que pruebe las otras sugerencias aquí si se necesita una mayor aclaración. – philwinkle

0

Aunque Bill tiene una buena consulta con varias tablas, esta también lo hace con las variables de SQL, por lo que no hay tabla adicional. La consulta interna se une a su tabla _pj_cust_email_view y tiene un límite de 10 para indicar que retrocede solo 10 meses desde el mes actual. Entonces, no hay una codificación dura de fechas, se calcula sobre la marcha ... si quieres más o menos meses, simplemente cambia la cláusula LIMIT.

Al establecer el @dt: = como el último campo en la consulta interna, sólo entonces la base de la fecha se asignan para el siguiente ciclo de registro para crear su período de calificación ...

select justDates.FirstOfMonth, 
     count(distinct EMCurr.customer_Email) UniqThisMonth, 
     count(distinct EMLast.customer_Email) RepeatCustomers 
    from 
     (SELECT 
       @dt FirstOfMonth, 
       last_day(@dt) EndOfMonth, 
       @dt:= date_sub(@dt, interval 1 month) nextCycle 
      FROM 
       (select @dt := date_sub(current_date(), interval dayofmonth(current_date())-1 day)) vars, 
       _pj_cust_email_view limit 10 
       ) JustDates 
     join _pj_cust_email_view EMCurr 
      on EMCurr.order_Date between JustDates.FirstOfMonth and JustDates.EndOfMonth 
     left join _pj_cust_email_view EMLast 
      on EMLast.order_Date < JustDates.FirstOfMonth 
      and EMCurr.customer_Email = EMLast.customer_Email 
    group by 
     1 
+0

Me gusta mucho lo que has hecho aquí. Voy a darle un giro y le haré saber los resultados muy pronto. – philwinkle

+0

@philwinkle, lo único que no hice fue la llamada a date_format(), pero eso simplemente debe ajustarse a la columna de fecha FirstOfMonth. – DRapp

+0

@philwinkle, ¿alguna actualización de los resultados de la consulta? – DRapp

Cuestiones relacionadas