Integrating a SELECT in an already written SQL query - sql

So I have been given an SQL query which is pretty much above my level of expertise and I can't figure out how to add a new column as the output when the query is ran.
SELECT ProductId,Name as Provider FROM (
SELECT COALESCE(ByExternalImageId.ProductId,EANProductImage.ProductId) as ProductId, COALESCE(ByExternalImageId.ProductImageProviderId,EANProductImage.ProductImageProviderId) as ProductImageProviderId FROM Product
LEFT JOIN
(
SELECT Product.Id as ProductId,MIN(EANProductImage.ProductImageProviderId) as ProductImageProviderId FROM Product
INNER JOIN ProductImage as EANProductImage ON
EANProductImage.ExternalImageId = Product.EAN
INNER JOIN ProductImageAngle as EANProductImageAngle ON
EANProductImage.AngleId = EANProductImageAngle.Id
WHERE HasImage=1
GROUP BY Product.Id
) as EANProductImage ON
EANProductImage.ProductId = Product.Id
LEFT JOIN (
SELECT top 1 Product.Id as ProductId,MIN(ExternalProductImage.ProductImageProviderId) as ProductImageProviderId FROM Product
INNER JOIN ProductImage as ExternalProductImage ON
ExternalProductImage.ExternalImageId = Product.ExternalImageId
INNER JOIN ProductImageAngle as ExternalProductImageAngle ON
ExternalProductImage.AngleId = ExternalProductImageAngle.Id
WHERE HasImage=1
GROUP BY Product.Id
) as ByExternalImageId ON
ByExternalImageId.ProductId = Product.Id
WHERE COALESCE(ByExternalImageId.ProductImageProviderId,EANProductImage.ProductImageProviderId) IS NOT NULL
) as images INNER JOIN ProductImageProvider ON ProductImageProvider.Id = images.ProductImageProviderId where Name='GS1'
Right, so above is the query that I have been given. This outputs the following:
What I couldn't figure out is how can I also select column [Name] from dbo.Product and join it with the current output on ProductId? I know the basic syntax needed to do this, but I don't know where I should integrate it with the code above.
So what I would need is something like: SELECT Product.Name FROM Product inner join [the query above], eventually the output being like:
ProductId Provider Name
.... .... ....
I do realize how stupid this question sounds, but I've tried numerous ways on how to integrate this small thing into the query presented above. My problem is that I do not understand the code fully (especially because it was not written by me) in order to actually put it into practice.

You are very close you have to encapsulate the entire inner query with parens.
Using a Sub-Query
SELECT Product.Name,SubqueryAlias.* FROM Product inner join
(
[the query above]
)
AS SubqueryAlias ON SubqueryAlias.ProductID=Product.ProductID
Using a CTE
;WITH As MyCTE
(
[the query above]
)
SELECT Product.Name, M.Provider FROM Product
inner join MyCTE M ON M.ProductID=Product.ProductID

You can remove one layer of subselect to get the answer you want, it should be noted that Provider can only ever be 'GS1' as this is what your query filters on. I have removed any columns or joins which aren't used in your top query:
SELECT COALESCE(ByExternalImageId.ProductId,EANProductImage.ProductId) as ProductId,
Product.Name As Provider
FROM Product
LEFT JOIN
(
SELECT Product.Id as ProductId,
MIN(EANProductImage.ProductImageProviderId) as ProductImageProviderId
FROM Product
INNER JOIN ProductImage as EANProductImage
ON EANProductImage.ExternalImageId = Product.EAN
INNER JOIN ProductImageAngle as EANProductImageAngle
ON EANProductImage.AngleId = EANProductImageAngle.Id
WHERE HasImage=1
GROUP BY Product.Id
) as EANProductImage
ON EANProductImage.ProductId = Product.Id
LEFT JOIN
(
SELECT top 1 Product.Id as ProductId,
MIN(ExternalProductImage.ProductImageProviderId) as ProductImageProviderId
FROM Product
INNER JOIN ProductImage as ExternalProductImage
ON ExternalProductImage.ExternalImageId = Product.ExternalImageId
INNER JOIN ProductImageAngle as ExternalProductImageAngle
ON ExternalProductImage.AngleId = ExternalProductImageAngle.Id
WHERE HasImage=1
GROUP BY Product.Id
) as ByExternalImageId
ON ByExternalImageId.ProductId = Product.Id
WHERE COALESCE(ByExternalImageId.ProductImageProviderId,EANProductImage.ProductImageProviderId) IS NOT NULL AND
Product.Name='GS1'

