Ranking customers by orders per customer and items per order - sql-server

I'm trying to rank a query by not just one count, but by two.
I want to rank customers by the order items per orders.
WITH CTE AS
(
SELECT
o.CustomerId,
COUNT(DISTINCT o.OrderId) AS OrderCount,
COUNT(oi.OrderItemId) AS OrderItemCount
FROM
OrderItem oi
INNER JOIN
Order o ON o.OrderId = oi.OrderId
WHERE
o.CategoryId = 52 -- website sales
GROUP BY
o.CustomerId
)
SELECT
cust.Code,
cust.DisplayTitle,
CTE.OrderCount,
CTE.OrderItemCount,
--AVG(CTE.OrderItemCount/CTE.OrderCount) AS SumProduct ????
FROM
CTE
INNER JOIN
Customer cust ON cust.CustomerId = CTE.CustomerId
GROUP BY
cust.Code,
cust.DisplayTitle,
CTE.OrderCount,
CTE.OrderItemCount
ORDER BY
SumProduct DESC
I'm basically trying to implement the T-SQL equivalent of SUMPRODUCT() in Excel.

SELECT
o.CustomerId,
COUNT(DISTINCT o.OrderId) AS OrderCount,
COUNT(oi.OrderItemId) AS OrderItemCount,
COUNT(oi.OrderItemId) / COUNT(DISTINCT o.OrderId) avg
FROM OrderItem oi
INNER JOIN Order o ON o.OrderId = oi.OrderId
WHERE o.CategoryId = 52 -- website sales
GROUP BY o.CustomerId
order by COUNT(oi.OrderItemId) / COUNT(DISTINCT o.OrderId) desc
Just add in the join to customer

Related

How to join select statements together

I have a sample db with 3 different tables, customers, orders, orderdetails.
The assignment is to show customer name, and address from a customers table and then show each order total amount by order id. Order details has order id several times and it is by unit x price so I have to sum these after performing the calculation.
Customers has a field customerid, which I can use to join with orders which has the same field, the orders table has orderid which I can use to join to orderdetails and sum the order total but I do not know how to put the information together. Customers table does not have the fields to calculate the total order, and only has customerid. So, I'm trying to pinch together from 3 tables where there is some related column but not all present in each table.
I can do 2 separate select statements and each do what I expect but I have been trying to get the info together and have been unable to.
SELECT c.CustomerID, c.[Address], o.orderid
FROM Customers c
Join Orders o
ON c.CustomerID = o.CustomerID
--how to join these together?
SELECT od.orderid, SUM(od.UnitPrice*od.Quantity) as 'Subtotal'
FROM OrderDetails od
Join Orders o
ON od.OrderID = o.OrderID
Group by od.OrderID
I am trying to show this with the following information:
Customer Name, Address, OrderID, and Order Total.
try this -
SELECT c.CustomerID, c.[Address], od.orderid, SUM(od.UnitPrice* od.Quantity) as 'Subtotal'
FROM OrderDetails od
Join Orders o
ON od.OrderID = o.OrderID
join Customers c ON c.CustomerID = o.CustomerID
Group by c.CustomerID, c.[Address], od.OrderID
You can join three tables together like given below. I am using derived table OrderDetails to calculate subtotal at orderId level.
SELECT c.CustomerID, c.[Address], o.orderid, SUM(od.Subtotal) as 'Subtotal'
FROM Customers c
Join Orders o
ON c.CustomerID = o.CustomerID
join (SELECT orderid, SUM(od.UnitPrice*od.Quantity) as Subtotal from OrderDetails od GROUP BY OrderId) as OrderDetails od
ON od.OrderID = o.OrderID
group by c.CustomerID, c.[Address], o.orderid
Mukesh's answer led me directly to the finish, I was able to leave the customerid out of the result with the following. This helped a lot and I appreciate everyone's input.
SELECT c.CompanyName, c.[Address], od.orderid, SUM(od.UnitPrice* od.Quantity) as
'Subtotal'
FROM OrderDetails od
Join Orders o
ON od.OrderID = o.OrderID
join Customers c ON c.CustomerID = o.CustomerID
Group by c.CompanyName, c.[Address], od.OrderID

SQL Repeated Outer Join Improving Efficiency

