Update one table with near values from another table - mysql

I have two tables.
Products
------------
ID | Sales | Rank
==================
1 | 0 | 100
2 | 0 | 105
3 | 0 | 200
4 | 0 | 900
Sales
ID | Sales | Rank
==================
1 | 2000 | 99
2 | 5000 | 106
3 | 8000 | 800
4 | 2500 | 950
I want to update sales.products with sales.sales based on the rank. for example
set products.sales=sales.sales where sales.sales is nearest to product.sales
In the above case below will be the results of the query.
Products
------------
ID | Sales | Rank
==================
1 | 2000 | 100
2 | 5000 | 105
3 | 5000 | 200
4 | 2500 | 900
Try to find sales of the product from sales table based on rank, If not found then find anything where Product.Rank is NEAREST value to sales.rank.
thanks

Because MySQL doesn't have window functions, you have to do it the "hard way". First, build a query that calculates what the minimum difference is for each product rank:
select p.rank, min(abs(s.rank - p.rank)) diff
from sales s
cross join products p
group by 1
Then use that to find which rank is closest by finding the rank with that difference, joining again and discarding all but the highest sales to break ties:
update Products
join (select p.rank, min(abs(s.rank - p.rank)) diff
from Sales s
cross join Products p
group by 1) x on Products.rank = x.rank
join Sales s1 on abs(Products.rank - s1.rank) = x.diff
left join Sales s2 on abs(Products.rank - s2.rank) = x.diff
and s2.sales > s1.sales
and s1.rank != s2.rank
set Products.sales = s1.sales
where s2.rank is null
Since SQLFiddle is down for the count, here's the full script:
create table Products (ID int, Sales int, Rank int);
insert into Products values
(1,0,100),
(2,0,105),
(3,0,200),
(4,0,900);
create table Sales (ID int, Sales int, Rank int);
insert into Sales values
(1,2000,99),
(2,5000,106),
(3,8000,800),
(4,2500,950);
update Products
join (select p.rank, min(abs(s.rank - p.rank)) diff
from Sales s
cross join Products p
group by 1) x on Products.rank = x.rank
join Sales s1 on abs(Products.rank - s1.rank) = x.diff
left join Sales s2 on abs(Products.rank - s2.rank) = x.diff
and s2.sales > s1.sales
and s1.rank != s2.rank
set Products.sales = s1.sales
where s2.rank is null;
select * from Products;
Output:
+------+-------+------+
| ID | Sales | Rank |
+------+-------+------+
| 1 | 2000 | 100 |
| 2 | 5000 | 105 |
| 3 | 5000 | 200 |
| 4 | 2500 | 900 |
+------+-------+------+

Related

Select SUM from multiple tables for every record in MySQL table

