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
Related
i need a query that should first look the oldest order which has status 0 (zero). and retrieves all the similar orders of that kind(matches exact total qty, itemSku and number of distinct items ordered).
***OrdersTable***
ID OrderNumber CustomerId Status created_at
1 123456 1 0 2018-01-01
2 234567 1 0 2018-01-02
3 345678 1 0 2018-01-03
4 456789 1 0 2018-01-04
***PurchasedProductsTable***
OrderId itemSku Qty
1 1000001 1
1 1000002 2
2 1000001 3
3 1000001 1
3 1000002 2
4 1000001 3
In the above table the query should first look at the oldest (created_at ASC) order (i.e with Id 1) having status 0 (in order table). and along with that order it should retrieves all the other orders that matches the same itemSku, qty and total distinct items count (in purchasedProducts table).
here order 1 and 3 matches the same itemSKu (1000001 and 1000002) and qty ( 1 and 2) and both have (2) distinct items count respectively so order 1 and 3 should be retrived at first.and when i marked order 1 and 3 as shipped (i.e chang status to 2).
and if i run query again it should retrive similar oders. now order 2 and 4 as order 2 and 4 are similar orders. (have same itemSkus (1000001, Qty (3) and distinct items count (1)).
please help thanks
You have to go trough your tables two times :)
Something like this :
SELECT DISTINCT O2.ID
FROM OrdersTable O1
INNER JOIN PurchasedProductsTable P1 ON O1.ID = P1.OrderId
INNER JOIN PurchasedProductsTable P2 ON P1.itemSku = P2.itemSku
AND P1.Qty = P2.Qty
INNER JOIN OrdersTable O2 ON O2.ID = P2.OrderId
WHERE O1.ID =
(SELECT ID FROM OrdersTable WHERE Status = 0
ORDER BY created_at ASC LIMIT 1)
AND (SELECT COUNT(*) FROM PurchasedProductsTable WHERE OrderId = O1.ID)
= (SELECT COUNT(*) FROM PurchasedProductsTable WHERE OrderId = O2.ID)
ORDER BY O2.ID ASC;
https://www.db-fiddle.com/f/65t9GgSfqMpzNVgnrJp2TR/2
You can get the earliest order via a limit and ordered by the date.
Then you can left join to get that order and any other order that at least has the same items.
Then once you have those order id's from the sub-query result, you can get the order details.
SELECT o.*
FROM
(
SELECT DISTINCT ord2.ID as OrderId
FROM
(
SELECT ID, CustomerId, Status
FROM OrdersTable
WHERE Status = 0
ORDER BY created_at
LIMIT 1
) AS ord1
JOIN PurchasedProductsTable AS pprod1
ON pprod1.OrderId = ord1.ID
LEFT JOIN OrdersTable ord2
ON ord2.CustomerId = ord1.CustomerId
AND ord2.Status = ord1.Status
LEFT JOIN PurchasedProductsTable pprod2
ON pprod2.OrderId = ord2.ID
AND pprod2.itemSku = pprod1.itemSku
AND pprod2.Qty = pprod1.Qty
GROUP BY ord1.CustomerId, ord1.ID, ord2.ID
HAVING COUNT(pprod1.itemSku) = COUNT(pprod2.itemSku)
) q
JOIN OrdersTable AS o ON o.ID = q.OrderId;
Test on RexTester here
I have 2 tables with information: ID, persona_id, total_amount
The persona ID can repeat dozen of times. So i get all the one persons id total_amount with query:
select d.id as debt_id, p.name as persona, sum(d.total_amount) as total_amount
from debt d
join persons p on d.persona_id = p.id group by p.name
I want to get data from each table in one query and do aritmethic propertys with the total_amount column and return it as 1 tabel.
TABLE 1
id persons_id total_amount
1 2 50
2 3 100
3 2 200
4 5 300
5 1 500
TABLE 2
id persons_id total_amount
1 2 25
2 1 100
3 5 50
4 3 100
5 4 300
As a result i want to get the 2 tables comined with arithmetic operation (-, +, / , * ) of Total amount columns.Basicaly a change to get the ending result total amount in form i want for different cases.
What worked for me based on JohnHC answear was :
select c.id, c.persona_id, c.total_amount - d.total_amount as new_total
from ( select c.id , c.persona_id, sum(c.total_amount) as total_amount from credit c
join persons p on c.persona_id = p.id
group by p.name) c
inner join ( select d.id, d.persona_id, sum(d.total_amount) as total_amount from debt d
join persons p on d.persona_id = p.id
group by p.name) d
on c.persona_id = d.persona_id
group by c.id, c.persona_id
If you want the total, try:
select id, person_id, sum(total_amount)
from
(
select id, person_id, total_amount
from table1
union all
select id, person_id, total_amount
from table2
)
group by id, person_id
If you want to do other things, try:
select t1.id, t1.person_id, t1.total_amount [+ - / *] t2.total_amount as new_total
from table1 t1
inner join table2 t2
on t1.id = t2.person_id
group by t1.id, t1.person_id
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 two tables customers and orders, where important columns are:
/* Customers: */
id | login | membershipid
100 | email1 | 0
101 | email2 | 0
/* Orders: */
userid | total
100 | 150
101 | 120
101 | 450
I want to update membershipid depending of total purchases for each customer. Lets say it will be zero if the sum is less than 500, one if sum is 500-1000, two if sum is over 1000.
I get the sum this way:
SELECT customers.login, sum( orders.total ) AS total_purchased
FROM xcart_customers AS customers
LEFT JOIN xcart_orders AS orders ON customers.id = orders.userid
GROUP BY customers.login
but I am not sure how to update back customers table. Something like:
UPDATE xcart_customers set (membershipid) = (
SELECT sum( orders.total ) AS total_purchased
FROM xcart_customers AS customers
LEFT JOIN xcart_orders AS orders ON customers.id = orders.userid
GROUP BY customers.login
)
but where and how to use conditions (and will GROUP BY work?):
CASE
WHEN
..
ELSE
END
UPDATE xcart_customers
join
(
SELECT customers.login, sum( orders.total ) AS total_purchased
FROM xcart_customers AS customers
LEFT JOIN xcart_orders AS orders ON customers.id = orders.userid
GROUP BY customers.login
) tmp on tmp.login = xcart_customers.login
set xcart_customers.membershipid = case when total_purchased is null or total_purchased < 500 then 0
when total_purchased between 500 and 1000 then 1
else 2
end
One way to do this is with a join operation. Take your query, and turn it into an inline view, and join that back to the customers table.
We might want to handle the case when total_purchased is NULL as being less than 500 (when a customer doesn't have any related rows in xcart_orders.)
As a SELECT it would look like this:
SELECT t.login
, t.membershipid
, CASE
WHEN IFNULL(s.total_purchased,0) < 500 THEN 0
WHEN s.total_purchased <= 1000 THEN 1
WHEN s.total_purchased > 1000 THEN 2
ELSE NULL
END AS new_membershipid
FROM customers t
JOIN ( SELECT c.login
, SUM(o.total) AS total_purchased
FROM xcart_customers c
LEFT
JOIN xcart_orders o
ON o.userid = c.id
GROUP BY c.login
) s
ON s.login = t.login
To convert that into an UPDATE, replace the SELECT ... FROM with UPDATE
and add a SET clause at the end of the statement (before the WHERE clause if there was a WHERE clause).
For example:
UPDATE customers t
JOIN ( SELECT c.login
, SUM(o.total) AS total_purchased
FROM xcart_customers c
LEFT
JOIN xcart_orders o
ON o.userid = c.id
GROUP BY c.login
) s
ON s.login = t.login
SET t.membershipid =
CASE
WHEN IFNULL(s.total_purchased,0) < 500 THEN 0
WHEN s.total_purchased <= 1000 THEN 1
WHEN s.total_purchased > 1000 THEN 2
ELSE NULL
END
NOTE: This assumes that login is UNIQUE` in the customers table.
FOLLOWUP
Given that membershipid cannot be assigned a NULL value, we can tweak the CASE expression. Do the check for > 1000 first, then a check for >= 500, else 0.
For example:
CASE
WHEN s.total_purchased > 1000 THEN 2
WHEN s.total_purchased >= 500 THEN 1
ELSE 0
END
I have a couple of tables:
Invoice
-----------------
ID total
1 500.00
2 100.00
3 10.00
Payment
---------------------------------------
ID invoiceId Amount Method
1 1 400 CASH
2 2 60 CASH
3 2 40 CREDIT
I need a query that gets all invoices where at least one payment.method is CREDIT and the sum of all payments for that invoice is greater than the total of the invoice.
And I need it to be fast.
How can I do this?
SELECT a.ID InvoiceID,
a.Total TotalInvoice,
b.TotalPayment
FROM Invoice a
INNER JOIN
(
SELECT InvoiceID, SUM(Amount) TotalPayment
FROM Payment
GROUP BY InvoiceID
HAVING SUM(Method = 'CREDIT') > 0
) b ON a.ID = b.InvoiceID AND
a.Total < b.TotalPayment
SQLFiddle Demo
Another method:
SELECT
i.`id`,
i.`total` AS `total_invoiced`,
SUM(p.`amount`) AS `total_payments`,
SUM(IF(p.`method`='credit', 1, 0)) AS `count_credit`
FROM `invoices` i
LEFT JOIN `payments` p ON (p.`invoice_id`=i.`id`)
WHERE 1=1
GROUP BY i.`id`
HAVING (`total_payments` > i.`total`) AND (`count_credit` > 0)
I changed some table/field names. Sorry for the inconvenience.
http://www.sqlfiddle.com/#!2/7402d/1/0
try this one:
select iagg.ID
from (
select i.ID
, sum (p.amount) tl_paid
, min (i.total) tl
from invoice i
join payment p on ( p.invoiceID = i.ID )
group by i.ID
) iagg
where exists (
select 1
from payment p2
where p2.invoiceId = iagg.ID
and p2.method = 'CREDIT'
)
and iagg.tl_paid > iagg.tl
;
the minimum operator over the total attribute is mandated by the grouping operator, the minimum is actually taken from a set of identical values.