Probelm Statement:
Write a query to return 2015 sales information for each supplier. We would like to include all suppliers in the result set, regardless of whether their products were sold in 2015.
Sales are determined using Sales.Orders and Sales.OrderLines as in the previous two questions. However, since we are asking for this information from the perspective of the supplier, you also need to use the tables Warehouse.StockItems and Purchasing.Suppliers.
The columns required in the result set are:
SupplierID - As it appears in the table Purchasing.Suppliers.
SupplierName - As it appears in the table Purchasing.Suppliers.
OrderCount - The number of orders placed on products for each supplier.
Sales - The subtotal from the orders placed, calculated from Quantity and UnitPrice of the table Sales.OrderLines.
The results should be sorted such that the supplier with the highest sales is at the top. If two suppliers have the same sales, next use the order count with the highest count at the top. If two suppliers have the same sales and order count, use the supplier name in ascending order as the final tie breaker. This will ensure a deterministic result.
I am using the WorldWideImporters Microsoft sample database tables. I am trying to return the 2015 sales information for each supplier in Purchasing.Suppliers. I am returning the OrderCount and the Sum of the 2015 sales in respective columns. I am having trouble with joins here since I have to connect Suppliers to the Warehouse.StockItems and then connect these items to specific OrderLines which have a field for StockItemID.
The problem is that usually I would join orders to orderlines, so that I could filter only orders and thus orderlines in 2015. However, with the table structure that I have specified, it seems I have to connect OrderLines to Orders.
So what I did was to join those Orders back with OrderLines to provide the result I am used to. Here is my attempt at a solution:
<pre>
SELECT S.SupplierID
,S.SupplierName
,COUNT(DISTINCT O.OrderID) AS OrderCount
,ISNULL(SUM(OLP.Quantity * OLP.UnitPrice), 0.00) AS Sales
FROM Purchasing.Suppliers AS S
LEFT OUTER JOIN Warehouse.StockItems AS W ON S.SupplierID = W.SupplierID
LEFT OUTER JOIN Sales.OrderLines AS OL ON W.StockItemID = OL.StockItemID
LEFT OUTER JOIN Sales.Orders AS O ON OL.OrderID = O.OrderID
AND O.OrderDate BETWEEN '2015-01-01' AND '2015-12-31'
LEFT OUTER JOIN Sales.OrderLines AS OLP ON O.OrderID = OLP.OrderID
GROUP BY S.SupplierID
,S.SupplierName
ORDER BY Sales DESC
,OrderCount
,SupplierName;
</pre>
Edit:
Results:
Look to have every supplier as expected even ones that had no sales or orders. I am not sure if the calculated sales is correct though and I am not sure how to verify. Didn't know if anyone saw a flaw in my query.
I have no idea if this is correct or the most efficient way to solve this problem. I do have constraints that I can only use joins, no subqueries, unions, etc.
Any help in understanding would be appreciated. Thank you.
To benchmark the orders, without regard to suppliers or order lines:
/* query 1 */
SELECT
COUNT(*) AS ordercount
FROM Sales.Orders AS o
WHERE o.OrderDate >= '20150101' AND o.OrderDate < '20160101'
Then to benchmark the orders lines, without regard to suppliers:
/* query 2 */
SELECT
COUNT(DISTINCT o.OrderID) AS ordercount
, SUM(olp.Quantity * olp.UnitPrice) AS sales
FROM Sales.Orders AS o
INNER JOIN Sales.OrderLines AS olp ON olP.OrderID = o.OrderID
WHERE o.OrderDate >= '20150101' AND o.OrderDate < '20160101'
Now start introducing more joins, and if the values alter then the most recent join is at fault:
/* query 3 */
SELECT
COUNT(DISTINCT o.OrderID) AS ordercount
, SUM(olp.Quantity * olp.UnitPrice) AS sales
FROM Sales.Orders AS o
INNER JOIN Sales.OrderLines AS olp ON olP.OrderID = o.OrderID
INNER JOIN Warehouse.StockItems AS w ON w.StockItemID = olp.StockItemID
WHERE o.OrderDate >= '20150101' AND o.OrderDate < '20160101'
and then:
/* query 4 */
SELECT
COUNT(DISTINCT o.OrderID) AS ordercount
, SUM(olp.Quantity * olp.UnitPrice) AS sales
FROM Sales.Orders AS o
INNER JOIN Sales.OrderLines AS olp ON olP.OrderID = o.OrderID
INNER JOIN Warehouse.StockItems AS w ON w.StockItemID = olp.StockItemID
INNER JOIN Purchasing.Suppliers s ON s.SupplierID = w.SupplierID
WHERE o.OrderDate >= '20150101' AND o.OrderDate < '20160101'
You certainly don't need to join order lines twice, regarding the left joins it depends on what it is you are trying to achieve, e.g.:
Only suppliers with orders in the date range:
/* query 5 */
SELECT
s.SupplierID
, s.SupplierName
, COUNT(DISTINCT o.OrderID) AS ordercount
, ISNULL(SUM(olp.Quantity * olp.UnitPrice), 0.00) AS sales
FROM Purchasing.Suppliers s
INNER JOIN Warehouse.StockItems AS w ON s.SupplierID = w.SupplierID
INNER JOIN Sales.OrderLines AS olp ON w.StockItemID = olp.StockItemID
INNER JOIN Sales.Orders AS o ON olP.OrderID = o.OrderID
WHERE o.OrderDate >= '20150101'
AND o.OrderDate < '20160101' -- note: this is "the next" day
GROUP BY
s.SupplierID
, s.SupplierName
ORDER BY
sales DESC
, ordercount
, SupplierName;
All suppliers with stock references:
/* query 6 */
SELECT
s.SupplierID
, s.SupplierName
, COUNT(DISTINCT o.OrderID) AS ordercount
, ISNULL(SUM(olp.Quantity * olp.UnitPrice), 0.00) AS sales
FROM Purchasing.Suppliers s
INNER JOIN Warehouse.StockItems AS w ON s.SupplierID = w.SupplierID
LEFT JOIN Sales.OrderLines AS olp ON w.StockItemID = olp.StockItemID
LEFT JOIN Sales.Orders AS o ON olP.OrderID = o.OrderID
AND o.OrderDate >= '20150101'
AND o.OrderDate < '20160101' -- note: this is "the next" day
GROUP BY
s.SupplierID
, s.SupplierName
ORDER BY
sales DESC
, ordercount
, SupplierName;
Every supplier:
/* query 7 */
SELECT
s.SupplierID
, s.SupplierName
, COUNT(DISTINCT o.OrderID) AS ordercount
, ISNULL(SUM(olp.Quantity * olp.UnitPrice), 0.00) AS sales
FROM Purchasing.Suppliers s
LEFT JOIN Warehouse.StockItems AS w ON s.SupplierID = w.SupplierID
LEFT JOIN Sales.OrderLines AS olp ON w.StockItemID = olp.StockItemID
LEFT JOIN Sales.Orders AS o ON olP.OrderID = o.OrderID
AND o.OrderDate >= '20150101'
AND o.OrderDate < '20160101' -- note: this is "the next" day
GROUP BY
s.SupplierID
, s.SupplierName
ORDER BY
sales DESC
, ordercount
, SupplierName;
Please be very cautious about using between for date ranges, the most reliable way to define a date range is to use >= and < as shown above, this way it does not matter what the time precision of the data is. Also YYYYMMDD is the safest date literal format in TSQL.