I'm having a table with main invoice data, and two table with invoice items:
items which are based on hourly work, with an hourly rate and an amount of hours
items which are products, with a unit count an unit price
For the invoice overview page, I'd like to retrieve all invoices and their total amounts with one query.
A simplified schema
invoices_main
| invoice_id |
| 1 |
| 2 |
| 3 |
invoices_items_products
| item_id | invoice_id | item_count | item_unit_price |
| 1 | 1 | 1 | 999.95 |
| 2 | 1 | 20 | 49.50 |
| 3 | 2 | 3 | 15.00 |
| 4 | 2 | 5 | 5.00 |
| 5 | 3 | 2 | 150.00 |
invoices_items_hourly
| item_id | invoice_id | item_hours | item_hourly_rate |
| 1 | 1 | 3.50 | 90.00 |
| 2 | 1 | 1.00 | 140.00 |
| 3 | 2 | 12.00 | 90.00 |
| 4 | 3 | 1.50 | 90.00 |
With the help of this question, I've constructed the following query:
SELECT
I.invoice_id,
IFNULL(
SUM(ROUND(P.item_unit_price * P.item_count, 2)),
0
) + IFNULL(
SUM(ROUND(H.item_hourly_rate * H.item_hours, 2)),
0
) AS invoice_total_amount
FROM
invoices_main I
LEFT JOIN invoices_items_products P ON I.invoice_id = P.invoice_id
LEFT JOIN invoices_items_hours H ON I.invoice_id = H.invoice_id
GROUP BY
I.invoice_id
It works kind of, but if an invoice has both products and hourly items, with at least multiple entries for one of both, items are duplicated due to the joins and the total amount becomes way too high.
Thus, in the above example schema, it goes wrong with invoice_id 1 and 2, but work with 3.
How can I retrieve a list of invoices with their respective total amounts, in a way that works even if an invoice has multiple products and multiple hourly items?
Try putting both left join's into a subquery instead.
SELECT
I.invoice_id,
IFNULL
(
(
SELECT SUM(ROUND(H.item_hourly_rate * H.item_hours, 2))
FROM invoices_items_hours AS H
WHERE H.invoice_id = I.invoice_id
)
, 0
) +
IFNULL
(
(
SELECT SUM(ROUND(P.item_unit_price * P.item_count, 2))
FROM invoices_items_products AS P
WHERE P.invoice_id = I.invoice_id
)
, 0
) AS invoice_total_amount
FROM invoices_main AS I
GROUP BY I.invoice_id
As mentioned in the comments, you should sum up the revenue in each table per invoice_id before doing the join. If you're looking to get the revenue from both of these places then you can add (B.unit_revenue + C.hourly_revenue) total_revenue to the first SELECT statement below.
SELECT A.invoice_id, B.unit_revenue, C.hourly_revenue FROM
invoices_main AS A
JOIN (
SELECT invoice_id, SUM(item_count * item_unit_price) unit_revenue
FROM invoices_items_products GROUP BY invoice_id
) B
ON
A.invoice_id = B.invoice_id
JOIN (
SELECT invoice_id, SUM(item_hours * item_hourly_rate) hourly_revenue FROM
invoices_items_hours GROUP BY invoice_id
) C
ON
A.invoice_id = C.invoice_id

Get SUM of two fields in 2 different related tables

I'd like to get the SUM of the amount column in two related tables.
Invoices Table:
-----------------------------------------
| id | student_id | created | updated |
-----------------------------------------
| 5 | 25 | date | date |
-----------------------------------------
Invoice Items Table:
------------------------------
| id | invoice_id | amount |
------------------------------
| 1 | 5 | 250 |
------------------------------
| 2 | 5 | 100 |
------------------------------
| 3 | 5 | 40 |
------------------------------
Payments Table:
------------------------------
| id | invoice_id | amount |
------------------------------
| 1 | 5 | 100 |
------------------------------
| 2 | 5 | 290 |
------------------------------
Desired Output:
--------------------------------------
| id | invoiceTotal | paymentTotal |
--------------------------------------
| 1 | 390 | 390 |
--------------------------------------
The query I've tried
SELECT
i.id,
sum(ii.amount) as invoiceTotal,
sum(p.amount) as paymentTotal
FROM
invoices i
LEFT JOIN
invoice_items ii ON i.id = ii.invoice_id
LEFT JOIN
payments p ON i.id = p.invoice_id
WHERE
i.student_id = '25'
GROUP BY
i.id
What this seems to do is calculate the sum of the payments properly but the invoice_items.amount appears to have been duplicated by 6 (which is the number of payments there are).
I have read similar questions on SO here and here but the examples are so much more complex than what I'm trying to do and I can't figure out what to put where.
The join causes a problem with cartesian products. If a student has multiple invoice items and payments, then the totals will be wrong.
One approach that works best for all invoices is a union all/group by approach:
select i.id, sum(invoiceTotal) as invoiceTotal, sum(paymentTotal) as paymentTotal
from ((select i.id, 0 as invoiceTotal, 0 as paymentTotal
from invoices i
) union all
(select ii.invoiceId, sum(ii.amount) as invoiceTotal, NULL
from invoiceitems ii
group by ii.invoiceId
) union all
(select p.invoiceId, 0, sum(p.amount) as paymentTotal
from payments p
group by p.invoiceId
)
) iip
group by id;
For a single student, I would recommend correlated subqueries:
select i.id,
(select sum(ii.amount)
from invoiceitems ii
where ii.invoiceid = i.id
) as totalAmount,
(select sum(p.amount)
from payment p
where p.invoiceid = i.id
) as paymentAmount
from invoices i
where i.studentid = 25;

