T-SQL, repeated same scalar subquery performance in views - sql-server

Below is a simple query that retrieves Students and their exam results. The same student can take the same exam multiple times. The subqueries retrieve the latest exam results for each student. As you can see, the Line X (which retrieves the latest Exam ID) is exactly the same in every subquery for each row. How to store or cache the result of Line X to prevent three times execution for each row?
I cannot use stored procedure or functions for this task, it has to be a VIEW for additional filtering.
SELECT S.*,
(
SELECT COUNT(*) FROM ExamAnswers WHERE
IsCorrectAnswer IS NOT NULL AND
IsCorrectAnswer = 1 AND
ExamID =
(SELECT TOP(1) ID FROM Exams E WHERE E.StudentID = S.ID ORDER BY ID DESC) --Line X
) CorrectAnswerCount,
(
SELECT COUNT(*) FROM ExamAnswers EA WHERE
EA.IsCorrectAnswer IS NOT NULL AND
EA.IsCorrectAnswer = 0 AND
EA.ExamID =
(SELECT TOP(1) ID FROM Exams E WHERE E.StudentID = S.ID ORDER BY ID DESC) --Line X
) WrongAnswerCount,
(
SELECT COUNT(*) FROM ExamAnswers WHERE
IsCorrectAnswer IS NULL AND
ExamID =
(SELECT TOP(1) ID FROM Exams E WHERE E.StudentID = S.ID ORDER BY ID DESC) --Line X
) UnansweredQuestionCount
FROM Students S

You can do it like this
SELECT S.*,
CA.*
FROM Students S
CROSS APPLY (SELECT SUM(CASE WHEN IsCorrectAnswer = 1 THEN 1 ELSE 0 END) AS CorrectAnswerCount,
SUM(CASE WHEN IsCorrectAnswer = 0 THEN 1 ELSE 0 END) AS WrongAnswerCount,
SUM(CASE WHEN IsCorrectAnswer IS NULL THEN 1 ELSE 0 END) AS UnansweredQuestionCount
FROM ExamAnswers EA
WHERE EA.ExamID = (SELECT TOP(1) ID
FROM Exams E
WHERE E.StudentID = S.ID
ORDER BY ID DESC)) CA

What about this approach :
WITH
T AS
(
SELECT Student_id,
SUM(CASE IsCorrectAnswer WHEN 1 THEN 1 END) AS COUNT_TRUE,
SUM(CASE IsCorrectAnswer WHEN 0 THEN 1 END) AS COUNT_FALSE,
SUM(CASE WHEN IsCorrectAnswer IS NULL THEN 1 END) AS COUNT_UNKNOWN
FROM ExamAnswers AS EA
WHERE EA.ExamID = (SELECT MAX(ID)
FROM Exams E
WHERE E.StudentID = S.ID)
GROUP BY Student_id
)
SELECT S.*, COUNT_TRUE, COUNT_FALSE, COUNT_UNKNOWN
FROM Students AS S
JOIN T ON S.ID = T.Student_id

Related

how to add left join with CTE , check my query

