2013-03-13 73 views
1

我有一個報表的SQL查詢,它包括幾個子查詢。它運行速度很慢。我嘗試了幾種方法(如使用連接而不是子查詢,添加幾個索引)。但他們都沒有工作。以下是查詢:TSQL查詢運行緩慢,如何加速?

declare @time_from  datetime 
declare @time_to   datetime 
set @time_from ='2012-01-01' 
set @time_to = '2014-01-01' 
select a.a_id, c.c_id, c.c_chat_line_id, a.a_first_name, a.a_last_name 
    ,(select isnull(SUM(ac.ac_amount),0) from t_actress_credit ac join t_order o on o.o_id = ac.order_id where o.o_status = 1 and ac.actress_id = a.a_id and ac.ac_time>[email protected]_from and ac.ac_time<[email protected]_to) as credit 
    ,(select isnull(SUM(ac.ac_amount),0) from t_actress_credit ac join t_order o on o.o_id = ac.order_id where o.o_status = 1 and ac.ac_is_paid = 1 and ac.actress_id = a.a_id and ac.ac_time>[email protected]_from and ac.ac_time<[email protected]_to) as paid_credit 
    ,(select COUNT(1) from t_message pm join t_call_log l1 on pm.call_log_id = l1.c_id where pm.m_type = 2 and l1.caller_id = c.c_id and pm.m_time>[email protected]_from and pm.m_time<[email protected]_to) as pmsg_sent 
    ,(select COUNT(1) from t_message pm join t_call_log l2 on pm.m_to_call_log_id = l2.c_id where pm.m_type = 2 and l2.caller_id = c.c_id and pm.m_time>[email protected]_from and pm.m_time<[email protected]_to) as pmsg_received 
    ,(select COUNT(1) from t_message pm join t_call_log l3 on pm.call_log_id = l3.c_id where pm.m_type = 1 and l3.caller_id = c.c_id and pm.m_time>[email protected]_from and pm.m_time<[email protected]_to) as lcmsg_sent 
    ,(select COUNT(1) from t_message pm join t_call_log l4 on pm.m_to_call_log_id = l4.c_id where pm.m_type = 1 and l4.caller_id = c.c_id and pm.m_time>[email protected]_from and pm.m_time<[email protected]_to) as lcmsg_received 
    ,(select COUNT(1) from t_actress_live_minute where actress_id = a.a_id and alm_time>[email protected]_from and alm_time<[email protected]_to) as live_calls 
    ,(select isnull(SUM(alm_minutes),0) from t_actress_live_minute where actress_id = a.a_id and alm_time>[email protected]_from and alm_time<[email protected]_to) as live_call_minutes 
    ,(select isnull(count(1),0) from t_call_log l where l.caller_id = c.c_id and l.c_time_out is not null and c_time_in >[email protected]_from and c_time_in <= @time_to) as total_calls 
    ,(select isnull(SUM(DATEDIFF(minute, l.c_time_in, l.c_time_out)),0) from t_call_log l where c_time_in >[email protected]_from and c_time_in <= @time_to and l.caller_id = c.c_id and l.c_time_out is not null) as total_call_minutes 
from t_actress a 
join t_caller c on c.c_id = a.caller_id 
group by a.a_id,c.c_id, c.c_chat_line_id, a.a_first_name, a.a_last_name 

任何一個可以給出一些建議嗎?

非常感謝!

艾倫

+0

,我會做的第一件事是註釋掉所有的子查詢,看看外部查詢需要多長時間運行,假設它的快速一次添加一個子查詢,看看哪些是造成下跌的最慢。 – 2013-03-13 20:48:09

+1

請向我們展示執行計劃。 – 2013-03-13 20:48:12

+0

從你的表名中,我猜測消息的計數將是最慢的操作。 – grahamj42 2013-03-13 20:50:08

回答

0

