2014-01-17 4 views
0

X 일 동안 주문하지 않은 고객을 찾기 위해이 SQL을 만들었습니다.SQL 최적화 : x 일 동안 주문하지 않은 고객

결과 집합이 반환되므로이 게시물은 주로 두 번째 의견을 제시하고 최적화가 가능합니다.

SELECT o.order_id, 
     o.order_status, 
     o.order_created, 
     o.user_id, 
     i.identity_firstname, 
     i.identity_email, 

    (SELECT COUNT(*) 
    FROM orders o2 
    WHERE o2.user_id=o.user_id 
    AND o2.order_status=1) AS order_count, 

    (SELECT o4.order_created 
    FROM orders o4 
    WHERE o4.user_id=o.user_id 
    AND o4.order_status=1 
    ORDER BY o4.order_created DESC LIMIT 1) AS last_order 
FROM orders o 
INNER JOIN user_identities ui ON o.user_id=ui.user_id 
INNER JOIN identities i ON ui.identity_id=i.identity_id 
    AND i.identity_email!='' 
INNER JOIN subscribers s ON i.identity_id=s.identity_id 
    AND s.subscriber_status=1 
    AND s.subsriber_type=e 
    AND s.subscription_id=1 
WHERE DATE(o.order_created) = "2013-12-14" 
    AND o.order_status=1 
    AND o.user_id NOT IN 
    (SELECT o3.user_id 
    FROM orders o3 
    WHERE o3.user_id=o.user_id 
     AND o3.order_status=1 
     AND DATE(o3.order_created) > "2013-12-14") 

이 SQL의 잠재적 인 문제점을 발견 할 수 있습니까? 날짜가 동적으로 삽입됩니다.

프로덕션 환경에 넣은 최종 SQL에는 기본적으로 o.order_id, i.identity_id 및 o.order_count 만 포함됩니다.이 order_count는 정확해야합니다. 다른 선택된 필드와 'last_order'하위 쿼리는 포함되지 않으며 테스트 용으로 만 사용됩니다.

이것은 특정 날짜에 마지막으로 주문한 사용자의 목록을 제공하며 뉴스 레터 구독자입니다. 나는 WHERE 절의 NOT IN 부분과 order_count 부질의 정확성에 대해 의심 스럽다.

+4

>이 SQL의 잠재적 인 문제점을 발견 할 수 있습니까? 어쩌면, 개인적으로는 내가 원하는 DDL (그리고/또는 sqlfiddle)과 함께 원하는 것을 가지고 처음부터 시작하는 것을 선호 할 것입니다. – Strawberry

답변

2

몇 가지 문제가 있습니다 색인 컬럼에

A. 사용 기능을

당신은 DATE(order_created)과 몇 가지 상수를 비교하여 주문을 검색합니다. a)는 DATE() 기능은 모든 행 (CPU 실행된다) 및 b) 상기베이스는 열 인덱스를 사용할 수 있기 때문에, (

B. 사용) 중 하나의 존재를 가정 끔찍한 생각 WHERE ID NOT IN (...)

NOT IN (...)을 사용하는 것은 거의 항상 나쁜 생각입니다. 옵티 마이저에는 일반적으로이 구성에 문제가있어 계획을 잘못 이해하기 때문입니다. 외부가 결합 된 컬럼에 대한 IS NULL 조건을 사용하여 누락에 대한 필터링하는 WHERE 조건 가입으로 (DISTINCT을 필요로하지 않는 측면 혜택 및 추가 오직 하나의 미스가 반환이 있기 때문에) 당신은 거의 항상 그것을 표현할 수

C 행의 많은 부분을 필터링하는 조인은 너무 늦음

이전에는 조인을 더 잘하지 않음으로써 행을 마스크 처리 할 수 ​​있습니다. 이 작업은 조인 된 테이블 목록의 이전에 조인 할 가능성이 적은 테이블에 조인하고 가능한 한 조기에 제외 된 행을 얻기 위해 키가 아닌 조건을 where 절 대신 조인으로 넣음으로써 수행 할 수 있습니다. 어쨌든 개의 옵티 마이저가 있지만 어쨌든 나는 그렇지 않습니다.

D. 전염병과 같은 상관 관계가있는 서브 쿼리를 피하십시오!

주 테이블의 모든 행에 대해 에 대해 실행되는 몇 개의 상관 하위 쿼리가 있습니다. 그건 정말 나쁜 생각입니다. 때때로 옵티마이 저는이를 조인으로 만들 수 있지만, 왜 그렇게 기대 하느냐 (희망). 대부분의 상호 연관된 서브 쿼리는 조인으로 표현 될 수 있습니다. 당신도 예외는 아닙니다.마음에 위의와

, 일부 특정 변경이 있습니다 O4 완전히 생략 할 수있다, 그래서

  • O2와 O4에서, 같은 가입입니다 - 단지
  • 를 조인으로 변환 후 O2를 사용
  • DATE(order_created) = "2013-12-14"order_created between "2013-12-14 00:00:00" and "2013-12-14 23:59:59"

로 작성해야이 쿼리는 당신이 원하는해야한다 :