Related

How to pick first or random row from multiple rows returned in SQL?

Code:
select product.code, product.description, salesorder.number, customer.name, 'Quantity' as Quantity, barcode.barcode
from salesorderline
join salesorder on salesorderline.salesorderid = salesorder.id
join product on salesorderline.productid = product.id
join barcode on product.id = barcode.productid
join customer on salesorder.customerid = customer.id
Output:
Image
Problem:
More than one codes are returned, because there is multiple barcodes attached to it.
Within the database, more than 1 barcode is attached to the codes.
How can I return just 1 random value of barcode instead of all? that are attached to the code.
SQL DATABASE:
Database Is stored in Microsoft SQL
What I have tried:
select product.code, product.description, salesorder.number, customer.name, 'Quantity' as Quantity, barcode.barcode
from salesorderline
join salesorder on salesorderline.salesorderid = salesorder.id
join product on salesorderline.productid = product.id
join barcode on product.id = barcode.productid
join customer on salesorder.customerid = customer.id
group by barcode.barcode
but the error is:
Error: Column 'product.code' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
In SQL Server
SELECT TOP 1 MyColumn FROM MyTable
ORDER BY NEWID()
There's no RAND in SQL server as far as i know
but you can use NEWID() which create a unique value which is basiclly the same as RAND()
More Info about NEWID() can be foudn at MSDN
I would recommend cross apply:
select p.code, p.description, so.number, c.name,
'Quantity' as Quantity, bc.barcode
from salesorderline sol join
salesorder so
on sol.salesorderid = so.id join
product p
on sol.productid = p.id join
customer c
on so.customerid = c.id cross apply
(select top (1) bc.*
from barcode bc
where p.id = bc.productid
order by newid()
) bc;
Technically, apply implements lateral joins. These are a very powerful type of join. In this case, the join is equivalent to a correlated subquery, but a correlated subquery that returns multiple columns.

How to add TOP 1 in query with left join in views?

I have 3 same product in ID=42, with 3 different images. I want to take the first image from the product ID, I try adding "TOP 1", error
This is my query
CREATE OR REPLACE VIEW UserOrdersView
AS
SELECT
u.[User_ID],
p.Product_Name,
p.Price,
o.Order_Price,
o.Order_ID,
i.[Image]
FROM Product p
LEFT JOIN Orders o ON o.Product_ID = p.Product_ID
INNER JOIN Users u ON u.[User_ID]= o.[User_ID]
LEFT JOIN Product_Images i ON i.Product_ID = p.Product_ID
WHERE o.[User_ID] = 42
You need to use OUTER APPLY to get top 1 image data from Product_image table based on Product ID.
Please check this Real life example, when to use OUTER / CROSS APPLY in SQL stackoverflow link for more knowledge.
Please check below updated view code for your answer.
CREATE OR REPLACE VIEW UserOrdersView
AS
BEGIN
SELECT
u.[User_ID],
p.Product_Name,
p.Price,
o.Order_Price,
o.Order_ID,
i.[Image]
FROM Product p
INNER JOIN Users u ON u.[User_ID]= o.[User_ID]
LEFT JOIN Orders o ON o.Product_ID = p.Product_ID
OUTER APPLY
(
SELECT TOP 1
T2.[Image]
FROM Product_Images T2
WHERE T2.Product_ID = p.Product_ID
) i
WHERE o.[User_ID] = 42
END
GO
WITH cte as (
SELECT
u.[User_ID],
p.Product_Name,
p.Price,
o.Order_Price,
o.Order_ID,
i.[Image],
ROW_NUMBER() OVER (PARTITION BY i.[Image] ORDER BY p.Product_Name) AS rn
FROM Product p
LEFT JOIN Orders o ON o.Product_ID = p.Product_ID
INNER JOIN Users u ON u.[User_ID]= o.[User_ID]
LEFT JOIN Product_Images i ON i.Product_ID = p.Product_ID
)
SELECT [User_ID],Product_Name,Price,Order_Price,Order_ID,[Image] FROM cte
WHERE rn=1
Put your all query inside a CTE with a new column that you will use to filter the results.
This new column is produced with ROW_NUMBER() function partitioned by Product_Name