i have following query i want add my query left join with CTE how do this please help me because i have driver id i want second last driver id but i want add left join with CTE
select d.Id,d.DriverNo,d.DriverName,TransId=dc.Id,dc.FromDate,dc.ToDate,dc.IsPaid,
Active=(case when (dc.weekoff is null or dc.weekoff=0) then 'Active' else 'Off' end),
Rent=(case when (IsNull(dc.CommissionTotal,0))> IsNull(dc.AccJobsTotal,0) then IsNull(dc.CommissionTotal,0)-(IsNull(dc.AccJobsTotal,0)) else 0 end),
BalanceDue=IsNull(dc.OldBalance,0),
AgentCommission=IsNull(dc.AgentFeesTotal,0),
PDA= (case when (dc.weekoff is null or dc.weekoff=0) then (IsNull(dc.PDARent,0)+IsNull(dc.CollectionDeliveryCharges,0)) else 0 end),
Total=(case when (IsNull(dc.CommissionTotal,0))> IsNull(dc.AccJobsTotal,0) then IsNull(dc.CommissionTotal,0)-(IsNull(dc.AccJobsTotal,0)) else 0 end)
+((IsNull(dc.OldBalance,0))
+((IsNull(dc.AgentFeesTotal,0)))
+(case when (dc.weekoff is null or dc.weekoff=0) then (IsNull(dc.PDARent,0)+IsNull(dc.CollectionDeliveryCharges,0)) else 0 end))
from Fleet_Driver d
inner join Fleet_DriverCommision dc
on d.Id=dc.DriverId
where dc.Id in (select Max(Id) from Fleet_DriverCommision
group by DriverId) as T1
left join on
> LEFT JOIN WITH CTE
With cte as
(select AgentFeesTotal,DriverId,Row_Number()over(Partition by DriverID order by Transdate desc) as Rn,
count(1)over(Partition by DriverID) as cnt from Fleet_DriverCommision)
Select AgentFeesTotal,DriverId
from cte
Where (Rn = 2 and cnt > 1) or (Rn = 1 and cnt = 1)
This is example
with cte
as
(select AgentFeesTotal,DriverId,Row_Number()over(Partition by DriverID order by Transdate desc) as Rn,
count(1)over(Partition by DriverID) as cnt from Fleet_DriverCommision)
Select AgentFeesTotal,DriverId
from cte
Where (Rn = 2 and cnt > 1) or (Rn = 1 and cnt = 1)
select t2.DriverNo from Fleet_Driver t2
left join
cte c
on c.DriverId=t2.Id
It looks like you are struggling with the syntax for using CTEs. The CTE declaration needs to happen before the rest of the query and then behaves like another table. Also note that the WITH statement must be the first statement or follow a semi-colon. This should get you on the right track. Also be sure to check the examples in the MSDN documentation.
--With statement first - must follow ; if there are multiple statements...
With cte as
(select AgentFeesTotal,DriverId,
Row_Number()over(Partition by DriverID order by Transdate desc) as Rn,
count(1)over(Partition by DriverID) as cnt
from Fleet_DriverCommision
)
-- ...then select statement...
select d.Id,d.DriverNo,d.DriverName,TransId=dc.Id,
dc.FromDate,dc.ToDate,dc.IsPaid,
Active=(case when (dc.weekoff is null or dc.weekoff=0) then 'Active' else 'Off' end),
Rent=(case when (IsNull(dc.CommissionTotal,0))> IsNull(dc.AccJobsTotal,0) then IsNull(dc.CommissionTotal,0)-(IsNull(dc.AccJobsTotal,0)) else 0 end),
BalanceDue=IsNull(dc.OldBalance,0),
AgentCommission=IsNull(dc.AgentFeesTotal,0),
PDA= (case when (dc.weekoff is null or dc.weekoff=0) then (IsNull(dc.PDARent,0)+IsNull(dc.CollectionDeliveryCharges,0)) else 0 end),
Total=(case when (IsNull(dc.CommissionTotal,0))> IsNull(dc.AccJobsTotal,0) then IsNull(dc.CommissionTotal,0)-(IsNull(dc.AccJobsTotal,0)) else 0 end)
+((IsNull(dc.OldBalance,0))
+((IsNull(dc.AgentFeesTotal,0)))
+(case when (dc.weekoff is null or dc.weekoff=0) then (IsNull(dc.PDARent,0)+IsNull(dc.CollectionDeliveryCharges,0)) else 0 end))
from Fleet_Driver d
inner join Fleet_DriverCommision dc
on d.Id=dc.DriverId
--...join in cte as a normal table
left join cte
on --join criteria here
where dc.Id in (select Max(Id) from Fleet_DriverCommision
group by DriverId) as T1
--move the remainder of the logic into your query
Select AgentFeesTotal,DriverId
from cte
Where (Rn = 2 and cnt > 1) or (Rn = 1 and cnt = 1)

Add a WHERE clause in a complex SQL query

