2011-02-23 30 views
8

我想完成一個查詢,需要一個計算列使用通過變量傳遞日期引用的子查詢。我不確定我是不是「做對了」,但基本上查詢從來沒有完成,並在幾分鐘內旋轉。這是我的查詢:用戶定義的變量的MySQL子查詢

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; 

子查詢有inner join實現自聯接,只有給我的已經在@groupdate日之前購買的人的數量。該EXPLAIN低於:

+----+----------------------+---------------------+------+---------------+-----------+---------+---------------------------+--------+---------------------------------+ 
| 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      | 
+----+----------------------+---------------------+------+---------------+-----------+---------+---------------------------+--------+---------------------------------+ 

而且表_pj_cust_email_view的結構是這樣:

'_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' 

同樣,正如我剛纔所說,我真的不知道,這是最好的方式做到這一點。任何批評,方向感謝!

更新

我已經取得了一點進步,現在我做上面的程序上通過所有已知的迭代幾個月,而不是在數據庫個月,提前設定的時間瓦爾。我不喜歡這個。這是我現在得到:

設置中定義的用戶瓦爾

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

獲取總不同的電子郵件在給定範圍內

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

獲取的總數在給定範圍之前購買的顧客

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; 

其中@startdate設置爲月初,@enddate表示該月的範圍結束。

我真的覺得這仍然可以在一個完整的查詢中完成。

+0

查看更新對於目前的解決方案,它返回我正在尋找,但迭代,我希望它在一個完整的查詢。 – philwinkle 2011-02-25 14:56:54

+0

如果您提供了一些樣本輸入數據和一些預期輸出,這將有所幫助。 – Thomas 2011-02-27 05:10:26

回答

8

我不認爲你需要使用子查詢,也不需要重複幾個月。

相反,我建議您創建一個表來存儲所有月份。即使你用100年的時間預先填充它,它也只有1200行,這是微不足道的。

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'); 

存儲實際開始和結束日期,這樣你就可以使用DATE數據類型和索引兩列正常。

編輯:我想我理解你的要求更好一點,我已經清理了這個答案。如果您在您的表中的下列複合索引

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; 

:下面的查詢可以對您的權利

CREATE INDEX order_email on _pj_cust_email_view (order_date, customer_email); 

然後查詢有被僅索引查詢的最佳機會,將運行得更快。

以下是此查詢的EXPLAIN優化報告。每個表的註釋type: index

*************************** 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 

下面是一些測試數據:

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'); 

這裏給出的數據,包括電子郵件的拼接列表,使其更容易看到的結果。

+---------+---------+--------------------------+---------+----------------------------------+ 
| 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

我認爲其中一個問題是爲以前的訂單收集所需的邏輯。例如,是否在當前開始日期之前訂購日期的不同客戶的總數?如果這是OP所建議的情況,那麼使用您的測試數據,2011-03的先前訂單欄應該是4(hermione,neville,harry和molly在2011-03-01之前都有訂單)和對於2011-02,它應該是2(赫敏,內維爾)。 – Thomas 2011-03-02 00:58:04

+0

@Thomas:我同意,從OP的問題來看,我們不清楚期望的行爲是什麼。 – 2011-03-02 02:06:25

+0

對不起 - 這是一個艱難的一週。意圖與托馬斯在此描述的完全相同,它是對當前範圍的所有以前範圍內的所有特徵的計數,包括當前不同的電子郵件。我試圖找到一種更好地描述它的方法,如果需要進一步澄清,我可以在此嘗試其他建議,我可以讚揚上述問題。 – philwinkle 2011-03-02 05:31:12

0

雖然Bill有一個很好的使用多個表的查詢,但是這個也使用了SQL變量,所以沒有額外的表。內部查詢加入到您的_pj_cust_email_view表中,並且限制10表示僅從當前月起回溯10個月。因此,不需要對日期進行硬編碼,它會即時計算......如果您想要更多或更少的月份,只需更改LIMIT子句即可。

通過設置@dt:在內部查詢=作爲最後一個字段,最後才日期基礎得到分配爲下一個記錄週期,以創建合格的日期......以上

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

我真的很喜歡你在這裏做的。我要給它一個旋轉,我會很快讓你知道結果。 – philwinkle 2011-03-02 05:27:20

+0

@philwinkle,我沒有做的唯一事情就是調用date_format(),但是應該簡單地包裝FirstOfMonth日期列。 – DRapp 2011-03-02 12:05:53

+0

@philwinkle,查詢結果的任何更新? – DRapp 2011-03-03 21:54:06