Self join and inner join to remove duplicates

I am stuck on this and I am relatively new to SQL.
Here is the question we were given:
List the productname and vendorid for all products that we have
purchased from more than one vendor (Hint: you’ll need a Self-Join and
an additional INNER JOIN to solve, don't forget to remove any
duplicates!!)
Here is a screenshot of tables we are working with:
Here is what I have.....I know it is wrong. It works to a degree, just not exactly how the prof wants it.
SELECT DISTINCT productname, product_vendors.vendorid
FROM products INNER JOIN Product_Vendors
ON products.PRODUCTNUMBER = PRODUCT_VENDORS.PRODUCTNUMBER
INNER JOIN vendors ON Product_Vendors.VENDORID = vendors.VENDORID
ORDER BY products.PRODUCTNAME;
Expected output provided the prof:
I agree with #jarlh that additional information would be helpful- i.e. are there triplicates in the data or just duplicates, etc.
That said, this should get your started
SELECT
c.productname AS 'Product'
,a.vendorid AS 'Vendor1'
,b.vendorid AS 'Vendor2'
FROM
product_vendors AS a
JOIN
product_vendors AS b
ON
a.productnumber = b.productnumber
AND a.vendorid <> b.vendorid
JOIN
dbo.products AS c
ON
a.productnumber = c.productnumber
This will limit the population of 'Product Vendors' just to products with unmatching vendors.
From there you are joining to products to pull back product name.
Also- work on coding format, clean code makes the dream work :)
The solution to this problem is usually to count vendors per product with COUNT OVER and only stick with products with more than one. Simply:
select productname, vendorid
from
(
select
p.productname,
pv.vendorid,
count(*) over (partition by product) as cnt
from products p
join product_vendors pv using (productnumber)
)
where cnt > 1;
If this shall be done without window functions, then one option is to aggregate product_vendors and use this result:
select p.productname, pv.vendorid
from
(
select productid
from product_vendors
group by productname
having count(*) > 1
) px
join products p using (productid)
join product_vendors pv using (productid);
or check whether exists another vendor for the product:
select
p.productname,
pv.vendorid,
count(*) over (partition by product) as cnt
from products p
join product_vendors pv on pv.productnumber = p.productnumber
where exists
(
select *
from product_vendors other
where other.productnumber = pv.productnumber
and other.vendorid <> pv.vendorid
);
In neither of these approaches I see the need to eliminate duplicates, as there should be one row per product in products and one row per product and vendor in product_vendors. So I guess what your prof was thinking of is:
select distinct
p.productname,
pv.vendorid
from products p
join product_vendors pv on pv.productnumber = p.productnumber
join product_vendors other on other.productnumber = pv.productnumber
and other.vendorid <> pv.vendorid
This, however, is an approach I don't recommend. You'd combine all vendors for a product (e.g. with 10 vendors for one product you already have 45 combinations for that product only, if I'm not mistaken). So you'd create a large intermediate result only to dismiss most of it with DISTINCT later. Don't do that. Remember: SELECT DISTINCT is often an indicator for a poorly written query (i.e. unnecessary joins leading to too many combinations you are not actually interested in).
SELECT DISTINCT p.name AS product, v.id
FROM products p
INNER JOIN product_vendors pv ON p.id = pv.productid
INNER JOIN product_vendors pv2 ON pv.productid = pv2.productid AND pv.vendorid != pv2.vendorid
INNER JOIN vendors v ON v.id = pv.vendorid
ORDER BY p.name

Combine Two Tables in Select (SQL Server 2008)

If I have two tables, like this for example:
Table 1 (products)
id
name
price
agentid
Table 2 (agent)
userid
name
email
How do I get a result set from products that include the agents name and email, meaning that products.agentid = agent.userid?
How do I join for example SELECT WHERE price < 100?
Edited to support price filter
You can use the INNER JOIN clause to join those tables. It is done this way:
select p.id, p.name as ProductName, a.userid, a.name as AgentName
from products p
inner join agents a on a.userid = p.agentid
where p.price < 100
Another way to do this is by a WHERE clause:
select p.id, p.name as ProductName, a.userid, a.name as AgentName
from products p, agents a
where a.userid = p.agentid and p.price < 100
Note in the second case you are making a natural product of all rows from both tables and then filtering the result. In the first case you are directly filtering the result while joining in the same step. The DBMS will understand your intentions (regardless of the way you choose to solve this) and handle it in the fastest way.
This is a very rudimentary INNER JOIN:
SELECT
products.name AS productname,
price,
agent.name AS agentname
email
FROM
products
INNER JOIN agent ON products.agentid = agent.userid
I recommend reviewing basic JOIN syntax and concepts. Here's a link to Microsoft's documentation, though what you have above is pretty universal as standard SQL.
Note that the INNER JOIN here assumes every product has an associated agentid that isn't NULL. If there are NULL agentid in products, use LEFT OUTER JOIN instead to return even the products with no agent.
select p.name productname, p.price, a.name as agent_name, a.email
from products p
inner join agent a on (a.userid = p.agentid)
This is my join for slightly larger tables in Prod.Hope it helps.
SELECT TOP 1000 p.[id]
,p.[attributeId]
,p.[name] as PropertyName
,p.[description]
,p.[active],
a.[appId],
a.[activityId],
a.[Name] as AttributeName
FROM [XYZ.Gamification.V2B13.Full].[dbo].[ADM_attributeProperty] p
Inner join [XYZ.Gamification.V2B13.Full].[dbo].[ADM_activityAttribute] a
on a.id=p.attributeId
where a.appId=23098;
select ProductName=p.[name]
, ProductPrice=p.price
, AgentName=a.[name]
, AgentEmail=a.email
from products p
inner join agent a on a.userid=p.agentid
If you don't want to use inner join (or don't have possibility to do it!) and would combine rows, you can use a cross join :
SELECT *
FROM table1
CROSS JOIN table2
or simply
SELECT *
FROM table1, table2

