SQL Server: query optimization - sql

I have the following query which takes around 4 minutes to execute.
DECLARE #tdate DATETIME = '2019-09-01 00:00:00.000'
SELECT c.id AS clid,
h.id AS hlid,
h.holdinNo,
c.cliendID,
c.clientName,
h.floor,
h.connect_radius
FROM [db_land].[dbo].tbl_client AS c
INNER JOIN [db_land].[dbo].tx_holding AS h
ON c.id = h.clid
WHERE h.status = 1
AND h.connect_radius IS NOT NULL
AND c.status = 1
AND h.type = 'Residential'
AND h.holdinNo NOT IN (SELECT holdingNo
FROM [db_land].[dbo].tbl_bill
WHERE year(date_month) = YEAR(#tdate)
AND MONTH(date_month) = MONTH(#tdate)
AND ( update_by IS NOT NULL
OR ispay = 1 ))
I found the inner join takes only few seconds.
SELECT c.id AS clid,
h.id AS hlid,
h.holdinNo,
c.cliendID,
c.clientName,
h.floor,
h.connect_radius
FROM [db_land].[dbo].tbl_client AS c
INNER JOIN [db_land].[dbo].tx_holding AS h
ON c.id = h.clid
WHERE h.status = 1
AND h.connect_radius IS NOT NULL
AND c.status = 1
AND h.type = 'Residential'
It's the NOT IN checking which takes a lot of time. How I can optimize this query? For me it's needed to execute the query at least with in minute.

Make sure the WHERE and JOIN clause predicates are sargable. Applying a function to a column (e.g. YEAR(date_month)) prevents indexes on the column from being used efficiently.
Try this expression instead to avoid the functions. There are other methods depending on the SQL Server version.
WHERE
date_month >= DATEADD(day, 1, DATEADD(month, -1, EOMONTH(#tdate)))
AND date_month < DATEADD(day, 1, DATEADD(month, 1, EOMONTH(#tdate)))

Try by replacing NOT IN with a LEFT JOIN of the table [db_land].[dbo].tbl_bill on all the conditions and adding in the WHERE clause holdingNo is null so the returned rows are the non matching rows:
select c.id as clid, h.id as hlid,h.holdinNo, c.cliendID, c.clientName, h.floor, h.connect_radius
from [db_land].[dbo].tbl_client as c
inner join [db_land].[dbo].tx_holding as h
on c.id= h.clid
left join [db_land].[dbo].tbl_bill as b
on b.holdingNo = h.holdinNo and year(b.date_month) = YEAR(#tdate) and MONTH(b.date_month) = MONTH(#tdate)
and (b.update_by is not null or b.ispay = 1)
where h.status = 1 and h.connect_radius is not null and c.status=1 and h.type='Residential' and b.holdingNo is null

I would recommend changing the NOT IN to NOT EXISTS and adding an index:
WHERE . . . AND
NOT EXISTS (SELECT 1
FROM [db_land].[dbo].tbl_bill b
WHERE b.holdingNo = h.holdingNo AND
b.date_month >= DATEFROMPARTS(YEAR(#tdate), MONTH(#tdate), 1) AND
b.date_month < DATEADD(month, 1, DATEFROMPARTS(YEAR(#tdate), MONTH(#tdate), 1)) AND
(b.update_by IS NOT NULL OR b.ispay = 1
)
Then the index that you want is on tbl_bill(holdingNo, date_month, update_by, ispay).

Put your sub query into temp table :
DECLARE #tdate DATETIME = '2019-09-01 00:00:00.000'
SELECT holdingNo
into #TmpholdingNo
FROM [db_land].[dbo].tbl_bill
WHERE year(date_month) = YEAR(#tdate)
AND MONTH(date_month) = MONTH(#tdate)
AND ( update_by IS NOT NULL
OR ispay = 1 )
SELECT c.id AS clid,
h.id AS hlid,
h.holdinNo,
c.cliendID,
c.clientName,
h.floor,
h.connect_radius
FROM [db_land].[dbo].tbl_client AS c
INNER JOIN [db_land].[dbo].tx_holding AS h
ON c.id = h.clid
WHERE h.status = 1
AND h.connect_radius IS NOT NULL
AND c.status = 1
AND h.type = 'Residential'
AND h.holdinNo NOT IN (SELECT holdingNo from #TmpholdingNo)
drop table #TmpholdingNo

Rather than using functions in your WHERE clause try calculating the start and end filter dates, using OPTION (RECOMPILE) can help SQL to use the actual values of your variables in your query plan. I would also change NOT IN to NOT EXISTS:
DECLARE #tdate DATETIME = '2019-09-01 00:00:00.000'
DECLARE #startDate DATE = DATEFROMPARTS(YEAR(#tdate), MONTH(#tdate), 1)
DECLARE #endDate DATE = DATEADD(day,1,EOMONTH(#tdate))
SELECT c.id AS clid,
h.id AS hlid,
h.holdinNo,
c.cliendID,
c.clientName,
h.floor,
h.connect_radius
FROM [db_land].[dbo].tbl_client AS c
INNER JOIN [db_land].[dbo].tx_holding AS h
ON c.id = h.clid
WHERE h.status = 1
AND h.connect_radius IS NOT NULL
AND c.status = 1
AND h.type = 'Residential'
AND NOT EXISTS (SELECT holdingNo
FROM [db_land].[dbo].tbl_bill
WHERE holdingNo = h.holdinNo AND
date_month >= #startDate AND
date_month < #endDate AND
AND ( update_by IS NOT NULL
OR ispay = 1 ))
OPTION (RECOMPILE)

give a try try this:
select main.* from
(SELECT c.id AS clid,
h.id AS hlid,
h.holdinNo,
c.cliendID,
c.clientName,
h.floor,
h.connect_radius
FROM [db_land].[dbo].tbl_client AS c
INNER JOIN [db_land].[dbo].tx_holding AS h
ON c.id = h.clid
WHERE h.status = 1
AND h.connect_radius IS NOT NULL
AND c.status = 1
AND h.type = 'Residential')main
left join
(select holdingNo from
(SELECT holdingNo, update_by, ispay
FROM [db_land].[dbo].tbl_bill
WHERE year(date_month) = YEAR(#tdate)
AND MONTH(date_month) = MONTH(#tdate))bill1
where update_by IS NOT NULL OR ispay = 1)bill2
on main.holdinNo = bill2.holdinNo
where bill2.holdinNo is null

put the filter list at variable,then them apply the filter
DECLARE #filter TABLE INSERT INTO #filter SELECT FROM [db_land].[dbo].tbl_bill
them apply the filter
DECLARE #tdate DATETIME = '2019-09-01 00:00:00.000'
SELECT c.id AS clid,
h.id AS hlid,
h.holdinNo,
c.cliendID,
c.clientName,
h.floor,
h.connect_radius
FROM [db_land].[dbo].tbl_client AS c
INNER JOIN [db_land].[dbo].tx_holding AS h ON c.id= h.clid
WHERE h.status=1
AND h.connect_radius IS NOT NULL
AND c.status=1
AND h.type='Residential'
AND h.holdinNo NOT IN (filter)

Related

How to Update field using subquery with aliases

I have an Update query that will help me to update a certain field and right now it is throwing an error on the last two lines of code where the d.column is mentioned. Does anyone know how I can still use the d.column fields at the end or know a work around that will produce the same results? Help is greatly appreciated. The error is where I am using d.LastUpdateSchedule and d.Due_Dte in the last two lines of code.
update ods.Customer
set NumberTPD = a.NumberCode
from ods.Customer r
left join
(
Select d.CustomerNumber
, d.due_Dte
, l.NumberCode
from (select r.CustomerNumber
, r.due_Dte
, r.NumberTPD
, max(l.UpdateSchedule) as LastUpdateSchedule
from ods.Customer r
left join ods.CustomerHistory l
on r.CustomerNumber = l.CustomerNumber
and r.due_Dte >= l.UpdateSchedule
and l.Examine = 1
and r.ExamineFrequency in ('MONTHLY','MNTHLYLDAY')
and isnull(r.ScheduleEndDate,'1970-01-01') < r.due_Dte
group by r.CustomerNumber, r.Due_Dte, r.NumberTPD) d
left join ods.CustomerHistory l
on d.CustomerNumber = l.CustomerNumber
and d.LastUpdateSchedule = l.UpdatedSchedule
) a
on r.CustomerNumber = a.CustomerNumber
and r.Due_Dte = a.Due_Dte
where d.Due_Dte > '2018-08-03'
and d.LastUpdateSchedule is not null
this is because your table d is inside your table a move your where clause to table d
update ods.Customer
set NumberTPD = a.NumberCode
from ods.Customer r
left join
(Select d.CustomerNumber
, d.due_Dte
, l.NumberCode
from
(select r.CustomerNumber
, r.due_Dte
, r.NumberTPD
, max(l.UpdateSchedule) as LastUpdateSchedule
from ods.Customer r
left join ods.CustomerHistory l on r.CustomerNumber = l.CustomerNumber
and r.due_Dte >= l.UpdateSchedule
and l.Examine = 1
and r.ExamineFrequency in ('MONTHLY','MNTHLYLDAY')
and isnull(r.ScheduleEndDate,'1970-01-01') < r.due_Dte
group by r.CustomerNumber, r.Due_Dte, r.NumberTPD) d
left join ods.CustomerHistory l on d.CustomerNumber = l.CustomerNumber
and d.LastUpdateSchedule = l.UpdatedSchedule
where d.Due_Dte > '2018-08-03'
and d.LastUpdateSchedule is not null
) a on r.CustomerNumber = a.CustomerNumber
and r.Due_Dte = a.Due_Dte
-- only what you need
UPDATE cust
SET cust.NumberTPD = histRow.NumberCode
FROM ods.Customer cust
JOIN
(SELECT c.CustomerNumber,
max(h.UpdateSchedule) AS LastUpdateSchedule
FROM ods.Customer c
JOIN ods.CustomerHistory h ON c.CustomerNumber = h.CustomerNumber
AND c.due_Dte >= h.UpdateSchedule
WHERE c.Due_Dte > '2018-08-03'
AND c.ExamineFrequency IN ('MONTHLY',
'MNTHLYLDAY')
AND isnull(c.ScheduleEndDate, '1970-01-01') < c.due_Dte --sure about this?
AND h.Examine = 1
GROUP BY c.CustomerNumber) maxes
JOIN ods.CustomerHistory histRow ON maxes.CustomerNumber = histRow.CustomerNumber
AND maxes.LastUpdateSchedule = histRow.UpdatedSchedule

UPDATE all records from existing SELECT query

I have query to select data from related tables.
SELECT
s.id,
CASE
WHEN count(DISTINCT e.id) <> 0
THEN count(DISTINCT o.id) / count(DISTINCT e.id)
END OrdersAverageNumber
FROM
[School] s
JOIN
[SchoolStore] ss ON ss.SchoolId = s.Id
JOIN
[Event] e ON e.SchoolId = ss.SchoolId
AND e.IsDeleted = 0
AND e.Status = 1
AND e.Date >= #startDate
AND e.Date <= #endDate
JOIN
[Order] o ON o.EventId = e.id
AND o.OrderStatus = 1
AND o.CreatedDate >= #startDate
AND o.CreatedDate <= #endDate
GROUP BY
s.id;
But I can't understand what I need to change to update all OrdersAverageNumber records in School table with values from selection above.
You can use update:
with q as (< your query here >)
update s
set OrdersAverageNumber = q.OrdersAverageNumber
from school s join
q
on s.id = q.id;

extra sql query column

I need to select extra columns from another table in my sql query.
SELECT
d.UnitID,
b.BookingID,
d.ProjectID,
b.ClientName,
(SELECT LetterTypeID
FROM Letters
WHERE ProjectID = 27 AND BookingID = b.BookingID)
FROM
ScheduledDues AS d
INNER JOIN
Booking AS b ON d.BookingID = b.BookingID
INNER JOIN
Units AS u ON d.UnitID = u.UnitID
WHERE
d.ProjectID = 27
AND DueFrom <= GETDATE()
GROUP BY
d.BookingID, d.UnitID, d.ProjectID,
u.UnitNo, b.ClientName
HAVING
SUM(DueTill) = 0
How can I do this? and have it in group by. Is selecting LetterTypeID possible?
Need to use LetterTypeID in group by like below or You can fetch results without letters table into temp and join it with letters to get LetterTypeID
SELECT d.UnitID,
b.BookingID,
d.ProjectID,
b.ClientName,
l.LetterTypeID
FROM ScheduledDues AS d INNER JOIN
Booking AS b ON d.BookingID = b.BookingID INNER JOIN
Units AS u ON d.UnitID = u.UnitID INNER JOIN
Letters AS l ON b.BookingID=l.BookingID AND l.ProjectID=27
WHERE d.ProjectID = 27 AND
DueFrom <= GETDATE()
GROUP BY d.BookingID,
d.UnitID,
d.ProjectID,
u.UnitNo,
b.ClientName,
l.LetterTypeID
HAVING SUM(DueTill) = 0
Can you try this query
SELECT BookingID, UnitID, ProjectID
UnitNo, ClientName ,LetterTypeID FROM (SELECT
d.UnitID,
b.BookingID,
d.ProjectID,
b.ClientName,
DueTill,
(SELECT LetterTypeID
FROM Letters
WHERE ProjectID = 27 AND BookingID = b.BookingID) LetterTypeID
FROM
ScheduledDues AS d
INNER JOIN
Booking AS b ON d.BookingID = b.BookingID
INNER JOIN
Units AS u ON d.UnitID = u.UnitID
WHERE
d.ProjectID = 27
AND DueFrom <= GETDATE()) x
GROUP BY
BookingID, UnitID, ProjectID,
UnitNo, ClientName ,LetterTypeID
HAVING
SUM(DueTill) = 0

How to convert correlated sub query containing duplicate table to non-correlated one?

I have to convert the correlated sub-query to non-correlated sub-query cuz of performance issues .
like that :
The correlated sub-query :(So slow ) returns 4000 row
SELECT a.personid,a.name,b.conid,d.condat,e.connam
FROM main_empr a INNER JOIN coninr b
ON a.personid = b.personid AND a.calc_year = b.calc_year
INNER JOIN mainconinr c
ON b.conid = c.conid
INNER JOIN coninr d
ON a.personid = d.personid AND a.calc_year = d.calc_year
INNER JOIN mainconinr e
ON d.conid = e.conid
WHERE c.active_flag = 1 and c.endreward_flag = 1
AND d.condat = (SELECT MIN(bb.condat) FROM coninr bb WHERE bb.personid = b.personid AND bb.calc_year = b.calc_year AND ((bb.conid > 0 AND bb.conid < 4 ) OR (bb.conid IN(16,6) )) )
AND b.condat = (SELECT MAX(bb.condat) FROM coninr bb WHERE bb.personid = b.personid AND bb.calc_year = b.calc_year AND ((bb.conid > 0 AND bb.conid < 4 ) OR (bb.conid IN(16,6) )) )
AND ( 0 = ( SELECT COUNT(*) FROM servmain x WHERE x.personid = a.personid AND x.calc_year = a.calc_year )
OR b.condat > ( SELECT MAX(x.serv_date) FROM servmain x WHERE x.personid = a.personid AND x.calc_year = a.calc_year ) )
AND a.calc_year = 2018
The non-correlated query :returns about 12300 rows!!
SELECT a.personid,a.name,b.conid,d.condat,e.connam
FROM main_empr a INNER JOIN
coninr b
ON a.personid = b.personid AND a.calc_year = b.calc_year
INNER JOIN mainconinr c
ON b.conid = c.conid
INNER JOIN coninr d
ON a.personid = d.personid AND a.calc_year = d.calc_year
INNER JOIN mainconinr e ON d.conid = e.conid
INNER JOIN
(SELECT MAX(bb.condat) AS condat ,bb.personid,bb.calc_year ,bb.conid
FROM coninr bb
GROUP BY bb.personid,bb.calc_year,bb.conid
)Max_cont
ON Max_cont.personid = b.personid AND Max_cont.calc_year = b.calc_year AND Max_cont.condat = b.condat AND ((Max_cont.conid > 0 AND Max_cont.conid < 4 ) OR (Max_cont.conid IN(16,6) ))
INNER JOIN
(SELECT MIN(dd.condat) AS condat ,dd.personid,dd.calc_year,dd.conid
FROM coninr dd GROUP BY dd.personid,dd.calc_year,dd.conid
)Min_cont
ON Min_cont.personid = d.personid AND Min_cont.calc_year = d.calc_year AND Min_cont.condat = d.condat AND ((Min_cont.conid > 0 AND Min_cont.conid < 4 ) OR (Min_cont.conid IN(16,6) ))
WHERE c.active_flag = 1 and c.endreward_flag = 1
AND ( 0 = ( SELECT COUNT(*) FROM servmain x WHERE x.personid = a.personid AND x.calc_year = a.calc_year )
OR b.condat > ( SELECT MAX(x.serv_date) FROM servmain x WHERE x.personid = a.personid AND x.calc_year = a.calc_year ) )
AND a.calc_year = 2018
The problem is :
I use the coninr table twice to get the last and the first contract date in the same row .
It works fine in the first query but it was so slow because of the correlated sub-query,but in the second query it brings more than one row for the same person one of them for the first contract date and the other for the last one !!
How to fix this problem ?
This looks reasonable, but I've no way to know how it'll perform:
SELECT a.personid,a.name,b.conid,d.condat,e.connam
FROM main_empr a INNER JOIN coninr b
ON a.personid = b.personid AND a.calc_year = b.calc_year
INNER JOIN mainconinr c
ON b.conid = c.conid
INNER JOIN coninr d
ON a.personid = d.personid AND a.calc_year = d.calc_year
INNER JOIN mainconinr e
ON d.conid = e.conid
inner join
(
SELECT bb.personid, bb.calc_year, bb.conid, MIN(bb.condat) MinDate, MAX(bb.condat) MaxDate
FROM coninr bb WHERE
where (bb.conid > 0 and bb.conid < 4) or (bb.conid in (6, 16))
group by bb.personid, bb.calc_year, bb.conid
) zz on d.concat = zz.MinDate and b.condat = zz.MaxDate and b.personid = zz.personid and b.calc_year = zz.calc_year
left outer join
(
select s.personid, s.calc_year, max(s.serv_date) MaxServDate
from servmain s
group by s.personid, s.calc_year
) s on a.personid = s.personid and a.calc_year = s.calc_year
WHERE c.active_flag = 1 and c.endreward_flag = 1
and (s.MaxServDate is null or b.condat ? s.MaxServDate
AND a.calc_year = 2018
You don't need two queries for table coninr, you can get min and max in the same query with the group by. Also, for ServMain, doing a left outer join and putting in the where that either it's null (equivalent to count(*) = 0) or is less than b.condat takes care of that.

TSQL query is running slow, how to speed it up?

I have a sql query for a report, it includes a few sub queries. it runs very slow. I tried a few ways (like use join instead of subquery, add a few more index). but none of them worked. Here is the query:
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
Can any one give some suggestions?
Thanks a lot!
Alan
You could try combining subqueries that pull from the same table or set of tables into a single subquery. To account for variations in the conditions, you could use conditional aggregation (employing CASE expressions).
I can see four, possibly five, such groups in your query. Here it is rewritten to use four subqueries:
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
;
It could be five subqueries if you split the m subquery into two by joining separately on call_log_id and on m_to_call_log_id (and thus potentially giving the query planner more room for optimisation), i.e. instead of
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
it would be
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
changing also the corresponding references in the main SELECT clause.
I'm not sure whether which of the variations is better, you'll need to test both to find out.
Note that I've omitted the main query's GROUP BY clause. It seems unnecessary both in your query and in mine, because, as far as I can see, it includes primary keys from both t_actress and t_caller and those combinations would be unique anyway. I assume that the GROUP BY is a leftover from your previous attempts at rewriting the query using joins.
thanks for your reply. I tried your way, it is still slow. here is what I did and finally works. I basically put all the sub queries into table, and then join the table at the end. not sure why, but it is faster: here is the code;
-- 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