MySQL Group by complex script - mysql

I have an script that works perfect, but need to add values from another table
Current script is
select v.id, vm.producto_id, sum(vm.total), count(v.id)
from visita v, reporte r, visitamaquina vm, maquina m,
(select r.id, empleado_id, fecha, cliente_id from ruta r, rutacliente rc where r.id=rc.ruta_id and
fecha>='2016-10-01' and fecha<='2016-10-30' group by fecha, cliente_id, empleado_id) as rem
where rem.fecha=v.fecha and v.cliente_Id=rem.cliente_id and r.visita_id=v.id and vm.visita_id=v.id and m.id=vm.maquina_id
group by vm.visita_id, vm.producto_id
Current Script returns this (I need some extra columns but for this purpose I only leave the ones with issues):
| Producto_Id | Id | Total | count(id) |
|---------------|--------------|-----------|-----------|
| 1 | 31 | 21 | 2 |
| 2 | 31 | 15 | 3 |
| 3 | 31 | 18 | 2 |
Table VisitaMaquina has multiple records for same producto_id
VisitaMaquina has this:
| Producto_Id | Visita_Id | Total |
|---------------|--------------|-----------|
| 1 | 31 | 8 |
| 1 | 31 | 13 |
| 2 | 31 | 9 |
Same situation happens with table called reporteproducto, where multiple times producto_id is repeated.
Table reporteproducto has
| Producto_Id | Visita_Id | Quantity |
|---------------|--------------|-----------|
| 1 | 31 | 4 |
| 1 | 31 | 7 |
| 2 | 31 | 5 |
My previous query works fine, and I just need to get the sum of quantity
I used this Script and this is what I got
select v.id, vm.producto_id, sum(vm.total), sum(quantity), count(id)
from visita v, reporte r, visitamaquina vm, maquina m, reporteproducto rp,
(select r.id, empleado_id, fecha, cliente_id from ruta r, rutacliente rc where r.id=rc.ruta_id and
fecha>='2016-10-01' and fecha<='2016-10-30' group by fecha, cliente_id, empleado_id) as rem
where rem.fecha=v.fecha and v.cliente_Id=rem.cliente_id and r.visita_id=v.id and vm.visita_id=v.id and m.id=vm.maquina_id and rp.visita_Id=v.id and rp.producto_id=vm.producto_id
group by vm.visita_id, vm.producto_id
I got this
|Producto_Id | Visita_Id | Total |Quantity | count(id)
|---------------|--------------|-----------|-----------|-----------|
| 1 | 31 | 42 | 11 | 4 |
| 2 | 31 | 45 | 18 | 6 |
| 3 | 31 | 36 | 44 | 4 |
The desired result is (focus on producto_id=1):
|Producto_Id | Visita_Id | Total |Quantity |
|---------------|--------------|-----------|-----------|
| 1 | 31 | 21 | 11 |
| 2 | 31 | 15 | 18 |
| 3 | 31 | 18 | 44 |
Any Idea on how to solve this?

Better group the sub table that has multiple data with the same group of your outer group by columns.In your case the VisitaMaquina and reporteproducto should be group by with visita_id, producto_id since they all have repeat rows with the same combination of vid=31 and pid=1.
You can change the visitamaquina vm and reporteproducto rp table alias to sub query form of the following:
(select visita_id, Producto_Id, sum(Total) as Total from visitamaquina
group by visita_id, Producto_Id) vm,
(select Producto_Id, Visita_Id, sum(Quantity) as Quantity from reporteproducto
group by Producto_Id, Visita_Id) rp
Also I found that there is vm.maquina_id in your where clause, maybe this causes your problem.Because if the visitamaquina and reporteproducto both have repeat values of visita_id, producto_id then the output should have Total, Quantity both doubled.In your output the Quantity is right, that's odd.

My Mistake
I got this
|Producto_Id | Visita_Id | Total |Quantity | count(id)
|---------------|--------------|-----------|-----------|-----------|
| 1 | 31 | 42 | 22 | 4 |
| 2 | 31 | 45 | 36 | 6 |
| 3 | 31 | 36 | 88 | 4 |

Related

How to subtract a value from a sum of total in MySql LEFT JOIN Query

