SQL查询运行缓慢,如何加快速度?

时间:2013-03-13 20:43:04

标签: sql sql-server join subquery

我有一个报告的SQL查询,它包含一些子查询。它运行得很慢。我尝试了几种方法(比如使用join而不是子查询,添加一些索引)。但他们都没有工作。这是查询:

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>=@time_from and ac.ac_time<=@time_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>=@time_from and ac.ac_time<=@time_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>=@time_from and pm.m_time<=@time_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>=@time_from and pm.m_time<=@time_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>=@time_from and pm.m_time<=@time_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>=@time_from and pm.m_time<=@time_to) as lcmsg_received
    ,(select COUNT(1) from t_actress_live_minute where actress_id = a.a_id and alm_time>=@time_from and alm_time<=@time_to ) as live_calls
    ,(select isnull(SUM(alm_minutes),0) from t_actress_live_minute where actress_id = a.a_id  and alm_time>=@time_from and alm_time<=@time_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 >=@time_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 >=@time_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

任何人都可以提出一些建议吗?

非常感谢!

艾伦

2 个答案:

答案 0 :(得分:0)

您可以尝试将从同一个表或一组表中提取的子查询组合到一个子查询中。为了解释条件的变化,您可以使用条件聚合(使用CASE表达式)。

我可以在您的查询中看到四个,可能是五个这样的组。在这里重写它以使用四个子查询:

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
;

如果通过在mcall_log_id上单独加入,将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是您之前尝试使用连接重写查询的剩余部分。

答案 1 :(得分: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