Using conditionals, I'm getting different results depending on whether I use SUM or not

I have a query that adds up the total value of an order (sum of quantity*price for each product). I want to have a column that contains the total value of only "A" size products. So it should sum up quantity*price for all products except for the "C" size product.
As long as I return a row for each product, I get the expected results: $5 for each A size product and $0 for the C size product (in the smallsize column).
SELECT
part_num, email, b.item, b.size_code,
IF(b.size_code IN ('A','B'), quan * price,0) AS smallsize,
quan * price AS price
FROM `orders` AS b
INNER JOIN `groups` AS a
ON b.ordnum = a.line_num
INNER JOIN `prices` AS c
ON c.item = b.item AND c.size_code = b.size_code
| part_num | email | item | size_code | smallsize | price |
|----------|--------------|------|-----------|-----------|-------|
| 2 | me#email.com | 7400 | A | 5 | 5 |
| 2 | me#email.com | 7400 | C | 0 | 20 |
| 2 | me#email.com | 7790 | A | 5 | 5 |
| 2 | me#email.com | 7870 | A | 5 | 5 |
| 2 | me#email.com | 7910 | A | 5 | 5 |
But when I try to sum up the prices, the C size product is included in the total ($5 + $5 + $5 + $5 + $20 = $40).
SELECT
part_num, email,
IF(b.size_code IN ('A','B'), sum(quan * price),0) AS smallsize,
sum(quan * price) AS price
FROM `orders` AS b
INNER JOIN `groups` AS a
ON b.ordnum = a.line_num
INNER JOIN `prices` AS c
ON c.item = b.item AND c.size_code = b.size_code;
| part_num | email | smallsize | price |
|----------|--------------|-----------|-------|
| 2 | me#email.com | 40 | 40 |
Why does the first query return a total of $20 (if you add up the smallsize column) and the second query return $40 for smallsize?
I've created a sqlfiddle to illustrate: http://sqlfiddle.com/#!9/b5ccd8/4
You should put the IF expression inside the SUM. Otherwise you're summing up all the rows, and then the IF is deciding whether to show the sum or 0 in the final result (which is indeterminate, it depends on which row's b.size it happens to choose).
SELECT
part_num, email,
sum(IF(b.size_code IN ('A','B'), quan * price,0)) AS smallsize,
sum(quan * price) AS price
FROM `orders` AS b
INNER JOIN `groups` AS a
ON b.ordnum = a.line_num
INNER JOIN `prices` AS c
ON c.item = b.item AND c.size_code = b.size_code;
DEMO

How to SUM column in MySQL left join

