问题
I am having an issue with this simple query. I get the error
"Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <=, >, >= or when the subquery is used as an expression."
SELECT TOP 100 PERCENT
dbo.Inventory.PARTNO
,( SELECT ISNULL(SUM(dbo.PurchaseOrderReceived.QtyReceived), 0)
FROM dbo.PurchaseOrderReceived
JOIN dbo.PurchaseOrderlineItems
ON dbo.PurchaseOrderReceived.POLIID = dbo.PurchaseOrderlineItems.POLIID
JOIN dbo.Inventory
ON dbo.PurchaseOrderlineItems.InvMasID = dbo.Inventory.InvMasID
JOIN dbo.Duties Duties_1 ON dbo.Inventory.DutyClass = Duties_1.DutyID
WHERE (Duties_1.DutyClass = 252)
AND (dbo.PurchaseOrderlineItems.Deleted = 0)
AND (dbo.PurchaseOrderReceived.ReceivedDate > CONVERT(DATETIME, '2018-08-22 00:00:00', 102))
GROUP BY dbo.Inventory.PARTNO
) AS Purchased
,ISNULL(SUM(dbo.OrderItems.QtyShipped), 0) AS Ordered
,ISNULL(SUM(DISTINCT dbo.MRP.QtyOnHand), 0) AS QOH
FROM dbo.Orders
JOIN dbo.OrderItems
JOIN dbo.Inventory
ON dbo.OrderItems.InvMasID = dbo.Inventory.InvMasID
ON dbo.Orders.OrderID = dbo.OrderItems.OrderID
JOIN dbo.MRP ON dbo.Inventory.InvMasID = dbo.MRP.InvMasID
JOIN dbo.Duties ON dbo.Inventory.DutyClass = dbo.Duties.DutyID
WHERE (dbo.Orders.ShipDate > CONVERT(DATETIME, '2018-08-22 00:00:00', 102))
AND (dbo.Duties.DutyClass = 252)
GROUP BY dbo.Inventory.PARTNO
I have tried working through this problem and know that there is a simple solution I am missing. The subquery, on its own, retrieves the information I am looking for, as does the main query, when separated. Thanks for any help!
回答1:
put this condition AND dbo.Orders.PARTNO = dbo.Inventory.PARTNO and Top 1
SELECT TOP 100 PERCENT
dbo.Inventory.PARTNO
,( SELECT Top 1 ISNULL(SUM(dbo.PurchaseOrderReceived.QtyReceived), 0)
FROM dbo.PurchaseOr,derReceived
JOIN dbo.PurchaseOrderlineItems
ON dbo.PurchaseOrderReceived.POLIID = dbo.PurchaseOrderlineItems.POLIID
JOIN dbo.Inventory
ON dbo.PurchaseOrderlineItems.InvMasID = dbo.Inventory.InvMasID
JOIN dbo.Duties Duties_1 ON dbo.Inventory.DutyClass = Duties_1.DutyID
WHERE (Duties_1.DutyClass = 252)
AND (dbo.PurchaseOrderlineItems.Deleted = 0)
AND (dbo.PurchaseOrderReceived.ReceivedDate > CONVERT(DATETIME, '2018-08-22 00:00:00', 102))
AND dbo.Orders.PARTNO = dbo.Inventory.PARTNO
GROUP BY dbo.Inventory.PARTNO
) AS Purchased
,ISNULL(SUM(dbo.OrderItems.QtyShipped), 0) AS Ordered
,ISNULL(SUM(DISTINCT dbo.MRP.QtyOnHand), 0) AS QOH
FROM dbo.Orders
JOIN dbo.OrderItems
JOIN dbo.Inventory
ON dbo.OrderItems.InvMasID = dbo.Inventory.InvMasID
ON dbo.Orders.OrderID = dbo.OrderItems.OrderID
JOIN dbo.MRP ON dbo.Inventory.InvMasID = dbo.MRP.InvMasID
JOIN dbo.Duties ON dbo.Inventory.DutyClass = dbo.Duties.DutyID
WHERE (dbo.Orders.ShipDate > CONVERT(DATETIME, '2018-08-22 00:00:00', 102))
AND (dbo.Duties.DutyClass = 252)
GROUP BY dbo.Inventory.PARTNO
回答2:
I can only see this sub query in your query above
SELECT ISNULL(SUM(dbo.PurchaseOrderReceived.QtyReceived),0)
FROM dbo.PurchaseOrderReceived
INNER JOIN dbo.PurchaseOrderlineItems ON dbo.PurchaseOrderReceived.POLIID = dbo.PurchaseOrderlineItems.POLIID
INNER JOIN dbo.Inventory ON dbo.PurchaseOrderlineItems.InvMasID = dbo.Inventory.InvMasID
INNER JOIN dbo.Duties Duties_1 ON dbo.Inventory.DutyClass = Duties_1.DutyID
WHERE (Duties_1.DutyClass = 252)
AND (dbo.PurchaseOrderlineItems.Deleted = 0)
AND (dbo.PurchaseOrderReceived.ReceivedDate > CONVERT(DATETIME,'2018-08-22 00:00:00',102))
GROUP BY dbo.Inventory.PARTNO
Grouped by PARTNO this is supposed to return multiple values. Make sure that there are not more than one part No that satisfies the condition on your where
and inner join
conditions
回答3:
The sub query needs to return one value, while in yours it returned multiple values, that's because the inner group by PARTNO
. Either you remove that group by, or use TOP 1, or just contain the sub query with SUM,MAX,MIN to solve it.
Another thing, since your sub-query is also using the same and relative sources, you can join them instead (which will be better).
Something like this :
SELECT TOP 100 PERCENT
dbo.Inventory.PARTNO
, ISNULL(SUM(dbo.por.QtyReceived), 0) AS Purchased
, ISNULL(SUM(dbo.OrderItems.QtyShipped), 0) AS Ordered
, ISNULL(SUM(DISTINCT dbo.MRP.QtyOnHand), 0) AS QOH
FROM dbo.Orders
JOIN dbo.OrderItems ON dbo.Orders.OrderID = dbo.OrderItems.OrderID
JOIN dbo.Inventory ON dbo.OrderItems.InvMasID = dbo.Inventory.InvMasID
JOIN dbo.MRP ON dbo.Inventory.InvMasID = dbo.MRP.InvMasID
JOIN dbo.Duties ON dbo.Inventory.DutyClass = dbo.Duties.DutyID
LEFT JOIN dbo.PurchaseOrderlineItems poli ON poli.InvMasID = dbo.Inventory.InvMasID AND poli.Deleted = 0
LEFT JOIN dbo.PurchaseOrderReceived por ON por.POLIID = poli.POLIID AND (dbo.por.ReceivedDate > CONVERT(DATETIME, '2018-08-22 00:00:00', 102))
WHERE
dbo.Orders.ShipDate > CONVERT(DATETIME, '2018-08-22 00:00:00', 102)
AND (dbo.Duties.DutyClass = 252)
GROUP BY dbo.Inventory.PARTNO
I can't verify the results since you didn't provide any samples, but I think it'll be good enough to show that approach.
回答4:
The simple solution seems to be aliasing the inventory table in either the subquery or in the main SELECT statement and filter the records in the subquery by PARTNO
from the main query. I think that aliasing is anyway a good idea:
SELECT TOP 100 PERCENT
i.PARTNO
, ( SELECT ISNULL(SUM(por.QtyReceived), 0)
FROM dbo.PurchaseOrderReceived por
JOIN dbo.PurchaseOrderlineItems poli ON por.POLIID = poli.POLIID
JOIN dbo.Inventory i1 ON poli.InvMasID = i1.InvMasID
JOIN dbo.Duties d1 ON i1.DutyClass = d1.DutyID
WHERE (d1.DutyClass = 252)
AND (i1.PARTNO = i.PARTNO)
AND (poli.Deleted = 0)
AND (por.ReceivedDate > CONVERT(DATETIME, '2018-08-22 00:00:00', 102))
) AS Purchased
, ISNULL(SUM(oi.QtyShipped), 0) AS Ordered
, ISNULL(SUM(DISTINCT m.QtyOnHand), 0) AS QOH
FROM dbo.Orders o
JOIN dbo.OrderItems oi ON o.OrderID = oi.OrderID
JOIN dbo.Inventory i ON oi.InvMasID = i.InvMasID
JOIN dbo.MRP m ON i.InvMasID = m.InvMasID
JOIN dbo.Duties d ON i.DutyClass = d.DutyID
WHERE (o.ShipDate > CONVERT(DATETIME, '2018-08-22 00:00:00', 102))
AND (d.DutyClass = 252)
GROUP BY i.PARTNO
回答5:
Time to start over and apply some best coding practices. Your joining is structured in an "atypical" fashion that many will struggle to understand. And code that confuses is likely to lead to mistakes. Move your join clauses to follow the actual joined tables. E.g., you have
FROM dbo.Orders
JOIN dbo.OrderItems
JOIN dbo.Inventory
ON dbo.OrderItems.InvMasID = dbo.Inventory.InvMasID
ON dbo.Orders.OrderID = dbo.OrderItems.OrderID
JOIN dbo.MRP ON dbo.Inventory.InvMasID = dbo.MRP.InvMasID
JOIN dbo.Duties ON dbo.Inventory.DutyClass = dbo.Duties.DutyID
You did not follow the first join to OrderItems with the associated ON clause - somehow it wound up after the join to Inventory. Oddly, the on clause for the join to MRP does immediately follow that table. Inconsistency is a bad developer habit. IMO worse than doing things "atypically". And lastly, use alias to reduce the clutter and make your code easier to read. Easier to read means your code is more likely to be understood. The improved version is:
FROM dbo.Orders as Ord
JOIN dbo.OrderItems as Itm on Ord.OrderID = Itm.OrderID
JOIN dbo.Inventory as Inv on Itm.InvMasID = Inv.InvMasID
JOIN dbo.MRP as MRP ON Inv.InvMasID = MRP.InvMasID
JOIN dbo.Duties as Duties ON Inv.DutyClass = Duties.DutyID
It is good that you schema-qualify your tables - it is rare to see posters do that. However, it also makes the names very long which is one reason why the use of an alias is recommended.
Next, you have "select top 100 percent". Whenever you think you need this, stop writing code and go do something else for a bit. This is NEVER useful. Don't believe SSMS if it tries to "fix" something by adding it.
The problem you are posting about is the subquery. You tried to "fix" the original problem but you did not tell us what that problem was. It was that the query returned the wrong summed value. Why did it do that? Because you did not correlate the subquery to the outer query so that it calculated a value for each individual inventory row.
To do that, you need to remove the join to Inventory in the subquery. Why? Because the key to the appropriate inventory row will come from the outer query - that is how you correlate the inner with the outer. There will be no group by clause because your goal is to compute a single value for each row in the outer query. The group by clause logically indicates that you intend to produce multiple rows in that query - which is not what you want to happen (and cannot happen because the engine will produce an error in that situation - which you have discovered). Some additional problems:
- The alias issue (obviously) which makes the subquery difficult to read.
- The use of isnull is likely not needed where currently located but is needed elsewhere. It is always possible that a subquery will find no matching rows and return a null value. You wrap the entire subquery with isnull if you don't want null values. Because you do this, you don't need it where you currently have it. And where you currently have it is likely not needed for "matching" situations - but I can't say that with certainty without knowing your schema. No matter, the correct placement will negate the need for it around the sum computation.
- Duplicative conditions in the where clause. This is not needed once you correlate correctly.
So applying all those changes should leave something like:
,( SELECT SUM(POR.QtyReceived)
FROM dbo.PurchaseOrderReceived as POR
JOIN dbo.PurchaseOrderlineItems as POItm
ON POR.POLIID = POItm.POLIID
WHERE Inv.InvMasID = POItm.InvMasID --this is the correlation
AND POItm.Deleted = 0
AND POR.ReceivedDate > CONVERT(DATETIME, '2018-08-22 00:00:00', 102))
) AS Purchased
Hopefully I did not add any typos or errors - I obviously cannot check this myself. But that at least gives you an improved starting point. Your use of dates looks suspect but - again - I don't know your schema or your data or how it is used or what you query is intended to do.
来源:https://stackoverflow.com/questions/53017310/select-subquery-group-by