Can SQL Sub-query return two/more values but still compare against one of them? - sql

I have this query:
SELECT Items.Name, tblBooks.AuthorLastName, tblBooks.AuthorFirstName
FROM Items WHERE Items.ProductCode IN (
SELECT TOP 10 Recommended.ProductCode
FROM
Recommended
INNER JOIN Stock ON Recomended.ProductCode = Stock.ProductCode
AND Stock.StatusCode = 1
WHERE (Recommended.Type = 'TOPICAL') ORDER BY CHECKSUM(NEWID()));
It is fine for my data, except that the Recommended table has a SKU field I need also however I cannot put it next to Recommended.ProductCode and have the query still work.
I have used JOINS for this query and these work - but this query runs faster I just need the ProductCode and SKU from the Recommended table - how can this be done without needing yet another sub query?
Database: MS SQL Server 2000

The subquery seems to be picking 10 random recommendations. I think you can do that without a subquery:
SELECT TOP 10
Items.*,
Recommended.*,
Stock.*
FROM Items
INNER JOIN Recommended
ON Items.ProductCode = Recommended.ProductCode
AND Recommended.Type = 'TOPICAL'
INNER JOIN Stock
ON Recomended.ProductCode = Stock.ProductCode
AND Stock.StatusCode = 1
ORDER BY CHECKSUM(NEWID())
This gives you access to all columns, without having to pass them up from the subquery.

You can only return one value with the subselect, so you have to obtain the fields from the Recommended table by a join - which I presume is what you have already:
SELECT Items.Name, tblBooks.AuthorLastName, tblBooks.AuthorFirstName, Recommended.SKU
FROM Items
INNER JOIN Recommended ON Recommended.ProductCode = Items.ProductCode
WHERE Items.ProductCode IN (
SELECT TOP 10 Recommended.ProductCode
FROM
Recommended
INNER JOIN Stock ON Recomended.ProductCode = Stock.ProductCode
AND Stock.StatusCode = 1
WHERE (Recommended.Type = 'TOPICAL') ORDER BY CHECKSUM(NEWID()));
Most likely the Join in reality is an outer too I guess. This really shouldn't have any performance issues so long as you have both the Items and and Recommended tables indexed on ProductCode.

I think you need to move the subquery out of the where clause:
SELECT Items.Name, tblBooks.AuthorLastName, tblBooks.AuthorFirstName, R.SKU
FROM Items
INNER JOIN
(SELECT TOP 10 Recommended.ProductCode, Recommended.SKU FROM Recommended
INNER JOIN Stock ON Recommended.ProductCode = Stock.ProductCode AND
Stock.StatusCode = 1 WHERE (Recommended.Type = 'TOPICAL')
ORDER BY CHECKSUM(NEWID()))
AS Rec ON Items.ProductCode = Rec.ProductCode;
The above is valid syntax in MySQL, your mileage may vary...

