Gross sales of top ten customers that made at least 5 purchases of some Category in a specified year - sql

I need to get (sales.customers) | year | gross_sales of top ten customers that made at least 5 purchases of Category "Beverages" in the year 2014. I have already written these SELECT queries, but since I am new to SQL, I think I am very inefficient in writing code. This does not work properly and there is probably a simpler way of doing it. I have also pinned a picture of an ER diagram .
SELECT
T6.COMPANYNAME, YEAR, GROSS_SALES
FROM
(SELECT T1.CUSTID
FROM
(SELECT R.CUSTID, COUNT(R.CUSTID) AS NUMBEROFSALES
FROM SALES.ORDERDETAILS O
RIGHT JOIN PRODUCTION.PRODUCTS P ON P.PRODUCTID = O.PRODUCTID
RIGHT JOIN SALES.ORDERS R ON R.ORDERID = O.ORDERID
INNER JOIN SALES.CUSTOMERS C1 ON R.CUSTID = C1.CUSTID
INNER JOIN PRODUCTION.CATEGORIES C2 ON P.CATEGORYID = C2.CATEGORYID
WHERE C2.CATEGORYNAME = 'Beverages' AND YEAR(R.ORDERDATE) = 2014
GROUP BY R.CUSTID
ORDER BY SUM(R.CUSTID) DESC) T1
--HAVING COUNT(R.CUSTID) > 5
RIGHT JOIN
(SELECT R.CUSTID, SUM(O.UNITPRICE) AS MONEYSPENT
FROM SALES.ORDERDETAILS O
RIGHT JOIN PRODUCTION.PRODUCTS P ON P.PRODUCTID = O.PRODUCTID
RIGHT JOIN SALES.ORDERS R ON R.ORDERID = O.ORDERID
INNER JOIN SALES.CUSTOMERS C1 ON R.CUSTID = C1.CUSTID
INNER JOIN PRODUCTION.CATEGORIES C2 ON P.CATEGORYID = C2.CATEGORYID
WHERE C2.CATEGORYNAME = 'Beverages' AND YEAR(R.ORDERDATE) = 2014
GROUP BY R.CUSTID
ORDER BY SUM(O.UNITPRICE) DESC) T2 ON T1.CUSTID = T2.CUSTID
ORDER BY T1.NUMBEROFSALES DESC
LIMIT 10) T5
INNER JOIN
(SELECT DISTINCT(T4.COMPANYNAME), T4.CUSTID, YEAR, GROSS_SALES
FROM
(SELECT R.CUSTID AS CUSTID, YEAR(R.ORDERDATE) AS YEAR, SUM(O.UNITPRICE * O.QTY * (1 - O.DISCOUNT)) AS GROSS_SALES
FROM SALES.ORDERDETAILS O
RIGHT JOIN SALES.ORDERS R ON R.ORDERID = O.ORDERID
INNER JOIN SALES.CUSTOMERS C1 ON R.CUSTID = C1.CUSTID
GROUP BY R.CUSTID, YEAR(R.ORDERDATE)
ORDER BY YEAR(R.ORDERDATE)) T3
INNER JOIN
(SELECT C.COMPANYNAME, C.CUSTID
FROM SALES.ORDERS R
INNER JOIN SALES.CUSTOMERS C ON R.CUSTID = C.CUSTID) T4 ON T3.CUSTID = T4.CUSTID) T6 ON T5.CUSTID = T6.CUSTID