I want to pass a ShowRoomId value to the query below. The Employees table has a ShowRoomId column.
How can I do it?
My SQL query is as following:
SELECT *
FROM Employees A
OUTER APPLY (SELECT TOP 1 *
FROM EmployeeBasics B
WHERE (A.EmployeeID = B.EmployeeID)
ORDER BY B.BasicUpdateDate DESC) AS B
OUTER APPLY (
SELECT C.EmployeeId , count(*) AS TotalAbsent
FROM EmployeeAbsents C
WHERE C.AbsentDate BETWEEN '2016-05-01' AND '2016-05-30' AND A.EmployeeID = C.EmployeeID
GROUP BY C.EmployeeId
) AS C
OUTER APPLY (
SELECT EmployeeId,
SUM(CASE WHEN TransctionTypeId = 1 THEN Amount ELSE 0 END) AS Payment,
SUM(CASE WHEN TransctionTypeId = 2 THEN Amount ELSE 0 END) AS RecoverSalary,
SUM(CASE WHEN TransctionTypeId = 3 THEN Amount ELSE 0 END) AS RecoverCash
FROM dbo.EmployeeAdvances D
WHERE A.EmployeeID = D.EmployeeID
GROUP BY EmployeeId
) AS D
Simply use a WHERE clause at the end as following:
... YOUR SELECT ...
WHERE Col = ...YourCondition...
OR
Use WITH keyword to keep your current SELECT-statement in a cte. Then do your query on it.
WITH cte AS
(
... YOUR SELECT ...
)
SELECT *
FROM cte
WHERE Col = ...YourCondition...
OR
You can add your SELECT-statement in to parentheses and name it with an allias name. So you can do query on it too.
SELECT *
FROM
(
... YOUR SELECT ...
) t
WHERE t.Col = ...YourCondition...
As per Giorgi Nakeuri's advice, I added the WHERE clause at the end of the statement.
And it works for me. Revised code is here:
SELECT *
FROM Employees A
OUTER APPLY (SELECT TOP 1 *
FROM EmployeeBasics B
WHERE (A.EmployeeID = B.EmployeeID)
ORDER BY B.BasicUpdateDate DESC) AS B
OUTER APPLY (
SELECT C.EmployeeId , count(*) AS TotalAbsent
FROM EmployeeAbsents C
WHERE C.AbsentDate BETWEEN '2016-05-01' AND '2016-05-30' AND A.EmployeeID = C.EmployeeID
GROUP BY C.EmployeeId
) AS C
OUTER APPLY (
SELECT EmployeeId,
SUM(CASE WHEN TransctionTypeId = 1 THEN Amount ELSE 0 END) AS Payment,
SUM(CASE WHEN TransctionTypeId = 2 THEN Amount ELSE 0 END) AS RecoverSalary,
SUM(CASE WHEN TransctionTypeId = 3 THEN Amount ELSE 0 END) AS RecoverCash
FROM dbo.EmployeeAdvances D
WHERE A.EmployeeID = D.EmployeeID
GROUP BY EmployeeId
) AS D
WHERE A.ShowRoomId = 2

CASE Statement with a JOIN and GROUP BY

I'm trying to select the winners of a race from an event by the specific competition they entered, for example.
The competition table
competition_ID, eventss_ID, competitor_ID, stageName, roundNo, startTime, finisheTime, judges_ID
The eventss table
eventss_ID, eventsName, noOfStages, eventsDate, entryFee, venue_ID, judges_ID
The results I want are;
Event RoundNo competitior_ID Competiton Winner
swimming 1 COM101 1st Place
swimming 1 COM213 2nd Place
swimming 2 COM101 1st Place
swimming 2 COM234 2nd Place
golf 1 COM654 1st Place
golf 1 COM874 2nd Place
Query I tried:
SELECT *
,CASE
WHEN finshTime = (SELECT MIN(finshTime) FROM competition) THEN '1st Place'
WHEN finshTime = (SELECT MAX(finshTime) FROM competition) THEN '2nd Place'
ELSE 'Draw'
END [Competition Winner]
FROM competition
JOIN eventss on eventss.eventss_ID = competition.eventss_ID
GROUP BY competition.roundNo
This would probably work to produce the desired output but it's hard to tell without proper sample data.
; WITH CTE AS (
SELECT *
, ROW_NUMBER() OVER (PARTITION BY eventss_ID, roundNo ORDER BY finishTime) RN
FROM competition)
, CTE2 AS (
SELECT *
, CASE (SELECT finishTime FROM CTE WHERE RN = 1 AND eventss_ID = c.eventss_ID AND roundNo = c.roundNO) - (SELECT finishTime FROM CTE WHERE RN = 2 AND eventss_ID = c.eventss_ID AND roundNo = c.roundNO)
WHEN 0 THEN 'Draw'
ELSE CASE RN
WHEN 1 THEN '1st Place'
ELSE '2nd Place' END END Drawn
FROM CTE c
WHERE RN IN (1, 2))
SELECT e.eventsName [Event], CTE2.RoundNo, CTE2.competitor_ID, CTE2.Drawn [Competition Winner]
FROM eventss e
JOIN CTE2 ON CTE2.eventss_ID = e.eventss_ID
ORDER BY e.eventsName, CTE2.roundNo, CTE2.Drawn
Note: I'm making the assumption that "finishtime" is stored as TIME or DATETIME. If it's stored as something else, this won't work.
EDIT: In the case of more than two people tied for first place, or in the case of a tie for second place, this query should work...
; WITH CTE AS (
SELECT *
, ROW_NUMBER() OVER (PARTITION BY eventss_ID, roundNo ORDER BY finishTime) RN
FROM competition)
, tiesforfirst AS (
SELECT *
, '1st Place' Drawn
FROM CTE T
WHERE finishTime = (SELECT finishTime FROM CTE WHERE RN = 1 AND eventss_ID = T.eventss_ID AND roundNo = T.roundNo))
, tiesforsecond AS (
SELECT *
, '2nd Place' Drawn
FROM CTE T
WHERE finishTime = (SELECT finishTime FROM CTE WHERE RN = 2 AND eventss_ID = T.eventss_ID AND roundNo = T.roundNo)
AND (SELECT COUNT(*) FROM tiesforfirst WHERE eventss_ID = T.eventss_ID AND roundNo = T.roundNo) = 1)
SELECT e.eventsName [Event], tf.RoundNo, tf.competitor_ID
, CASE (SELECT COUNT(*) FROM tiesforfirst WHERE eventss_ID = tf.eventss_ID AND roundNo = tf.roundNo) WHEN 1 THEN tf.Drawn ELSE 'Drawn 1st' END [Competition Winner]
FROM eventss e
JOIN tiesforfirst tf ON tf.eventss_ID = e.eventss_ID
UNION
SELECT e.eventsName [Event], tf.RoundNo, tf.competitor_ID
, CASE (SELECT COUNT(*) FROM tiesforsecond WHERE eventss_ID = tf.eventss_ID AND roundNo = tf.roundNo) WHEN 1 THEN tf.Drawn ELSE 'Drawn 2nd' END [Competition Winner]
FROM eventss e
JOIN tiesforsecond tf ON tf.eventss_ID = e.eventss_ID
ORDER BY e.eventsName, roundNo

