How to return an extra column in a sub query that is a UNION - sql

I want to somehow get the Promotions Id from the second select in a UNION which is a sub query that brings back one column.
Is it somehow possible to achieve this or will I have to duplicate the 2nd query somehow again?
Updated
So, basically this code will get a Promotion Price from an older Table in the same Database and then it will UNION it to a new Promotions Table and select the best or lowest price from the two, but the newer Promotion Table has a Promotion Id (P.Id) which I need to return.
Additional Unrelated Info
The DateValidFrom and DateValidTo is of type Datetime because the Promotion can have a start and end date with a specific time.
Expected Outcome
I want to return the Promotions Id which is P.Id from the Promotions Table.
ALTER FUNCTION [dbo].[GetCustomerPricingForProduct] (#StockParentId int,
#CustomerCode varchar(30),
#PricingDate date,
#ChannelID int,
#QTY int,
#CustCategoryNo int,
#PriceType int,
#CustomerDealerType int)
RETURNS table
AS
RETURN (SELECT ISNULL(F.Price, ISNULL(R.SellingPrice, 0)) AS StandardPrice,
(SELECT DiscountLines.amount
FROM Customers
INNER JOIN CustDiscounts ON Customers.pricelist = CustDiscounts.no
INNER JOIN DiscountLines ON CustDiscounts.no = DiscountLines.no
INNER JOIN FinGoodsParent FP ON FP.Id = DiscountLines.FinGoods_ID
WHERE Customer_Code = #CustomerCode
AND CONVERT(date, CustDiscounts.startdate) <= #PricingDate
AND CONVERT(date, CustDiscounts.enddate) >= #PricingDate
AND DiscountLines.TYPE = #PriceType
AND FP.Id = #StockParentId) AS PricelistPrice,
(SELECT TOP 1
Pricing
FROM (SELECT Pricing
FROM PROMGEN
INNER JOIN FinGoodsParent FP ON FP.Id = PROMGEN.FinGoods_ID
INNER JOIN PromCateg ON PROMGEN.No = PromCateg.PromGen_No
INNER JOIN PromGen_Channels ON PROMGEN.No = PromGen_Channels.PromGen_No
INNER JOIN Channels ON PromGen_Channels.Channels_Id = Channels.Id
WHERE PROMGEN.MINQUANT <= #QTY
AND #PricingDate >= CONVERT(date, PROMGEN.START_DATE)
AND #PricingDate <= CONVERT(date, PROMGEN.END_DATE)
AND Channels_Id = #ChannelID
AND PromCateg.CustCateg_No = #CustCategoryNo
AND FP.Id = #StockParentId
AND PROMGEN.TYPE = #PriceType
UNION
SELECT MIN(SP_P.PricingRuleTypeAmount) AS Pricing
FROM StockParent_Promotions SP_P
INNER JOIN Promotions P ON P.Id = SP_P.Promotions_Id
INNER JOIN Promotions_Channels PC ON PC.Promotions_Id = P.Id
INNER JOIN StockParent SP ON SP_P.StockParent_Id = SP.Id
INNER JOIN Promotions_CustCateg PCC ON PCC.Promotions_Id = P.Id
INNER JOIN CUSTCATEG CC ON CC.NO = PCC.CustCateg_No
WHERE SP_P.MinPurchaseQuantity <= #QTY
AND #PricingDate >= CONVERT(date, P.DateValidFrom)
AND #PricingDate <= CONVERT(date, P.DateValidTo)
AND Channels_Id = #ChannelID
AND PCC.CustCateg_No = #CustCategoryNo
AND SP.Id = #StockParentId
AND SP_P.PricingRuleType = #PriceType
AND ((P.IsPromotionForEndUsers = 1
AND #CustomerDealerType = 1)
OR (P.IsPromotionForDealers = 1
AND #CustomerDealerType = 0))
AND P.IsDeleted = 0) OldAndNewPromotions
WHERE Pricing IS NOT NULL
ORDER BY Pricing ASC) AS PromotionPrice
FROM StockParent
LEFT JOIN Fingoods F ON F.Id = StockParent.ID
LEFT JOIN RAWMAT R ON R.Id = StockParent.ID
WHERE StockParent.Id = #StockParentId);

Related

SQL Update with AVG of Joined Tables

I'm trying to update the supplier lead time in a table by calculating the difference between date of order and stock receipt date....
UPDATE cr_accs
SET cr_accs.leadtime = Avg(Datediff(day, purchord_hdr.orderdate,
stock_trans.transdate))
FROM stock_items
INNER JOIN stock_trans
ON stock_trans.stockcode = stock_items.stockcode
INNER JOIN purchord_hdr
ON purchord_hdr.seqno = stock_trans.ref1
WHERE cr_accs.accno = purchord_hdr.accno
AND stock_trans.location = 1
AND stock_trans.ref2 = 'RECEIPT'
AND purchord_hdr.orderdate >= Dateadd(day, Datediff(day, 0, Getdate()),-730)
AND stock_items.isactive = 'Y'
AND stock_items.bincode NOT IN ( 'OIO', 'CON' )
However I'm getting an error
An aggregate may not appear in the set list of an UPDATE statement
I've seen other solutions where you would change the query to:
UPDATE cr_accs
SET cr_accs.leadtime = h.calc_lead_time
FROM (SELECT AVG(DATEDIFF(day, purchord_hdr.orderdate, stock_trans.transdate)) AS calc_lead_time
FROM stock_items
INNER JOIN stock_trans
ON stock_trans.stockcode = stock_items.stockcode
INNER JOIN purchord_hdr
ON purchord_hdr.seqno = stock_trans.ref1
INNER JOIN cr_accs
ON cr_accs.accno = purchord_hdr.accno
WHERE cr_accs.accno = purchord_hdr.accno
AND stock_trans.location = 1
AND stock_trans.ref2 = 'RECEIPT'
AND purchord_hdr.orderdate >= Dateadd(day, Datediff(day, 0, Getdate()),-730)
AND stock_items.isactive = 'Y'
AND stock_items.bincode NOT IN ( 'OIO', 'CON' ) ) h
However this is not the solution for me as it doesn't define that the lead time is unique to each supplier...
It may be helpful to point out that each supplier is identified by cr_accs.accno
Any ideas please?
You can also do what you want using a correlated subquery, which might be what you were trying to do:
UPDATE cr_accs
SET cr_accs.leadtime =
(SELECT AVG(DATEDIFF(day, p.orderdate, st.transdate)) AS calc_lead_time
FROM stock_items si JOIN
stock_trans st
ON st.stockcode = si.stockcode JOIN
purchord_hdr p
ON p.seqno = st.ref1
WHERE cr_accs.accno = p.accno AND
st.location = 1 AND
st.ref2 = 'RECEIPT' AND
p.orderdate >= Dateadd(day, Datediff(day, 0, Getdate()), -730) AND
si.isactive = 'Y' AND
si.bincode NOT IN ( 'OIO', 'CON' )
);
I introduced table aliases so the query is easier to write and to read.
SQL Server also gives you the ability to express this using APPLY:
UPDATE a
SET a.leadtime = p.calc_lead_time
FROM cr_accs a CROSS APPLY
(SELECT AVG(DATEDIFF(day, p.orderdate, st.transdate)) AS calc_lead_time
FROM stock_items si JOIN
stock_trans st
ON st.stockcode = si.stockcode JOIN
purchord_hdr p
ON p.seqno = st.ref1
WHERE a.accno = p.accno AND
st.location = 1 AND
st.ref2 = 'RECEIPT' AND
p.orderdate >= Dateadd(day, Datediff(day, 0, Getdate()), -730) AND
si.isactive = 'Y' AND
si.bincode NOT IN ( 'OIO', 'CON' )
) p;
Try this join of the table to the query:
UPDATE c
SET c.leadtime = h.calc_lead_time
FROM cr_accs c
INNER JOIN (
SELECT purchord_hdr.accno,
AVG(DATEDIFF(day, purchord_hdr.orderdate, stock_trans.transdate)) AS calc_lead_time
FROM stock_items
INNER JOIN stock_trans ON stock_trans.stockcode = stock_items.stockcode
INNER JOIN purchord_hdr ON purchord_hdr.seqno = stock_trans.ref1
WHERE stock_trans.location = 1
AND stock_trans.ref2 = 'RECEIPT'
AND purchord_hdr.orderdate >= Dateadd(day, Datediff(day, 0, Getdate()),-730)
AND stock_items.isactive = 'Y'
AND stock_items.bincode NOT IN ('OIO', 'CON')
GROUP BY purchord_hdr.accno
) h ON h.accno = c.accno
I assume (by your code and the error message) that you are using SQL Server.

Query is working fine in second execution but taking too much time in first execution

I am writing a query which is accepting a comma separated string and calculating the sum of transaction. which is working fine as result wise but taking too much time to execute in first attempt. I understand its need tuning but didn't find out the exact reason can any one point me whats wrong with my query.
Declare #IDs nvarchar(max)='1,4,5,6,8,9,43,183'
SELECT isnull(isnull(SUM(FT.PaidAmt),0) - isnull(SUM(CT.PaidAmt),0),0) [Amount], convert(char(10),FT.TranDate,126) [Date]
from FeeTransaction FT
Inner Join (
Select max(P.Id) [Id], P.TranMainId, isnull(SUM(P.AmtToPay),0) [Amt]
From Patient_Account P
Group By P.TranMainId
) PA ON FT.Id = PA.TranMainId
Inner Join Patient_Account XP ON PA.Id = XP.Id
Inner Join Master_Fee MF ON XP.FeeId = MF.Id
INNER Join Master_Patient MP ON FT.PID = MP.Id
Inner Join Master_FeeType TY ON MF.FeeTypeId = TY.Id
Left JOIN FeeTransaction CT on FT.TransactionId = CT.TransactionId AND CT.TranDate between '2019'+'08'+'01' and '2019'+'08'+'31' and CT.[Status] <> 'A' AND isnull(CT.IsCancel,0) = 1
Where convert(nvarchar,FT.TranDate,112) between '2019'+'08'+'01' and '2019'+'08'+'31' AND FT.[Status] = 'A' AND XP.FeeId in (SELECT val FROM dbo.f_split(#IDs, ','))
AND isnull(FT.IsCancel,0) = 0 AND FT.EntryBy = 'rajan'
Group By convert(char(10),FT.TranDate,126)
I would rephrase the query a bit:
select coalesce(SUM(FT.PaidAmt), 0) - coalesce(SUM(CT.PaidAmt), 0)as [Amount],
convert(char(10),FT.TranDate,126) [Date]
from FeeTransaction FT join
(select xp.*,
coalesce(sum(p.amttopay) over (TranMainId), 0) as amt
from Patient_Account XP ON PA.Id = XP.Id
) xp join
Master_Fee MF
on XP.FeeId = MF.Id join
Master_Patient MP
on FT.PID = MP.Id join
Master_FeeType TY
on MF.FeeTypeId = TY.Id left join
FeeTransaction CT
on FT.TransactionId = CT.TransactionId and
CT.TranDate between '20190801' and '20190831' and
CT.[Status] <> 'A' and
CT.IsCanel = 1
where FT.TranDate >= '20190801' and and
FT.TranDate < '20190901'
FT.[Status] = 'A' AND
XP.FeeId in (SELECT val FROM dbo.f_split(#IDs, ',')) and
(FT.IsCancel = 0 or FT.IsCancel IS NULL) and
FT.EntryBy = 'rajan'
Group By convert(char(10), FT.TranDate, 126)
Then for this version, you specifically an index on FeeTransaction(EntryBy, Status, TranDate, Cancel).
Note the following changes:
You do not need to aggregate Patient_Account as a subquery. Window functions are quite convenient.
Your date comparisons preclude the use of indexes. Converting dates to strings is a bad practice in general.
You have over-used isnull().
I assume that the appropriate indexes are in place for the joins.
I would use STRING_SPLIT and Common Table Expressions and do away with date conversions:
Declare #IDs nvarchar(max)='1,4,5,6,8,9,43,183'
;WITH CTE_ID AS
(
SELECT value AS ID FROM STRING_SPLIT(#IDs, ',');)
),
MaxPatient
AS
(
SELECT MAX(P.Id) [Id], P.TranMainId, isnull(SUM(P.AmtToPay),0) [Amt]
From Patient_Account P
Group By P.TranMainId
)
SELECT isnull(isnull(SUM(FT.PaidAmt),0) - isnull(SUM(CT.PaidAmt),0),0) As [Amount],
convert(char(10),FT.TranDate,126) [Date]
FROM FeeTransaction FT
INNER JOIN MaxPatient PA
ON FT.Id = PA.TranMainId
INNER JOIN Patient_Account XP
ON PA.Id = XP.Id
INNER JOIN Master_Fee MF
ON XP.FeeId = MF.Id
INNER Join Master_Patient MP
ON FT.PID = MP.Id
INNER JOIN Master_FeeType TY
ON MF.FeeTypeId = TY.Id
INNER JOIN CTE_ID
ON XP.FeeId = CTE_ID.ID
LEFT JOIN FeeTransaction CT
ON FT.TransactionId = CT.TransactionId AND
CT.TranDate >= '20190801' AND CT.TranDate < '20190831' AND
CT.[Status] <> 'A' AND isnull(CT.IsCancel,0) = 1
WHERE FT.TranDate >= '20190801' and FT.TranDate < '20190831' AND
FT.[Status] = 'A' AND
ISNULL(FT.IsCancel,0) = 0 AND
FT.EntryBy = 'rajan'
GROUP BY CAST(FT.TranDate AS Date)
Not only is your query slow, but it appear that it is giving incorrect output.
i) When you are not using any column of Patient_Account in your resultset then why are you writing this sub query?
Select max(P.Id) [Id], P.TranMainId, isnull(SUM(P.AmtToPay),0) [Amt]
From Patient_Account P
Group By P.TranMainId
ii) Avoid using <>.So Status must be either 'A' or 'I'
so write this instead CT.[Status] = 'I'
iii) What is the correct data type of TranDate ?Don't use function in where condition. .
iv) No need of isnull(CT.IsCancel,0) = 1,instead write CT.IsCancel = 1
So my script is just outline, but it is easy to understand.
Declare #IDs nvarchar(max)='1,4,5,6,8,9,43,183'
create table #temp(id int)
insert into #temp(id)
SELECT val FROM dbo.f_split(#IDs, ',')
declare #FromDate Datetime='2019-08-01'
declare #toDate Datetime='2019-08-31'
-- mention all column of FeeTransaction that you need in this query along with correct data type
-- Store TranDate in this manner convert(char(10),FT.TranDate,126) in this table
create table #Transaction()
select * from FeeTransaction FT
where FT.TranDate>=#FromDate and FT.TranDate<#toDate
and exists(select 1 from #temp t where t .val=ft.id)
-- mention all column of Patient_Account that you need in this query along with correct data type
create table #Patient_Account()
Select max(P.Id) [Id], P.TranMainId, isnull(SUM(P.AmtToPay),0) [Amt]
From Patient_Account P
where exists(select 1 from #Transaction T where t.id=PA.TranMainId)
Group By P.TranMainId
SELECT isnull(isnull(SUM(FT.PaidAmt),0) - isnull(SUM(CT.PaidAmt),0),0) [Amount], TranDate [Date]
from #Transaction FT
Inner Join #Patient_Account XP ON PA.Id = XP.Id
Inner Join Master_Fee MF ON XP.FeeId = MF.Id
INNER Join Master_Patient MP ON FT.PID = MP.Id
Inner Join Master_FeeType TY ON MF.FeeTypeId = TY.Id
Left JOIN #Transaction CT on FT.TransactionId = CT.TransactionId AND CT.[Status] = 'I' AND CT.IsCancel = 1
Where AND FT.[Status] = 'A' AND XP.FeeId in (SELECT val FROM #temp t)
AND FT.IsCancel = 0 AND FT.EntryBy = 'rajan'
Group By TranDate

Conditionally Join the Table with a Condition

I have 3 tables. BaseProducts, Products and ProductsMerchants. I need to find the count using a condition. This is my SQL,
ALTER PROCEDURE [dbo].[GetTotalProductsCount]
(
#SuperUser bit,
#MarchantId int
)
AS
BEGIN
IF(#SuperUser = 1)
BEGIN
SELECT COUNT(*) AS Total
FROM [dbo].[BaseProducts]
END
ELSE
BEGIN
SELECT COUNT(*) AS Total
FROM [dbo].[BaseProducts] BP
INNER JOIN [dbo].[Products] P ON P.BaseProductId = BP.Id
INNER JOIN ProductsMerchants PM ON PM.ProductId = P.Id
WHERE PM.MarchantId = #MarchantId;
END
END
The problem is that I need to rewrite the same query just for checking a condition. Can I make it one query?
You can do this:
SELECT COUNT(*) AS Total
FROM [dbo].[BaseProducts]
WHERE #SuperUser = 1
UNION ALL
SELECT COUNT(*) AS Total
FROM [dbo].[BaseProducts] BP
INNER JOIN [dbo].[Products] P ON P.BaseProductId = BP.Id
INNER JOIN ProductsMerchants PM ON PM.ProductId = P.Id
WHERE PM.MarchantId = #MarchantId AND #SuperUser <> 1;
Personally, I find the if form more understandable.
If the inner joins are being used for filtering and don't increase the number of rows, you could also do:
SELECT COUNT(*) AS Total
FROM [dbo].[BaseProducts] BP
LEFT JOIN [dbo].[Products] P ON P.BaseProductId = BP.Id
LEFT JOIN ProductsMerchants PM ON PM.ProductId = P.Id
WHERE PM.MarchantId = #MarchantId OR #SuperUser = 1;
(The PM.MarchantId = #MarchantId undoes the left outer join.)
But once again, I find that the intent of the if is clearer.
Or even this:
SELECT (CASE WHEN #SuperUser = 1 THEN CNT ELSE COUNT(*) END) AS Total
FROM (SELECT COUNT(*) as CNT FROM [dbo].[BaseProducts] BP) const CROSS JOIN
[dbo].[BaseProducts] BP
INNER JOIN [dbo].[Products] P ON P.BaseProductId = BP.Id
INNER JOIN ProductsMerchants PM ON PM.ProductId = P.Id
WHERE PM.MarchantId = #MarchantId OR #SuperUser = 1;

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...)?