I have 2 tables.
SELECT * FROM purchases;
+------+---------+-----------+
|purid | total_q | dstatus |
+------+---------+-----------+
| 1 | 45 | DELIVERED |
| 2 | 50 | LOADING |
| 3 | 24 | DELIVERED |
| 4 | 15 | DELIVERED |
| 5 | 10 | DELIVERED |
+------+---------------------+
SELECT * FROM warehouse;
+------+-------+---------+
| wid | purid | total_q |
+------+-------+---------+
| 4 | 1 | 45 |
| 5 | 4 | 15 |
| 9 | 3 | 10 |
| 12 | 3 | 5 |
+------+-------+---------+
I want to get "delivered" purchases with its amounts which are not already included in warehouse table. Here is the demo where I stuck: DEMO
The query which I use is:
SELECT p.purid as purid, (p.total_q - IFNULL(w.total_q,0)) as ntq
FROM `purchases` as p
LEFT JOIN `warehouse` as w ON p.purid=w.purid
WHERE p.dstatus = "DELIVERED" AND (p.total_q - IFNULL(w.total_q,0)) > 0
My desired output:
+-------+------+
| purid | ntq |
+-------+------+
| 5 | 10 |
| 3 | 9 |
+------+-------+
The problem is I could not subtract "total_q (24) from purchases table" from "sum total_q(10+5) from warehouse table".
You can try to use subquery aggregate warehouse by purid before join otherwise you might get multiple rows.
Query #1
SELECT p.purid as purid,
p.total_q - IFNULL(w.total_q,0) as ntq
FROM `purchases` as p
LEFT JOIN (
SELECT purid,SUM(total_q) total_q
FROM warehouse
GROUP BY purid
) as w ON p.purid=w.purid
WHERE p.dstatus = "DELIVERED"
AND p.total_q - IFNULL(w.total_q,0) > 0;
purid
ntq
3
9
5
10
View on DB Fiddle

Subtract two columns of different tables with different number of rows

How can I write a single query that will give me SUM(Entrance.quantity) - SUM(Buying.quantity) group by product_id.
The problem is in rows that not exist in the first or second table. Is possible to do this?
Entrance:
+---+--------------+---------+
| id | product_id | quantity|
+---+--------------+---------+
| 1 | 234 | 15 |
| 2 | 234 | 35 |
| 3 | 237 | 12 |
| 4 | 237 | 18 |
| 5 | 101 | 10 |
| 6 | 150 | 12 |
+---+--------------+---------+
Buying:
+---+------------+-------------+
| id | product_id | quantity|
+---+------------+-------------+
| 1 | 234 | 10 |
| 2 | 234 | 20 |
| 3 | 237 | 10 |
| 4 | 237 | 10 |
| 5 | 120 | 15 |
+---+------------+------------+
Desired result:
+--------------+-----------------------+
| product_id | quantity_balance |
+--------------+-----------------------+
| 234 | 20 |
| 237 | 10 |
| 101 | 10 |
| 150 | 12 |
| 120 | -15 |
+--------------+-----------------------+
This is tricky, because products could be in one table but not the other. One method uses union all and group by:
select product_id, sum(quantity)
from ((select e.product_id, quantity
from entrance e
) union all
(select b.product_id, - b.quantity
from buying b
)
) eb
group by product_id;
SELECT product_id ,
( Tmp1.enterquantity - Tmp2.buyquantity ) AS Quantity_balance
FROM entrance e1
CROSS APPLY ( SELECT SUM(quantity) AS enterquantity
FROM Entrance e2
WHERE e1.product_id = e2.product_id
) Tmp1
CROSS APPLY ( SELECT SUM(quantity) AS buyquantity
FROM Buying b2
WHERE e1.product_id = b2.product_id
) Tmp2
GROUP BY Product_id,( Tmp1.enterquantity - Tmp2.buyquantity )

Select the entire row of the lowest price for distinct product