Under those circumstances I would normally use an inner join to get the row filtering from the where clause I needed and the extra columns. Something like below; if this is what you did that gave you a performance hit then you might need to flip the query; go from recommended and join to items; as that will probably lead to more data filtering before the join.
SELECT Items.Name, tblBooks.AuthorLastName, tblBooks.AuthorFirstName
FROM Items
Inner Join
(
SELECT TOP 10 Recommended.ProductCode, SKUID
FROM
Recommended
INNER JOIN Stock ON Recomended.ProductCode = Stock.ProductCode
AND Stock.StatusCode = 1
WHERE (Recommended.Type = 'TOPICAL')
) reccomended
on items.productcode - reccomended.ProductCode
ORDER BY CHECKSUM(NEWID()

Related

How to do operations between a column and a subquery

I would like to know how I can do operations between a column and a subquery, what I want to do is add to the field Subtotal what was obtained in the subquery Impuestos, the following is the query that I am using for this case.
Select
RC.PURCHID;
LRC.VALUEMST as 'Subtotal',
isnull((
select sum((CONVERT(float, TD1.taxvalue)/100)*LRC1.VALUEMST ) as a
FROM TAXONITEM TOI1
inner join TAXDATA TD1 ON (TD1.TAXCODE = TOI1.TAXCODE and RC.DATAAREAID = TD1.DATAAREAID)
inner join TRANS LRC1 on (LRC1.VEND = RC.RECID)
WHERE TOI1.TAXITEMGROUP = PL.TAXITEMGROUP and RC.DATAAREAID = TOI1.DATAAREAID
), 0) Impuestos
from VEND RC
inner join VENDTABLE VTB on VTB.ACCOUNTNUM = RC.INVOICEACCOUNT
inner join TRANS LRC on (LRC.VEND = RC.RECID)
inner join PURCHLINE PL on (PL.LINENUMBER =LRC.LINENUM and PL.PURCHID =RC.PURCHID)
where year (RC.DELIVERYDATE) =2021 and RC.PURCHASETYPE =3 order by RC.PURCHID;
Hope someone can give me some guidance when doing operations with subqueries.
A few disjointed facts that may help:
When a SELECT statement returns only one row with one column, you can enclose that statement in parenthesis and use it as a plain value. In your case, let's say that select sum(......= TOI1.DATAAREAID returns 500. Then, your outer select's second column is equivalent to isnull(500,0)
You mention in your question "subquery Impuestos". Keep in mind that, although you indeed used a subquery as we mentioned earlier, by the time it was enclosed in parentheses it is not treated as a subquery (more accurately: derived table), but as a value. Thus, the "Impuestos" is only a column alias at this point
I dislike and avoid subqueries before the from, makes things much harder to read. Here is a solution with apply which will keep your code mostly intact:
Select
RC.PURCHID,
LRC.VALUEMST as 'Subtotal',
isnull(subquery1.a, 0) as Impuestos
from VEND RC
inner join VENDTABLE VTB on VTB.ACCOUNTNUM = RC.INVOICEACCOUNT
inner join TRANS LRC on (LRC.VEND = RC.RECID)
inner join PURCHLINE PL on (PL.LINENUMBER =LRC.LINENUM and PL.PURCHID =RC.PURCHID)
outer apply
(
select sum((CONVERT(float, TD1.taxvalue)/100)*LRC1.VALUEMST ) as a
FROM TAXONITEM TOI1
inner join TAXDATA TD1 ON (TD1.TAXCODE = TOI1.TAXCODE and RC.DATAAREAID = TD1.DATAAREAID)
inner join TRANS LRC1 on (LRC1.VEND = RC.RECID)
WHERE TOI1.TAXITEMGROUP = PL.TAXITEMGROUP and RC.DATAAREAID = TOI1.DATAAREAID
) as subquery1
where year (RC.DELIVERYDATE) =2021 and RC.PURCHASETYPE =3 order by RC.PURCHID;

How does this SQL query return results with same id_product?

I am facing a complex SQL query in some code, which is suppose to return products without duplicates (by the use of DISTINCT keywork at the beginning), here is the query:
SELECT DISTINCT p.`id_product`, p.*, product_shop.*, pl.* , m.`name` AS manufacturer_name, x.`id_feature` , x.`id_feature_value` , s.`name` AS supplier_name
FROM `ps_product` p
INNER JOIN ps_product_shop product_shop
ON (product_shop.id_product = p.id_product AND product_shop.id_shop = 1)
LEFT JOIN `ps_product_attribute` y ON (y.`id_product` = p.`id_product`)
LEFT JOIN `ps_product_attribute_combination` ac ON (y.`id_product_attribute` = ac.`id_product_attribute`)
LEFT JOIN `ps_product_lang` pl ON (p.`id_product` = pl.`id_product` AND pl.id_shop = 1 )
LEFT JOIN `ps_manufacturer` m ON (m.`id_manufacturer` = p.`id_manufacturer`)
LEFT JOIN `ps_feature_product` x ON (x.`id_product` = p.`id_product`)
LEFT JOIN `ps_supplier` s ON (s.`id_supplier` = p.`id_supplier`)
LEFT JOIN `ps_category_product` c ON (c.`id_product` = p.`id_product`)
WHERE pl.`id_lang` = 1 AND c.`id_category` = 18 AND p.`price` between 0 and 1000
AND product_shop.`visibility` IN ("both", "catalog") AND product_shop.`active` = 1
ORDER BY p.`id_product` ASC LIMIT 1,4
But it returns 4 product with 2 products with same "id_product" (11941)
What I need is to return 4 products but of different ids each.
Anyone ?
Thanks a lot
Aymeric
[EDIT]
The result of this query shows 4 rows, with 2 having the same exact columns values EXCEPT for the id_feature_value column which 36 for one and 38 for the other.
SELECT DISTINCT gets all the distinct combinations of all selected fields in your query, not just the first field.
Now, you could solve that by using GROUP BY to select only distinct values of id_product specifically, like:
SELECT p.`id_product`, p.*, product_shop.*, pl.* , m.`name` AS manufacturer_name, x.`id_feature` , x.`id_feature_value` , s.`name` AS supplier_name
FROM `ps_product` p
INNER JOIN ps_product_shop product_shop
ON (product_shop.id_product = p.id_product AND product_shop.id_shop = 1)
LEFT JOIN `ps_product_attribute` y ON (y.`id_product` = p.`id_product`)
LEFT JOIN `ps_product_attribute_combination` ac ON (y.`id_product_attribute` = ac.`id_product_attribute`)
LEFT JOIN `ps_product_lang` pl ON (p.`id_product` = pl.`id_product` AND pl.id_shop = 1 )
LEFT JOIN `ps_manufacturer` m ON (m.`id_manufacturer` = p.`id_manufacturer`)
LEFT JOIN `ps_feature_product` x ON (x.`id_product` = p.`id_product`)
LEFT JOIN `ps_supplier` s ON (s.`id_supplier` = p.`id_supplier`)
LEFT JOIN `ps_category_product` c ON (c.`id_product` = p.`id_product`)
WHERE pl.`id_lang` = 1 AND c.`id_category` = 18 AND p.`price` between 0 and 1000
AND product_shop.`visibility` IN ("both", "catalog") AND product_shop.`active` = 1
GROUP BY p.`id_product`
ORDER BY p.`id_product` ASC LIMIT 1,4
However, the problem now is that your query has multiple different values of all the other fields you are selected to choose from, and no deterministic way to pick from them. Even though the id_product is unique in it's table, it's not unique in the result set because in at least one of your JOINs there is a one-to-many relationship, meaning there are several rows that match the JOIN conditions.
On older versions of MySQL, it will just pick the first value it finds in this case, but on SQL Server it will actually error out and tell you that the remaining fields either have to be mentioned in the GROUP BY clause, or they have to be aggregated. So, you've got a few ways you can go from here:
You are on an old version of MySQL and you don't particularly care which values are returned for the rest of the fields, so leave the query as I've posted and use that. I wouldn't recommend this, as it's undefined behaviour so in theory it could change at MySQL's whim. All the values returned will be from the same result row though.
Add aggregate functions, such as MIN() or MAX() to the rest of the remaining fields in the select clause. This will reduce the possible values for the fields down to one, but you will probably end up with a mixture of values from different rows.
Remove any one-to-many JOINs from your query so that you only ever get one row back in the result set for each individual id_product. Then, fetch the remaining data you need in a separate query.
There may be other alternative solutions, but it depends a lot on which values you want returned for the rest of the rows and what RDBMS you are using. For example, on SQL Server you could potentially make use of PARTITION BY to select the first row for each distinct id_product deterministically.

SQL Count uses info from join

I need to count the amount of times InternalMenuLinkItemNumber appears per sitenumber and per order mode. Then i need to show MenuItemID and i do that with a inner join using item numbers, but when i add this join it skews the QTY result. I've tried using distinct in the COUNT but then all the QTY is 1. Please assist.
Query and result where QTY result is 100% correct but no MenuItemID.
SELECT ST_Sites.BusinessUnit,[ST_SalesMixTransactions_RealTimeFeed].SiteNumber,InternalMenuLinkItemNumber,[ST_SalesMix].MenuItemID,OrderMode,SellingPrice,COUNT(ST_SalesMixTransactions_RealTimeFeed.InternalMenuLinkItemNumber) as QTY
FROM ST_AlohaSalesMixTransactions_RealTimeFeed
inner join ST_Sites on ST_Sites.SiteNumber= [ST_SalesMixTransactions_RealTimeFeed].SiteNumber
where [ST_SalesMixTransactions_RealTimeFeed].BusinessDate between'2017-06-27'and'2017-07-03' and [ST_SalesMixTransactions_RealTimeFeed].SiteNumber = '1001006'
group by InternalMenuLinkItemNumber,[ST_SalesMixTransactions_RealTimeFeed].SiteNumber,OrderMode,SellingPrice,ST_Sites.BusinessUnit,[ST_SalesMix].MenuItemID
order by InternalMenuLinkItemNumber
Result where QTY comes out as expected:
If I add the inner join to get MenuItemID:
Query:
SELECT ST_Sites.BusinessUnit,[ST_SalesMixTransactions_RealTimeFeed].SiteNumber,InternalMenuLinkItemNumber,[ST_SalesMix].MenuItemID,OrderMode,SellingPrice,COUNT(ST_SalesMixTransactions_RealTimeFeed.InternalMenuLinkItemNumber) as QTY
FROM ST_AlohaSalesMixTransactions_RealTimeFeed
inner join ST_SalesMix on [ST_AlohaSalesMixTransactions_RealTimeFeed].InternalMenuLinkItemNumber= ST_SalesMix.ItemNumber
inner join ST_Sites on ST_Sites.SiteNumber= [ST_SalesMixTransactions_RealTimeFeed].SiteNumber
where [ST_SalesMixTransactions_RealTimeFeed].BusinessDate between'2017-06-27'and'2017-07-03' and [ST_SalesMixTransactions_RealTimeFeed].SiteNumber = '1001006'
group by InternalMenuLinkItemNumber,[ST_SalesMixTransactions_RealTimeFeed].SiteNumber,OrderMode,SellingPrice,ST_Sites.BusinessUnit,[ST_SalesMix].MenuItemID
order by InternalMenuLinkItemNumber
Result where QTY is now way off:
If I use distinct:
Query:
SELECT ST_Sites.BusinessUnit,[ST_SalesMixTransactions_RealTimeFeed].SiteNumber,InternalMenuLinkItemNumber,[ST_SalesMix].MenuItemID,OrderMode,SellingPrice,COUNT(distinct ST_SalesMixTransactions_RealTimeFeed.InternalMenuLinkItemNumber) as QTY
FROM ST_AlohaSalesMixTransactions_RealTimeFeed
inner join ST_SalesMix on [ST_AlohaSalesMixTransactions_RealTimeFeed].InternalMenuLinkItemNumber= ST_SalesMix.ItemNumber
inner join ST_Sites on ST_Sites.SiteNumber= [ST_SalesMixTransactions_RealTimeFeed].SiteNumber
where [ST_SalesMixTransactions_RealTimeFeed].BusinessDate between'2017-06-27'and'2017-07-03' and [ST_SalesMixTransactions_RealTimeFeed].SiteNumber = '1001006'
group by InternalMenuLinkItemNumber,[ST_SalesMixTransactions_RealTimeFeed].SiteNumber,OrderMode,SellingPrice,ST_Sites.BusinessUnit,[ST_SalesMix].MenuItemID
order by InternalMenuLinkItemNumber
Result for QTY is now all 1:
If I understand correctly, you want something like
SELECT SiteNumber, OrderMode, count([DISTINCT?] InternalMenuLinkItemNumber)
...
GROUP BY SiteNumber, OrderMode
You want to count the InternalMenuLinkItemNumber, so InternalMenuLinkItemNumber must not occur in the GROUP BY clause.
EDIT:
When using GROUP BY, the SELECT list may only contain columns also mentioned in the GROUP BY clause, or aggregate functions (on arbitrary columns).
Try this:
SELECT a.InternalMenuLinkItemNumber, a.SiteNumber, a.OrderMode, a.SellingPrice, a.BusinessUnit, a.MenuItemID, a.QTY, CASE WHEN MAX(b.MenuItemID) = MIN(b.MenuItemID) THEN MAX(b.MenuItemID) ELSE -1 END AS MenuItemID
FROM
(SELECT ST_Sites.BusinessUnit, [ST_SalesMixTransactions_RealTimeFeed].SiteNumber, InternalMenuLinkItemNumber, [ST_SalesMix].MenuItemID, OrderMode, SellingPrice, COUNT(ST_SalesMixTransactions_RealTimeFeed.InternalMenuLinkItemNumber) as QTY
FROM ST_AlohaSalesMixTransactions_RealTimeFeed
INNER JOIN ST_Sites on ST_Sites.SiteNumber = [ST_SalesMixTransactions_RealTimeFeed].SiteNumber
WHERE [ST_SalesMixTransactions_RealTimeFeed].BusinessDate between'2017-06-27'and'2017-07-03' and [ST_SalesMixTransactions_RealTimeFeed].SiteNumber = '1001006'
GROUP BY InternalMenuLinkItemNumber, [ST_SalesMixTransactions_RealTimeFeed].SiteNumber, OrderMode, SellingPrice, ST_Sites.BusinessUnit, [ST_SalesMix].MenuItemID
) a
INNER JOIN ST_SalesMix b ON a.InternalMenuLinkItemNumber = b.ItemNumber
GROUP BY a.InternalMenuLinkItemNumber, a.SiteNumber, a.OrderMode, a.SellingPrice, a.BusinessUnit, a.MenuItemID, a.QTY
ORDER BY a.InternalMenuLinkItemNumber
It works on the theory that your first query gives good counts, so keep that as it is (it's now the inner query) and then do the problematic join outside of it. Obviously there are many rows from ST_SalesMix for each properly counted row in the first query, so I'm grouping on the original group list but that means that you might get multiple MenuItemIDs. I'm checking for that in the CASE statement by testing the MAX and MIN MenuItemIDs - if they are the same return MAX(MenuItemID) otherwise I'm returning -1 as an error flag to indicate that there were multiple MenuItemIDs associated with this group. It might not be the most efficient method but I didn't have much to go on.
I hope this helps.
all is sorted now. Thanks to everyone.
#jwolf your suggested query was the answer.

Using COALESCE with JOIN on a different database column

Trying to populate the location column of a query and was hoping that the use of the COALESCE function would help me get what I want.
SELECT OrderItem.Code AS ItemCode, MAX(COALESCE(OrderItem.Location, [Picklist].[dbo].[ItemData].InventoryLocation)) AS Location, SUM(OrderItem.Quantity) AS Quantity, MAX(Store.StoreName) AS Store
FROM OrderItem
INNER JOIN [Order] ON OrderItem.OrderID = [Order].OrderID
INNER JOIN [Store] ON [Order].StoreID = [Store].StoreID
LEFT JOIN [AmazonOrder] ON [AmazonOrder].OrderID = [Order].OrderID
JOIN [Picklist].[dbo].[ItemData] ON [Picklist].[dbo].[ItemData].[InventoryNumber] = [OrderItem].[Code]
WHERE (CASE WHEN [Order].[LocalStatus] = 'Recently Downloaded' AND [AmazonOrder].FulfillmentChannel = 2 THEN 1
WHEN [Order].[LocalStatus] = 'Recently Downloaded' AND [Store].StoreName != 'Amazon' THEN 1 ELSE 0 END ) = 1
GROUP BY OrderItem.Code
ORDER BY ItemCode
There will not be a location when the Store is Amazon so I need to Join on another table in another database. I don't believe I'm using this correctly. Also I do get the right Location results returned if I use :
SELECT InventoryLocation From [Picklist].[dbo].[ItemData] WHERE InventoryNumber = 'L1201-2W-EA'
Perhaps this is more like the query that you want:
SELECT oi.Code AS ItemCode, COALESCE(oi.Location, id.InventoryLocation) AS Location,
oi.Quantity, s.StoreName AS Store
FROM OrderItem oi INNER JOIN
[Order] o
ON oi.OrderID = o.OrderID INNER JOIN
[Store]
ON o.StoreID = s.StoreID LEFT JOIN
AmazonOrder ao
ON ao.OrderID = o.OrderID JOIN
[Picklist].[dbo].[ItemData] id
ON id.InventoryNumber = oi.[Code]
WHERE o.LocalStatus = 'Recently Downloaded' AND
(ao.FulfillmentChannel = 2 OR s.StoreName <> 'Amazon')
ORDER BY ItemCode
Here are the changes:
Removed the aggregation. It does not seem to be part of the question.
Introduced table aliases, so the query is easier to write and to read.
Simplified the logic in the where clause.
As the comment above says, the max seems somewhat strange, an arbitrary aggregation no doubt due to one of the joins bringing back more information than you might of expected.
Then the statement has a few issues:
The coalesce is using two fields, neither if which is in a left join, only the AmazonOrder is left joined, so that seems a bit strange, that would only work if the first field in the coalesce (OrderItem.Location) is nullable - which it might be, there is no schema posted.
The left join itself is an inner join in disguise at present - within the where clause you have given explicit conditions on a field from that table - AND [AmazonOrder].FulfillmentChannel = 2 - if the record was actually missing the left join would return null for that field, and the where clause would then drop it out of the results. If you want this to properly work as a left join, any condition on fields from that table must move into the join condition, or the where clause itself must allow for that field being null (explicitly or using a coalesce.)
SELECT OrderItem.Code AS Code,
CASE WHEN (LEN(ISNULL(MAX([OrderItem].[Location]),'')) = 1)
THEN MAX([OrderItem].[Location])
ELSE MAX([Picklist].[dbo].[ItemData].InventoryLocation)
END AS Location,
SUM(OrderItem.Quantity) AS Quantity,
MAX(Store.StoreName) AS Store
FROM OrderItem
INNER JOIN [Order] ON OrderItem.OrderID = [Order].OrderID
INNER JOIN [Store] ON [Order].StoreID = [Store].StoreID
LEFT JOIN [AmazonOrder] ON [AmazonOrder].OrderID = [Order].OrderID
LEFT JOIN [Picklist].[dbo].[ItemData] ON [Picklist].[dbo].[ItemData].[InventoryNumber] = [OrderItem].[Code] OR
[Picklist].[dbo].[ItemData].[MediaCreator] = [OrderItem].[Code]
WHERE [Order].LocalStatus = 'Recently Downloaded' AND (AmazonOrder.FulfillmentChannel = 2 OR Store.StoreName <> 'Amazon')
GROUP BY OrderItem.Code
ORDER BY OrderItem.Code
Decided to go with case statement on location column route because I could not get COALESCE to work for me. Schema, some not all data, at SQLFiddle.
I guess if someone gets COALESCE to work I'll change the answer?
#Gordon Linoff I used the re-written WHERE clause because it looked cleaner than using the CASE statement. It worked and guessed there was a simpler way to go about it but was more worried about getting COALESCE to work. As for the Aliases sometimes I like to use them but in this case since there was a lot of tables I like to code out what I'm actually working in. Just my preference .

Error single-row subquery returns more than one row

This query returns collection of data. but I only need customer list.
select * from customer where cust_acnt_nbr = (select cust_acnt_nbr from Asset where dstrct_id = (select dstrct_id from dstrct where dstrct_nm = 'ATLANTA'))
please help me to get customer list
Use in instead of = if a subquery can return multiple rows:
select * from customer where cust_acnt_nbr IN
(select cust_acnt_nbr from Asset where dstrct_id IN
(select dstrct_id from dstrct where dstrct_nm = 'ATLANTA'))
I try to avoid subqueries as they can have poor performance and make it unclear what you're trying to achieve. You can usually convert it to a series of simple joins which are easier to read.
select c.*
from customer c
inner join Asset a on c.cust_acnt_nbr = a.cust_acnt_nbr
inner join dstrct d on d.dstrct_id =a.dstrct_id
where dstrct_nm = 'ATLANTA'