Use Count() with self join in SQL Server - sql-server

I'm working with the northwind database and my exercise is:
Which suppliers offer two products in the same category? Show company name, category and the both product names
My code:
SELECT DISTINCT
c.CategoryID, s.CompanyName, p1.ProductName, p2.ProductName
FROM
Suppliers s
INNER JOIN
Products p1 ON s.SupplierID = p1.SupplierID
INNER JOIN
Products p2 ON p1.CategoryID = p2.CategoryID
AND p1.ProductID <> p2.ProductID
INNER JOIN
Categories c ON p2.CategoryID = c.CategoryID
GROUP BY
c.CategoryID,s.CompanyName, p1.ProductName, p2.ProductName`
How can I filter that with COUNT() I tried to do it with HAVING but I failed.
I'll appreciate some help, which bringing me back on the right way.

Building on Gordon's answer the code below will get all the data you need. If you absolutely have to have both products in the same row, you can use pivot:
select s.CompanyName
,p.ProductName
from Suppliers s
-- This join filters your Suppliers table to only those with two Products in the same Category
inner join (select SupplierID
,CategoryID
from Products
group by SupplierID
,CategoryID
having count(1) = 2
) pc
on(s.SupplierID = pc.SupplierID)
-- This join returns the two products in the Category returned in the join above.
inner join Products p
on(s.SupplierID = p.SupplierID
and pc.CategoryID = p.CategoryID
)

You can get the list of suppliers/categories with exactly two products using a query like this:
select supplierId, categoryId
from products
group by supplierId, categoryId
having count(*) = 2;
Then, write a query to show the supplier and products names, and use the above to filter the results from that query. You can use either exists or an additional join.

Related

Avoid select data from other tables

I'm coding a movies web app, for showing some tags I'm trying to write a query that returns the movie name and the count of how many categorys has assigned it. I'm trying to add a filter that for example: if X movie contains a "Comedy" category this movie doesn't even need to be consider in my query.
At the moment this is the query that I have:
SELECT A.*, B.*, C.* -- A.name, count(C.name) [Categories]
FROM movies A
INNER JOIN moviesGenres B ON A.id = B.movieId
INNER JOIN genres C ON B.genreId = C.id
WHERE C.name <> 'Comedy'
-- group by A.name, C.name
-- having count(C.name) > 2
At the moment this query is working to return the expected output. But if you run that query with the data in this SQL Fiddle you'll see that is considering the movie "Bad Boys" but this movie has assigned one "Comedy" category so any data from this movie should't be considered.
You need to put that condition in the having clause wich is the where clause of a group.
SELECT m.name, count(g.name) [Genres]
FROM movies m
INNER JOIN moviesGenres mg ON m.id = mg.movieId
INNER JOIN genres g ON mg.genreId = g.id
GROUP BY m.name
HAVING sum(case when g.name = 'Comedy' then 1 else 0 end) = 0
If you really want to select columns from [moviesGenres] and [genres], go for juergen's answer. If you don't need them, anti join them:
SELECT A.*, B.*, C.* -- A.name, count(C.name) [Categories]
FROM
movies A
WHERE NOT EXISTS
(
SELECT 1
FROM
moviesGenres Bneg
inner join genres Cneg ON Bneg.genreId = Cneg.id
WHERE
A.id = Bneg.movieId
and Cneg.name = 'Comedy'
)

Count relationed items

I have a query like this:
SELECT
*
FROM Product.Stock AS PS
INNER JOIN Product.Product AS P ON PS.ProductId = P.ProductId
INNER JOIN Product.ProductDetail AS PD ON P.ProductId = PD.ProductId
INNER JOIN Product.ProductSize AS PSI ON P.ProductId = PSI.ProductId
I want to know how many ProductsId with same SizeId I have, for example:
in this case I have two products with same ProductId and SizeId, so I wish get: 2 because I have 2 products with sizeId 1
ProductId comes from table: Product.Product
SizeId comes from table: Product.ProductSize
How can I achieve it? Regards
SELECT
PSI.SizeId, COUNT(DISTINCT P.ProductId)
FROM Product.Stock AS PS
INNER JOIN Product.Product AS P ON PS.ProductId = P.ProductId
INNER JOIN Product.ProductDetail AS PD ON P.ProductId = PD.ProductId
INNER JOIN Product.ProductSize AS PSI ON P.ProductId = PSI.ProductId
GROUP BY PSI.SizeId
I'm assuming that the SizeId column is in the Product.ProductSize table.
Agree with #Jack's answer, still I think joins seem to be redundant here.
SELECT
SizeId, COUNT(DISTINCT ProductId)
FROM Product.ProductSize
GROUP BY SizeId
Use Having Count in order to just show sizes with multiple products and no need for inner joins, since you already have all the fields in one table:
SELECT SizeId, COUNT(Distinct ProductId)
FROM Product.ProductSize
GROUP BY SizeId
Having Count(Distinct ProductId)> 1

SQL UNION INNER JOIN

Im trying to select from 2 tables that have the same columns, but both tables have an inner join -
select e.ID,
c.FullName,
car.VIN,
car.Registration,
e.Telephone,
e.Mobile,
e.Email,
e.EstimateTotal,
e.LastUpdated,
e.LastUpdateBy from (select id from Estimates UNION ALL select id from PrivateEstimates) e
inner join Customers c on c.ID = e.CustomerID
inner join Cars car on car.ID = e.CarID
where e.Status = 0
The trouble is, it can't find e.CustomerID, e.CarID or e.Status on the inner join? Any ideas?
Your subquery
select id from Estimates
union all
select id from PrivateEstimates
returns only a single id column. Include necessary columns in the subquery if you want to use those columns in JOIN statements

SQL Server SELECT composite table

I have the below query:
SELECT p.id as prod_id, * FROM products AS p
LEFT JOIN Product_UPC AS UPC ON UPC.ProductID = p.id
LEFT JOIN Brands AS b ON p.brand = b.id
LEFT JOIN productCategoryLink AS c ON c.ProductID = p.id
WHERE (p.id = '$this->prod_id')
A product can be assigned to multiple categories and therefore I have a composite table consisting of product and category IDs. I want to amend the above query so that it only brings out one row of data as at the moment it bring out multiple depending on how many categories there are in the composite table. I would like to somehow have the rows of category IDs brought out and added to the one row.
eg.
id | name | desc | category1| category2| category3 | price
Is this possible? If so how?
You could try this to obtain categories in one column (you can split them later):
SELECT p.id as prod_id, * FROM products AS p
GROUP_CONCAT(c.category),
LEFT JOIN Product_UPC AS UPC ON UPC.ProductID = p.id
LEFT JOIN Brands AS b ON p.brand = b.id
LEFT JOIN productCategoryLink AS c ON c.ProductID = p.id
WHERE (p.id = '$this->prod_id')
If you want to get all products, change last row with this
GROUP BY p.id

SQL Complex Select - Trouble forming query

I have three tables, Customers, Sales and Products.
Sales links a CustomerID with a ProductID and has a SalesPrice.
select Products.Category, AVG(SalePrice) from Sales
inner join Products on Products.ProductID = Sales.ProductID
group by Products.Category
This lets me see the average price for all sales by category. However, I only want to include customers that have more than 3 sales records or more in the DB.
I am not sure the best way, or any way, to go about this. Ideas?
You haven't mentioned the customer data anywhere so I'll assume it's in the Sales table
You need to filter and restrict the Sales table first to the customers with more the 3 sales, then join to get product category and get the average across categories
select
Products.Category, AVG(SalePrice)
from
(SELECT ProductID, SalePrice FROM Sales GROUP BY CustomerID HAVING COUNT(*) > 3) S
inner join
Products on Products.ProductID = S.ProductID
group by
Products.Category
I'd try the following:
select Products.Category, AVG(SalePrice) from Sales s
inner join Products on Products.ProductID = s.ProductID
where
(Select Count(*) From Sales Where CustomerID = s.CustomerID) > 3
group by Products.Category
I'd create a pseudo-table of "big customer IDs" with a select, and then join it to your query to limit the results:
SELECT Products.Category, AVG(SalePrice) FROM Sales
INNER JOIN Products ON Products.ProductID = Sales.ProductID
INNER JOIN (
SELECT CustomerID FROM Sales WHERE COUNT(CustomerID) >= 3 GROUP BY CustomerID
) BigCustomer ON Sales.CustomerID = BigCustomer.CustomerID
GROUP BY Products.Category
Too lazy to test this out though, so let me know if it works ;o)
Another way
;WITH FilteredSales AS
(
SELECT Products.Category, Sales.SalesPrice, COUNT(Sales.CustomerId) OVER(PARTITION BY Sales.CustomerId) AS SaleCount
FROM Sales
INNER JOIN Products ON Products.ProductID = Sales.ProductID
)
select Category, AVG(SalePrice)
from FilteredSales
WHERE SaleCount > 3
group by Category

Resources