select count over partition by

I am learning window functions in sql server. I am using AdventrueWorks2012 database for practice. I want to calculate total number of sales and purchases for each item in the store.
The classic solution can be like
SELECT ProductID,
Quantity,
(SELECT Count(*)
FROM AdventureWorks.Purchasing.PurchaseOrderDetail
WHERE PurchaseOrderDetail.ProductID = p.ProductID) TotalPurchases,
(SELECT Count(*)
FROM AdventureWorks.Sales.SalesOrderDetail
WHERE SalesOrderDetail.ProductID = p.ProductID) TotalSales
FROM (SELECT DISTINCT ProductID,
Quantity
FROM AdventureWorks.Production.ProductInventory) p
Trying to convert to window functions gives me wrong results:
SELECT DISTINCT d.ProductID,
Quantity,
Count(d.ProductID)
OVER(
PARTITION BY d.ProductID) TotalPurchases,
Count(d2.ProductID)
OVER(
PARTITION BY d2.ProductID) TotalSales
FROM (SELECT DISTINCT ProductID,
Quantity
FROM AdventureWorks.Production.ProductInventory) p
INNER JOIN AdventureWorks.Purchasing.PurchaseOrderDetail d
ON p.ProductID = d.ProductID
INNER JOIN AdventureWorks.Sales.SalesOrderDetail d2
ON p.ProductID = d2.ProductID
ORDER BY d.ProductID
Why this is wrong? How can I correct it?
You should change INNER JOIN to LEFT JOIN
Because when you inner join, result will miss productid which from ProductInventory table does not have PurchaseOrderDetail or SalesOrderDetail.

