Replace ISNULL function in WHERE clause - sql

I was asked to rewrite the below queries by eliminating the ISNULL Function in where clause due to performance issues. How can I rewrite the below queries by replacing the ISNULL Function in WHERE clause and in JOIN condition. Below are the sample queries.
Select *
from child c
inner join Parent P on c.id=p.id
where ISNULL(p.ParentId, 0) <> 0
Select *
from child c
inner join Parent P on isnull(c.Parentid,0)=p.id
select *
from parent
where isnull(Status, '') != 'Active'
Select *
from child c
inner join Parent P on c.id=p.id
where CAST(P.PostedDate AS DATE)
BETWEEN CAST(isnull(#FromDate,P.PostedDate) AS DATE)
AND CAST(#ToDate AS DATE)
SELECT *
FROM TEMP
WHERE EDATE=ISNULL(#EDATE,EDATE)

In most cases you can simply remove the isnull function, and it need not be replaced by anything. For example:
select *
from parent
where isnull(Status, '') != 'Active'
Can be rewritten as
select *
from parent
where Status != 'Active'
Since if Status is null, the comparison still returns FALSE. The rule is:
NULL = value: NULL (interpreted as FALSE, since it is not TRUE).
If NULL has to be included in your results, you can add Attribute/#Variable IS NULL.

Be carefull with this
Look at this example :
declare #table table(ID int, status varchar(50))
insert into #Table (ID, status)
values (1, 'active'), (2, ''), (3, null), (4, 'active')
select t.*
from #Table t
where t.status <> 'active'
This will return just one record (ID: 2)
while this
select t.*
from #Table t
where isnull(t.status, '') <> 'active'
will return 2 records (ID: 2 and 3)
So it depends on what you want in your result set, do you want records where the field Status is null also returned or not ?
In case you dont want them then the answer is simple
select t.*
from #Table t
where t.status <> 'active'
Otherwise you need something like this
Since the original query had IsNull in it, I suspect this is what you want.
select t.*
from #Table t
where (t.status is null or t.status <> 'active')
This does not use the IsNull() but I am not sure if much performance will be gained here.

Select * from child c inner join Parent P on c.id=p.id where ISNULL(p.ParentId, 0) <> 0
Here, neither you need ISNULL nor any alternative of this because in this case
NULL <> 0 will be FALSE similarly 0 <> 0 also FALSE
NULL = NULL will be FALSE if the setting SET ANSI_NULLS ON else TRUE
And here, the alternative of this is,
If you want to return NULL as TRUE then you can use
WHERE (p.ParentId IS NULL OR p.ParentId <> 0)
Otherwise,
(p.ParentId <> 0 OR 0 <> 0) --But it doesn't have any meaning and it's exactly same to simply use p.ParentId <> 0
You can handle ISNULL accordingly in your all the questions.

Please try like this.
Select * from child c inner join Parent P
on c.id=p.id OR where p.ParentId IS NULL
Select * from child c inner join Parent P on c.Parentid=p.id OR c.ParentId IS NULL
select * from parent
where [Status] != 'Active' AND [Status] IS NOT NULL
Select * from child c inner join Parent P on c.id=p.id
where
( (P.PostedDate) BETWEEN CAST(#FromDate) AS DATE) AND #FromDate IS NOT NULL AND CAST(#ToDate AS DATE) )
OR
( (P.PostedDate) BETWEEN P.PostedDate AND CAST(#ToDate AS DATE) )
SELECT * FROM TEMP
WHERE EDATE = CASE WHEN #EDATE IS NULL THEN EDATE ELSE #EDATE END

Substitute as below to make SARGABLE:
where ISNULL(p.ParentId, 0) <> 0 --- where p.ParentId = 0 or p.ParentID is null
Select * from child c inner join Parent P on isnull(c.Parentid,0)=p.id --- Select * from child c inner join Parent P on c.Parentid = p.id OR (c.Parentid is null and p.id = 0)
where isnull(Status, '') != 'Active' --- where Status <> 'Active'
where CAST(P.PostedDate AS DATE) BETWEEN CAST(isnull(#FromDate,P.PostedDate) AS DATE) AND CAST(#ToDate AS DATE) --- where P.PostedDate BETWEEN isnull(#FromDate,P.PostedDate) AND #ToDate
WHERE EDATE=ISNULL(#EDATE,EDATE) --- WHERE #EDATE IS NULL OR EDATE = #EDATE

When values can be null you have to use the Three-value logic.
Read this article.
So if you want to make your arguments SARGABLE avoiding to use ISNULL or COALESCE functions, you have to combine OR/AND/NOT/CASE operators, depending on logic of your query
In your examples:
Select *
from child c
inner join Parent P on c.id=p.id
-- where ISNULL(p.ParentId, 0) <> 0
where p.ParentId <> 0 -- when p.ParentId is null condition is evaluated as unknown and record are excluded.
Select *
from child c
-- inner join Parent P on isnull(c.Parentid,0)=p.id
inner join Parent P on (c.Parentid=p.id) OR (c.Parentid IS NULL AND p.id = 0)
select *
from parent
--where isnull(Status, '') != 'Active'
where Status IS NULL OR Status <> 'Active'
-- IN THIS CASE YOU HAVE NO BENEFIT BECAUSE CONVERSIONS MAKES YOUR ARGUMENTS NOT SARGABLE!!!
Select *
from child c
inner join Parent P on c.id=p.id
where CAST(P.PostedDate AS DATE)
BETWEEN CAST(CASE WHEN #FromDate IS NULL THEN P.PostedDate ELSE #FromDate END) AS DATE)
AND CAST(#ToDate AS DATE)
SELECT *
FROM TEMP
--WHERE EDATE=ISNULL(#EDATE,EDATE)
WHERE EDATE=#EDATE

Related

Stored Procedure Case statement override each other

This is the modified version of my previous post. please help me with this.
In case sub query i am getting correct result. but when i run full sp Alert case and Field case are overriding each other.
Getting null value in status field, can anyone explain what I am going wrong?
(CASE
WHEN EXISTS(SELECT [GunSerialNo] FROM [dbo].[ArmouryIssueGun]
WHERE aig.ModifiedOn IS NOT NULL
AND aig.CreatedOn IS NOT NULL)
THEN 'In Armory'
WHEN EXISTS(SELECT aig.GunSerialNo
FROM [dbo].[ArmouryIssueGun] AS aig
INNER JOIN (SELECT *
FROM
(SELECT GunSerialNo, DATEADD(HOUR, EstimatedTime, CreatedOn) AS TIME_ADDED
FROM [ArmouryIssueGun]) ag
WHERE ag.TIME_ADDED<GETUTCDATE()) abd ON abd.GunSerialNo = aig.GunSerialNo
WHERE aig.ModifiedOn IS NULL
AND aig.CreatedBy IS NOT NULL)
THEN 'Alert'
WHEN EXISTS (SELECT aig.GunSerialNo
FROM [dbo].[ArmouryIssueGun] AS aig
INNER JOIN (SELECT *
FROM
(SELECT GunSerialNo, DATEADD(HOUR, EstimatedTime, CreatedOn) AS TIME_ADDED
FROM [ArmouryIssueGun]) ag
WHERE ag.TIME_ADDED>GETUTCDATE()) abd ON abd.GunSerialNo = aig.GunSerialNo
WHERE aig.ModifiedOn IS NULL
AND aig.CreatedBy IS NOT NULL)
THEN 'In Field'
END) AS [Status],
FROM
[dbo].[CarryAndUseLicence] cl
INNER JOIN
[dbo].[Branch] b ON b.[BranchId] = cl.[BranchId]
INNER JOIN
[dbo].[Gun] gun ON cl.[GunSerialNo] = gun.[SerialNo]
INNER JOIN
[dbo].[ArmouryIssueGun] aig ON aig.[StaffId] = cl.[StaffId]
The problem is the double use of the alias aig. The alias in the Exists-query overrides the alias from the top query. As a result of this, the Exists-query does not include the current record but looks at the entire table.
How It works:
WHEN EXISTS(SELECT aig2.GunSerialNo
FROM [dbo].[ArmouryIssueGun] AS aig2
INNER JOIN (SELECT *
FROM
(SELECT GunSerialNo, DATEADD(HOUR, EstimatedTime, CreatedOn) AS TIME_ADDED
FROM [ArmouryIssueGun]) ag
WHERE ag.TIME_ADDED<GETUTCDATE()) abd ON abd.GunSerialNo = aig.GunSerialNo
WHERE aig2.ModifiedOn IS NULL
AND aig2.CreatedBy IS NOT NULL)
THEN 'Alert'
WHEN EXISTS (SELECT aig2.GunSerialNo
FROM [dbo].[ArmouryIssueGun] AS aig2
INNER JOIN (SELECT *
FROM
(SELECT GunSerialNo, DATEADD(HOUR, EstimatedTime, CreatedOn) AS TIME_ADDED
FROM [ArmouryIssueGun]) ag
WHERE ag.TIME_ADDED>GETUTCDATE()) abd ON abd.GunSerialNo = aig.GunSerialNo
WHERE aig2.ModifiedOn IS NULL
AND aig2.CreatedBy IS NOT NULL)
THEN 'In Field'
or shorter
WHEN EXISTS(SELECT *
FROM [ArmouryIssueGun]
WHERE GunSerialNo = aig.GunSerialNo
AND DATEADD(HOUR, EstimatedTime, CreatedOn) < GETUTCDATE()
AND ModifiedOn IS NULL
AND CreatedBy IS NOT NULL
)
THEN 'Alert'
WHEN EXISTS(SELECT *
FROM [ArmouryIssueGun]
WHERE GunSerialNo = aig.GunSerialNo
AND DATEADD(HOUR, EstimatedTime, CreatedOn) > GETUTCDATE()
AND ModifiedOn IS NULL
AND CreatedBy IS NOT NULL
)
THEN 'In Field'

not able to select a column outside left join

I am working with the below query
SELECT * FROM
(SELECT DISTINCT
a.Number
,a.Description
,ISNULL(temp.Quantity,0) Quantity
,LastReceived
,LastIssued
FROM Article a
LEFT JOIN (
select ss.ArticleId
, ss.Quantity
, max(lastreceiveddate) as LastReceived
, max(lastissueddate) as LastIssued
from StockSummary ss
where ss.UnitId = 8
group by ss.ArticleId, ss.StockQuantity
having (MAX(ss.LastReceivedDate) < '2014-09-01' or MAX(ss.LastReceivedDate) is NULL)
AND (MAX(ss.LastIssuedDate) < '2014-09-01' or MAX(ss.LastIssuedDate) is NULL)
) temp on a.Id = temp.ArticleId
WHERE a.UnitId = 8
) main
ORDER BY main.Number
What i want to achieve is to select the articles only with the MAX(ss.LastReceivedDate) and MAX(ss.LastIssuedDate) condition in the Left join query and then do the Quantity Select in the main query.
Note: the quantity column can be 0 or NULL.
Kindly help

How can I optimize the SQL query?

I have a query an SQL query as follows, can anybody suggest any optimization for this; I think most of the effort is being done for the Union operation - is there anything else can be done to get the same result ?
Basically I wanna query first portion of the UNION and if for each record there is no result then the second portion need to be run. Please help.
:
SET dateformat dmy;
WITH incidentcategory
AS (
SELECT 1 ord, i.IncidentId, rl.Description Category FROM incident i
JOIN IncidentLikelihood l ON i.IncidentId = l.IncidentId
JOIN IncidentSeverity s ON i.IncidentId = s.IncidentId
JOIN LikelihoodSeverity ls ON l.LikelihoodId = ls.LikelihoodId AND s.SeverityId = ls.SeverityId
JOIN RiskLevel rl ON ls.RiskLevelId = rl.riskLevelId
UNION
SELECT 2 ord, i.incidentid,
rl.description Category
FROM incident i
JOIN incidentreportlikelihood l
ON i.incidentid = l.incidentid
JOIN incidentreportseverity s
ON i.incidentid = s.incidentid
JOIN likelihoodseverity ls
ON l.likelihoodid = ls.likelihoodid
AND s.severityid = ls.severityid
JOIN risklevel rl
ON ls.risklevelid = rl.risklevelid
) ,
ic AS (
SELECT ROW_NUMBER() OVER (PARTITION BY i.IncidentId ORDER BY (CASE WHEN incidentTime IS NULL THEN GETDATE() ELSE incidentTime END) DESC,ord ASC) rn,
i.incidentid,
dbo.Incidentdescription(i.incidentid, '',
'',
'', '')
IncidentDescription,
dbo.Dateconverttimezonecompanyid(closedtime,
i.companyid)
ClosedTime,
incidenttime,
incidentno,
Isnull(c.category, '')
Category,
opencorrectiveactions,
reportcompleted,
Isnull(classificationcompleted, 0)
ClassificationCompleted,
Cast (( CASE
WHEN closedtime IS NULL THEN 0
ELSE 1
END ) AS BIT)
IncidentClosed,
Cast (( CASE
WHEN investigatorfinishedtime IS NULL THEN 0
ELSE 1
END ) AS BIT)
InvestigationFinished,
Cast (( CASE
WHEN investigationcompletetime IS NULL THEN 0
ELSE 1
END ) AS BIT)
InvestigationComplete,
Cast (( CASE
WHEN investigatorassignedtime IS NULL THEN 0
ELSE 1
END ) AS BIT)
InvestigatorAssigned,
Cast (( CASE
WHEN (SELECT Count(*)
FROM incidentinvestigator
WHERE incidentid = i.incidentid
AND personid = 1588
AND tablename = 'AdminLevels') = 0
THEN 0
ELSE 1
END ) AS BIT)
IncidentInvestigator,
(SELECT dbo.Strconcat(osname)
FROM (SELECT TOP 10 osname
FROM incidentlocation l
JOIN organisationstructure o
ON l.locationid = o.osid
WHERE incidentid = i.incidentid
ORDER BY l.locorder) loc)
Location,
Isnull((SELECT TOP 1 teamleader
FROM incidentinvestigator
WHERE personid = 1588
AND tablename = 'AdminLevels'
AND incidentid = i.incidentid), 0)
TeamLeader,
incidentstatus,
incidentstatussearch
FROM incident i
LEFT OUTER JOIN incidentcategory c
ON i.incidentid = c.incidentid
WHERE i.isdeleted = 0
AND i.companyid = 158
AND incidentno <> 0
--AND reportcompleted = 1
--AND investigatorassignedtime IS NOT NULL
--AND investigatorfinishedtime IS NULL
--AND closedtime IS NULL
),
ic2 AS (
SELECT * FROM ic WHERE rn=1
)
SELECT * FROM ic2
--WHERE rownumber >= 0
-- AND rownumber < 0 + 10
--WHERE ic2.incidentid in(53327,53538)
--WHERE ic2.incidentid = 53338
ORDER BY incidentid DESC
Following is the execution plan I got:
https://www.dropbox.com/s/50dcpelr1ag4blp/Execution_Plan.sqlplan?dl=0
There are several issues:
1) use UNION ALL instead of UNION ALL to avoid the additional operation to aggregate the data.
2) try to modify the numerous function calls (e.g. dbo.Incidentdescription() ) to be an in-lie table valued function so you can reference it using CROSS APPLY or OUTER APPLY. Especially, if those functions referencing a table again.
3) move the subqueries from the SELECT part of the query to the FROM part using CROSS APPLY or OUTER APPLY again.
4) after the above is done, check the execution plan again for any missing indexes. Also, run the query with STATISTICS TIME, IO on to verify that the number of times a table
is referenced is correct (sometimes the execution plan put you in the wrong direction, especially if function calls are involved)...
Since the first inner query produces rows with ord=1 and the second produces rows with ord=2, you should use UNION ALL instead of UNION. UNION will filter out equal rows and since you will never get equal rows it is more efficient to use UNION ALL.
Also, rewrite your query to not use the WITH construct. I've had very bad experiences with this. Just use regular derived tables instead. In the case the query is still abnormally slow, try to serialize some derived tables to a temporary table and query the temporary table instead.
Try alternate approach by removing
(SELECT dbo.Strconcat(osname)
FROM (SELECT TOP 10 osname
FROM incidentlocation l
JOIN organisationstructure o
ON l.locationid = o.osid
WHERE incidentid = i.incidentid
ORDER BY l.locorder) loc)
Location,
Isnull((SELECT TOP 1 teamleader
FROM incidentinvestigator
WHERE personid = 1588
AND tablename = 'AdminLevels'
AND incidentid = i.incidentid), 0)
TeamLeader
from the SELECT. Avoid using complex functions/sub-queries in select.

Using a CTE within an UPDATE statement that is using a CASE

I have the following code:
WITH CTE_List AS
(
SELECT B.Chain_Code, B.State
FROM Company.dbo.Company_Master AS A
LEFT JOIN Company.dbo.StateChain_List AS B
ON A.State = B.State
AND A.Chain_Code = B.Chain_Code
)
UPDATE Company.dbo.Company_Master
SET MFN_Ind = (
CASE
WHEN (CTE_List.Chain_Code IS NULL) AND (CTE_List.State IS NULL) THEN 'N'
ELSE 'Y'
END
)
FROM CTE_List
The select statement in CTE_List returns values like this, but corresponding to every record in the Company_Master table:
Chain_Code | State
00992 | IL
NULL | NULL
NULL | NULL
00732 | MA
NULL | NULL
My ultimate goal is to update the MFN_Ind column in Company.dbo.Company_Master based off this CTE_List. If Chain_Code and State are populated, then MFN_Ind = 'Y'. If Chain_Code and State are NULL, then MFN_Ind = 'N'.
As it's set up now, the query updates the MFN_Ind column with everything set as 'Y' so clearly the the first portion of the CASE is not catching the NULL fields. Any tips on how I can fix this?
Thank you!
I think you're making this far more complex than it needs to be.
UPDATE m
SET m.MFN_Ind = CASE WHEN l.State IS NULL THEN 'N' ELSE 'Y' END
FROM Company.dbo.Company_Master AS m
LEFT OUTER JOIN Company.dbo.StateChain_List AS l
ON m.State = l.State
AND m.Chain_Code = l.Chain_Code;
Or maybe better to just split it up into 2 - update all the rows to N, then update the matching rows to Y:
UPDATE Company.dbo.Company_Master SET m.MFN_Ind = 'N';
UPDATE m SET m.MFN_Ind = 'Y'
FROM Company.dbo.Company_Master AS m
WHERE EXISTS (SELECT 1 FROM Company.dbo.StateChain_List AS l
WHERE m.State = l.State
AND m.Chain_Code = l.Chain_Code);
WITH CTE_List AS
(
SELECT B.Chain_Code, B.State
FROM Company.dbo.Company_Master AS A
LEFT OUTER JOIN Company.dbo.StateChain_List AS B
ON A.State = B.State
AND A.Chain_Code = B.Chain_Code
)
---UPDATE THE RECORD
UPDATE m
SET MFN_Ind = (
CASE WHEN (CTE.Chain_Code IS NULL) AND (CTE.State IS NULL) THEN 'N'
ELSE 'Y'
END
)
FROM CTE_List cte
INNER JOIN Company.dbo.Company_Master m
on m.State =cte.state

Selecting Parent Rows With Multiple Conditions

I had most of this query down until a new condition arose and it has confounded me. Given the following simplified table schema:
Parent Table:
ID
FName
LName
Child Table:
[Index]
ParentID
Active_Flag
ExpirationDate
What I want to do is get Parent rows for which:
There are no children.
There are children whose Active_Flag is 1 but whose expiration dates are blank or NULL.
There are indeed children but none have the Active_Flag set to 1.
The following query came up with my first two criteria:
SELECT p.ID, p.LNAME, p.FNAME,
CASE
WHEN COUNT(ct.indx) = 0 THEN 'None'
WHEN ct.ExpirationDate is NULL or ct.ExpirationDate = '' THEN 'No expiration date'
END AS Issue
FROM ParentTable AS p
LEFT JOIN ChildTable ct
ON p.ID = ct.ParentID
GROUP BY p.ID, p.LNAME, p.FNAME, ct.[INDEX], ct.ExpirationDate
HAVING (COUNT(ct.[INDEX]) = 0) OR (ct.ExpirationDate IS NULL OR ct.ExpirationDate = '')
ORDER BY p.LNAME
I don't know how to account for #3. Any help is appreciated. Thanks in advance.
You can also do this in the HAVING clause:
SELECT p.ID, p.LNAME, p.FNAME,
(CASE WHEN COUNT(ct.indx) = 0 THEN 'None'
WHEN ct.ExpirationDate is NULL or ct.ExpirationDate = '' THEN 'No expiration date'
WHEN sum(case when ActiveFlag = 1 then 1 else 0 end) = 0 then 'No active children'
END) AS Issue
FROM ParentTable p LEFT JOIN
ChildTable c
ON p.ID = ct.ParentID
GROUP BY p.ID, p.LNAME, p.FNAME
HAVING (COUNT(ct.[INDEX]) = 0) OR
(ct.ExpirationDate IS NULL OR ct.ExpirationDate = '') or
sum(case when ActiveFlag = 1 then 1 else 0 end) = 0
ORDER BY p.LNAME
The DISTINCT in the SELECT is redundant. You do not need it with an aggregation.
You can simplify the having to "sum(ActiveFlag)" if the activeFlag is indeed an integer. If not, then it should be "= '1'" rather than "= 1'.
you can use unions for this query.....
I am not sure about this query ....but it will help you in solving your prolem
select p.id,p.lname,p.fname from parents where (p.id not in (select pid from children))
union
select p.id,p.lname,p.fname from parents p,children c inner join on c.pid=p.id where c.active_flag=1 and c.expiration_date is null or c.expiration_date=''
union
select p.id,p.lname,p.fname from parents p inner join on c.pid=p.id where c.active_flag<>1;
SELECT *
FROM parenttable pt
-- condition 1: There should be no parents without children at all
WHERE NOT EXISTS (
SELECT * FROM childtable c1
WHERE c1.parent_id = pt.id
)
-- condition 2: There should be no children with a flag but without a date
OR EXISTS (
SELECT * FROM childtable c2
WHERE c2.parent_id = pt.id
AND c2.active_flag = 1 AND c2.expire_date IS NULL
)
-- condition 3: There should at least be a child with the active_flag
OR NOT EXISTS (
SELECT * FROM childtable c3
WHERE c3.parent_id = pt.id
AND c2.active_flag = 1
)
;
-- Active flags for children c1 and c2
-- c1 c2 (X= child doesn't exists)
-----+-----+---+
-- X X rule1+rule3 # no children at all
-- 0 X rule3 # only one child
-- 1 X # idem
-- 0 0 rule 3 # two children
-- 0 1
-- 1 0
-- 1 1
--
-- , which means that rule3 implies rule1, and rule1 is redundant
-------------------------
SELECT *
FROM parenttable pt
-- condition 1+3: There should at least be a child with the active_flag
WHERE NOT EXISTS (
SELECT * FROM childtable c1
WHERE c1.parent_id = pt.id
AND c1.active_flag = 1
)
-- condition 2: There should be no children with a flag but without a date
OR EXISTS (
SELECT * FROM childtable c2
WHERE c2.parent_id = pt.id
AND c2.active_flag = 1 AND c2.expire_date IS NULL
)
;