How to improve SQL query performance (correlated subqueries)? - sql

I Would like to write the below query in a better & efficient way any help?
SELECT a.assetnum as Asset,
a.assettag as Asset_Tag,
a.manufacturer as Manufacturer,
a.serialnum as Serial,
a.description as Description,
(
SELECT CASE a.isrunning
WHEN 1
THEN 'Operational'
WHEN 0
THEN 'Down'
END
) AS Condition ,
l.kbs_loctag as Location,
(
SELECT TOP 1 wo.wonum
FROM workorder wo
WHERE wo.assetnum = a.assetnum
and wo.worktype = 'UN'
ORDER BY wo.reportdate DESC
) AS Last_Workorder,
(
SELECT wo.statusdate
FROM workorder wo
WHERE wo.wonum IN
(
SELECT top 1 wo.wonum
FROM workorder wo
WHERE wo.assetnum = a.assetnum
AND wo.worktype = 'UN'
ORDER BY wo.reportdate DESC
)
) AS Last_Status_Date,
(
SELECT top 1 lt.memo
FROM labtrans lt
WHERE lt.assetnum = a.assetnum
AND lt.transtype = 'REPAIR'
ORDER BY lt.transdate DESC
) AS Action
FROM asset a
LEFT OUTER JOIN locations l
ON a.location = l.location
WHERE (
a.description like '%WASH%'
or a.description LIKE '%DRYER%'
)
ORDER BY l.location,
a.description

In most cases I prefer to use APPLY operator instead of correlated subquery.
In your case I would suggest the next solution:
SELECT a.assetnum as Asset,
a.assettag as Asset_Tag,
a.manufacturer as Manufacturer,
a.serialnum as Serial,
a.description as Description,
CASE a.isrunning
WHEN 1 THEN 'Operational'
WHEN 0 THEN 'Down'
END AS Condition,
l.kbs_loctag as Location,
wo.wonum AS Last_Workorder,
wo.statusdate AS Last_Status_Date,
lt.memo AS Action
FROM asset a
LEFT OUTER JOIN locations l ON a.location = l.location
OUTER APPLY (
SELECT TOP 1 wonum, statusdate
FROM workorder
WHERE assetnum = a.assetnum
and worktype = 'UN'
ORDER BY reportdate DESC) AS wo
OUTER APPLY (
SELECT top 1 memo
FROM labtrans
WHERE assetnum = a.assetnum
AND transtype = 'REPAIR'
ORDER BY transdate DESC) AS lt
WHERE (
a.description like '%WASH%'
or a.description LIKE '%DRYER%'
)
ORDER BY l.location, a.[description]
BTW - you can find amazing video lesson (from Itzik Ben-Gan)about using APPLY Operator here.

Related

Adding a Helper SQL Index

I have the following View which seems to work quickly enough but when I look at the Execution Plan, it shows the Top N Sort in the second query taking ~90% due to it being repeated for every row in the first query.
Should I be adding an Index to the Loan table to help the ORDER BY clause?
CREATE VIEW [dbo].[ResourceItemStatus] AS
SELECT
i.ID AS ItemID,
i.ResourceID,
i.DateAdded,
i.LocationID,
i.OwnerID,
i.Barcode,
i.MissingReasonID,
i.DateRemoved,
ll.PatronID,
ll.ID AS LoanID,
ll.IssueDateTime,
ll.DueDate,
ll.ReturnDateTime,
ll.LoanTypeID,
ll.RenewalCount,
ll.DeleteSummary,
ll.ReturnStatusID,
ll.FineID,
(SELECT COUNT(*) FROM Loan WHERE Loan.ItemID = i.ID) AS LoanCount,
(SELECT COUNT(*) FROM Item WHERE Item.DateRemoved IS NULL AND Item.ResourceID = i.ResourceID) AS AvailableItemCount
FROM Item i
OUTER APPLY
(
SELECT TOP 1
l.ID,
l.ItemID,
l.PatronID,
l.IssueDateTime,
l.DueDate,
l.ReturnDateTime,
l.LoanTypeID,
l.RenewalCount,
l.DeleteSummary,
l.ReturnStatusID,
l.FineID
FROM Loan l
WHERE l.ItemID = i.ID
ORDER BY l.IssueDateTime DESC, l.ID DESC
) AS ll
Try Windowed Aggregates instead of Scalar Subqueries/Outer Apply:
SELECT
i.ID AS ItemID,
i.ResourceID,
i.DateAdded,
i.LocationID,
i.OwnerID,
i.Barcode,
i.MissingReasonID,
i.DateRemoved,
ll.PatronID,
ll.ID AS LoanID,
ll.IssueDateTime,
ll.DueDate,
ll.ReturnDateTime,
ll.LoanTypeID,
ll.RenewalCount,
ll.DeleteSummary,
ll.ReturnStatusID,
ll.FineID,
coalesce(ll.LoanCount, 0)
COUNT(case when Item.DateRemoved IS NULL then 1 end)
over (partition by ResourceID) AS AvailableItemCount
FROM Item i
LEFT JOIN
(
SELECT
l.ID,
l.ItemID,
l.PatronID,
l.IssueDateTime,
l.DueDate,
l.ReturnDateTime,
l.LoanTypeID,
l.RenewalCount,
l.DeleteSummary,
l.ReturnStatusID,
l.FineID,
COUNT(*) over (partition by ItemId) AS LoanCount,
row_number()
over (partition by ItemId
order by l.IssueDateTime DESC, l.ID DESC) as rn
FROM Loan l
) as ll
on ll.ItemID = i.ID
and ll.rn = 1