Find combination which does not have 'AC' status

Consider the data below which is stored in temp table. How to get all those ID / State / Group combination which does not have Status record having 'AC' value ?
Data Image
The result should yield
ID State Group
2 FL LI
3 FL VA
5 FL LI
There are several query patterns that will achieve that result.
An example of one of those patterns, using an GROUP BY operation, with aggregation of a condition (to exclude the groupings where there's a row in the grouping that has a status value of 'AC')
SELECT t.id
, t.state
, t.group
FROM mytable t
GROUP
BY t.id
, t.state
, t.group
HAVING MAX(CASE WHEN t.status='AC' THEN 1 ELSE 0 END) = 0
An example of another pattern, using an anti-join:
SELECT t.id
, t.state
, t.group
FROM mytable t
LEFT
JOIN mytable s
ON s.id = t.id
AND s.state = t.state
AND s.group = t.group
AND s.status = 'AC'
WHERE s.id IS NULL
GROUP
BY t.id
, t.state
, t.group
Select *
FROM TableName t
WHERE NOT EXISTS (SELECT 1
FROM TableName
WHERE [State] = t.[State]
AND [Group] = t.[Group]
AND [Status] = 'AC')

Inefficient SQL query with joined tables

I have a table with 1700 rows of data. I'm querying it using the query below which pulls complimentary data from related tables too. It's currently running very slowly (around 10 seconds).
How can I improve the efficiency of this query?
SELECT [jobID] ,
(SELECT orgname
FROM pm_clients c
WHERE c.orgID IN
(SELECT orgid
FROM pm_jobs j
WHERE j.jobid=t.jobid
AND j.jobStatus>=13)) AS orgname ,
(SELECT sector
FROM pm_clients c
WHERE c.orgID IN
(SELECT orgid
FROM pm_jobs j
WHERE j.jobid=t.jobid
AND j.jobStatus>=13)) AS sector ,
(SELECT region= CASE country
WHEN 1 THEN region
ELSE
(SELECT countryname
FROM AT_A_CountryCodes x
WHERE x.id= l.country)
END
FROM PM_ClientDetails l
WHERE l.userid =
(SELECT userid
FROM pm_jobs j
WHERE j.jobid=t.jobid)) AS region ,
(SELECT postcode
FROM PM_ClientDetails l
WHERE l.userid =
(SELECT userid
FROM pm_jobs j
WHERE j.jobid=t.jobid)) AS postcode ,
(SELECT firstname
FROM users u
WHERE u.userid =
(SELECT pmid
FROM pm_jobs j
WHERE j.jobid=t.jobid)) AS PM ,
[creationDate] ,
(SELECT statusName
FROM pm_jobstatus j
WHERE j.[statusID]=t.jobStatus) AS JobStatus ,
[completionDate] ,
[deadline],
[jobTitle] ,
(SELECT currencysymbol
FROM at_a_currency c
WHERE c.currencyID =
(SELECT top(1) quoteCurrency
FROM PM_Quotes q
WHERE q.taskid IN
(SELECT taskid
FROM pm_tasks x
WHERE x.jobID=t.jobid))) AS currency ,
(SELECT sum(quoteSubTotal)
FROM PM_Quotes q
WHERE q.taskid IN
(SELECT taskid
FROM pm_tasks x
WHERE x.jobID=t.jobid)) AS subtotal ,
(SELECT sum(quoteVAT)
FROM PM_Quotes q
WHERE q.taskid IN
(SELECT taskid
FROM pm_tasks x
WHERE x.jobID=t.jobid)) AS VAT ,
(SELECT sum(quoteTotal)
FROM PM_Quotes q
WHERE q.taskid IN
(SELECT taskid
FROM pm_tasks x
WHERE x.jobID=t.jobid)) AS total ,
(SELECT [purchaseOrder]
FROM pm_jobs j
WHERE j.jobid=t.jobid) AS purchaseOrder ,
(SELECT [clientReference]
FROM pm_jobs j
WHERE j.jobid=t.jobid) AS clientReference ,
(SELECT CASE
WHEN [deadline]='1900-01-01 00:00:00' THEN 1
WHEN [completiondate]>dateadd(dd,1,[deadline]) THEN 0
WHEN [completiondate]<=dateadd(dd,1,[deadline])THEN 1
WHEN [completiondate] IS NULL THEN 0
END) AS completedOnTime
FROM [PM_jobs] t
WHERE jobStatus>=13
Edit
Thanks to #GuidoG for the response. Here's the amended query which is much faster now.
SELECT j.jobid,
c.orgname,
c.sector,
(SELECT region= CASE country
WHEN 1 THEN region
ELSE (SELECT countryname
FROM at_a_countrycodes x
WHERE x.id = l.country)
END) AS region,
l.postcode,
(SELECT firstname
FROM users u
WHERE u.userid = J.pmid) AS PM,
j.creationdate,
(SELECT statusname
FROM pm_jobstatus x
WHERE x.[statusid] = j.jobstatus) AS JobStatus,
j.[completiondate],
j.[deadline],
j.[jobtitle],
j.purchaseorder,
j.clientreference,
(SELECT currencysymbol
FROM at_a_currency c
WHERE c.currencyid = l.clientcurrency) AS currency,
Sum(q.quotesubtotal) AS subtotal,
Sum(q.quotevat) AS VAT,
Sum(q.quotetotal) AS total,
(SELECT CASE
WHEN j.[deadline] = '1900-01-01 00:00:00' THEN 1
WHEN j.[completiondate] > Dateadd(dd, 1, j.[deadline]) THEN 0
WHEN j.[completiondate] <= Dateadd(dd, 1, j.[deadline])THEN 1
WHEN j.[completiondate] IS NULL THEN 0
END) AS completedOnTime,
Count(t.taskid) AS taskcount
FROM [pm_jobs] j
INNER JOIN pm_clients c
ON j.orgid = c.orgid
INNER JOIN pm_clientdetails l
ON j.userid = l.userid
INNER JOIN pm_tasks t
ON j.jobid = t.jobid
INNER JOIN pm_quotes q
ON q.taskid = t.taskid
AND t.jobid = j.jobid
WHERE jobstatus >= 13
GROUP BY j.jobid,
c.orgname,
c.sector,
l.country,
l.region,
l.postcode,
l.firstname,
j.creationdate,
j.jobstatus,
j.completiondate,
j.deadline,
j.jobtitle,
j.purchaseorder,
j.clientreference,
l.clientcurrency,
J.pmid
ORDER BY completiondate DESC
You should consider joining instead of subquerying. Here is a small example to get you on your way:
SELECT t.jobID ,
c.orgName,
c.sector
FROM [SQL2012_921487_atlas].[dbo].[PM_jobs] t
inner join pm_clients c on t.orgID = c.orgID
WHERE jobStatus>=13
When subquerying like you did, you force SQL Server to read table pm_clients several times; joining enables it to read pm_clients only 1 time.

Resources