I'm trying to generate a report that shows outstanding balances based on dynamic calculations across several tables. Currently this report is being generated using a server side language that runs a query, loops through the query, and runs another query against the result. My goal is to perform all the calculations within MSSQL.
Here are the tables / columns with some example data:
TEST_ORDER Table
orderid customerid serviceamount
1001 2001 75.00
1002 2002 85.00
1003 2001 25.00
1004 2003 10.00
TEST_CUSTOMER Table
customerid customername
2001 'Initech'
2002 'Dunder Mifflin'
2003 'Paper Street Soap Co'
TEST_ORDERITEM Table
orderitemid orderid itemamount
5001 1001 50.00
5002 1001 150.00
5003 1002 15.00
5004 1004 200.00
5005 1004 200.00
TEST_TRANSACTION Table
transactionid orderid amount
9001 1001 75.00
9002 1002 25.00
9003 1002 50.00
9004 1003 55.00
9005 1001 50.00
9006 1001 150.00
The results I'm looking for are:
orderid customername totalorder totalpaid balance
1001 'Initech' 275.00 275.00 0.00
1002 'Dunder Mifflin' 100.00 75.00 -25.00
1003 'Initech' 25.00 55.00 30.00
1004 'Paper Street Soap Co' 410.00 0.00 -410.00
Here's the query I came up with:
SELECT
o.orderid, c.customername, o.serviceamount,
(o.serviceamount + SUM( i.itemamount )) AS totalorder,
SUM( t.amount ) AS totalPaid,
( SUM( t.amount ) - (o.serviceamount + SUM( i.itemamount )) ) AS balance
FROM
test_order o
INNER JOIN test_customer c ON o.customerID = c.customerID
LEFT OUTER JOIN test_transaction t ON o.orderID = t.orderID
LEFT OUTER JOIN test_orderitem i ON o.orderID = i.orderID
WHERE
1=1
GROUP BY
o.orderid, c.customername, o.serviceamount,
o.serviceamount
And here are the results:
orderid customername totalorder totalpaid balance
1001 'Initech' 675.00 550.00 -125.00
1002 'Dunder Mifflin' 115.00 75.00 -40.00
1003 'Initech' null 55.00 null
1004 'Paper Street Soap Co' 410.00 null null
The problems I'm having are:
1. Records in test_orderitem are duplicated, e.g. for order 1002, the serviceamount is $85 and there is only one orderitem for $15, however, th totalorder is calculated for $115. I think the problem has to do with the JOIN iterating twice.
2. 'null' is showing up when no records are returned (for obvious reasons). I haven't started working on this, as I've been testing in MySQL, and I want to figure out the JOIN issue before carrying this back into MSSQL.
Thank you for any assistance you might be able to offer...
For MS-SQL: Here you, just replace table names in final query with your own.
DECLARE #Test_Order TABLE (orderid int, customerid int, serviceamount money)
INSERT INTO #Test_Order(orderid,customerid,serviceamount)
SELECT 1001,2001,75.00 UNION ALL
SELECT 1002,2002,85.00 UNION ALL
SELECT 1003,2001,25.00 UNION ALL
SELECT 1004,2003,10.00
DECLARE #Test_Customer TABLE (customerid int, customername varchar(100))
INSERT INTO #Test_Customer(customerid,customername)
SELECT 2001,'Initech' UNION ALL
SELECT 2002,'Dunder Mifflin' UNION ALL
SELECT 2003,'Paper Street Soap Co'
DECLARE #Test_OrderItem TABLE (orderitemid int, orderid int, itemamount money)
INSERT INTO #Test_OrderItem (orderitemid, orderid, itemamount)
SELECT 5001,1001,50.00 UNION ALL
SELECT 5002,1001,150.00 UNION ALL
SELECT 5003,1002,15.00 UNION ALL
SELECT 5004,1004,200.00 UNION ALL
SELECT 5005,1004,200.00
DECLARE #Test_Transaction TABLE (transactionid int, orderid int, amount money)
INSERT INTO #Test_Transaction (transactionid, orderid, amount)
SELECT 9001,1001,75.00 UNION ALL
SELECT 9002,1002,25.00 UNION ALL
SELECT 9003,1002,50.00 UNION ALL
SELECT 9004,1003,55.00 UNION ALL
SELECT 9005,1001,50.00 UNION ALL
SELECT 9006,1001,150.00
SELECT O.orderid,
C.customername,
SUM(ISNULL(O.serviceamount,0)+ISNULL(I.itemamount,0)) AS totalorder,
SUM(ISNULL(T.amount,0)) AS totalpaid,
(SUM(ISNULL(T.amount,0))) - (SUM(ISNULL(O.serviceamount,0)+ISNULL(I.itemamount,0))) as balance
FROM #Test_Customer C
JOIN #Test_Order O
ON C.customerid=O.customerid
LEFT JOIN (SELECT orderid, SUM(itemamount) AS itemamount FROM #Test_OrderItem GROUP BY orderid) I
ON O.orderid=I.orderid
LEFT JOIN (SELECT orderid, SUM(amount) AS amount FROM #Test_Transaction GROUP BY orderid) T
ON O.orderid=T.orderid
GROUP BY O.orderid, C.customername
ORDER BY O.orderid, C.customername
http://sqlfiddle.com/#!9/ae0be/1
SELECT
o.orderid,
c.customername,
(coalesce(o.serviceamount,0)+coalesce(i.itemsum,0)) totalorder,
t.t_sum totalpaid,
(coalesce(t.t_sum,0)-coalesce(o.serviceamount,0)-coalesce(i.itemsum,0)) balance
FROM TEST_ORDER o
LEFT JOIN (
SELECT orderid, SUM(itemamount) itemsum
FROM TEST_ORDERITEM
GROUP BY orderid
) i
ON o.orderid = i.orderid
LEFT JOIN (
SELECT orderid, SUM(amount) t_sum
FROM TEST_TRANSACTION
GROUP BY orderid
) t
ON o.orderid = t.orderid
LEFT JOIN TEST_CUSTOMER c
ON o.customerid = c.customerid
ORDER BY o.orderid
Give this a try:
SELECT
summary.orderid,
summary.customername,
summary.totalorder,
summary.totalpaid,
summary.totalorder - summary.totalpaid AS balance
FROM
(SELECT
o.orderid,
c.customername,
(SELECT SUM(oi.itemamount) FROM test_orderitem oi WHERE oi.orderid = o.orderid) AS totalorder,
(SELECT IFNULL(SUM(t.amount), 0) FROM test_transaction t WHERE t.orderid = o.orderid) AS totalpaid
FROM
test_order o,
test_customer c
WHERE
o.customerid = c.customerid) summary
Related
This question already has answers here:
SQL select only rows with max value on a column [duplicate]
(27 answers)
Closed 1 year ago.
I have some task that I should do it with sql.
Below is the function I currently use.
(SELECT ProductName, sum(Quantity*Price) Revenue, Country
FROM products p
JOIN orderdetails d
ON p.ProductID = d.ProductID
JOIN orders o
ON o.OrderID = d.OrderID
JOIN customers c
ON c.CustomerID = o.CustomerID
WHERE Country =
(
SELECT DISTINCT Country
FROM customers
LIMIT 1
)
GROUP BY ProductName
ORDER BY Revenue DESC
LIMIT 1)
UNION
(SELECT ProductName, sum(Quantity*Price) Revenue, Country
FROM products p
JOIN orderdetails d
ON p.ProductID = d.ProductID
JOIN orders o
ON o.OrderID = d.OrderID
JOIN customers c
ON c.CustomerID = o.CustomerID
WHERE Country =
(
SELECT DISTINCT Country
FROM customers
LIMIT 1,1
)
GROUP BY ProductName
ORDER BY Revenue DESC
LIMIT 1)
UNION
(SELECT ProductName, sum(Quantity*Price) Revenue, Country
FROM products p
JOIN orderdetails d
ON p.ProductID = d.ProductID
JOIN orders o
ON o.OrderID = d.OrderID
JOIN customers c
ON c.CustomerID = o.CustomerID
WHERE Country =
(
SELECT DISTINCT Country
FROM customers
LIMIT 2,1
)
GROUP BY ProductName
ORDER BY Revenue DESC
LIMIT 1)
My task is "Find best selling products based on revenue for each country!"
The result I want is below:
ProductName
Revenue
Country
Tofu
279
Argentina
Côte de Blaye
18445
Austria
You can access the data I use from this link RawDatabase
the sample data that I use is like this
ProdName
Country
Revenue
coco
Argentina
120
bread
Austria
10000
crunch
Austria
13265
Cote de Blaye
Austria
18445
milk
Argentina
254
Tofu
Argentina
279
From this data I want to select only the best product for each country by revenue. In the data there were 21 country. What should I do so I can get the result below
ProductName
Revenue
Country
Tofu
279
Argentina
Côte de Blaye
18445
Austria
The only way in my mind is only filtering the data by each country then get the best product then union all of them like the code I give on top. I wonder if there's another way.
Using row_number window function or compare to country maxrevenue
DROP TABLe if exists t;
create table t
(ProdName varchar(20), Country varchar(20), Revenue int);
insert into t values
('coco' ,'Argentina' ,120),
('bread' ,'Austria' ,10000),
('crunch','Austria' ,13265),
('Cote de Blaye' ,'Austria', 18445),
('milk' ,'Argentina' ,254),
('Tofu' ,'Argentina' ,279);
select *
from
(
select prodname,country,revenue,
row_number() over(partition by country order by revenue desc) rn
from t
) s
where rn = 1;
or
select *
from t
join (select t.country,max(t.revenue) maxrevenue from t group by t.country) t1
on t1.country = t.country and t1.maxrevenue = t.revenue;
+---------------+-----------+---------+----+
| prodname | country | revenue | rn |
+---------------+-----------+---------+----+
| Tofu | Argentina | 279 | 1 |
| Cote de Blaye | Austria | 18445 | 1 |
+---------------+-----------+---------+----+
2 rows in set (0.001 sec)
i have the data like this
purchase_order (po)
po_id pi_id store
112 789 ABC
113 101 DEF
114 102 GHI
115 103 JKL
purchase_items (pi)
pi_id barcode price date
789 123 500 2020-06-04
101 123 400 2020-06-03
102 456 500 2020-06-02
103 456 400 2020-06-01
product
barcode product
123 milk
456 tea
I want to get the latest price for each barcode if the store exclude "GHI"
expected result
barcode price
123 500
456 400
how should i do ?
One option uses a correlated subquery:
select
p.barcode,
(
select pi.price
from purchase_items pi
inner join purchase_orders po on po.pi_id = pi.pi_id
where pi.barcode = p.barcode and po.store <> 'GHI'
order by pi.date desc limit 1
) price
from product p
row_number() is one method:
select pi.barcode, pi.price
from (select pi.*, row_number() over (partition by pi.barcode order by pi.date desc) as seqnum
from purchase_items pi
where not exists (select 1
from purchase_orders po
where po.pi_id = pi.pi_id and po.store = 'GHI'
)
) pi
where seqnum = 1;
Use join with max-function:
select pi.barcode, max(pi.price)
from purchase_items pi
join purchase_order po on po.pi_id=pi.pi_id
where po.store!='GHI'
group by pi.barcode
A way of using subqueries.
SELECT
DISTINCT
a.`barcode`,
a.`price`
FROM
purchase_items a
WHERE a.`date` =
(SELECT
MAX(`date`)
FROM
purchase_items i
WHERE i.barcode = a.barcode
AND 'GHI' !=
(SELECT
store
FROM
purchase_order
WHERE pi_id = i.`pi_id`
LIMIT 1)) ;
i have 2 table's one with customers and one with orders
SELECT customers.customer_name, orders.order_date
FROM customers
Left
Join orders on (customers.customer_id = orders.customer_id)
WHERE not orders.customer_id IN (SELECT customer_id from orders where Year(order_date) = Year(#Parameter1))
and not orders.order_date is null
this works but i want to do this for each year to get somthing like this as result
|Year | customer_id |
|2010 | 1 |
|2010 | 2 |
|2011 | 2 |
|2011 | 3 |
|2012 | 1 |
You want a list showing customers and years that are not present in the orders table. So get a list of all customers combined with all years and then subtract the customers and years that you find in the orders table:
select o.yr, c.customer_id
from customers c
cross join (select distinct year(order_date) as yr from orders) o
where (c.customer_id, o.yr) not in (select customer_id, year(order_date) from orders);
Scorpio is sort of right, you do have to use the year function:
SELECT Year(orders.order_date), customers.customer_id
FROM customers
LEFT JOIN orders
ON (customers.customer_id = orders.customer_id)
WHERE NOT orders.customer_id
IN (
SELECT customer_id
FROM orders
WHERE Year(order_date) = Year(#Parameter1)
)
AND
NOT orders.order_date is NULL
You can use the year method in the SELECT section of your query
Update:
To display all the customers who didn't order in a year, you don't need the join. You can do it with a single subselect:
SELECT #Parameter1 AS Year, customer_id
FROM customers
WHERE customers.customer_id NOT IN (
SELECT customer_id
FROM orders
WHERE Year(order_date) = Year(#Parameter1)
)
Table - Customers
id FirstName LastName
23 James Smith
24 Tom Raven
25 Bob King
Table - Orders
id CustomerID
30 23
31 24
32 23
33 25
Table - Products
OrderID Product1 Product2 Product3
30 1 0 0
31 2 0 0
32 0 1 1
33 2 1 0
I want to count the total number of Products for each Customer so answers would be:
CustomerID FirstName LastName Total
23 James Smith 3
24 Tom Raven 2
25 Bob King 3
So far I've got for the SQL query:
SELECT Customers.id, Orders.id,
FROM Customers
INNER JOIN Orders ON Customers.id = Orders.CustomerID
INNER JOIN Products ON Orders.id = Products.OrderID
Not sure how to count up the products though.
Any help would be much appreciated.
select c.id as CustomerID
,(sum(p.Product1) + sum(p.Product2) + sum(p.Product3)) as ProductCount
from Customers c
inner join Orders o on c.id = o.CustomerID
inner join Products p on o.id = p.OrderID
group by c.id
You could use
SELECT c.id as CustomerID, c.firstname, c.lastname,
sum(p.Product1 + p.Product2 + p.Product3) as total
FROM Customers c
INNER JOIN Orders o
ON c.id=o.CustomerID
INNER JOIN Products p
ON o.id=p.OrderID
group by c.id,c.firstname, c.lastname;
And as #Thorsten Kettner's comment, you should consider normalizing your table design.
Add a GROUP BY clause! Normally it includes the selected columns that are not arguments to a set function.
SELECT O.CustomerID, C.FirstName, C.LastName, count(*) as Total
FROM Customers C
INNER JOIN Orders O ON C.id = O.CustomerID
INNER JOIN Products P ON O.id = P.OrderID
GROUP BY O.CustomerID, C.FirstName, C.LastName
PS. Now using table aliases, to save some typing.
SELECT Customers.id, Customers.firstname, Customers.lastname,count(*) as total FROM Customers
INNER JOIN Orders
ON Customers.id=Orders.CustomerID
INNER JOIN Products
ON Orders.id=Products.OrderID
group by Customers.id,Customers.firstname, Customers.lastname
A more conventional approach might be as follows:
DROP TABLE IF EXISTS customers;
CREATE TABLE customers
(customer_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,firstname VARCHAR(20) NOT NULL
,lastname VARCHAR(20) NOT NULL
);
INSERT INTO customers VALUES
(23,'James','Smith'),
(24,'Tom','Raven'),
(25,'Bob','King');
DROP TABLE IF EXISTS orders;
CREATE TABLE orders
(order_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,customer_id INT NOT NULL
);
INSERT INTO orders VALUES
(30 ,23),
(31 ,24),
(32 ,23),
(33 ,25);
DROP TABLE IF EXISTS order_detail;
CREATE TABLE order_detail
(order_id INT NOT NULL
,product_id INT NOT NULL
,quantity INT NOT NULL
,PRIMARY KEY(order_id,product_id)
);
INSERT INTO order_detail VALUES
(30 ,1 ,1),
(31 ,1 ,2),
(33 ,1 ,2),
(32 ,2 ,1),
(33 ,2 ,1),
(32 ,3 ,1);
SELECT c.*
, SUM(od.quantity) total
FROM customers c
JOIN orders o
ON o.customer_id = c.customer_id
JOIN order_detail od
ON od.order_id = o.order_id
GROUP
BY c.customer_id;
+-------------+-----------+----------+-------+
| customer_id | firstname | lastname | total |
+-------------+-----------+----------+-------+
| 23 | James | Smith | 3 |
| 24 | Tom | Raven | 2 |
| 25 | Bob | King | 3 |
+-------------+-----------+----------+-------+
SELECT DISTINCT CustomerID, FirstName, LastName, Total
FROM ( SELECT id AS CustomerID, FirstName, LastName FROM Customers ) AS c
NATURAL JOIN
( SELECT id AS OrderID, CustomerID FROM Orders ) AS o
NATURAL JOIN
( SELECT OrderID, SUM( Product1 + Product2 + Product3 ) AS Total
FROM Products
GROUP
BY OrderID ) AS p;
I have a table setup like this
Liabilities:
Id | CustomerId | liabilities
---------------------------
9 90 1000
...
Payments:
Id | CustomerId | Payment
---------------------------
3 90 2500
4 91 1000
...
Customer:
Id | balance
---------------------------
90 1500
91 1000
...
As you can see, the balance for a customer is the sum of all its payments minus the sum of all its liabilities. What is an SQL query to update the balance?
You can do it using an UPDATE statement with LEFT JOIN operations to derived tables containing Payments and Liabilities aggregates:
UPDATE Customer AS c
LEFT JOIN (
SELECT CustomerId, SUM(Payment) AS TotalPayment
FROM Payments
GROUP BY CustomerId
) AS p ON c.Id = p.CustomerId
LEFT JOIN (
SELECT CustomerId, SUM(liabilities) AS TotalLiabilities
FROM Liabilities
GROUP BY CustomerId
) AS l ON c.Id = l.CustomerId
SET balance = COALESCE(TotalPayment, 0) - COALESCE(TotalLiabilities, 0)
Demo here
Alternatively, you can use correlated subqueries in the UPDATE statement:
UPDATE Customer AS c
SET balance = COALESCE((SELECT SUM(Payment)
FROM Payments
WHERE CustomerId = c.Id) , 0)
-
COALESCE((SELECT SUM(liabilities)
FROM Liabilities
WHERE CustomerId = c.Id) , 0)
Demo here