Use of Analytical Funtions and Keep clause

I have a lenghtly query that can be shorten with the correct functionally (I beleive). Can we use existing functions such as Max Min Keep to make this query more efficient? My entire query is posted below.
For example: Can we remove the CTEs and use analytical functions such as max and min This would also elimate ranks and several joins
SQL:
WITH LAST_VALUE_BEFORE_START_DT AS (
SELECT DISTINCT * FROM(
SELECT
P.CL_ID,
HISTORYID,
H.MENT_DT,
H.ROLE AS MAX_ROLE,
H.PM_ID AS MAX_P_ID,
DENSE_RANK() OVER (PARTITION BY P.CL_ID ORDER BY H.MENT_DT DESC )AS RNK
FROM MANAGER_HISTORY H
INNER JOIN CP CCP ON H.CLIID = CCP.CLIID
INNER JOIN PROGRAM CP ON PROGRAMID = CP.PROGRAMID
WHERE 1=1
AND CP.TYPEID IN (13,200,11001)
AND H.ROLE = 'RED'
AND H.MENT_DT < START_DT
--AND P.CL_ID = 920917
)LAST_VALUE_BEFORE_START_DT_RNK
WHERE 1=1
AND RNK =1
)
,MIN_VALUE_BETWEEN_PROGRAM AS (
SELECT * FROM(
SELECT DISTINCT
P.CL_ID,
HISTORYID,
TRUNC(H.MENT_DT) AS MENT_DT,
H.ROLE AS MIN_ROLE,
H.PM_ID AS MIN_PM_ID,
DENSE_RANK() OVER (PARTITION BY P.CL_ID ORDER BY H.MENT_DT)AS RNK
FROM MANAGER_HISTORY H
INNER JOIN CP CCP ON H.CLIID = CCP.CLIID
INNER JOIN PROGRAM CP ON PROGRAMID = CP.PROGRAMID
WHERE 1=1
AND CP.TYPEID IN (13,200,11001)
AND H.ROLE = 'RED'
AND H.PM_ID IS NOT NULL
AND TRUNC(H.MENT_DT) BETWEEN TRUNC(START_DT) AND NVL(END_DT,SYSDATE)
--AND P.CL_ID = 920917
) MIN_VALUE_BETWEEN_PROGRAM_RNK
WHERE 1=1
AND RNK =1
)
SELECT * FROM (
SELECT
X.*,
DENSE_RANK() OVER (PARTITION BY CL_ID ORDER BY FIRST_ASSGN_DT,MENT_DT ) AS RNK
FROM(
SELECT DISTINCT
C.CL_ID,
P.CL_ID,
CP.PROGRAM,
START_DT,
END_DT,
H.ROLE,
H.MENT_DT,
H.PM_ID,
LVBS.MAX_ROLE,
LVBS.MAX_P_ID,
MVBP.MIN_ROLE,
MVBP.MIN_PM_ID
,CASE
WHEN H.MENT_DT < START_DT AND LVBS.MAX_ROLE = 'RED' AND LVBS.MAX_P_ID IS NOT NULL THEN TRUNC(START_DT)
WHEN H.MENT_DT BETWEEN START_DT AND NVL(END_DT,SYSDATE) AND H.ROLE = 'RED' AND H.PM_ID IS NOT NULL
THEN MVBP.MENT_DT
ELSE NULL --TESTING PURPOSES
END FIRST_ASSGN_DT
FROM MANAGER_HISTORY H
INNER JOIN CP CCP ON H.CLIID = CCP.CLIID
INNER JOIN CLIENT C ON CCP.CLIID = C.CLIID
INNER JOIN PROGRAM CP ON PROGRAMID = CP.PROGRAMID
LEFT JOIN LAST_VALUE_BEFORE_START_DT LVBS ON P.CL_ID = LVBS.CL_ID
LEFT JOIN MIN_VALUE_BETWEEN_PROGRAM MVBP ON P.CL_ID = MVBP.CL_ID
WHERE 1=1
AND CP.TYPEID IN (13,200,11001)
)X)Z
WHERE 1=1
AND Z.RNK = 1

