2011-02-23 13 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

+0

일부 샘플 입력 데이터와 일부 예상 출력을 제공하면 도움이됩니다. – Thomas

답변

8

하위 쿼리를 전혀 사용하지 않아도되고 몇 달 동안 반복해야한다고 생각하지 않습니다.

대신 모든 달을 저장하는 테이블을 만드는 것이 좋습니다. 당신이 100 년의 기간으로 그것을 prepopulate하더라도, 그것은 단지 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을 기록하십시오. 여기에 이메일의 연결된 목록을 포함하여 데이터를 참조하기 쉽도록하기 위해 주어진 결과의

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

:

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

는 몇 가지 테스트 데이터입니다.

+---------+---------+--------------------------+---------+----------------------------------+ 
| 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 (hermione, neville) 여야합니다. – Thomas

+0

@ 토마스 : 동의합니다. OP의 질문에서 원하는 행동이 무엇인지 명확하지 않습니다. –

+0

결석을 드려 죄송합니다. 힘든 한 주간되었습니다. 의도는 thomas가 현재의 고유 한 전자 메일을 포함하여 현재 범위에 대한 모든 이전 범위의 모든 고유 한 개수입니다. 나는 그것을 더 잘 묘사 할 수있는 방법을 찾으려고 노력하고 있으며, 더 명확한 설명이 필요하면 다른 제안을 시도한 후에 위의 질문에 답할 수 있습니다. – philwinkle

0

빌 (Bill)은 여러 개의 테이블을 사용하는 멋진 쿼리를 가지고 있지만이 테이블은 SQL 변수를 사용하기 때문에 추가 테이블이 필요하지 않습니다. 내부 쿼리는 _pj_cust_email_view 테이블에 조인되며 현재 10 개월 만 돌아가는 것을 의미하는 제한 인 10을 수행합니다. 따라서 날짜를 하드 코딩하지 않아도 즉시 계산됩니다 ... 더 많거나 적은 개월이 필요하면 LIMIT 절을 변경하십시오. @dt을 설정하여

: =를 마지막 필드로 내부 쿼리 만 THEN

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

+0

@philwinkle, 내가하지 않은 유일한 것은 date_format() 호출 이었지만, FirstOfMonth 날짜 열을 감싸기 만하면됩니다. – DRapp

+0

@philwinkle, 쿼리 결과에 대한 업데이트? – DRapp

관련 문제