Conditionally joining tables - sql

I want to create conditional join to a table in my T-SQL query. Tables used in this example are from Northwind database (with only one additional table ProductCategories)
Table Products and table Categories have many-to-many relationship, hence table ProductCategories comes into picture.
I need the sum of Quantity column on table OrderDetails for each of the products falling under certain category. So I have a query like one below
Select p.ProductName, Sum(od.Quantity) As Qty
From Products p
Join OrderDetails od On od.ProductID = p.ProductID
Join ProductCategories pc On pc.ProductID = p.ProductID
And pc.CategoryID = #CategoryID
Group By p.ProductName
#CategoryID is an optional parameter. So in case it's not supplied, the join to table ProductCategories will not be required
and query should look like one below
Select p.ProductName, Sum(od.Quantity) As Qty
From Products p
Join OrderDetails od On od.ProductID = p.ProductID
Group By p.ProductName
I want to achieve this without repeating the whole query with If conditions (as below)
If #CategoryID Is Null
Select p.ProductName, Sum(od.Quantity) As Qty
From Products p
Join OrderDetails od On od.ProductID = p.ProductID
Group By p.ProductName
Else
Select p.ProductName, Sum(od.Quantity) As Qty
From Products p
Join OrderDetails od On od.ProductID = p.ProductID
Join ProductCategories pc On pc.ProductID = p.ProductID
And pc.CategoryID = #CategoryID
Group By p.ProductName
This is simplified version of the query which has many other tables and conditions like ProductCategories. And will require multiple multiple If conditions and repetition of the query. I have also tried dynamically generating the query. It works but query is not readable at all.
Any solutions?
Thank you.

Try this, if you'll use properly parametrized query - there will be no performance impact but may be gain:
Select p.ProductName, Sum(od.Quantity) As Qty
From Products p
Join OrderDetails od On od.ProductID = p.ProductID
WHERE #CategoryID IS NULL OR EXISTS (SELECT * FROM ProductCategories WHERE CategoryID = #CategoryID AND ProductID = p.ProductID)
Group By p.ProductName
Actually
in your query if there can be multiple rows in ProductCategories for one row in OrderDetails - then you get duplicates of od.Quantity in your SUM - is it an intended behavior?

I believe you can left join the tables vs the implicit inner join you are doing.
In an inner join it matches the key on the source table with each instance of the key on the destination table.
Each instance of a match on the destination table generates a set of rows displaying the
match types.
With an outer join it will display the source row EVEN IF there is no matching row in the other table. If there is it will do essentially the same as an inner join and you'll get a row back for each instance of match. So you're getting the data you need when it's available and not getting what data is unavailable.
Take this as an example
select * from Products
Left join ProductAndCategory on ProductAndCategory.ProductID = Products.ProductID
left join Categories on Categories.CategoryID = ProductAndCategory.CategoryID
Where I have a simple Product table with a ProductID and a ProductName a ProductAndCategory table with a ProductID and a CategoryID and a Categories table with a CategoryID and a CategoryName
It will show the rows that have categories with the joined categories and the rows that don't have categories will just show the one row with null for the values that don't exist.