I'm not going to try and fix your code or explain the misakes there, as there are many of them. Instead based on your requirements I wrote a query that solves the problem. I show it in steps below which should make clear the process I used to solve the problem.
First how do we find the top ten customers that made 5 purchases of Beverages?
Take customer table and join to orders with beverages (inner join will exclude customers that don't meet criteria)
SELECT CUSTOMERID
FROM CUSTOMERS C
JOIN ORDERS O ON C.CUSTOMERID = O.CUSTOMERID
JOIN ORDER_DETAILS OD ON O.ORDERID = OD.ORDERID
JOIN PRODUCTS P ON OD.PRODUCTID = P.PRODUCTID
JOIN CATEGORIES C ON P.CATEGORYID = C.CATEGORYID AND C. CATEGORY_NAME = 'Beverages'
WHERE YEAR(ORDER_DATE) = 2014
GROUP BY CUSTOMERID
HAVING COUNT(ORDER_DETAILS) >= 5
Now we need the sum of order (for 2014) by customers which looks like this:
SELECT CUSTOMERID, YEAR(ORDER_DATE) AS YEAR, SUM(OD.UNIT_PRICE*OD.QUANTITY) AS TOTAL_SPEND
FROM CUSTOMERS C
JOIN ORDERS O ON C.CUSTOMERID = O.CUSTOMERID
JOIN ORDER_DETAILS OD ON O.ORDERID = OD.ORDERID
WHERE YEAR(ORDER_DATE) = 2014
GROUP BY CUSTOMERID, YEAR(ORDER_DATE)
Now we just combine these two queries like this:
SELECT CUSTOMERID, YEAR(ORDER_DATE) AS YEAR, SUM(OD.UNIT_PRICE*OD.QUANTITY) AS TOTAL_SPEND
FROM CUSTOMERS C
JOIN ORDERS O ON C.CUSTOMERID = O.CUSTOMERID
JOIN ORDER_DETAILS OD ON O.ORDERID = OD.ORDERID
JOIN (
SELECT CUSTOMERID
FROM CUSTOMERS C
JOIN ORDERS O ON C.CUSTOMERID = O.CUSTOMERID
JOIN ORDER_DETAILS OD ON O.ORDERID = OD.ORDERID
JOIN PRODUCTS P ON OD.PRODUCTID = P.PRODUCTID
JOIN CATEGORIES C ON P.CATEGORYID = C.CATEGORYID AND C. CATEGORY_NAME = 'Beverages'
WHERE YEAR(ORDER_DATE) = 2014
GROUP BY CUSTOMERID
HAVING COUNT(ORDER_DETAILS) >= 5
) as SUB ON SUB.CUSTOMERID = C.CUSTOMERID
WHERE YEAR(ORDER_DATE) = 2014
GROUP BY CUSTOMERID, YEAR(ORDER_DATE)
ORDER BY SUM(OD.UNIT_PRICE*OD.QUANTITY)
LIMIT 10
Note I did not test this but just wrote the SQL since I don't have a db to test against so there might be typos
Also Note: I'm expect it is possible to remove the sub query as it is doing a lot of the same joins the outer query is-- but we want to make sure we get the correct result and it is easier to see it is correct this way. You can also test the sub-query by itself to make sure it returns expected results.

Related

Is there a better way to write this code without having to use so many table queries?

am new to this coding lark and dont want to tell you how long it took me to get the output below... it does do what i wanted but im sure there must be a more efficient way to achieve it, ive spent that long on it now though that i cant see anything else!
WITH
Italian_Sales as(
SELECT sum(od.quantityOrdered) as total_it, od.productCode as product_it
FROM orderdetails as od
JOIN orders as o
ON od.orderNumber = o.orderNumber
JOIN customers as c
ON o.customerNumber = c.customerNumber
JOIN products as p
ON p.productCode = od.productCode
WHERE c.country in ("Italy")
GROUP BY product_it
),
Spanish_Sales as(
SELECT sum(od.quantityOrdered) as total_sp, od.productCode as product_sp
FROM orderdetails as od
JOIN orders as o
ON od.orderNumber = o.orderNumber
JOIN customers as c
ON o.customerNumber = c.customerNumber
JOIN products as p
ON p.productCode = od.productCode
WHERE c.country in ("Spain")
GROUP BY product_sp
),
French_Sales as(
SELECT sum(od.quantityOrdered) as total_fr, od.productCode as product_fr
FROM orderdetails as od
JOIN orders as o
ON od.orderNumber = o.orderNumber
JOIN customers as c
ON o.customerNumber = c.customerNumber
JOIN products as p
ON p.productCode = od.productCode
WHERE c.country in ("France")
GROUP BY product_fr
),
UK_Sales as(
SELECT sum(od.quantityOrdered) as total_uk, od.productCode as product_uk
FROM orderdetails as od
JOIN orders as o
ON od.orderNumber = o.orderNumber
JOIN customers as c
ON o.customerNumber = c.customerNumber
JOIN products as p
ON p.productCode = od.productCode
WHERE c.country in ("UK")
GROUP BY product_uk
),
Total_Sales as(
SELECT sum(od.quantityOrdered) as total_sales, od.productCode as product_total
FROM orderdetails as od
JOIN orders as o
ON od.orderNumber = o.orderNumber
JOIN customers as c
ON o.customerNumber = c.customerNumber
JOIN products as p
ON p.productCode = od.productCode
WHERE c.country in ("Spain", "France", "UK", "Italy")
GROUP BY product_total
)
SELECT p.productCode, p.productLine, p.productName, ts.total_sales as "Total Sales", its.total_it as "Italian Sales",
ss.total_sp as "Spanish Sales", fs.total_fr as "French Sales", us.total_uk as "UK Sales"
FROM products as p
LEFT JOIN Italian_Sales as its
ON p.productCode = product_it
LEFT JOIN Spanish_Sales as ss
ON p.productCode = product_sp
LEFT JOIN French_Sales as fs
ON p.productCode = product_fr
LEFT JOIN UK_Sales as us
ON p.productCode = product_uk
LEFT JOIN Total_Sales as ts
ON p.productCode = product_total
GROUP BY p.productCode
ORDER BY ts.total_sales DESC;
the desired output is below with the sales for each country and the total amount but also showing null values where applicable (this is what took me ages before i remembered left join)
enter image description here
This is a pretty standard PIVOT, especially given the aggregate, however there is an even simpler method you can use... SUM(CASE)
All of your sub-queries are identical except for the filter, so we can use the filter as the condition for a CASE statement and can return the value we want to count only when the row matches the filter criteria and return a zero or null for all other records. So we replace each subquery with a separate CASE:
SELECT p.productCode, p.productLine, p.productName
, SUM(od.quantityOrdered) AS [Total Sales]
, SUM(CASE c.country WHEN 'Italy' THEN od.quantityOrdered END) AS [Italian Sales]
, SUM(CASE c.country WHEN 'Spain' THEN od.quantityOrdered END) AS [Spanish Sales]
, SUM(CASE c.country WHEN 'France' THEN od.quantityOrdered END) AS [French Sales]
, SUM(CASE c.country WHEN 'UK' THEN od.quantityOrdered END) AS [UK Sales]
FROM products p
LEFT OUTER JOIN orderdetails od ON p.productCode = od.productCode
LEFT OUTER JOIN orders o ON od.orderNumber = o.orderNumber
LEFT OUTER JOIN customers c ON o.customerNumber = c.customerNumber
GROUP BY p.productCode, p.productLine, p.productName
When you start to get too many terms, an actual PIVOT query might be simpler, but for now this is very manageable.

Find Top 5 Customers for Beverages based on their total purchase value SQL

Here is the link to the Data Set.
https://www.w3schools.com/sql/trysql.asp?filename=trysql_asc
I have been trying to solve this but couldn't find a way to get the total purchase value while grouping with the customer table
I would recommend using a Common Table Expression (CTE) as, in my experience, it helps with scalability/maintenance down the road and easily enables you to see what the data is under the hood if you wanted to simply run the CTE itself.
I join the Customer to the Order to get the OrderID
I join the Order to OrderDetails to get the ProductID and Order Quantity
I join the OrderDetails to Products to get the Price
I join the Categories to filter for just Beverages
All this is wrapped as a CTE (similar to a subquery), on top of which I can now aggregate at the Customer level and sequence by Order Value in a descending fashion.
with beverage_orders_cte as(
SELECT c.CustomerName, o.OrderID
, od.OrderDetailID, od.ProductID, od.Quantity
, p.ProductName, p.Price
, od.Quantity * p.Price as OrderVal
,cat.CategoryName FROM Customers c
inner join Orders o
on c.CustomerID = o.CustomerID
inner join OrderDetails od
on o.OrderID = od.OrderID
inner join Products p
on od.ProductID = p.ProductID
inner join Categories cat
on p.CategoryID = cat.CategoryID and cat.CategoryID = 1
)
select CustomerName, SUM(OrderVal) as Revenue
From beverage_orders_cte
Group by CustomerName
Order by Revenue desc
Limit 5
Hope this helps, good luck.
Something like that?
SELECT c.customerid,
Sum(p.price)
FROM customers AS c
INNER JOIN orders AS o
ON o.customerid = c.customerid
INNER JOIN orderdetails AS od
ON od.orderid = o.orderid
INNER JOIN products AS p
ON p.productid = od.productid
GROUP BY c.customerid
ORDER BY Sum(p.price) DESC
LIMIT 5
Just following on from your quantity comment...
SELECT c.customerid,
Sum(p.price),
Sum(p.price * od.quantity)
FROM customers AS c
INNER JOIN orders AS o
ON o.customerid = c.customerid
INNER JOIN orderdetails AS od
ON od.orderid = o.orderid
INNER JOIN products AS p
ON p.productid = od.productid
GROUP BY c.customerid
ORDER BY Sum(p.price) DESC
LIMIT 5
I think this is the best optimized code.
Please try with this.
SELECT CustomerID, Count(Quantity * Price) AS Total
FROM Orders, OrderDetails, Products
Where Orders.OrderID = OrderDetails.OrderID AND Products.ProductID = OrderDetails.ProductID
Group by CustomerID
ORDER BY Total DESC
LIMIT 5

Save SQL Query Results in Variable

I have the following SQL Query, but it is very repetitive and so I am wondering if this can be cleaned up by storing the results of a SQL Query in a variable.
What I have is (which works, can be tested here)
SELECT ProductName FROM (
SELECT ProductName, SUM(Quantity) AS ProductQuantity FROM
(SELECT CustomerID FROM Customers AS C
WHERE Country = 'Germany') AS CG
INNER JOIN Orders AS O
ON CG.CustomerID = O.CustomerID
INNER JOIN OrderDetails AS OD
ON O.OrderID = OD.OrderID
INNER JOIN Products as P
ON OD.ProductID = P.ProductID
GROUP BY ProductName)
WHERE ProductQuantity = (
SELECT MAX(ProductQuantity) FROM (
SELECT ProductName, SUM(Quantity) AS ProductQuantity FROM
(SELECT CustomerID FROM Customers AS C
WHERE Country = 'Germany') AS CG
INNER JOIN Orders AS O
ON CG.CustomerID = O.CustomerID
INNER JOIN OrderDetails AS OD
ON O.OrderID = OD.OrderID
INNER JOIN Products as P
ON OD.ProductID = P.ProductID
GROUP BY ProductName))
And as you could tell the following code is repeated twice, is there any way I can do this without repeating this code:
(SELECT ProductName, SUM(Quantity) AS ProductQuantity FROM
(SELECT CustomerID FROM Customers AS C
WHERE Country = 'Germany') AS CG
INNER JOIN Orders AS O
ON CG.CustomerID = O.CustomerID
INNER JOIN OrderDetails AS OD
ON O.OrderID = OD.OrderID
INNER JOIN Products as P
ON OD.ProductID = P.ProductID
GROUP BY ProductName)
You can use CTE to avoid looping, maybe like this:
with cte as (
SELECT ProductName, SUM(Quantity) AS ProductQuantity FROM
(SELECT CustomerID FROM Customers AS C
WHERE Country = 'Germany') AS CG
INNER JOIN Orders AS O
ON CG.CustomerID = O.CustomerID
INNER JOIN OrderDetails AS OD
ON O.OrderID = OD.OrderID
INNER JOIN Products as P
ON OD.ProductID = P.ProductID
GROUP BY ProductName
)
SELECT ProductName
FROM cte
where ProductQuantity = (SELECT MAX(ProductQuantity) FROM cte)

Is it possible to get one row by grouping more than one column

I have a query as below. DB from http://www.w3schools.com/sql/default.asp
SELECT count(distinct C.CustomerID),C.Country
FROM Customers C
inner join Orders O
on C.CustomerID = O.CustomerID
inner join OrderDetails D
on O.OrderID = D.OrderID
inner join Products P
on D.ProductID = P.ProductID
group by C.Country,P.CategoryID
order by C.Country
Here is the result from above.
But I want to get one row per country(as below pic) by counting CustomerIDs where any CustomerIDs are in the same country and have a same CategoryID as well. So I have to group by 2 columns. Is there any way to do it? Could you please kindly suggest me?
Thank you.
That's quite simple. Just remove P.CategoryID in your GROUP BY clause.
SELECT COUNT(DISTINCT C.CustomerID), C.Country
FROM Customers C
INNER JOIN Orders O
ON C.CustomerID = O.CustomerID
INNER JOIN OrderDetails D
ON O.OrderID = D.OrderID
INNER JOIN Products P
ON D.ProductID = P.ProductID
GROUP BY C.Country
ORDER BY C.Country;
Update
Following your comment, this should be correct approach then:
SELECT T.Country, SUM(T.Cnt)
FROM (
SELECT COUNT(DISTINCT C.CustomerID) AS Cnt, C.Country
FROM Customers C
INNER JOIN Orders O
ON C.CustomerID = O.CustomerID
INNER JOIN OrderDetails D
ON O.OrderID = D.OrderID
INNER JOIN Products P
ON D.ProductID = P.ProductID
GROUP BY C.Country, P.CategoryID
) AS T
GROUP BY T.Country
ORDER BY T.Country;

2 Questions on Northwind SQL

I have 2 questions about the Northwind SQL Server sample database that I don't know how to solve
Show CustomerID for all customers who have at least three different products, but never use both products in the same category.
Code I tried for this question:
SELECT
CustomerID, p.ProductID,ProductName, CategoryID
FROM
(Orders o
JOIN
[Order Details] od ON o.OrderID = od.OrderID)
JOIN
Products p ON od.ProductID = p.ProductID
Show CustomerID for customers who have orders from all categories.
I've been stuck on these queries for hours, please help guys!
This is link for Northwind sample database: https://northwinddatabase.codeplex.com/
For #2, you could use something like this:
SELECT
c.CustomerID, COUNT(DISTINCT p.CategoryID)
FROM
dbo.Customers c
INNER JOIN
dbo.Orders o ON o.CustomerID = c.CustomerID
INNER JOIN
dbo.[Order Details] od ON od.OrderID = o.OrderID
INNER JOIN
dbo.Products p ON p.ProductID = od.ProductID
GROUP BY
c.CustomerID
HAVING
COUNT(DISTINCT p.CategoryID) = (SELECT COUNT(*) FROM dbo.Categories)
This works for question #1:
SELECT c.CustomerID,
od.productid,
p.ProductName,
COUNT(od.productid),
ct.Category
FROM [Order Details] od
INNER JOIN [dbo].[Products] p on od.ProductID = p.ProductID
INNER JOIN [dbo].[Categories] ct on p.CategoryID = c.CategoryID
INNER JOIN [dbo].[Orders] o on od.OrderID = o.OrderID
INNER JOIN [dbo].[Customers] c on o.CustomerID = c.CustomerID
GROUP BY od.productid,
ct.CategoryID,
p.ProductName,
c.CustomerID
HAVING COUNT(od.productid) > 3
ORDER BY COUNT(od.productid) desc
;