I have the below table about products:
| id | name | product_id | price | seller_id | discount_id |
--------------------------------------------------------------
| 1 | phone | 11 | 400 | 7 | 19 |
| 2 | cpu | 78 | 120 | 33 | 4 |
| 3 | phone | 11 | 380 | 8 | 22 |
| 4 | phone | 11 | 460 | 5 | 19 |
| 5 | memory | 80 | 45 | 12 | 16 |
| 6 | router | 98 | 115 | 7 | 16 |
| 7 | cpu | 78 | 115 | 33 | 66 |
I need to select all the columns of distinct product_id with the lowest price. Also to ORDER the result by price ASC. For this example:
| id | name | product_id | price | seller_id | discount_id |
--------------------------------------------------------------
| 5 | memory | 80 | 45 | 12 | 16 |
| 6 | router | 98 | 115 | 7 | 16 |
| 7 | cpu | 78 | 115 | 33 | 66 |
| 3 | phone | 11 | 380 | 8 | 22 |
I have no problems doing this using GROUP BY product_id and min(price) but I also need other columns (seller_id & discount_id)
MySQL version: 5.7.17
sql_mode=only_full_group_by
Table is temporary (ENGINE=MEMORY) and can't JOIN multiple times
How can I produce the result above from MySQL?
Add a subquery with the min price and join on min price and product.
SELECT id, name,product_id,price,seller_id,discount_id FROM t
JOIN
(SELECT tt.product_id,MIN(tt.price) minp FROM t as tt
GROUP BY tt.product_id)x
ON x.product_id=t.product_id AND x.price = t.price
Another option with LIMIT
SELECT * FROM T WHERE EXISTS
(SELECT 1 FROM T as TT ORDER BY TT.price ASC LIMIT 1
WHERE t.id= TT.id)
Given that the MEMORY engine is so restricting go the caveman way
SELECT SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY price),',',1),
SUBSTRING_INDEX(GROUP_CONCAT(name ORDER BY price),',',1),
product_id,MIN(price),
SUBSTRING_INDEX(GROUP_CONCAT(seller_id ORDER BY price),',',1),
SUBSTRING_INDEX(GROUP_CONCAT(discount_id ORDER BY price),',',1) FROM t
GROUP BY product_id
You can join the table with itself (on product_id). As a join-condition add left.price > right.price - and then choose the rows, where right.price is null, because for that join, there is no lower right price, meaning the one you have left is the lowest:
SELECT l.id, l.name, l.product_id, l.price, l.seller_id, l.discount_id
FROM
products l
LEFT JOIN
products r
on
l.product_id = r.product_id AND l.price > r.price
WHERE
isnull (r.price) -- that means: no cheaper price for this position.
intermediate result (SELECT * no WHERE) would look like (shortened):
| l.id | l.name | l.product_id | l.price | r.id | r.name | r.product_id | r.price
| 3 | phone | 11 | 380 | null | null | null | null
| 4 | phone | 11 | 460 | 3 | phone | 3 | 380
Side node: For very large datasets there might be performance-issues, because every additional line of a component would add multiple result rows. i.e. consider another phone:
| l.id | l.name | l.product_id | l.price | r.id | r.name | r.product_id | r.price
| 3 | phone | 11 | 380 | null | null | null | null
| 4 | phone | 11 | 460 | 3 | phone | 3 | 380
| 5 | phone | 11 | 500 | 3 | phone | 3 | 380
| 5 | phone | 11 | 500 | 4 | phone | 3 | 460
So, if you want to get the lowest price in the past 60 days with daily changes, that will be a huge amount of rows just for "that"... (Actually 60+59+58+...+2+1 I think, cause the most expensive price will produce 59 comparision rows and so on)

mysql: Intergrate values of a table alog time

