SQL Server need to find suppliers who supply the most different products - sql

I have two tables I need information from, Products and Suppliers. Both these tables have a SupplierID column I am trying to use to join them together to retrieve the right info.
The output I need is SupplierID and ContactName from the Suppliers table. The correct output should contain only two suppliers, so I attempted something like this, but ran into a conversion error converting nvarchar value to a data type int. I am not supposed to count how many products they supply but aggregate functions seem like the best method to me.
SELECT TOP 2 ContactName, COUNT(Products.SupplierID) AS Supply
FROM Products
LEFT JOIN Suppliers ON Suppliers.ContactName = Products.SupplierID
GROUP BY Products.SupplierID, Suppliers.ContactName
ORDER BY Supply;
I have tried many different queries but none will work. I am confused on how to join these tables without running into errors. All the products have a unique ProductID as well. The correct output should look something like this:
7 John Smith
12 John Sample

Both these tables have a SupplierID column I am trying to use to join them together to retrieve the right info
If so, you should be joining on that column accross tables.
Also, it is a good practice to use table aliases and prefix each column with the table it belongs to.
Another remark is that if you want suppliers that sell the most different products, then you want to order by descending count (not ascending).
Finally, if you want to left join, then you should start from the suppliers and then bring in the products, not the other way around.
Consider:
select top 2
s.SupplierID,
s.ContactName,
COUNT(*) as Supply
from Suppliers s
left join Products p on p.SupplierID = s.SupplierID
group by s.SupplierID, s.ContactName
order by Supply desc;

You're currently joining on two different fields:
on Suppliers.ContactName = Products.SupplierID
Presumably this should be as follows?
on Suppliers.SupplierID = Products.SupplierID

Related

How do I use the . properly with sql?

I am new to sql and have a question about joining 2 tables. Why is there a . in between customers.custnum in this example. What is its significance and what does it do?
Ex.
Select
customers.custnum, state, qty
From
customers
Inner join
sales On customers.custnum = sales.custnum
The . is to specify a column of a table.
Let's use your customer table; we could do:
SELECT c.custnum, c.state, c.qty FROM customers as c INNER JOIN
sales as s ON c.custnum = s.custnum
You don't really need the . unless two tables have columns with the same name.
In the below query, there are two tables being referred. One is CUSTOMERS another is STATE. Since both has same column CUSTNUM, we need a way to tell the database which CUSTNUM are we referring to. Same as there may be many Bob's, if so their last name is used for disambiguation.
I would consider the below style as more clearer. That's opinionated.
Select
cust.custnum, cust.state, s.qty
From
customers cust -- use alias for meaningful referencing, you may be self-joining, during that time you can use cust1, cust2 as aliases.
Inner join
sales as s On cust.custnum = s.custnum
Think of it as a way to categorize the hierarchical nature of the database. Within a DB, there are tables, and within tables there are columns. It's just a way of keeping track, especially if you are working with multiple tables that may have the same column name.
For example, a table called Sales and a table called Customers might both have a column called Date. You may be writing a query where you only want the date from the Sales table, so you would specify that by writing:
Select *
From Sales
inner join Customers on Sales.ID = Customers.ID
where Sales.Date = '1/1/2019'

SQL statement for many-to-many WHERE Item IN ALL results, not just one

Put into a simplified way, I have three tables: Products, ProductsCounties, and Counties. We can only sell certain products in certain counties, so there is a many-to-many relationship between Products and Counties with ProductsCounties.
To make it even simpler to ask (because I'm using split strings from a comma separated list in a stored procedure... among magic), let's say I just have a table called WantedCounties with CountyName's County1, County2, and County3 as records. This is basically the result of the split string function based on what I give it. My problem is, I want to give three counties (think WantedCounties is going to be what I'm giving) and that Product has to be available in ALL THREE and not just one, hence why my IN statement fails here.
SELECT DISTINCT p.* FROM Products p
JOIN ProductsCounties pc ON pc.ProductId = p.ProductId
JOIN Counties c ON pc.CountyId = c.CountyId
WHERE c.CountyName IN (SELECT CountyName FROM WantedCounties)
This is as much as I can narrow down my problem for sake of making it easy to ask. Does anyone know how to do this? I want only products that have a relationship with every single county given, not just one match IN the subquery.
I think my issue is I can't grasp how to deal with there being multiple rows being selected because of each county match on the join.
I have also tried something along the lines of = ALL (SELECT CountyName FROM WantedCounties) to no avail.
Edit: I found the solution here. I had to include this:
GROUP BY --All my refrences
HAVING COUNT(DISTINCT c.CountyName) = (
SELECT CountyName
FROM WantedCounties)
)
This only returns products that have a match in every County in WantedCounties. So there had to be all 3 counties in my results to match the 3 counties in WantedCounties in order for it to say "that's all of them."
I found the solution here. I had to include this:
GROUP BY --All my refrences
HAVING COUNT(DISTINCT c.CountyName) = (
SELECT CountyName
FROM WantedCounties)
)
This only returns products that have a match in every County in WantedCounties. So there had to be all 3 counties in my results to match the 3 counties in WantedCounties in order for it to say "that's all of them."

Get the min() value from columns where duplicates are associated with ids