So i have table cont_selling
---------------------------------
cont_selling_id | date |
---------------------------------
1 | 2015-05-24 |
2 | 2015-06-06 |
---------------------------------
table 02 cont_sold
----------------------------------------------------
cont_sold_id | cont_selling_id | price |
---------------------------------------------------
1 | 1 | 10 |
2 | 1 | 10 |
3 | 1 | 30 |
4 | 2 | 20 |
5 | 2 | 10 |
--------------------------------------------------
and table 03 payment
----------------------------------------------
payment_id | cont_selling_id | paid |
-----------------------------------------------
1 | 1 | 10 |
2 | 2 | 10 |
3 | 1 | 20 |
4 | 1 | 10 |
5 | 2 | 10 |
-----------------------------------------------
now i need to SELECT table based on
now i want to merge all these three tables based on cont_selling table cont_selling_id column
and want to SUM cont_sold table price column and payment table paid column
this is what i want to do
expecting output
---------------------------------------------
cont_selling_id | price | paid |
---------------------------------------------
1 | 50 | 40 |
2 | 30 | 20 |
---------------------------------------------
so i tried like this in mysql query but it give wrong sum result
SELECT
SUM(Z.price) as total,
SUM(P.amount) as paid
FROM cont_selling S
LEFT JOIN cont_sold Z
ON S.cont_selling_id = Z.cont_selling_id
LEFT JOIN payment P
ON S.cont_selling_id = P.cont_selling_id
GROUP BY S.cont_selling_id
for this above query i m getting output like this
---------------------------------------------
cont_selling_id | price | paid |
---------------------------------------------
1 | 150 | 40 |
2 | 60 | 120 |
---------------------------------------------
Here how you can do it using the aggegare part into inner queries and then join
select
cs.cont_selling_id,
price,
paid
from cont_selling cs
left join(
select sum(price) as price , cont_selling_id from cont_sold
group by cont_selling_id
)x on x.cont_selling_id = cs.cont_selling_id,
left join(
select sum(paid) as paid , cont_selling_id from payment
group by cont_selling_id
)y
on y.cont_selling_id = cs.cont_selling_id;
You should make two different queries with SUM and then combine them to get the desired result:
SELECT T1.cont_selling_id,T1.price,T2.paid
FROM
(SELECT c.cont_selling_id,SUM(cs.price) as price
FROM cont_selling c LEFT JOIN
cont_sold cs ON c.cont_selling_id=cs.cont_selling_id
GROUP BY c.cont_selling_id) as T1 JOIN
(SELECT c.cont_selling_id,SUM(p.paid) as paid
FROM cont_selling c LEFT JOIN
payment p ON p.cont_selling_id=c.cont_selling_id
GROUP BY c.cont_selling_id) as T2 ON T1.cont_selling_id=T2.cont_selling_id
Result:
cont_selling_id price paid
----------------------------
1 50 40
2 30 20
Sample result in SQL Fiddle.
This untested query should work:
with a as(select cont_selling_id , sum(price) as totalprice from cont_sold group by cont_selling_id),
with a as(select cont_selling_id , sum(paid) as totalpaid from payment group by cont_selling_id),
select c.cont_selling_id , totalprice, totalpaid from cont_selling c left join a.count_selling_id = c.count_selling_id
left join b.count_selling_id = c.count_selling_id
You have to create temporary tables, because there is no dependency between your table for price and paid.

MySQL subtract from isolated subquery

I have a table containing inventory
ID | Product ID | In_Transit | Quantity | Cumulative Quantity
=====+================+==============+==============+====================
1 | 1 | 0 | 1000 | 1000
2 | 1 | 0 | 1 | 1001
3 | 1 | 1 | 54 | 1055
4 | 1 | 1 | 1 | 1056
So the total inventory for product id 1 is '1056' I get this using a SELECT MAX(ID) subquery join with the table to get its cumulative quantity which is 1056.
I would like to get the Inventory total (subtracting all the amounts in transit)
So 1056 - 54 - 1 = 1001
How would I get this in one query so i get
Product ID | Total Inventory | Inventory on Hand (Excluding in Transit |
===========+=================+=========================================
1 | 1056 | 1001
Also i need to use the cumulative inventory to get the total as opposed to 'SUM', except for summing those in transit because (those not in transit) have a large number of records and they take ages to SUM. I can use it to sum those in transit because there are far fewer records
SELECT
a.product_id
, a.cumulative as total_inventory
, a.cumulative - COALESCE(b.quantity,0) AS inventory_on_hand
FROM table1 a
JOIN
( SELECT MAX(id) AS max_id
FROM table1
GROUP BY product_id
) m ON (m.max_id = a.id)
LEFT JOIN
( SELECT product_id, SUM(quantity)
FROM table1
WHERE in_transit = 1
GROUP BY product_id
) b ON (a.product_id = b.product_id)