Easy Left Join SQL Syntax

New to SQL and want to complete a LEFT JOIN.
I have two seperate tables with the below code:
SELECT
StockCode, SalesOrder, SUM(OrderQty)
FROM
dbo.IopSalesPerf
WHERE
dbo.IopSalesPerf.CustRequestDate BETWEEN '2017-07-01' AND '2017-07-31'
AND EntrySystemTime = 1
AND Warehouse = '01'
AND StockCode = '001013'
GROUP BY
StockCode,SalesOrder
ORDER BY
StockCode ASC
SELECT
SalesOrder, SUM(NetSalesValue), SUM(QtyInvoiced)
FROM
ArTrnDetail
GROUP BY
SalesOrder
I would like to LEFT JOIN the last table onto the first using SalesOrder as the joining column. Can anyone assist with the syntax?
Simpliest way would be:
SELECT * FROM
(
SELECT StockCode,SalesOrder,sum(OrderQty)
FROM dbo.IopSalesPerf
WHERE dbo.IopSalesPerf.CustRequestDate between '2017-07-01' and '2017-07-31'
and EntrySystemTime = 1 and Warehouse = '01' and StockCode = '001013'
GROUP BY StockCode,SalesOrder
Order BY StockCode ASc
) AS A
LEFT JOIN
(
SELECT SalesOrder,sum(NetSalesValue),sum(QtyInvoiced)
FROM ArTrnDetail
Group by SalesOrder
) AS B
ON A.SalesOrder = B.SalesOrder

Get Distinct results of all columns based on MAX DATE of one

Using SQL Server 2012
I have seen a few threads about this topic but I can't find one that involves multiple joins in the query. I can't create a VIEW on this database so the joins are needed.
The Query
SELECT
p.Price
,s.Type
,s.Symbol
, MAX(d.Date) Maxed
FROM AdventDW.dbo.FactPrices p
INNER JOIN dbo.DimSecurityMaster s
ON s.SecurityID = p.SecurityID
INNER JOIN dbo.DimDateTime d
ON
p.DateTimeKey = d.DateTimeKey
GROUP BY p.Price ,
s.Type ,
s.Symbol
ORDER BY s.Symbol
The query works but does not produce distinct results. I am using Order by to validate the results, but it is not required once I get it working. I The result set looks like this.
Price Type Symbol Maxed
10.57 bfus *bbkd 3/31/1989
10.77 bfus *bbkd 2/28/1990
100.74049 cbus 001397AA6 8/2/2005
100.8161 cbus 001397AA6 7/21/2005
The result set I want is
Price Type Symbol Maxed
10.77 bfus *bbkd 2/28/1990
100.74049 cbus 001397AA6 8/2/2005
Here were a few other StackOverflow threads I tried but couldn't get t work with my specific query
How can I SELECT rows with MAX(Column value), DISTINCT by another column in SQL?
SQL Selecting distinct rows from multiple columns based on max value in one column
If you want data for the maximum date, use row_number() rather than group by:
SELECT ts.*
FROM (SELECT p.Price, s.Type, s.Symbol, d.Date,
ROW_NUMBER() OVER (PARTITION BY s.Type, s.Symbol
ORDER BY d.Date DESC
) as seqnum
FROM AdventDW.dbo.FactPrices p INNER JOIN
dbo.DimSecurityMaster s
ON s.SecurityID = p.SecurityID INNER JOIN
dbo.DimDateTime d
ON p.DateTimeKey = d.DateTimeKey
) ts
WHERE seqnum = 1
ORDER BY s.Symbol;
You should use a derived table since you really only want to group the DateTimeKey table to get the MAX date.
SELECT p.Price ,
s.Type ,
s.Symbol ,
tmp.MaxDate
FROM AdventDW.dbo.FactPrices p
INNER JOIN dbo.DimSecurityMaster s ON s.SecurityID = p.SecurityID
INNER JOIN
( SELECT MAX(d.Date) AS MaxDate ,
d.DateTimeKey
FROM dbo.DimDateTime d
GROUP BY d.DateTimeKey ) tmp ON p.DateTimeKey = tmp.DateTimeKey
ORDER BY s.Symbol;
/*
this is your initial select which is fine because this is base from your original criteria,
I cannot ignore this so i'll keep this in-tact. Instead from here i'll create a temp
*/
SELECT
p.Price
, s.Type
, s.Symbol
, MAX(d.Date) Maxed
INTO #tmpT
FROM AdventDW.dbo.FactPrices p
INNER JOIN dbo.DimSecurityMaster s
ON s.SecurityID = p.SecurityID
INNER JOIN dbo.DimDateTime d
ON p.DateTimeKey = d.DateTimeKey
GROUP BY p.Price ,
s.Type ,
s.Symbol
ORDER BY s.Symbol
SELECT innerTable.Price, innerTable.Symbol, innerTable.Type, innerTable.Maxed
FROM (
SELECT
ROW_NUMBER () OVER (PARTITION BY t1.Symbol, t1.Type, t1.Maxed ORDER BY t1.Maxed DESC) as row
, *
FROM #tmpT AS t1
) AS innerTable
WHERE row = 1
DROP TABLE #tmpT