你可以嘗試合併來自同一個表或拉設置表成一個單一的子查詢子查詢。爲了解釋條件的變化,您可以使用條件聚合(使用CASE表達式)。

我可以在查詢中看到四個,可能是五個這樣的組。這被重寫使用4子查詢:

SELECT 
    a.a_id, 
    c.c_id, 
    c.c_chat_line_id, 
    a.a_first_name, 
    a.a_last_name, 
    ISNULL(cr.credit   , 0) AS credit 
    ISNULL(cr.paid_credit  , 0) AS paid_credit 
    ISNULL(m.pmsg_sent   , 0) AS pmsg_sent, 
    ISNULL(m.pmsg_received  , 0) AS pmsg_received, 
    ISNULL(m.lcmsg_sent   , 0) AS lcmsg_sent, 
    ISNULL(m.lcmsg_received  , 0) AS lcmsg_received, 
    ISNULL(alm.live_calls  , 0) AS live_calls, 
    ISNULL(alm.live_call_minutes, 0) AS live_call_minutes, 
    ISNULL(l.total_calls  , 0) AS total_calls, 
    ISNULL(l.total_call_minutes , 0) AS total_call_minutes, 

FROM t_actress AS a 

INNER JOIN t_caller AS c 
    ON c.c_id = a.caller_id 

LEFT JOIN (
    SELECT 
    ac.actress_id, 
    SUM(        ac.ac_amount ) AS credit, 
    SUM(CASE ac.ac_is_paid WHEN 1 THEN ac.ac_amount END) AS paid_credit 
    FROM t_actress_credit AS ac 
    JOIN t_order o ON o.o_id = ac.order_id 
    WHERE o.o_status = 1 
    AND ac.ac_time BETWEEN @time_from AND @time_to 
    GROUP BY ac.actress_id 
) AS ac 
    ON ac.actress_id = a.a_id 

LEFT JOIN (
    SELECT 
    l.caller_id, 
    COUNT(CASE WHEN m.  call_log_id = l1.c_id AND m.m_type = 2 THEN 1 END) AS pmsg_sent, 
    COUNT(CASE WHEN m.m_to_call_log_id = l2.c_id AND m.m_type = 2 THEN 1 END) AS pmsg_received, 
    COUNT(CASE WHEN m.  call_log_id = l3.c_id AND m.m_type = 1 THEN 1 END) AS lcmsg_sent, 
    COUNT(CASE WHEN m.m_to_call_log_id = l4.c_id AND m.m_type = 1 THEN 1 END) AS lcmsg_received 
    FROM t_message AS m 
    JOIN t_call_log AS l ON l.c_id IN (m.call_log_id, m.m_to_call_log_id) 
    WHERE m.m_type IN (1, 2) 
    AND m.m_time BETWEEN @time_from AND @time_to 
    GROUP BY l.caller_id 
) AS m 
    ON m.caller_id = c.c_id 

LEFT JOIN (
    SELECT 
    actress_id, 
    COUNT(*)   AS live_calls, 
    SUM(alm_minutes) AS live_call_minutes 
    FROM t_actress_live_minute 
    WHERE alm_time BETWEEN @time_from AND @time_to 
    GROUP BY actress_id 
) AS alm 
    ON alm.actress_id = a.a_id 

LEFT JOIN (
    SELECT 
    caller_id, 
    COUNT(*)          AS total_calls, 
    SUM(DATEDIFF(MINUTE, c_time_in, c_time_out)) AS total_call_minutes 
    FROM t_call_log 
    WHERE c_time_out IS NOT NULL 
    AND c_time_in BETWEEN @time_from AND @time_to 
    GROUP BY caller_id 
) AS l 
    ON l.actress_id = a.a_id 
; 

這可能有五個子查詢,如果你通過單獨連接上call_log_id分裂m子查詢了兩對m_to_call_log_id(從而可能給查詢規劃了優化空間) ,即代替