,536,913
SELECT 
    o.order_id, 
    o.order_status, 
    o.order_created, 
    o.user_id, 
    i.identity_firstname, 
    i.identity_email, 
    count(o2.user_id) AS order_count, 
    max(o2.order_created) AS last_order 
FROM orders o 
LEFT JOIN orders o2 ON o2.user_id = o.user_id AND o2.order_status=1 
LEFT JOIN orders o3 ON o3.user_id = o.user_id 
    AND o3.order_status=1 
    AND o3.order_created >= "2013-12-15 00:00:00" 
JOIN user_identities ui ON o.user_id=ui.user_id 
JOIN identities i ON ui.identity_id=i.identity_id AND i.identity_email != '' 
JOIN subscribers s ON i.identity_id=s.identity_id 
    AND s.subscriber_status=1 
    AND s.subsriber_type=e 
    AND s.subscription_id=1 
WHERE o.order_created between "2013-12-14 00:00:00" and "2013-12-14 23:59:59" 
AND o.order_status=1 
AND o3.order_created IS NULL -- This gets only missed joins on o3 
GROUP BY 
    o.order_id, 
    o.order_status, 
    o.order_created, 
    o.user_id, 
    i.identity_firstname, 
    i.identity_email; 

마지막 줄당신이 LEFT JOIN

면책 조항을 사용 NOT IN (...)와 같은을 달성하는 방법입니다 테스트하지 않습니다.

+0

질의에 문법 오류가있을 가능성이 있습니다. -'BETWEEN'은 일치하는 최종 값을 갖지 않습니다. (물론,'AND o.order_status = 1'을 시도하기로 결정하지 않는 한 ...) [당신은 BETWEEN과 날짜/시간 값 [어쨌든] (http://sqlblog.com/blogs/aaron_bertrand/archive/2011/10/19/what-do-between-and-the-devil-have-in-common.aspx) (또는 실제로 , 정수가 아닌 모든 값). –

+0

@ Clockwork-Muse 젠장! 코드에 주석을 넣지 만 코드가 아닙니다. 지금 수정 됨 Thx – Bohemian

+0

SQL 쿼리를 분석하고 실수에 대한 명확한 설명을 주셔서 감사합니다. 내가 작성한 쿼리 :-) 나는 당신의 쿼리를 테스트했고 그것은 내 초기 SQL과 똑같은 결과를 준다. 그리고 테이블 구조를 요구하지 않고 그것을 주셔서 감사합니다 !! 그리고 BETWEEN을 사용한 귀하의 질문은 실제로 일주일에 두 번만 발송되는 다음 자동 응답자에 도움이되며 며칠 내에 마지막 주문이있는 사용자를 수집합니다! – Phliplip

0

테이블 선언 또는 예제 데이터를 게시하지 않았으므로 결과에 주석을 달 수는 없지만 쿼리에 3 개의 상관 관계가있는 하위 쿼리가있어 성능이 좋지 않습니다 (확인 중 하나는 last_order와 테스트 용입니다).

상관 서브 쿼리를 제거하고 그들을 대체 이런 걸 줄 것입니다 조인 -

SELECT o.order_id, 
     o.order_status, 
     o.order_created, 
     o.user_id, 
     i.identity_firstname, 
     i.identity_email, 
     Sub1.order_count, 
     Sub2.last_order 
FROM orders o 
INNER JOIN user_identities ui ON o.user_id=ui.user_id 
INNER JOIN identities i ON ui.identity_id=i.identity_id 
    AND i.identity_email!='' 
INNER JOIN subscribers s ON i.identity_id=s.identity_id 
    AND s.subscriber_status=1 
    AND s.subsriber_type=e 
    AND s.subscription_id=1 
LEFT OUTER JOIN 
(
    SELECT user_id, COUNT(*) AS order_count 
    FROM orders 
    WHERE order_status=1 
    GROUP BY user_id 
) Sub1 
ON o.user_id = Sub1.user_id 
LEFT OUTER JOIN 
(
    SELECT user_id, MAX(order_created) as last_order 
    FROM orders 
    WHERE order_status=1 
    GROUP BY user_id 
) AS Sub2 
ON o.user_id = Sub2.user_id 
LEFT OUTER JOIN 
(
    SELECT DISTINCT user_id 
    FROM orders 
    WHERE order_status=1 
    AND DATE(order_created) > "2013-12-14" 
) Sub3 
ON o.user_id = Sub3.user_id 
WHERE DATE(o.order_created) = "2013-12-14" 
    AND o.order_status=1 
    AND Sub3.user_id IS NULL 
+0

'order_created'에'DATE (...) '를 사용하면 타임 스탬프 값을 사용하여 인덱스를 무시하게됩니다. 주문 수를 검색하는 하위 쿼리와 마지막 주문 날짜를 하나로 결합 할 수 있다는 사실을 놓쳤습니다 (처음에는 그렇게 했음에도 불구하고). 일반적으로'... JOIN (SELECT DISTINCT ...'는 MySQL의 성능상의 이점이 있는지 확신 할 수는 없지만'존재하지 않는 곳 '(또는'NOT')으로 바뀔 수 있습니다. –