Using sum with a nested select

I'm using SQL Server. This statement lists my products per menu:
SELECT menuname, productname
FROM [web].[dbo].[tblMenus]
FULL OUTER JOIN [web].[dbo].[tblProductsRelMenus]
ON [tblMenus].Id = [tblProductsRelMenus].MenuId
FULL OUTER JOIN [web].[dbo].[tblProducts]
ON [tblProductsRelMenus].ProductId = [tblProducts].ProductId
LEFT JOIN [web].[dbo].[tblOrderDetails]
ON ([tblProducts].Id = [tblOrderDetails].ProductId)
GROUP BY [tblProducts].ProductName
Some products don't have menus and vice versa. I use the following to establish what has been sold of each product.
SELECT [tblProducts].ProductName, SUM([tblOrderDetails].Ammount) as amount
FROM [web].[dbo].[tblProducts]
LEFT JOIN [web].[dbo].[tblOrderDetails]
ON ([tblProducts].ProductId = [tblOrderDetails].ProductId)
GROUP BY [tblProducts].ProductName
What I want to do is complement the top table with an amount column. That is, I want a table with the same number of rows as in the first table above but with an amount value if it exists, otherwise null.
I can't figure out how to do this. Any suggestions?
If I am not missing anything, the second query could be simplified, then incorporated into the first query like this:
SELECT
m.menuname,
p.productname,
t.amount
FROM [web].[dbo].[tblMenus] m
FULL JOIN [web].[dbo].[tblProductsRelMenus] pm ON m.Id = pm.MenuId
FULL JOIN [web].[dbo].[tblProducts] p ON pm.ProductId = p.ProductId
LEFT JOIN (
SELECT ProductId, SUM(Amount) as amount
FROM [web].[dbo].[tblOrderDetails]
GROUP BY ProductId
) t ON p.ProducId = t.ProductId