How to make my MS SQL Server Query run faster? - sql-server

I have this kind of query in my ms sql.
SELECT
OdeaMainDataBase.dbo.ItemChoicesTable.*,
CASE
WHEN EXISTS(SELECT * FROM StudentTestTablefromRemark
WHERE StudID = #studId AND ExamID = #examId
AND ItemBankID = #itemBankId
AND ChoiceID = OdeaMainDataBase.dbo.ItemChoicesTable.ChoiceID)
THEN 'True'
ELSE 'False'
END AS isAnswered
FROM
OdeaMainDataBase.dbo.ItemChoicesTable
WHERE
ItemBankID = #itemBankId
I observe that the CASE statement I made causes my query to run slow. When I remove that CASE, it run fast but I need that case for my output.
Here is the structure I want to display
Item Choice Table
ItemBankID ChoiceID ChoiceLetter
--------------------------------------
1 1 A
1 2 B
1 3 C
2 4 A
2 5 B
2 6 C
Student Answers Table
ItemBankID ChoiceID ChoiceLetter StudentId
-------------------------------------------------
1 2 B 123
2 6 C 123
Desired Output
ItemBankID ChoiceID ChoiceLetter IsAnswered
----------------------------------------------------
1 1 A False
1 2 B True
1 3 C False
2 4 A False
2 5 B False
2 6 C True
I want to achieve that output without slowing my program.
Any idea will do to me.
Thank you so much

Try this
SELECT i.*,
CASE
WHEN s.ChoiceID IS NULL THEN 'False'
ELSE 'True'
END
FROM OdeaMainDataBase.dbo.ItemChoicesTable i
LEFT OUTER JOIN StudentTestTablefromRemark s
ON s.ChoiceID = i.ChoiceID
AND s.ItemBankID = i.ItemBankID
AND s.StudID = #studId
AND s.ExamID = #examId
WHERE i.ItemBankID = #itemBankId