LEFT JOIN (
    SELECT 
    l.caller_id, 
    COUNT(CASE WHEN m.  call_log_id = l1.c_id AND m.m_type = 2 THEN 1 END) AS pmsg_sent, 
    COUNT(CASE WHEN m.m_to_call_log_id = l2.c_id AND m.m_type = 2 THEN 1 END) AS pmsg_received, 
    COUNT(CASE WHEN m.  call_log_id = l3.c_id AND m.m_type = 1 THEN 1 END) AS lcmsg_sent, 
    COUNT(CASE WHEN m.m_to_call_log_id = l4.c_id AND m.m_type = 1 THEN 1 END) AS lcmsg_received 
    FROM t_message AS m 
    JOIN t_call_log AS l ON l.c_id IN (m.call_log_id, m.m_to_call_log_id) 
    WHERE m.m_type IN (1, 2) 
    AND m.m_time BETWEEN @time_from AND @time_to 
    GROUP BY l.caller_id 
) AS m 
    ON m.caller_id = c.c_id 

這將是

LEFT JOIN (
    SELECT 
    l.caller_id, 
    COUNT(CASE WHEN m.call_log_id = l1.c_id AND m.m_type = 2 THEN 1 END) AS pmsg_sent, 
    COUNT(CASE WHEN m.call_log_id = l3.c_id AND m.m_type = 1 THEN 1 END) AS lcmsg_sent, 
    FROM t_message AS m 
    JOIN t_call_log AS l ON l.c_id = m.call_log_id 
    WHERE m.m_type IN (1, 2) 
    AND m.m_time BETWEEN @time_from AND @time_to 
    GROUP BY l.caller_id 
) AS mf 
    ON mf.caller_id = c.c_id 

LEFT JOIN (
    SELECT 
    l.caller_id, 
    COUNT(CASE WHEN m.m_to_call_log_id = l2.c_id AND m.m_type = 2 THEN 1 END) AS pmsg_received, 
    COUNT(CASE WHEN m.m_to_call_log_id = l4.c_id AND m.m_type = 1 THEN 1 END) AS lcmsg_received 
    FROM t_message AS m 
    JOIN t_call_log AS l ON l.c_id = m.m_to_call_log_id 
    WHERE m.m_type IN (1, 2) 
    AND m.m_time BETWEEN @time_from AND @time_to 
    GROUP BY l.caller_id 
) AS mt 
    ON mt.caller_id = c.c_id 

變化也心病在主SELECT子句中響應引用。

我不確定是否哪種變化更好,您需要測試兩者以找出答案。

請注意,我省略了主查詢的GROUP BY子句。在我的查詢和我的查詢中,似乎都沒有必要,因爲就我所知,它包括來自t_actresst_caller的主鍵,無論如何這些組合都是唯一的。我認爲GROUP BY是您以前嘗試使用連接重寫查詢時的剩餘部分。

0

感謝您的回覆。我試過你的方式,它仍然很慢。這是我所做的並且最終成功的。我基本上把所有的子查詢放入表中,然後在最後加入表。不知道爲什麼,但速度更快:這裏是代碼;

-- total calls 
declare @t_call table(
    a_id bigint, 
    total_calls bigint, 
    total_call_minutes bigint 
); 
insert into @t_call 
    SELECT a_id, COUNT(1) AS total_calls, isnull(SUM(DATEDIFF(MINUTE, c_time_in, c_time_out)),0) AS total_call_minutes 
     FROM t_actress aa 
      join t_call_log l on aa.caller_id = l.caller_id and c_time_in BETWEEN @time_from AND @time_to and c_time_out IS NOT NULL 
     GROUP BY a_id; 

-- total live minutes 
declare @t_live table(
    a_id bigint, 
    live_calls bigint, 
    live_call_minutes bigint 
); 
insert into @t_live 
    SELECT a_id, COUNT(*) AS live_calls, isnull(SUM(alm_minutes),0) AS live_call_minutes 
     FROM t_actress a 
      join t_actress_live_minute alm on alm.actress_id = a.a_id and alm_time BETWEEN @time_from AND @time_to 
     GROUP BY a_id 