Returning rows selected in with statement

I have a sql statement looking something like this
WITH [ResultPage] AS (
SELECT sub.id as id, ROW_NUMBER() OVER (ORDER BY [TAG].[Name] ASC, [TAG].[ID] ASC) As [RowID], [sub].[hasPending]
FROM
(SELECT t.[ID], p.[TagID],(CASE WHEN p.[TagID] is not null THEN 1 ELSE 0 END) AS [hasPending]
FROM [Tag] AS t
full outer Join [TagPending] AS p On t.[ID] = p.[TagID]
GROUP BY t.[ID],p.[TagID]
) AS [sub]
inner Join [Tag] On [Tag].[ID] = sub.[ID]
)
SELECT [ResultPage].[RowID], [Tag].[ID] As [Tag^ID], [Tag].[Name] As [Tag^Name]
FROM [ResultPage]
INNER JOIN [Tag] ON ([Tag].[ID] = [ResultPage].[TagID])
INNER JOIN [TagPending] ON ([TagPending].[TagID] = [KbResultPage].[TagID])
WHERE (([ResultPage].[RowID] BETWEEN 1 AND 50)
)
ORDER BY [ResultPage].[RowID] ASC
As can be seen, the select max returns 50 rows at a time, however I need to know the total number of rows found in the WITH statement. I could run it again with just a count, however it could be nice if there were a way to get both in one go.
You can use the COUNT(*) OVER() in the WITH statement
WITH [ResultPage] AS (
SELECT COUNT(*) OVER() AS totalRows,
sub.id as id, ROW_NUMBER() OVER (ORDER BY [TAG].[Name] ASC, [TAG].[ID] ASC) As [RowID], [sub].[hasPending]
FROM
(SELECT t.[ID], p.[TagID],(CASE WHEN p.[TagID] is not null THEN 1 ELSE 0 END) AS [hasPending]
FROM [Tag] AS t
full outer Join [TagPending] AS p On t.[ID] = p.[TagID]
GROUP BY t.[ID],p.[TagID]
) AS [sub]
inner Join [Tag] On [Tag].[ID] = sub.[ID]
)
SELECT
[ResultPage].totalRows,
[ResultPage].[RowID], [Tag].[ID] As [Tag^ID], [Tag].[Name] As [Tag^Name]
FROM [ResultPage]
INNER JOIN [Tag] ON ([Tag].[ID] = [ResultPage].[TagID])
INNER JOIN [TagPending] ON ([TagPending].[TagID] = [KbResultPage].[TagID])
WHERE ([ResultPage].[RowID] BETWEEN 1 AND 50)
ORDER BY [ResultPage].[RowID] ASC