Try this query
SELECT OdeaMainDataBase.dbo.ItemChoicesTable.*,
ISNULL(a,'False') as isAnswered
FROM OdeaMainDataBase.dbo.ItemChoicesTable
left outer join(Select 'True' as a,ChoiceID FROM StudentTestTablefromRemark
Where StudID=#studId AND ExamID=#examId AND ItemBankID=#itemBankId )b on b.ChoiceID=OdeaMainDataBase.dbo.ItemChoicesTable.ChoiceID
Where ItemBankID = #itemBankId

Related

Calculating precentage of a column with bool using two tables

Could somebody please help me create a view in SQL server to get a percentage in a new column. For example I have two tables as below.
Table 1---> Subject | Table 2---->Exam
|
SubjectID SubName | ExamID SubjectID Result (bool)
1 Science | 1 1 1
2 Maths | 2 1 1
3 English | 3 1 0
4 History | 4 2 0
5 Art | 5 2 1
6 Geography | 6 3 0
| 7 4 1
|
As you can see, many subjects do not have exams hence result will be null in a joined view. I want to show the pass percentage of a subject. For example, in result column, 1 = pass, 0 = Fail. I want the result to look like below showing null fields as well.
SubjectID SubName PassPercentage
1 Science 66.66
2 Maths 50
3 English 0
4 History 100
5 Art null
6 Geography null
Here:
SELECT
Subject.SubjectId,
Subject.SubName,
(AVG(CONVERT(decimal,Exam.Result))*100) AS PassPercentage
FROM Subject
LEFT JOIN Exam on Subject.SubjectId = Exam.SubjectId
GROUP BY Subject.SubjectId, Subject.SubName
You can round percentage result (2 or without decimals) and add % sign if you want.
Use this query:
Select *,
(Select Avg(Cast(Result as decimal)) From Exam Where SubjectID=S.SubjectID)*100 as PassPercentage
From Subject as S
Result is:
SubjectID SubName PassPercentage
----------- --------------- ---------------------------------------
1 Science 66.666600
2 Maths 50.000000
3 English 0.000000
4 History 100.000000
5 Art NULL
6 Geography NULL
(6 row(s) affected)
Subquery will be executed for each row of subject table.
do:
select s.SubjectID, s.SubName,
case COUNT(e.Result)
when 0 then null
else SUM(CAST(e.Result AS INT)) * 100 / (COUNT(s.SubjectID))
end PassPrec
from Subject s
left join Exam e on s.SubjectID = e.SubjectID
group by s.SubjectID, s.SubName
there case is to get the null. nulls don't get counted in "count" function.
you can use code like this
;with cte1 as (
select subjectid, sum(result) ResultCount
from exam group by subjectid
), cte2 as (
select e.subjectid, c.ResultCount, count(e.examid) TotalExams from cte1 c
left join exam e on e.subjectid = c.subjectid
group by e.subjectid, c.ResultCount
) select s.subname, convert(decimal(10,2), (c.ResultCount/convert(decimal(10,2),c.TotalExams)) *100) as Percentage from subject s left join cte2 c
on s.subjectid = c.subjectid

Performance issue with CTE SQL Server query

We have a table with a parent child relationship, that represents a deep tree structure.
We are using a view with a CTE to query the data but the performance is poor (see code and execution plan below).
Is there any way we can improve the performance?
WITH cte (ParentJobTypeId, Id) AS
(
SELECT
Id, Id
FROM
dbo.JobTypes
UNION ALL
SELECT
e.Id, cte.Id
FROM
cte
INNER JOIN
dbo.JobTypes AS e ON e.ParentJobTypeId = cte.ParentJobTypeId
)
SELECT
ISNULL(Id, 0) AS ParentJobTypeId,
ISNULL(ParentJobTypeId, 0) AS Id
FROM
cte
A quick example of using the range keys. As I mentioned before, hierarchies were 127K points and some sections where 15 levels deep
The cte Builds, let's assume the hier results will be will be stored in a table (indexed as well)
Declare #Table table(ID int,ParentID int,[Status] varchar(50))
Insert #Table values
(1,101,'Pending'),
(2,101,'Complete'),
(3,101,'Complete'),
(4,102,'Complete'),
(101,null,null),
(102,null,null)
;With cteOH (ID,ParentID,Lvl,Seq)
as (
Select ID,ParentID,Lvl=1,cast(Format(ID,'000000') + '/' as varchar(500)) from #Table where ParentID is null
Union All
Select h.ID,h.ParentID,cteOH.Lvl+1,Seq=cast(cteOH.Seq + Format(h.ID,'000000') + '/' as varchar(500)) From #Table h INNER JOIN cteOH ON h.ParentID = cteOH.ID
),
cteR1 as (Select ID,Seq,R1=Row_Number() over (Order by Seq) From cteOH),
cteR2 as (Select A.ID,R2 = max(B.R1) From cteOH A Join cteR1 B on (B.Seq Like A.Seq+'%') Group By A.ID)
Select B.R1
,C.R2
,A.Lvl
,A.ID
,A.ParentID
Into #TempHier
From cteOH A
Join cteR1 B on (A.ID=B.ID)
Join cteR2 C on (A.ID=C.ID)
Select * from #TempHier
Select H.R1
,H.R2
,H.Lvl
,H.ID
,H.ParentID
,Total = count(*)
,Complete = sum(case when D.Status = 'Complete' then 1 else 0 end)
,Pending = sum(case when D.Status = 'Pending' then 1 else 0 end)
,PctCmpl = format(sum(case when D.Status = 'Complete' then 1.0 else 0.0 end)/count(*),'##0.00%')
From #TempHier H
Join (Select _R1=B.R1,A.* From #Table A Join #TempHier B on A.ID=B.ID) D on D._R1 between H.R1 and H.R2
Group By H.R1
,H.R2
,H.Lvl
,H.ID
,H.ParentID
Order By 1
Returns the hier in a #Temp table for now. Notice the R1 and R2, I call these the range keys. Data (without recursion) can be selected and aggregated via these keys
R1 R2 Lvl ID ParentID
1 4 1 101 NULL
2 2 2 1 101
3 3 2 2 101
4 4 2 3 101
5 6 1 102 NULL
6 6 2 4 102
VERY SIMPLE EXAMPLE: Illustrates the rolling the data up the hier.
R1 R2 Lvl ID ParentID Total Complete Pending PctCmpl
1 4 1 101 NULL 4 2 1 50.00%
2 2 2 1 101 1 0 1 0.00%
3 3 2 2 101 1 1 0 100.00%
4 4 2 3 101 1 1 0 100.00%
5 6 1 102 NULL 2 1 0 50.00%
6 6 2 4 102 1 1 0 100.00%
The real beauty of the the range keys, is if you know an ID, you know where it exists (all descendants and ancestors).

Order by Specific id first then By rest

Consider a Sample Table with two Column RoleId and User Name
Role | Name
1 AB
3 A
1 ABC
2 D
2 B
3 Abb
1 E
4 TE
How can i use SQL queries to get following Output.
Role | Name
3 A
3 Abb
1 AB
1 ABC
1 E
2 B
2 D
4 TE
I just want to Order by Role Id 3 first then by remaining Roleid.
Currently i am using Union to achieve so //
SELECT * FROM (SELECT * From #temp
Where roleid=3
UNION ALL
SELECT * From #temp
Where roleid != 3
) as X
You can use case to make more complex ordering:
select *
from #temp
order by case when Role = 3 then 0 else 1 end, Role, Name
select *
from #temp
order by CASE WHEN Role = 3 THEN 0 ELSE Role END, Name
I usually use NULLIF, but case might be faster?
SELECT *
FROM #temp
ORDER BY NULLIF(Role,3), Name

TSQL Selection Criteria For Join - Left Join

Select *
From cacheAttendanceMeasures cam
Left Join dmcUserSelectedAttendanceMeasures usam on usam.attMeasureID = cam.attMeasureID
And usam.personID = #personID
And usam.pageID = #pageID
I understand that the above query will return all rows from cacheAttendanceMeasures as well as any rows from dmcUserSelectedAttendanceMeasures and dmcUserSelectedStudentMonths where the conditions match. What I would really like it to do is this:
If the left join(s) don't match, then return all rows from cacheAttendanceMeasures -- so what it is currently doing
However, if the left join(s) DO match, then return ONLY the rows that match from cacheAttendanceMeasures
Is this possible?
EDIT:
I simplified the query above to only include one Left Join table, I don't want to over-complicate the issue.
Below are dataset examples I'd like to see returned based on if there are no matches between the tables versus there is a match:
cam Table
cID attMeasureID Value
1 1 530
2 2 95.7
3 3 380
4 4 742.57
5 5 200
usam Table
uID personID pageID attMeasureID
1 877450 31 1
2 923450 28 2
3 877450 31 3
4 369842 28 4
5 212193 25 1
Dataset to Return if #personID = 577597 & #pageID = 20:
CID attMeasureID Value uID pageID attMeasureID
1 1 530 null nul null
2 2 95.7 null null null
3 3 380 null null null
4 4 742.57 null null null
5 5 200 null null null
Dataset to Return if #personID = 877450 & #pageID = 31:
CID attMeasureID Value uID personID pageID attMeasureID
1 1 530 1 877450 31 1
3 3 380 3 877450 31 3
I'm not really sure what results you want...
At first I thought maybe something like this..
SELECT *
FROM cacheAttendanceMeasures cam
LEFT JOIN dmcUserSelectedAttendanceMeasures usam
on usam.attMeasureID = cam.attMeasureID
And usam.personID = #personID
And usam.pageID = #pageID
LEFT JOIN dmcUserSelectedStudentMonths ussm
on ussm.monthSeq = cam.pupilMonth
And ussm.personID = USAM.PersonID
And ussm.pageID = USAM.PageId
--This where clause would seem silly as it negates the left joins; making them inner joins and violates #1 as to what you're after. so I don't think that's what you're after...
WHERE ussm.personID is not null
and usam.personID is not null
So with rule 2 are you saying... if a single record isn't null, then only return records which exist in both Left joined tables and the 1st table?
So given:
T1 T2 T3
X X X
Y NULL Y
Z Y NULL
T NULL NULL
I think you would want just record X
But Given
So given:
T1 T2 T3
X X NULL
Y NULL Y
Z Y NULL
T NULL NULL
I think you would want X, Y Z, and T... need better clairification.

SELECT CASE vs. CASE IN SQL

I do not quite understand why those two different codesamples return a different value.
somehow incorrect but working syntax, returns false results, e.g it returns 0 when the comparison is done over two equal values:
(SELECT CASE
WHEN
SUM(V.IsCompatible) OVER
(PARTITION BY ComputerName, UserID) = ApplicationCount
THEN 1 ELSE 0 END
) AS CompatibleUser
The one below returns the correct values, ie. 1 when there are two equal values compared.
(CASE
WHEN
SUM(V.IsCompatible) OVER
(PARTITION BY ComputerName, UserID) = ApplicationCount
THEN 1 ELSE 0 END
) AS CompatibleUser
or even simpler:
(SELECT CASE
WHEN
X = Y
THEN 1 ELSE 0 END
) AS Result
X = 22 AND Y = 22 => Result = 0
(CASE
WHEN
X = Y
THEN 1 ELSE 0 END
) AS Result
X = 22 AND Y = 22 => Result = 1
I understand applying the correct syntax is important, and I am aware of the SELECT CASE syntax in T-SQL, but I do not understand how the first code sample is evaluated and delivering an unexpected result.
update: full query in it's context
select userapplication.username,
computerdetails.computername,
sum(userapplication.iscompatible)
over (partition by computerdetails.computername,
userapplication.userid) as compatiblecount,
userapplication.applicationcount,
( case
when sum(userapplication.iscompatible)
over (partition by
computerdetails.computername,
userapplication.userid) <> userapplication.applicationcount
then 0
else 1
end
) as usercomputeriscompatible
from computerdetails
right outer join usercomputer
on computerdetails.computerid = usercomputer.computerid
right outer join userapplication
on usercomputer.gebruikerid = userapplication.userid
so userComputerIsCompatible is the result in question here
I think the reason for this behavior is the next one: the expressions like (SELECT ...) are considered to be sub-queries even they don't have FROM clause. Is assume the source of data for these (false) "sub-queries" is only the current row. So, (SELECT expression) is interpreted as (SELECT expression FROM current_row) and (SELECT SUM(iscompatible)OVER(...)) is executed as (SELECT SUM(iscompatible)OVER(current_row)).
Argument: analyzing execution plan for (SELECT SUM(IsWeb) OVER(PARTITION BY OrderDate) [FROM current_row]) expression
I see a Constant Scan (Scan an internal table of constants) operator instead of Clustered Index Scan before Segment and Stream Aggregate ([Expr1007] = Scalar Operator(SUM(#OrderHeader.[IsWeb] as [h].[IsWeb]))) operators. This internal table (Constant Scan) is constructed from current row.
Example (tested with SQL2005SP3 and SQL2008):
DECLARE #OrderHeader TABLE
(
OrderHeaderID INT IDENTITY PRIMARY KEY
,OrderDate DATETIME NOT NULL
,IsWeb TINYINT NOT NULL --or BIT
);
INSERT #OrderHeader
SELECT '20110101', 0
UNION ALL
SELECT '20110101', 1
UNION ALL
SELECT '20110101', 1
UNION ALL
SELECT '20110102', 1
UNION ALL
SELECT '20110103', 0
UNION ALL
SELECT '20110103', 0;
SELECT *
,SUM(IsWeb) OVER(PARTITION BY OrderDate) SumExpression_1
FROM #OrderHeader h
ORDER BY h.OrderDate;
SELECT *
,(SELECT SUM(IsWeb) OVER(PARTITION BY OrderDate)) SumWithSubquery_2
FROM #OrderHeader h
ORDER BY h.OrderDate;
Results:
OrderHeaderID OrderDate IsWeb SumExpression_1
------------- ----------------------- ----- ---------------
1 2011-01-01 00:00:00.000 0 2
2 2011-01-01 00:00:00.000 1 2
3 2011-01-01 00:00:00.000 1 2
4 2011-01-02 00:00:00.000 1 1
5 2011-01-03 00:00:00.000 0 0
6 2011-01-03 00:00:00.000 0 0
OrderHeaderID OrderDate IsWeb SumWithSubquery_2
------------- ----------------------- ----- -----------------
1 2011-01-01 00:00:00.000 0 0
2 2011-01-01 00:00:00.000 1 1
3 2011-01-01 00:00:00.000 1 1
4 2011-01-02 00:00:00.000 1 1
5 2011-01-03 00:00:00.000 0 0
6 2011-01-03 00:00:00.000 0 0
I tried your code and I get the same results for both queries. Here's the code I tried:
DECLARE #X INT = 22
DECLARE #Y INT = 22
SELECT (SELECT CASE
WHEN
#X = #Y
THEN 1 ELSE 0 END
) AS Result
SELECT (CASE
WHEN
#X = #Y
THEN 1 ELSE 0 END
) AS Result
Result is 1 and 1
I ran this on SQL Server 2008 R2

Resources