-- total message by caller 
declare @t_cm table(
    caller_id bigint, 
    pmsg_sent bigint, 
    pmsg_received bigint, 
    lcmsg_sent bigint, 
    lcmsg_received bigint 
) 
insert into @t_cm 
    SELECT l.caller_id, 
      COUNT(CASE WHEN m.call_log_id  = l.c_id AND m.m_type = 2 THEN 1 END) AS _pmsg_sent, 
      COUNT(CASE WHEN m.m_to_call_log_id = l.c_id AND m.m_type = 2 THEN 1 END) AS _pmsg_received, 
      COUNT(CASE WHEN m.call_log_id  = l.c_id AND m.m_type = 1 THEN 1 END) AS _lcmsg_sent, 
      COUNT(CASE WHEN m.m_to_call_log_id = l.c_id AND m.m_type = 1 THEN 1 END) AS _lcmsg_received 
      FROM t_message m 
      join t_call_log l on l.c_id in (m.call_log_id, m.m_to_call_log_id) 
      where m.m_time BETWEEN @time_from AND @time_to 
     GROUP BY l.caller_id 

-- total message by actress 
declare @t_msg table(
    a_id bigint, 
    pmsg_sent bigint, 
    pmsg_received bigint, 
    lcmsg_sent bigint, 
    lcmsg_received bigint 
) 
insert into @t_msg 
    select a_id, isnull(SUM(cm.pmsg_sent),0), isnull(SUM(cm.pmsg_received),0), isnull(SUM(cm.lcmsg_sent),0), isnull(SUM(cm.lcmsg_received),0) 
     from t_actress a 
      join @t_cm cm on a.caller_id = cm.caller_id 
    group by a_id 

-- total credit 
declare @t_credit table(
    a_id bigint, 
    credit money, 
    paid_credit money 
) 
insert into @t_credit 
    SELECT a_id, isnull(SUM(ac.ac_amount),0) AS credit, isnull(SUM(CASE ac.ac_is_paid WHEN 1 THEN ac.ac_amount else 0 END),0) AS paid_credit 
     FROM t_actress a 
      join t_actress_credit ac on ac.actress_id = a.a_id AND ac.ac_time BETWEEN @time_from AND @time_to 
      JOIN t_order o ON o.o_id = ac.order_id and o_status = 1 
    GROUP BY a_id 

-- the report  
select a.a_id, cl.c_id, cl.c_chat_line_id, a.a_first_name, a.a_last_name, 
     isnull(ac.credit,0) credit, isnull(ac.paid_credit,0) paid_credit, 
     isnull(m.pmsg_sent,0) pmsg_sent, isnull(m.pmsg_received,0) pmsg_received, isnull(m.lcmsg_sent,0) lcmsg_sent, isnull(m.lcmsg_received,0) lcmsg_received, 
     isnull(l.live_calls,0) live_calls, isnull(l.live_call_minutes,0) live_call_minutes, 
     isnull(c.total_calls,0) total_calls, isnull(c.total_call_minutes,0) total_call_minutes 
from t_actress a 
    join t_caller cl on cl.c_id = a.caller_id 
    left outer join @t_call c on c.a_id = a.a_id 
    left outer join @t_live l on l.a_id = a.a_id     
    left outer join @t_msg m on m.a_id = a.a_id 
    left outer join @t_credit ac on ac.a_id = a.a_id 
order by a_id 
+0

它更快,因爲相關的子查詢逐行運行(因此在我的意見中絕不應該用於生產代碼中)並且聯接在集合上運行。如果這些表中的任何一個有很多記錄,您可以進一步加快索引臨時表的速度。 – HLGEM 2013-03-14 19:12:09