I'm not very familiar with T-SQL so I don't know if this will cut it but in mysql you could do something like
Select p.ProductName, Sum(od.Quantity) As Qty
From Products p
Join OrderDetails od On od.ProductID = p.ProductID
Join ProductCategories pc On pc.ProductID = p.ProductID
And pc.CategoryID = if(#CategoryID is null , pc.CategoryID, #CategoryId)
Group By p.ProductName
Yes and if() still remains but just "one query", if that's what you're looking for.
At the same time, you say you were able to dynamically generate a single query. If so is the readability that much of an issue? If your generated query was more performant over what I suggested above I'd go with that. It's generated; you won' be manually tweaking/reading the result.

Related

Query returns cartesian product when not expected

Task: Select all orders having products belonging to ‘Sea Food’ category.
Result: OrderNo, OrderDate, Product Name
I write this query but it returns Cartesian products.
select o.orderid, o.orderdate as "Order Date", p.productname , ct.categoryname from orders o,
order_details od , products p , customers c ,categories ct
where
od.orderid = o.orderid and p.productid = od.productid and ct.categoryid = p.categoryid
and ct.categoryname = 'Seafood';
Question: What is wrong with my query ?
You're doing a CROSS JOIN on customers table since you forgot to specify the connection. This is why you should use explicit JOIN syntax rather than old syntax using commas in WHERE clause.
After translating your query into explicit syntax, you will see that there is no WHERE condition involving customers table:
select
o.orderid,
o.orderdate as "Order Date",
p.productname,
ct.categoryname
from
orders o,
inner join order_details od on od.orderid = o.orderid
inner join products p on p.productid = od.productid
inner join categories ct on ct.categoryid = p.categoryid
cross join customers c -- either you don't need this table, or you need to specify conditions
where
ct.categoryname = 'Seafood'
Basically the reason you got it was that your where clause omitted join condition involving customers table, so you were left with:
from (...), customers -- cross join when joining condition not applied in where clause

SQL query with w3schools db

I should have asked multiple questions in my other post. Thanks to all who have helped, I am now stuck on another one..
Using the w3schools db, List SupplierID, SupplierName and ItemSupplied (count of number of items supplied by a supplier), sort the list first by number of items supplied (descending) and then by supplier name (ascending)
SELECT supplierid,
suppliername,
p.productname,
Count(s.supplierid) AS itemssupplied
FROM [Suppliers] AS s
INNER JOIN [Products] AS p
ON p.supplierid = s.supplierid
GROUP BY p.productid,
p.productname
ORDER BY Count (p.productid, p.productname) DESC
order BY s.suppliername
It's giving me an error, then again I am ordering by multiple ones. I think there's something I am not quite understanding here.
My other question is
List customers for each category and the total of order placed by that customer in a given category. In the query show three columnm: CategoryName, CustomerName, and TotalOrders (which is price * quantity for orders for a given customer in a given category). Sort this data in descending order by TotalOrders.
SELECT cg.CategoryName,
c.CustomerName,
Sum(p.Price * od.Quantity) AS TotalOrders
FROM [products] AS p
INNER JOIN [orderdetails] AS od
ON od.ProductID = p.ProductID
INNER JOIN [orders] AS o
ON o.OrderID = od.OrderID
INNER JOIN [customers] AS c
ON c.customerID = o.CustomerID
INNER JOIN [categories] AS cg
ON cg.CategoryID = p.CategoryID
GROUP BY c.CustomerName
ORDER BY TotalOrders DESC
Can someone please check if my query is correct? Thank you once again!
Question 1
You are really close but you only need to state ORDER BY once (also make sure to include all shown fields in your GROUP BY unless you are aggregating them):
SELECT SupplierID, SupplierName, p.ProductName, count(s.SupplierID) AS ItemsSupplied
FROM [Suppliers] AS s
INNER JOIN [Products] AS p ON p.SupplierID = s.SupplierID
GROUP BY p.ProductID, p.ProductName, SupplierID, SupplierName -- Added SupplierID, SupplierName
ORDER BY COUNT (p.productID, p.ProductName) DESC, s.SupplierName
Notice that you just place multiple sorts on the same line with a comma separating them.
Question 2
You're almost there but you need to group by any field that is not being aggregated. So in order not to get a parsing error, I added the cg.CategoryName to the GROUP BY line.
SELECT cg.CategoryName, c.CustomerName, Sum(p.Price*od.Quantity) AS TotalOrders
FROM [Products] AS p
INNER JOIN [OrderDetails] AS od ON od.ProductID = p.ProductID
INNER JOIN [Orders] AS o ON o.OrderID = od.OrderID
INNER JOIN [Customers] AS c ON c.customerID = o.CustomerID
INNER JOIN [Categories] AS cg ON cg.CategoryID = p.CategoryID
GROUP BY c.CustomerName, cg.CategoryName --Added CategoryName
ORDER BY TotalOrders DESC
You have several problems with the first query:
You're grouping by ProductID and ProductName even though you want the number of items supplied by a supplier, which means that you want to group by SupplierID and SupplierName.
You're supplying too many arguments to the COUNT function, which takes a single column name or *.
You've included a ProductName column in your results, which is not called for.
You need to ORDER BY both the number of products supplied and the SupplierName.
With those points in mind:
SELECT
s.SupplierID,
s.SupplierName,
COUNT(p.ProductID) AS ItemsSupplied
FROM
[Suppliers] AS s
INNER JOIN [Products] AS p ON p.SupplierID = s.SupplierID
GROUP BY
s.SupplierID, s.SupplierName
ORDER BY
ItemsSupplied DESC,
s.SupplierName ASC
Your second query is quite close, you're just missing one point, which is that you're looking for total of order placed by that customer in a given category. This means that in addition to grouping by c.CustomerName, you need to group by cg.CategoryID:
SELECT
cg.CategoryName,
c.CustomerName,
SUM(p.Price*od.Quantity) AS TotalOrders
FROM
[Products] AS p
INNER JOIN [OrderDetails] AS od ON od.ProductID = p.ProductID
INNER JOIN [Orders] AS o ON o.OrderID = od.OrderID
INNER JOIN [Customers] AS c ON c.customerID = o.CustomerID
INNER JOIN [Categories] AS cg ON cg.CategoryID = p.CategoryID
GROUP BY
c.CustomerName, cg.CategoryID
ORDER BY
TotalOrders DESC
The first one has two order by clauses
ORDER BY COUNT (p.productID, p.ProductName) DESC
and
ORDER BY s.SupplierName
also some databases will complain when order by columns for queries using group by are not included in the selected columns

I want to find that how much number of products sold for particular category in northwind database?

I have three main Tables
categories:
categoryID(primary Key)
CategoryName
Description.
OrderDetails
OrderID (primary Key)
ProductID
Quantity
Products
productId (Primary Key)
ProductName
CategoryID
Now I have to write a query to get number of products sold for particular category?
here is my attempt
SELECT Products.ProductID,
Products.CategoryID,
SUM(OrderDetails.Quantity)
FROM Products
LEFT INNER JOIN OrderDetails
ON (SELECT OrderDetails.Quantity,
orderDetails.ProductID,
categories.categoryName
FROM orderDetails
LEFT INNER JOIN Categories
ON categories.categoryID=products.CategoryId
)
ON Products.ProductId = OrderDetaqils.ProductId
i am not getting any answer close to my requirement.its not correct
please give me the solution if possible
It would be great help if you can give me the solution. If you required any additional details please let me know
here is the SQL query
SELECT Product.ProductID,
sum(Quantity) as TotalSoldAmount,
categories.CategoryID,
CategoryName
FROM OrderDetails
-- INNER JOIN over all 3 Tables
INNER JOIN Product ON Product.ProductID = OrderDetails.ProductID
INNER JOIN categories ON categories.CategoryID = Product.CategoryID
-- we need to group our result because we used sum()
GROUP BY categories.CategoryID,
CategoryName,
Product.ProductID
and her the sqlfiddle as prove

sql issue to query product name

I am new to SQL , I have been looking for on line resources but have not find anything yet to solve my problem. Basically i have three tables below:
ORDERTBL(ORDERID,ORDERDATE,ORDERSTATUS),
PRODUCT(PRODUCTID,PRODUCT_NAME),
ORDERLINKPRODUCTS(ORDERID,PRODUCTID,QUANTITY,ORDERLINKPRODUCTID)
I want to get all the product name from the products tables for a specific order which i should query from ORDERLINKPRODUCTS since it contains all the orders.
for instance please find test data available in 3 tables:
Table ORDERTBL:
ORDERID=1,ORDERDATE= 24May, ORDERSTATUS=Process
Table PRODUCTS:
PRODUCID =1 PRODUCT_NAME = spoon PRODUCID =2 PRODUCT_NAME = soap
Table :ORDERLINKPRODUCTS:
ORDERID=1 PRODUCTID = 1 QUANTITY=3 ORDERLINKPRODUCTID=1 ORDERID=1 PRODUCTID = 2 QUANTITY=1 ORDERLINKPRODUCTID=2
I am trying to make a select statements that display all the product name of an order. For instance display all product name of orderid=1 which will return spoon,soap.
Any suggestion how to do it please?
Thanks in advance.
You should use JOIN keyword in SQL to join the three tables to be able to access PRODUCTNAME and show that table data. More about Join clause
SELECT prod.PRODUCT_NAME
FROM ORDERTBL ord
INNER JOIN ORDERLINKPRODUCTS link ON ord.ORDERID = link.ORDERID
INNER JOIN PRODUCT prod ON link.PRODUCTID = prod.PRODUCTID
WHERE ord.ORDERID = 1
SQLfiddle Demo
You can find orders with products ilsted using this
SELECT O.ORDERID, RTRIM(
XMLAGG(XMLELEMENT(e, P.PRODUCT_NAME ||',') ORDER BY O.ORDERID).EXTRACT('//text()'), ',') as Products
FROM ORDERTBL O
INNER JOIN ORDERLINKPRODUCTS L
ON O.ORDERID = L.ORDERID
INNER JOIN PRODUCT P
ON L.PRODUCTID = P.PRODUCTID
GROUP BY O.ORDERID
and products listed with its quantity using this:
SELECT O.ORDERID, RTRIM(
XMLAGG(XMLELEMENT(e, P.PRODUCT_NAME || '(' || L.QUANTITY || '),') ORDER BY O.ORDERID).EXTRACT('//text()'), ',') as Products
FROM ORDERTBL O
INNER JOIN ORDERLINKPRODUCTS L
ON O.ORDERID = L.ORDERID
INNER JOIN PRODUCT P
ON L.PRODUCTID = P.PRODUCTID
GROUP BY O.ORDERID
You can add WHERE clause clause to filted orders as you whish.
SQL FIDDLE DEMO

How do I use SQL to select rows that have > 50 related rows in another table?

I've been trying to find out how to write this query in sql.
What I need is to find the productnames (in the products table) that have 50 or more orders (which are in the order table).
only one orderid is matched up to a productname at a time so when I try to count the orderid's it counts all of them.
I can get distinct productnames but once i add in the orderid's then it goes back to having multiple productnames.
I also need to count the number of customers (in the order table) that have ordered those products.
I need some serious help ASAP! if anyone could help me figure out how to figure this out that would be awesome!
Table: Products
`productname` in the form of a text like 'GrannySmith'
Table: Orders
`orderid` in the form of '10222'..etc
`custid` in the form of something like 'SMITH'
Assuming the orders table has a field that relates back to the products table named ProductId. The SQL would translate to:
SELECT p.ProductName, Count(*)
FROM Orders o
JOIN Products p
on o.ProductId = p.ProductId
GROUP BY p.ProductName HAVING COUNT(*) >= 50
The key is in the having component of the Group By clause. I hope this helps.
You might be missing an "Order Details" table - typically, an order has several order details, and each of the order details then maps to a product - something like the sample in Northwind:
In that case, your SQL query would be something like this: join the [Order Details] table to both the [Orders] and [Products] tables, group by the product ID and name, and count the OrderID's:
select
p.ProductID, p.ProductName, count(o.OrderID)
from
[order details] od
inner join
orders o on od.OrderID = o.OrderID
inner join
products p ON od.productID = p.ProductID
group by
p.ProductID, p.ProductName
having
count(o.OrderID) > 50