Function in WHERE clause - dealing with poor performance - sql

I have a large table with over 100000 records.
I need to add one more condition in the where clause used to evalute the totalSum and return only records with the totalSum <>
0.
Ton of joins and temp tabes is used here and I did not intend to post it all.
Here is my function:
CREATE FUNCTION returnTotalSum(#clientID VARCHAR(20),#type INT,#currency VARCHAR(20),#date VARCHAR())
RETURNS INT
AS
BEGIN
DECLARE #totalSum BIGINT;
SET #totalSum = ( SELECT SUM (CONVERT(DECIMAL(18,4),P.iznos*(1-P.dp)/2))
FROM pts as p
INNER JOIN tippart t on p.tip = #type
INNER JOIN its i on p.partija = #clientID
WHERE p.currency = #currency and pts.dknizenja < #date
GROUP BY p.partija )
RETURN #totalSum
END
I use it here:(last AND in the WHERE clause)
...
880001,
NULL,
NULL,
NULL,
NULL,
CONVERT(INT,REPLACE('2017.12.31','.',''))
FROM ITS I WITH(NOLOCK)
JOIN TIPPART R WITH(NOLOCK) ON I.TIP = R.TIP
LEFT JOIN #UNPVT_TIPSTOPE_TS T (NOLOCK) ON I.KAMGRUPA = T.GRUPA AND I.TIP = T.TIP AND I.VALUTA = T.SIFVAL
LEFT JOIN #UNPVT_TIPSTOPE_TS T1 (NOLOCK) ON I.KAMGRUPA = T1.GRUPA AND I.TIP = T1.TIP AND I.VALUTA = T1.SIFVAL AND T.GRUPA IS NULL
LEFT JOIN #TMP_DODATNA_KS DS (NOLOCK) ON I.PARTIJA = DS.PARTIJA AND I.VALUTA = DS.SIFVAL AND I.KAMGRUPA = DS.GRUPA
LEFT JOIN #NML_RATE N (NOLOCK) ON I.TIP = N.TIP AND N.SIFVAL = I.VALUTA AND N.GRUPA = I.KAMGRUPA
-- LEFT JOIN TIPSTOPE TS (NOLOCK) ON I.TIP = TS.TIP AND TS.GRUPA = I.KAMGRUPA AND TS.SIFVAL = I.VALUTA
LEFT JOIN #NML_RATE_PERIOD NML (NOLOCK) ON I.TIP = NML.TIP AND I.VALUTA = NML.SIFVAL AND NML.GRUPA = I.KAMGRUPA AND NML.SIFVAL = I.VALUTA
--WHERE NOT EXISTS (SELECT * FROM [dbo].[IC_INPT_AR_X_INT_RATE_SNPST] WHERE PARTIJA = I.PARTIJA AND VALUTA = I.VALUTA AND APP = 'ST')
WHERE I.DOTVARANJE <= '2017.12.31'
AND (T.TIP IS NOT NULL
OR T1.TIP IS NOT NULL
OR DS.PARTIJA IS NOT NULL)
AND dbo.returnTotalSum(i.partija,r.tip,t.sifval,i.dotvaranje) <> 0
I assume the problem with this is that it has to go through each record, compare, evaluate the condition.
Considering there is no index in the table(I can't add index as I have no privileges) it tends to run forever.
Is there anything I can do to improve the performance of this
function, do you have any suggestions on using something else beside
functions and what?

In your implementation calling the function as part of filtering in where is causing huge performance drain, possibly due to multiple index scans.
As as general guidance you should be able to reduce it if you fine achieving this without the function.
It will be difficult to give you accurate solution, without data structure and sample data.
Try the code below:
...
880001,
NULL,
NULL,
NULL,
NULL,
CONVERT(INT,REPLACE('2017.12.31','.',''))
FROM ITS I WITH(NOLOCK)
JOIN TIPPART R WITH(NOLOCK) ON I.TIP = R.TIP
LEFT JOIN #UNPVT_TIPSTOPE_TS T (NOLOCK) ON I.KAMGRUPA = T.GRUPA AND I.TIP = T.TIP AND I.VALUTA = T.SIFVAL
LEFT JOIN #UNPVT_TIPSTOPE_TS T1 (NOLOCK) ON I.KAMGRUPA = T1.GRUPA AND I.TIP = T1.TIP AND I.VALUTA = T1.SIFVAL AND T.GRUPA IS NULL
LEFT JOIN #TMP_DODATNA_KS DS (NOLOCK) ON I.PARTIJA = DS.PARTIJA AND I.VALUTA = DS.SIFVAL AND I.KAMGRUPA = DS.GRUPA
LEFT JOIN #NML_RATE N (NOLOCK) ON I.TIP = N.TIP AND N.SIFVAL = I.VALUTA AND N.GRUPA = I.KAMGRUPA
-- LEFT JOIN TIPSTOPE TS (NOLOCK) ON I.TIP = TS.TIP AND TS.GRUPA = I.KAMGRUPA AND TS.SIFVAL = I.VALUTA
LEFT JOIN #NML_RATE_PERIOD NML (NOLOCK) ON I.TIP = NML.TIP AND I.VALUTA = NML.SIFVAL AND NML.GRUPA = I.KAMGRUPA AND NML.SIFVAL = I.VALUTA
--WHERE NOT EXISTS (SELECT * FROM [dbo].[IC_INPT_AR_X_INT_RATE_SNPST] WHERE PARTIJA = I.PARTIJA AND VALUTA = I.VALUTA AND APP = 'ST')
LEFT OUTER JOIN -- Added this join with same logic from function rather than calling a function.
(
SELECT SUM (CONVERT(DECIMAL(18,4),P.iznos*(1-P.dp)/2)) TotalSum
FROM pts as p
INNER JOIN tippart t on p.tip = r.tip
INNER JOIN its i on p.partija = i.partija
WHERE p.currency = t.sifval and pts.dknizenja < i.dotvaranje
GROUP BY p.partija
) SumTable
WHERE I.DOTVARANJE <= '2017.12.31'
AND (T.TIP IS NOT NULL
OR T1.TIP IS NOT NULL
OR DS.PARTIJA IS NOT NULL)
AND SumTable.TotalSum <> 0 -- This is similar to your old logic where you were comparing with function output.
Query explanation:
I have added in SumTable which is haivng left outer join with your existing query logic.
As the additional left outer join has group by p.partija, it won't mess up with your result set.
all the inputs of your old function has been replaced with related values, as we are doing inline query here.
Lastly, the where part which was indicated as culprit for performance drain is improved and won't call function, instead will use SumTable.TotalSum and compare it with 0.

Your function converted to an inline table valued function should something close to this.
CREATE FUNCTION returnTotalSum
(
#clientID VARCHAR(20)
,#type INT
,#currency VARCHAR(20)
,#date VARCHAR(10) --Don't store dates as strings...
)
RETURNS TABLE AS RETURN
SELECT TotalSum = SUM(CONVERT(DECIMAL(18,4), P.iznos * (1 - P.dp) / 2))
FROM pts as p
INNER JOIN tippart t on p.tip = #type
INNER JOIN its i on p.partija = #clientID
WHERE p.currency = #currency
and pts.dknizenja < #date
GROUP BY p.partija

Related

Where Clause Using Conditional Statement

i have query below
SELECT #RoleUser = MR.Code FROM MasterRole MR INNER JOIN MasterUsersRole MUR ON MR.Id = MUR.RoleId
INNER JOIN MasterUsers MU ON Mu.UserCode = MUR.UserCode
WHERE MU.UserCode = #UserLoginID
select 1 Num
, MyHistory.ID
, MyHistory.RequestNumber
, MyHistory.FlowID
, MyHistory.FlowProcessStatusID
from
(
select *
from Requests R
inner join
(
--DECLARE #UserLoginID nvarchar(200) = 'dum.testing.3'
select distinct
RequestID
from dbo.RequestTrackingHistory RTH
where IIF(#UserLoginID = 'admin', #UserLoginID, RTH.CreatedBy) = #UserLoginID
OR ( CreatedBy IN
SELECT Mu.UserCode from MasterUsers MU
INNER JOIN MasterUsersRole MUR ON MU.UserCode = MUR.UserCode
INNER JOIN MasterRole MR ON MUR.RoleId = MR.Id
WHERE MR.Code = #RoleUser
)
)
) RT on R.ID = RT.RequestID
) as MyHistory
inner join MasterFlow F on MyHistory.FlowID = F.ID
inner join
(
select FP.ID
, FP.Name
, FP.AssignType
, FP.AssignTo
, FP.IsStart
, case FP.AssignType
when 'GROUP' then
G.Name
end as 'AssignToName'
from MasterFlowProcess FP
left join dbo.MasterRole G on FP.AssignTo = G.ID and FP.AssignType = 'GROUP'
) FP on MyHistory.FlowProcessID = FP.ID
inner join MasterFlowProcessStatus FPS on MyHistory.FlowProcessStatusID = FPS.ID
left join MasterFlowProcessStatusNext FPSN on FPS.ID = FPSN.ProcessStatusFlowID
left join MasterFlowProcess FPN on FPSN.NextProcessFlowID = FPN.ID
left JOIN MasterRole MR ON MR.Id = FPN.AssignTo
left join MasterUsersRole MUR on MR.Id = MUR.RoleId
left join MasterUsers MURO on MUR.UserCode = MURO.UserCode
inner join MasterUsers UC on MyHistory.CreatedBy = UC.UserCode
left join MasterUsers UU on MyHistory.UpdatedBy = UU.UserCode
LEFT JOIN RequestMT RMT ON MyHistory.ID = RMT.RequestID
LEFT JOIN RequestGT RGT ON MyHistory.ID = RGT.RequestID
left join (SELECT sum(QtyCU) countQty , RequestId from dbo.RequestGTDetail where IsActive = 1 group by RequestId) RGTD on RGTD.RequestId = RGT.RequestId
left join (SELECT sum(QtyPCS) countQty , RequestId from dbo.RequestMTDetail where IsActive = 1 group by RequestId) RMTD on RMTD.RequestId = RMT.RequestId
left join (SELECT COUNT(IIF(returnable = 0, returnable, null)) countReturnable , RequestId from dbo.RequestMTDetail group by RequestId) RMTR on RMTR.RequestId = RMT.RequestId
left JOIN dbo.MasterDistributor md ON md.Code = RGT.CustId or md.Code = RMT.CustId
left JOIN dbo.MasterUsersDistributor MUD ON MUD.UserCode = MURO.UserCode AND md.Code = MUD.DistributorCode
LEFT JOIN dbo.MasterReason MRMT ON RMT.ReasonId = MRMT.Id
LEFT JOIN dbo.MasterReason MRGT ON RGT.ReasonId = MRGT.Id
LEFT JOIN dbo.MasterDistributorGroup MDG ON MDG.Id = MD.GroupId
OUTER APPLY dbo.FnGetHistoryApproveDate(MyHistory.Id) AS x
where REPLACE(FPS.Name, '#Requestor', uc.Name) <> 'DRAFT'
AND MUD.DistributorCode IN (SELECT DistributorCode FROM dbo.MasterUsersDistributor WHERE UserCode = #UserLoginID)
i want to add some logic in where clause
this line
==> AND MUD.DistributorCode IN (SELECT DistributorCode FROM dbo.MasterUsersDistributor WHERE UserCode = #UserLoginID)
it depend on the #RoleUser variable, if #RoleUser IN ('A','B') then where clause above is executed, but if #RoleUser Not IN ('A','B') where clause not executed
i,m trying this where clause
AND IIF(#RoleUser IN ('A','B'), MUD.DistributorCode, #RoleUser) IN (SELECT DistributorCode FROM dbo.MasterUsersDistributor WHERE UserCode = IIF(#RoleUser IN ('A','B'), #UserLoginID, NULL))
it didn't work, only executed if #RoleUser IS ('A','B') other than that it return 0 record
any help or advice is really appreciated
thank you
The cleanest way I'm implemented these kind of crazy rules is a
holderTable
and a countVariable against the holder table.
I'll give a generic examples.
This is a "approach" and "philosophy", not a specific answer....with complex WHERE clauses.
DECLARE #customerCountryCount int
DECLARE #customerCountry TABLE ( CountryName varchar(15) )
if ( "the moon is blue on tuesday" ) /* << whatever rules you have */
BEGIN
INSERT INTO #customerCountry SELECT "Honduras" UNION ALL SELECT "Malaysia"
END
if ( "favorite color = green" ) /* << whatever rules you have */
BEGIN
INSERT INTO #customerCountry SELECT "Greenland" UNION ALL SELECT "Peru"
END
SELECT #customerCountryCount = COUNT(*) FROM #customerCountry
Select * from dbo.Customers c
WHERE
(#customerCountryCount = 0)
OR
( exists (select null from #customerCountry innerVariableTable where UPPER(innerVariableTable.CountryName) = UPPER(c.Country) ))
)
This way, instead of putting all the "twisted logic" in an overly complex WHERE statement..... you have "separation of concerns"...
Your inserts into #customerCountry are separated from your use of #customerCountry.
And you have the #customerCountryCount "trick" to distinguish when nothing was used.
You can add a #customerCountryNotExists table as well, and code it to where not exists.
As a side note, you may want to try using a #temp table (instead of a #variabletable (#customerCountry above)... and performance test these 2 options.
There is no "single answer". You have to "try it out".
And many many variables go into #temp table performance (from a sql-server SETUP, not "how you code a stored procedure". That is way outside the scope of this question.
Here is a SOF link to "safe" #temp table usage.
Temporary table in SQL server causing ' There is already an object named' error

using a subquery as a column that's using another column in the 'where' clause

I probably messed that title up! So I have a column called "programID" and I want another column to tell me the most recent date an order was placed using the programID. I think I'd need a subcolumn but the problem is how do I use the row's programID so that it matches the programID for the same row?
DECLARE #ClientIDs varchar(4) = 6653;
DECLARE #ProgramStatus char(1) = 'Y'; -- Y, N, or R
SELECT
rcp.RCPID AS ProgramID
, rcp.RCPName AS Program
, rcp.RCPActive AS ProgramStatus
, aa.AACustomCardInternalReview AS VCCP
, aa.AAJobNo AS AAJobNo
, aas.AASName AS AAStatus
, rcp.RCPOpsApproved AS OpsApproved
, clt.CltID AS ClientID
, rcp.RCPSignatureRequired AS SignatureRequired
, st.STEnumValue AS DefaultShipType
, rcp.RCPShipMethodOverrideType AS ShipTypeOverride
,aa.AANetworkProgramID
,(Select max(cdconfirmationdate) from carddet where ) --can't figure this part out
FROM
RetailerCardProgram rcp WITH (NOLOCK)
INNER JOIN ClientRetailerMap crm WITH (NOLOCK)
ON crm.CRMRetailerID = rcp.RCPRetailerID
INNER JOIN Client clt WITH(NOLOCK)
ON clt.CltID = crm.CRMCltID
LEFT JOIN AssociationApproval aa WITH (NOLOCK)
ON aa.AARetailerID = rcp.RCPRetailerID
AND aa.AABin = rcp.RCPBin6
AND aa.AAFrontOfPlasticTemplateID = rcp.RCPFOCTemplateID
AND aa.AABackOfPlasticTemplateID = rcp.RCPBOCTemplateID
AND ISNULL(aa.AACardID, 0) = ISNULL(rcp.RCPDefaultPlasticCardID, 0)
-- AND LOWER(rcp.RCPName) NOT LIKE '%do not use%' -- Needed for AA Job Number 1594
LEFT JOIN AssociationApprovalStatus aas WITH (NOLOCK)
ON aas.AASID = aa.AAAssociationApprovalStatusID
LEFT JOIN OpenLoopAssociation ola WITH (NOLOCK)
ON ola.OLAID=rcp.RCPOLAID
LEFT JOIN ClientCardProgramMap ccpm WITH (NOLOCK)
ON ccpm.CCPMCardProgramID = rcp.RCPID
AND ccpm.CCPMClientID = clt.CltID
LEFT JOIN TippingModule tm WITH (NOLOCK)
ON tm.TMid = rcp.RCPTippingModuleID
LEFT JOIN GiftCardTemplate fgt WITH (NOLOCK)
ON fgt.gtid = rcp.RCPFOCTemplateID
AND fgt.GTPage='P'
LEFT JOIN GiftCardTemplate bgt WITH (NOLOCK)
ON bgt.gtid = rcp.RCPBOCTemplateID
AND bgt.GTPage='PB'
LEFT JOIN Card c WITH (NOLOCK)
ON c.CardID = rcp.RCPDefaultCarrierID
LEFT JOIN CardType ct WITH (NOLOCK)
ON ct.CTID = c.CardTypeID
LEFT JOIN RetailerCardProgramTCSheetMap rtm1 WITH (NOLOCK)
ON rtm1.RTMRCPID = rcp.RCPID
AND rtm1.RTMInsertOrder = 1
LEFT JOIN RetailerCardProgramTCSheetMap rtm2 WITH (NOLOCK)
ON rtm2.RTMRCPID = rcp.RCPID
AND rtm2.RTMInsertOrder = 2
LEFT JOIN RetailerCardProgramTCSheetMap rtm3 WITH (NOLOCK)
ON rtm3.RTMRCPID = rcp.RCPID
AND rtm3.RTMInsertOrder = 3
LEFT JOIN RetailerCardProgramTCSheetMap rtm4 WITH (NOLOCK)
ON rtm4.RTMRCPID = rcp.RCPID
AND rtm4.RTMInsertOrder = 4
LEFT JOIN TCSheet i1 WITH (NOLOCK)
ON i1.TCSID = rtm1.RTMTCSID
LEFT JOIN TCSheet i2 WITH (NOLOCK)
ON i2.TCSID = rtm2.RTMTCSID
LEFT JOIN TCSheet i3 WITH (NOLOCK)
ON i3.TCSID = rtm3.RTMTCSID
LEFT JOIN TCSheet i4 WITH (NOLOCK)
ON i4.TCSID = rtm4.RTMTCSID
LEFT JOIN ShipType st WITH (NOLOCK)
ON st.STId = rcp.RCPDefaultShipTypeID
WHERE
clt.CltID IN (#ClientIDs) -- 6653 and 6657.
AND rcp.RCPActive IN (#ProgramStatus)
ORDER BY
AAJobNo
, Program
You want to join with a nested select on the table carddet. I'm inferring that RCPID is the relationship between carddet and your main table RetainerCardProgram...
SELECT rcp.RCPID AS ProgramID,
date.MAXDATE AS MaxDate,
rest of your columns...
FROM RetailerCardProgram rcp WITH (NOLOCK)
INNER JOIN (
SELECT RCPID, MAX(cdconfirmationdate) as 'MAXDATE'
FROM carddet
GROUP BY RCPID
) date on date.RCPID = rcp.RCPID
rest of query...
You may want a left join if not all IDs have a date in carddet.
Obligatory addition: Also your use of NOLOCK is a bit terrifying. See http://blogs.sqlsentry.com/aaronbertrand/bad-habits-nolock-everywhere/

Stored Procedure with multiple inner joins not returning when value is passed

I am having an issue with an inner join. It executes but when I pass the value to it, it returns nothing.
CREATE PROCEDURE s_Get$Subject$For$Edit
(
#SubjectID int
)
as
select s.SubjectID, s.SubjectName, s.SubjectDescription, i.QuizID, i.GameID, i.VideoID, q.QuizID, q.QuizName, g.GameID, g.GameName,
v.VideoID, v.VideoName
from [Subjects] s
inner join [SubjectInfo] i on i.SubjectID = s.SubjectID
inner join [Quiz] q on q.QuizID = i.QuizID
inner join [Games] g on g.GameID = i.GameID
inner join [Video] v on v.VideoID = i.VideoID
where s.SubjectID = #SubjectID
What am I missing or overlooking?

sql server using where criteria for multiple values being passed

I am trying to pass from 1 to 10 criterias to a stored procedure that uses a select statement below in this procedure below:
ALTER PROCEDURE [dbo].[Original_Docs]
#Cat varchar(255),
#docType VARCHAR(255),
#IdNo VARCHAR(50)
AS
BEGIN
SELECT
d4.id as DOCUMENT_ID, c.name as Category, dt.name as Document_Type,
mv1.value as GUIDELINE_MASTER, mv2.value as ID_NUMBER, mv3.value as [DATE],
mv4.value as DESCRIPTION, mv5.value AS BUDGET_NUM, mv6.value as STATUS,
mv7.value AS [CLOSED DATE], mv8.value AS REPORT_TYPE
FROM Documents d4
JOIN (
SELECT d2.id
FROM Documents d2
JOIN (
SELECT d.fileStoreId, MIN(d.createdDate) as CreatedDate
FROM Documents d
JOIN FileStores fs ON d.fileStoreId = fs.id
WHERE fs.shared = 1 AND d.dateDeleted IS NULL
GROUP BY d.fileStoreId
) AS f ON d2.fileStoreId = f.fileStoreId AND d2.createdDate = f.CreatedDate
UNION
SELECT d3.id
FROM Documents d3
JOIN FileStores fs2 ON d3.fileStoreId = fs2.id
WHERE fs2.shared = 0 AND d3.dateDeleted IS NULL
) AS f2 ON f2.id = d4.id
JOIN DocumentTypes dt ON d4.documentTypeId = dt.id
JOIN Categories c ON dt.categoryId = c.id
LEFT OUTER JOIN mv_guideline_master mv1 ON d4.id = mv1.documentId
LEFT OUTER JOIN mv_id_number mv2 ON d4.id = mv2.documentId
LEFT OUTER JOIN mv_date mv3 ON d4.id = mv3.documentId
LEFT OUTER JOIN mv_description mv4 ON d4.id = mv4.documentId
LEFT OUTER JOIN mv_budgetnum mv5 ON d4.id = mv5.documentId
LEFT OUTER JOIN mv_status mv6 ON d4.id = mv6.documentId
LEFT OUTER JOIN mv_closed_date mv7 ON d4.id = mv7.documentId
LEFT OUTER JOIN mv_report_type mv8 ON d4.id = mv8.documentId
WHERE c.name = #Cat AND (dt.name = #docType OR mv2.value = #IdNo)
ORDER BY mv1.value
I will need to add the rest of the other criteria after I figure out how to do it with 3 criteria. In the where clause, I am passing parameters #Cat, #docType, and #IdNo. how do I query using these criteria with the one of these being filled in with the other one or 2 blank? Or if all of them where passed in or if only 2 were passed in. If I use AND then it doesn't work or if I use OR between the criteria, it will bring back the wrong result. Do I need Parens somewhere for it to work?
thanks!
Not sure I follow exactly, if each variable can be NULL:
WHERE (c.name = #Cat OR #Cat IS NULL)
AND ((dt.name = #docType OR #docType IS NULL)
OR (mv2.value = #IdNo OR #IdNo IS NULL))
or if blank:
WHERE (c.name = #Cat OR #Cat = '')
AND ((dt.name = #docType OR #docType = '')
OR (mv2.value = #IdNo OR #IdNo = ''))

Merge these two queries into a single query

I have the following queries:
SELECT Sites.EDISID, Sites.[Name], (SUM(DLData.Quantity) / 8) AS TiedDispense
FROM Sites
JOIN UserSites
ON UserSites.EDISID = Sites.EDISID
JOIN Users
ON Users.[ID] = UserSites.UserID
JOIN MasterDates
ON MasterDates.EDISID = UserSites.EDISID
JOIN DLData
ON DLData.DownloadID = MasterDates.[ID]
JOIN Products
ON Products.[ID] = DLData.Product
LEFT JOIN SiteProductTies
ON SiteProductTies.EDISID = UserSites.EDISID
AND SiteProductTies.ProductID = Products.[ID]
LEFT JOIN SiteProductCategoryTies
ON SiteProductCategoryTies.EDISID = UserSites.EDISID
AND SiteProductCategoryTies.ProductCategoryID = Products.CategoryID
WHERE Users.[ID] = #UserID
AND (COALESCE(SiteProductTies.Tied, SiteProductCategoryTies.Tied, Products.Tied) = #Tied OR #Tied IS NULL)
AND MasterDates.[Date] BETWEEN #From AND #To
AND MasterDates.[Date] >= Sites.SiteOnline
GROUP BY Sites.EDISID, Sites.[Name]
SELECT Sites.EDISID, Sites.[Name], SUM(Delivery.Quantity) AS TiedDelivered
FROM Sites
JOIN UserSites
ON UserSites.EDISID = Sites.EDISID
JOIN Users
ON Users.[ID] = UserSites.UserID
JOIN MasterDates
ON MasterDates.EDISID = UserSites.EDISID
JOIN Delivery
ON Delivery.DeliveryID = MasterDates.[ID]
JOIN Products
ON Products.[ID] = Delivery.Product
LEFT JOIN SiteProductTies
ON SiteProductTies.EDISID = UserSites.EDISID
AND SiteProductTies.ProductID = Products.[ID]
LEFT JOIN SiteProductCategoryTies
ON SiteProductCategoryTies.EDISID = UserSites.EDISID
AND SiteProductCategoryTies.ProductCategoryID = Products.CategoryID
WHERE Users.[ID] = #UserID
AND (COALESCE(SiteProductTies.Tied, SiteProductCategoryTies.Tied, Products.Tied) = #Tied OR #Tied IS NULL)
AND MasterDates.[Date] BETWEEN #From AND #To
AND MasterDates.[Date] >= Sites.SiteOnline
GROUP BY Sites.EDISID, Sites.[Name]
As you can see they are very similar - only the lines regarding whether the query is for DLData or Delivery are different. One returns the total delivered the other returns the total dispensed.
Currently I am using them as two separate sub-queries in a third query. Singly they take approximately 1-2 seconds each. As two subqueries they are taking between 6 and 10 seconds (depending on load) and both return just 47 rows (though they are touching thousands of rows total).
I was thinking that combining them will give me a decent speed up - especially as this query will be called a lot.
However my attempts have failed as the number of rows change when I try to combine the two. I have tried various JOIN combinations but nothing returns the correct results.
Do the SO'ers have any suggestions?
you could try:
SELECT Sites.EDISID,
Sites.[Name],
(SUM(DLData.Quantity) / 8) AS TiedDispense,
SUM(Delivery.Quantity) AS TiedDelivered
FROM Sites
JOIN UserSites
ON UserSites.EDISID = Sites.EDISID
JOIN Users
ON Users.[ID] = UserSites.UserID
JOIN MasterDates
ON MasterDates.EDISID = UserSites.EDISID
JOIN DLData
ON DLData.DownloadID = MasterDates.[ID]
JOIN Products
ON Products.[ID] = DLData.Product
LEFT JOIN Delivery
ON Delivery.DeliveryID = MasterDates.[ID]
LEFT JOIN SiteProductTies
ON SiteProductTies.EDISID = UserSites.EDISID AND SiteProductTies.ProductID = Products.[ID]
LEFT JOIN SiteProductCategoryTies
ON SiteProductCategoryTies.EDISID = UserSites.EDISID
AND SiteProductCategoryTies.ProductCategoryID = Products.CategoryID
WHERE Users.[ID] = #UserID
AND (COALESCE(SiteProductTies.Tied, SiteProductCategoryTies.Tied, Products.Tied) = #Tied
OR #Tied IS NULL)
AND MasterDates.[Date] BETWEEN #From AND #To
AND MasterDates.[Date] >= Sites.SiteOnline
GROUP BY Sites.EDISID, Sites.[Name]
I did a left join but an Inner join might work depending on your data. I'd also check to make sure that all of those foreign key fields are indexed.
After a quick look at your query, I can't be sure if this is correct without understanding the business rules behind the data. However, you can give this a shot if you'd like:
SELECT
Sites.EDISID, Sites.[Name],
CASE WHEN Delivery.DeliveryID IS NULL THEN 0 ELSE SUM(Delivery.Quantity) END TiedDelivered,
CASE WHEN DLData.[ID] IS NULL THEN 0 ELSE (SUM(DLData.Quantity) / 8) END TiedDispense
FROM Sites
JOIN UserSites
ON UserSites.EDISID = Sites.EDISID
JOIN Users
ON Users.[ID] = UserSites.UserID
JOIN MasterDates
ON MasterDates.EDISID = UserSites.EDISID
LEFT JOIN Products
ON Products.[ID] = DLData.Product
LEFT JOIN DLData
ON DLData.DownloadID = MasterDates.[ID]
LEFT JOIN Delivery
ON Delivery.DeliveryID = MasterDates.[ID]
LEFT JOIN SiteProductTies
ON SiteProductTies.EDISID = UserSites.EDISID
AND SiteProductTies.ProductID = Products.[ID]
LEFT JOIN SiteProductCategoryTies
ON SiteProductCategoryTies.EDISID = UserSites.EDISID
AND SiteProductCategoryTies.ProductCategoryID = Products.CategoryID
WHERE Users.[ID] = #UserID
AND (COALESCE(SiteProductTies.Tied, SiteProductCategoryTies.Tied, Products.Tied) = #Tied OR #Tied IS NULL)
AND MasterDates.[Date] BETWEEN #From AND #To
AND MasterDates.[Date] >= Sites.SiteOnline
AND (DLData.[DownloadID] IS NOT NULL OR DELIVERY.DeliveryID IS NOT NULL)
GROUP BY Sites.EDISID, Sites.[Name]
one key to it is this part:
AND (DLData.[DownloadID] IS NOT NULL OR DELIVERY.DeliveryID IS NOT NULL)
Which is heavily based on assumptions of your business rules but might make up for extra rows returned by the two left joins. You can also play with something like this if you'd like:
AND ( TiedDelivered != 0 AND TiedDispense != 0)
hope this helps.
-steve
I wrote a dumb answer to this and it bugged me so I started looking into it a bit more - basically you want to put the group bys inside the joins. I haven't got time to edit your code but I think this example should get you there:
create table #prod(
prodid int,
prodamount int)
create table #del(
delid int,
delamount int)
create table #main(
id int,
name varchar(50))
insert into #main(id,name)
select 1, 'test 1'
union select 2, 'test 2'
union select 3, 'test 3'
union select 4, 'test 4'
insert into #prod(prodid,prodamount)
select 1, 10
union select 1, 20
union select 1, 30
union select 2, 5
insert into #del(delid,delamount)
select 1, 9
union select 1, 8
union select 3, 7
/** wrong **/
select m.id, m.name, isnull(sum(p.prodamount),0), isnull(sum(d.delamount),0)
from #main m
left join #prod p on p.prodid = m.id
left join #del d on d.delid = m.id
group by m.id, m.name
/** right! **/
select id, name, isnull(myprod.prodtot,0) as prodtot, isnull(mydel.deltot,0) as deltot
from #main
left join
(SELECT prodid, SUM(prodamount) AS prodtot
FROM #prod
GROUP BY prodid) myprod on #main.id = myprod.prodid
left join
(SELECT delid, SUM(delamount) AS deltot
FROM #del
GROUP BY delid) mydel on #main.id = mydel.delid
drop table #prod
drop table #del
drop table #main
Here's my rewrite of you queries into a single query:
SELECT t.edisid,
t.name,
SUM(dd.quantity) / 8 AS TiedDispense,
SUM(d.quantity) AS TiedDelivered
FROM SITES t
JOIN USERSITES us ON us.edisid = t.esisid
JOIN USERS u ON u.id = us.userid
JOIN MASTERDATES md ON md.edisid = us.edisid
AND md.date >= t.siteonline
LEFT JOIN DLDATA dd ON dd.downloadid = md.id
LEFT JOIN DELIVERY d ON d.deliveryid = md.id
JOIN PRODUCTS p ON p.id IN (dd.product, d.product)
LEFT JOIN SITEPRODUCTTIES spt ON spt.edisid = us.edisid
AND spt.productid = p.id
LEFT JOIN SITEPRODUCTCATEGORYTIES spct ON spct.edisid = us.edisid
AND spct.productcategoryid = p.categoryid
WHERE u.id = #UserID
AND (#Tied IS NULL OR COALESCE(spt.tied, spct.tied, p.tied) = #Tied)
AND md.date BETWEEN #From AND #To
GROUP BY t.edisid, t.name
Depending on your data, the JOINs to DLDATA and DELIVERY could be inner joins.
It'd be good to get in the habit of using table aliases.
Without wanting to sound overly stupid, but I guess a union is not going to help (would require a small change to a returned column name...)?