So, to simplify my problem is as follows:
A. I pass in CustomerID, there can be multiple Orders per Customer. I need to find the minimum ProductID per Order per Customer.
B. Once that is complete, I need run the main query (to return the dataset) in which I would like to display all the Orders, along with Category and Code (per Product). The result should display NULL for Product, Category and Code where the ProductID <> the ProductID found in Part A.
Thanks
(All these are separate tables with foreign keys. I can handle the joins, it's just the "minimum ProductID per Order per Customer" thing that's throwing me.)
You did not provide much information about your specific database schema, so an exact answer is hard to provide. Basically, you want to use a combination of MIN and Group By (and perhaps a join or two depending on your schema).
The following provides a rough example:
select min(ProductID) from Orders o
inner join Customers c
on o.CustomerID = c.CustomerID
group by (productID)
having c.CustomerID = <customer id value>
Regards,

SQL Syntax for Complex Scenario (Deals)

i have a complex query to be written but cannot figure it out
here are my tables
Sales --one row for each sale made in the system
SaleProducts --one row for each line in the invoice (similar to OrderDetails in NW)
Deals --a list of possible deals/offers that a sale may be entitled to
DealProducts --a list of quantities of products that must be purchased in order to get a deal
now im trying to make a query which will tell me for each sale which deals he may get
the relevant fields are:
Sales: SaleID (PK)
SaleProducts: SaleID (FK), ProductID (FK)
Deals: DealID (PK)
DealProducts: DealID(FK), ProductID(FK), Mandatories (int) for required qty
i believe that i should be able to use some sort of cross join or outer join, but it aint working
here is one sample (of about 30 things i tried)
SELECT DealProducts.DealID, DealProducts.ProductID, DealProducts.Mandatories,
viwSaleProductCount.SaleID, viwSaleProductCount.ProductCount
FROM DealProducts
LEFT OUTER JOIN viwSaleProductCount
ON DealProducts.ProductID = viwSaleProductCount.ProductID
GROUP BY DealProducts.DealID, DealProducts.ProductID, DealProducts.Mandatories,
viwSaleProductCount.SaleID, viwSaleProductCount.ProductCount
The problem is that it doesn't show any product deals that are not fulfilled (probably because of the ProductID join). i need that also sales that don't have the requirements show up, then I can filter out any SaleID that exists in this query where AmountBought < Mandatories etc
Thank you for your help
I'm not sure how well I follow your question (where does viwSaleProductCount fit in?) but it sounds like you will want an outer join to a subquery that returns a list of deals along with their associated products. I think it would go something like this:
Select *
From Sales s Inner Join SaleProducts sp on s.SaleID = sp.SaleID
Left Join (
Select *
From Deals d Inner Join DealProducts dp on d.DealID = dp.DealId
) as sub on sp.ProductID = sub.ProductID
You may need to add logic to ensure that deals don't appear twice, and of course replace * with the specific column names you'd need in all cases.
edit: if you don't actually need any information from the sale or deal tables, something like this could be used:
Select sp.SaleID, sp.ProductID, sp.ProductCount, dp.DealID, dp.Mandatories
From SaleProducts sp
Left Join DealProducts as dp on sp.ProductID = dp.ProductID
If you need to do grouping/aggregation on this result you will need to be careful to ensure that deals aren't counted multiple times for a given sale (Count Distinct may be appropriate, depending on your grouping). Because it is a Left Join, you don't need to worry about excluding sales that don't have a match in DealProducts.

Select based on the number of appearances of an id in another table

I have a table B with cids and cities. I also have a table C that has these cids with extra information. I want to list all the cids in table C that are associated with ALL appearances of a given city in Table B.
My current solution relies on counting the number of times the given city appears in Table B and selecting only the cids that appear that many times. I don't know all the SQL syntax yet, but is there a way to select for this kind of pattern?
My current solution:
SELECT Agents.aid
FROM Agents, Customers, Orders
WHERE (Customers.city='Duluth')
AND (Agents.aid = Orders.aid)
AND (Customers.cid = Orders.cid)
GROUP BY Agents.aid
HAVING count(Agents.aid) > 1
It only works because I know right now with the HAVING statement.
Thanks for the help. I wasn't sure how to google this problem, since it's pretty specific.
EDIT: I'm pinpointing my problem a bit. I need to know how to determine if EVERY row in a table has a certain value for a field. Declaring a variable and counting the rows in a sub-selection and filtering out my results by IDs that appear that many times works, but It's really ugly.
There HAS to be a way to do this without explicitly count()ing rows. I hope.
Not an answer to your question, but a general improvement.
I'd recommend using JOIN syntax to join your tables together.
This would change your query to be:
SELECT Agents.aid
FROM Agents
INNER JOIN Orders
ON Agents.aid = Orders.aid
INNER JOIN Customers
ON Customers.cid = Orders.cid
WHERE Customers.city='Duluth'
GROUP BY Agents.aid
HAVING count(Agents.aid) > 1
What variant of SQL are you using?
To start with, you can (and should) use JOIN instead of doing it in the WHERE clause, e.g.,
select Agents.aid
from Agents
join Orders on Agents.aid = Orders.aid
join Customers on Customers.cid = Orders.cid
where Customers.city = 'Duluth'
group by Agents.aid
having count(Agents.aid) > 1
After that, I'm afraid I might be a little lost. Using the table names in your example query, what (in English, not pseudocode) are you trying to retrieve? For example, I think your sample query is retrieving the PK for all Agents that have been involved in at least 2 Orders involving Customers in Duluth.
Also, some table definitions for Agents, Orders, and Customers might help (then again, they might be irrelevant).
I'm not sure if I understood you problem, but I think the following query is what you want:
SELECT *
FROM customers b
INNER JOIN orders c USING (cid)
WHERE b.city = 'Duluth'
AND NOT EXISTS (SELECT 1
FROM customers b2
WHERE b2.city = b.city
AND b2.cid <> cid);
Probably you will need some indexes on these columns.