Count of Detail record for a Master table - sql

I have a Master Table and a Detail Table(like Categories and Products) in SQL server and some of the Categories Do not have Products.
I want to count Products of a Category and my Where condition is like this ProductID=100.
In Result i want to have 0 near the Categories that have not Products and the other Categories have Products count. the result must be only for ProductID=100 and the number off the Result is number off the Categories record.iwant to create a view and each time i run this query :
select * from -ViewName where ProductID=#newProductID

This could be done fairly simply in a query that doesn't use views - it would be something like:
select c.CategoryName, count(p.ProductID)
from Category c
left join Product p
on c.CategoryID = p.CategoryID and p.ProductID = 100
Note that the condition on ProductID has to be part of the join criteria, not in the where clause, otherwise the query will only return categories that include the specified product.
This could be done fairly inefficiently in a view, by using a cross join - something like:
create view vwCategoryProduct as
select c.CategoryName,
p.ProductID,
case when c.CategoryID = p.CategoryID then 1 else 0 end as ProductIncluded
from Category c
cross join Product p
- and then selecting from the view like so:
select * from vwCategoryProduct where ProductID = 100

Not sure I get you on all of that
but something like
Select Category_Name, IsNull(Count(Products.Category_ID),0)
From Categories
Outer join Products On Products.CategoryID = Categories.CategoryID
Where Products.ProductID = 100
Should get you away...

Try it this way:
select count(p.*) as 'Number of Products'
from Categories c
left outer join Products p on c.ProductID = p.ProductID and ProductID = 100

Related

How to write SQL Query (Get all Categories and get 10 Products on each Category)

I am very new to Database and I want to know raw sql query.
I have 2 tables Category and Product
Product table has foreign_key(category_id).
How can I get all categories with 10 products for each category? So I will display each category name and 10 products on first page.
SELECT *
FROM categories
INNER JOIN
(SELECT * FROM products ORDER BY price LIMIT 10) as p
ON
categories.id = p.category_id
And also want to query reviews table that product has. How can I join 2 tables(Category with Product(1), Review(2)
A simple way is a lateral join:
SELECT *
FROM categories c LEFT JOIN LATERAL
(SELECT p.*
FROM products p
WHERE c.id = p.category_id
ORDER_BY p.price
LIMIT 10
) p
ON 1 = 1

Row is listed twice in simple query

One row is Repeated twice and I can't seem to figure out why. I tried using Group by but couldn't figure that out either lol. Using Left outer Join, to list suppliers who have a discounted product, in the Northwind Database
Select *
From Suppliers s
Left Outer Join products p
On s.SupplierID = p.SupplierID
Where p.Discontinued = 1
You have two discontinued products for a supplier, and a row is created for each row in products that matches the join condition and the Discontinued = 1 predicate. You want something like that:
SELECT * FROM Suppliers s
WHERE EXISTS (SELECT 1
FROM Products p
WHERE p.SupplierID = s.SupplierID
AND p.Discontinued = 1)
You have two rows in one of those tables. You can determine which by querying both tables by themselves on that supplierId.
Now, to your query, by putting p.discontinued in the where, that join effectively becomes an inner join, so you should either flip it to an inner join or move that condition to the join.
To get suppliers with discontinued products, you can do this:
Select * from supplier where supplierId in (
select supplierId from products
where discontinued =1)
There is clearly a supplier that has multiple discontinued products.
If you want suppliers with at least one discounted product, then use exists:
select s.*
from suppliers s
where exists (select 1
from products p
where p.supplierid = s.supplierid and
p.Discontinued = 1
);
If you want the list of suppliers with the number of discontinued products, use join:
select s.*, p.num_discontinued
from supplier s join
(select p.supplierid, count(*) as num_discontinued
from products
where p.Discontinued = 1
group by p.supplierid
) p
on p.SupplierID = s.SupplierID ;
If you want the list of products that are discontinued with their suppliers, than use your query but change the left join to an inner join. An outer join is not necessary.

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

Conditionally joining tables

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.

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