SQL Server: How to obtain the SUM of the top 5 orders by country

This is my first time around here :p
I have SQL Server practice.
Based on the country where Northwind customers reside, show the sum of the 5 highest purchase orders by country. The results should be presented in two columns: country, amount
I tried:
SELECT
vt.ShipCountry, vt.suma
FROM
(SELECT
o.ShipCountry,
SUM( UnitPrice * Quantity * (1-discount)) as suma,
RANK() OVER (PARTITION BY SUM(UnitPrice * Quantity * (1-discount)) ORDER BY shipCountry DESC) AS Rank
FROM
orders o
JOIN
[Order Details] od ON o.OrderID = od.OrderID
GROUP BY
o.ShipCountry) as vt
WHERE
Rank <= 5
GROUP BY
vt.ShipCOUNTRY, vt.suma
but, it retrieves me the sum of all orders per country, only want the top 5 per country
Here's another one, same issue.
SELECT
ShipCountry, rk, amount
FROM
(SELECT
o.ShipCountry,
SUM(UnitPrice * Quantity * (1-discount)) amount,
DENSE_RANK() OVER(PARTITION BY o.ShipCountry ORDER BY SUM(UnitPrice * Quantity * (1-discount)) DESC) AS rk
FROM
Orders o
JOIN
[Order Details] od ON o.OrderID = od.OrderID
GROUP BY
o.shipCountry) AS L
WHERE
rk <= 5;
The two queries have the same behaviour
Try this:
-- first, sum up the total amount of each order
;WITH OrderDetails AS
(
SELECT
o.OrderID,
TotalOrderAmount = SUM(UnitPrice * Quantity * (1 - discount))
FROM
orders o
INNER JOIN
[Order Details] od ON o.OrderID = od.OrderID
GROUP BY
o.OrderID
),
-- secondly, join the "ShipCountry" to the order totals,
-- and define a ROW_NUMBER() for each country, based on
-- total order amount
OrderPerCountry AS
(
SELECT
o.ShipCountry,
od.TotalOrderAmount,
RowNum = ROW_NUMBER() OVER(PARTITION BY o.ShipCountry ORDER BY od.TotalOrderAmount DESC)
FROM
OrderDetails od
INNER JOIN
dbo.Orders o ON o.OrderID = od.OrderID
)
SELECT *
FROM OrderPerCountry
WHERE RowNum <= 5
This should do the trick for you - I hope!

How do you select multiple columns from multiple tables along with total cost?

I have four tables, but for this I only need three tables, and I want to display the customerid, orderid, productid, quantity and the total cost which isn't in any table but need to calculate? so far I have managed to get it to display total cost for one order with the order id, but I want it to display all the columns mentioned above
Order:
order_id(primary key)
customer_id( foreign key)
orderline:
orderid(fk) + productid(fk) (pk)
quantity
product:
productid(pk)
price
What I have done is
select orderid, sum(rowcost) as totalcost
from (select o.quantity, o.productid, o.orderid, os.customerid, p.price,
(o.quantity * p.price) as rowcost
from orderline o
inner join order os
on o.orderid = os.orderid
inner join product p
on p.productid = o.productid
where productid = 123)
group by orderid;
Now I want it to display all the orderids along with the productid, customerid, totalcost, orderid and quantity. The list should follow customerid order.
How would I do this?
when I add more variables in the select, it gives me errors. I have tried many ways, none of them worked.
do you mean you want something like this:
select o.quantity, o.productid, o.orderid, os.customerid, p.price,
(o.quantity * p.price) as rowcost,
sum(o.quantity * p.price) over (partition by os.customerid) as totalcost
from orderline o
inner join order os
on o.orderid = os.orderid
inner join product p
on p.productid = o.productid
where p.productid = 123
or this, to keep the sum correct if you wanted to filter afterwards on a product
select *
from (select o.quantity, o.productid, o.orderid, os.customerid, p.price,
(o.quantity * p.price) as rowcost,
sum(o.quantity * p.price) over (partition by os.customerid) as totalcost
from orderline o
inner join order os
on o.orderid = os.orderid
inner join product p
on p.productid = o.productid)
where productid = 123--or just remove this where clause
fiddle: http://sqlfiddle.com/#!4/3103d/1

Resources