I want to integrate the v values with timediffs of t from one row to the next, in a table like this: "p_values"=
+------------+-------+----------+
| measure_id | v | t |
+------------+-------+----------+
| 1 | 32 | 10:45:00 |
| 2 | 17 | 10:42:00 |
| 3 | 20 | 10:39:00 |
| 4 | 21 | 10:36:00 |
| 5 | 35 | 10:33:00 |
| 6 | 59 | 10:30:00 |
| 7 | 47 | 10:27:00 |
| 8 | 45 | 10:24:00 |
| 9 | 40 | 10:21:00 |
| 10 | 39 | 10:18:00 |
| 11 | 42 | 10:15:00 |
+------------+-------+----------+
I want to integrate the v values with timediffs of t:
result = v[1]*(t[1]-t[2]) + v[2]*(t[2]-t[3]) + v[3]*(t[3]-t[4]) + ...
Can I do this on a single query?
I'm trying creating a table joining each column with the column below, like this:
select * from
(select measure_id, v, t from p_values order by t desc) a,
(select measure_id, v, t from p_values order by t desc) b
where a.t < b.t group by b.t desc;
+------------+----+----------+------------+----+----------+
| measure_id | v | t | measure_id | v | t |
+------------+----+----------+------------+----+----------+
| 9 | 83 | 11:12:00 | 10 | 25 | 11:15:00 |
| 8 | 90 | 11:09:00 | 9 | 83 | 11:12:00 |
| 7 | 24 | 11:06:00 | 8 | 90 | 11:09:00 |
| 6 | 29 | 11:03:00 | 7 | 24 | 11:06:00 |
| 5 | 72 | 11:00:00 | 6 | 29 | 11:03:00 |
| 4 | 28 | 10:57:00 | 5 | 72 | 11:00:00 |
| 3 | 22 | 10:54:00 | 4 | 28 | 10:57:00 |
| 2 | 42 | 10:51:00 | 3 | 22 | 10:54:00 |
| 1 | 35 | 10:48:00 | 2 | 42 | 10:51:00 |
| 0 | 31 | 10:45:00 | 1 | 35 | 10:48:00 |
+------------+----+----------+------------+----+----------+
Based on this table, I calculate the integral value in a single query as:
select sum(v) from
(select (a.v + b.v)/2 * (TIME_TO_SEC(b.t) - TIME_TO_SEC(a.t))/3600 as v from
(select measure_id, v, t from p_values order by t desc) a,
(select measure_id, v, t from p_values order by t desc) b
where a.t < b.t group by b.t desc) as c;
+---------+
| sum(v) |
+---------+
| 246.948 |
+---------+
But I'm not sure if this is the most efficient way to do this.
Thanks.
If you assume that the measure_id is incremental with no gaps, then you can do this with a self join. The resulting query is something like this:
select sum(p1.v*(p2.t - p1.t))
from p_values p1 join
p_values p2
on p2.measure_id = p1.measure_id + 1;
A couple of notes. First, this ignores the last v value, because there is no matching row. The question doesn't specify what to do in this case, so I assume you don't want that difference included.
I also left the simple notation for difference of times. Your question appears to be about handling the values from different rows, not actually calculating the difference of the time column. That, in turn, depends on the data type for the column, which is not specified in the question.
Finally, your subquery has a fatal flaw. It has columns in the select that are not in the group by. This uses a group by extension that the documentation explicitly warns against using.
select sum(value)
from
(select (p.v*(t-#prev)) as value,
#prev:=t
from (select #prev:=0) sess, p_values p
order by p.measure_id desc) raw
Here we introduce a variable #prev where we store value from previous row (but we sort in desc order).
Then just sum the results
UPDATE query for the fiddle
select sum(value)
from
(select (p.v*(t-#prev)) as value,
#prev:=v
from (select #prev:=0) sess, p_values p
order by v desc) raw

Sum up values in SQL once all values are available

I have events flowing into a MySQL database and I need to group and sum the events to transactions and store away into another table. The data looks like:
+----+---------+------+-------+
| id | transid | code | value |
+----+---------+------+-------+
| 1 | 1 | b | 12 |
| 2 | 1 | i | 23 |
| 3 | 2 | b | 34 |
| 4 | 1 | e | 45 |
| 5 | 3 | b | 56 |
| 6 | 2 | i | 67 |
| 7 | 2 | e | 78 |
| 8 | 3 | i | 89 |
| 9 | 3 | i | 90 |
+----+---------+------+-------+
The events arrive in batches and I would like to create the transaction by summing up the values for each transid, like:
select transid, sum(value) from eventtable group by transid;
but only after all the events for that transid have arrived. That is determined by the event with the code e (b for the beginning, e for the end and i for varying amount of intermediates). Being a novice in SQL, how could I implement the requirement for the existance of the end code before the summing?
Perhaps with having:
select transid, sum(value)
from eventtable
group by transid
having max(case code when 'e' then 1 end)=1;
select transid, sum(value) from eventtable
group by transid
HAVING COUNT(*) = 3
you should count the records in the group. So when there is (b)egin, (i)?? don't know what it is and (e)nd this group is not filtered out.