MySQL SUM with same ID - mysql

Sorry for the real simple question, I just learn PHP & MySQL, I already googling it for more than a week but I didn't found any answer.
I create a simple finance script and the table is like below :
table_a
aid | value
1 | 100
2 | 50
3 | 150
table_b
bid | aid | value
1 | 1 | 10
2 | 1 | 15
3 | 2 | 5
4 | 2 | 10
5 | 3 | 25
6 | 3 | 40
I want the result like this
No | ID | Total | Balance
1 | 1 | 10 | 90
2 | 1 | 25 | 75
3 | 2 | 5 | 45
4 | 2 | 15 | 35
5 | 3 | 25 | 125
6 | 3 | 65 | 85
Can anybody help me with my problem?
Thanks

Try this running total: http://www.sqlfiddle.com/#!2/ce765/1
select
bid as no, value,
#rt := if(aid = #last_id, #rt + value, value) as total,
#last_id := aid
from table_b b, (select #rt := 0 as x, #last_id := null) as vars
order by b.bid, b.aid;
Output:
| NO | VALUE | TOTAL | #LAST_ID := AID |
|----|-------|-------|-----------------|
| 1 | 10 | 10 | 1 |
| 2 | 15 | 25 | 1 |
| 3 | 5 | 5 | 2 |
| 4 | 10 | 15 | 2 |
| 5 | 25 | 25 | 3 |
| 6 | 40 | 65 | 3 |
Then join to table A, final query:
select x.no, x.aid, x.value, x.total, a.value - x.total as balance
from
(
select
bid as no, aid, value,
#rt := if(aid = #last_id, #rt + value, value) as total,
#last_id := aid
from table_b b, (select #rt := 0 as x, #last_id := null) as vars
order by b.bid, b.aid
) as x
join table_a a using(aid)
Output:
| NO | AID | VALUE | TOTAL | BALANCE |
|----|-----|-------|-------|---------|
| 1 | 1 | 10 | 10 | 90 |
| 2 | 1 | 15 | 25 | 75 |
| 3 | 2 | 5 | 5 | 45 |
| 4 | 2 | 10 | 15 | 35 |
| 5 | 3 | 25 | 25 | 125 |
| 6 | 3 | 40 | 65 | 85 |
Live test: http://www.sqlfiddle.com/#!2/ce765/1
UPDATE
Not dependent on column bid sorting, running total on grouping will not be impacted: http://www.sqlfiddle.com/#!2/6a1e6/3
select x.no, x.aid, x.value, x.total, a.value - x.total as balance
from
(
select
#rn := #rn + 1 as no, aid, value,
#rt := if(aid = #last_id, #rt + value, value) as total,
#last_id := aid
from table_b b, (select #rt := 0 as x, #last_id := null, #rn := 0) as vars
order by b.aid, b.bid
) as x
join table_a a using(aid)
Output:
| NO | AID | VALUE | TOTAL | BALANCE |
|----|-----|-------|-------|---------|
| 1 | 1 | 10 | 10 | 90 |
| 2 | 1 | 15 | 25 | 75 |
| 3 | 1 | 7 | 32 | 68 |
| 4 | 2 | 5 | 5 | 45 |
| 5 | 2 | 10 | 15 | 35 |
| 6 | 3 | 25 | 25 | 125 |
| 7 | 3 | 40 | 65 | 85 |
Live test: http://www.sqlfiddle.com/#!2/6a1e6/3

SELECT
tb.bid as No,
ta.aid as ID,
tb.value as Total,
ta.value-tb.total as Balance
FROM
table_a AS ta
INNER JOIN (
SELECT
tbx.aid AS aid,
tbx.bid AS bid,
tbx.value AS value,
SUM(tby.value) AS total
FROM
table_b AS tbx
INNER JOIN table_b AS tby ON tby.aid=tbx.aid AND tby.bid<=tbx.bid
GROUP BY tbx.bid
ORDER BY tbx.bid
) AS tb ON tb.aid=ta.aid
ORDER BY tb.bid
As #Quassnoi pointed out, this is not very efficient with MySQL. I tried to use a freak join instead of a subquery, as the inner query might be of use in its own right.
Edit
Took some interest in this and found the join version to be twice as fast as the subquery version by #Quassnoi ... anybody having an idea why this would be?
Edit
Answer to the second question (in comment below):
SELECT
table_a.aid AS aid,
SUM(table_b.value) AS Total,
table_a.value-SUM(table_b.value) AS Balance
FROM
table_a
INNER JOIN table_b ON table_a.aid=table_b.aid
GROUP BY table_a.aid

You are looking for analytics functions. Unfortunately, MySQL lacks them.
You can implement it in a less efficient way:
SELECT bid, aid, total, value - total
FROM (
SELECT b.bid, b.aid, COALESCE(a.value, 0) AS value,
(
SELECT SUM(value)
FROM b bp
WHERE bp.aid = b.aid
AND bp.bid <= b.bid
) AS total
FROM b
LEFT JOIN
a
ON a.aid = b.aid
) q

Related

Count occurences in Mysql

Let's say, in given num_table, there is a column, in which only numbers from 1 to 35 are stored.
Code for count nums in last 25rows is:
select num, count(*)
from (select C_1 as num from num_table order by id desc limit 25) n
group by num
order by num asc;
Result:
| num | count(*) |
|------|----------|
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 2 |
| 10 | 1 |
| 11 | 1 |
| 12 | 1 |
| 15 | 1 |
| 16 | 2 |
| 17 | 1 |
| 20 | 1 |
| 21 | 1 |
| 22 | 1 |
| 23 | 1 |
| 25 | 1 |
| 28 | 2 |
| 29 | 2 |
| 30 | 1 |
| 32 | 2 |
|------|----------|
How to get a result, where nums from 1 to 35 - which occured 0 times within last 25 rows - will be also displayed?
Example of desired result:
| num | count(*) |
|------|----------|
| 1 | 0 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 2 |
| 6 | 0 |
| 7 | 0 |
| 8 | 0 |
| 9 | 0 |
| 10 | 1 |
| ... | ... |
| 35 | 0 |
Maybe the quickest way is to make your existing query as sub-query and LEFT JOIN your num_table with it like :
SELECT A.C_1, IFNULL(cnt,0) total_count
FROM num_table A
LEFT JOIN
(SELECT num, COUNT(*) cnt
FROM (SELECT C_1 AS num FROM num_table ORDER BY id DESC LIMIT 25) n
GROUP BY num) B
ON A.C_1=B.num
GROUP BY A.C_1, cnt
ORDER BY A.C_1 ASC;
Here's a fiddle for reference:
https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=3ced94d698fd8a55a8ad07a9d3b42f3d
And by the way, the current result you're showing is only 24 rows despite you did LIMIT 25 in the first sub-query. So in my example fiddle, the result is slightly different.
Here is another way to solve your problem.
In this solution, first, you need a table with numbers between 1 and 35, but only for the query, so then you can left join (because with a left join you can have also 0 counter values) it with your existent num_table.
You can do it like this:
WITH RECURSIVE numbers(id) AS (
SELECT 1 as id
UNION ALL
SELECT id+1 FROM numbers WHERE id < 35
)
SELECT numbers.id AS num, count(nt.id) AS total
FROM numbers
LEFT JOIN (SELECT C_1 FROM num_table ORDER BY id DESC LIMIT 25) nt ON (nt.C_1 = numbers.id)
GROUP BY numbers.id

Get last balance sign change in (My)SQL

I have a Transaction table that records every amount added to or subtracted from the balance of a Customer, with the new balance:
+----+------------+------------+--------+---------+
| id | customerId | timestamp | amount | balance |
+----+------------+------------+--------+---------+
| 1 | 1 | 1000000001 | 10 | 10 |
| 2 | 1 | 1000000002 | -20 | -10 |
| 3 | 1 | 1000000003 | -10 | -20 |
| 4 | 2 | 1000000004 | -5 | -5 |
| 5 | 2 | 1000000005 | -5 | -10 |
| 6 | 2 | 1000000006 | 10 | 0 |
| 7 | 3 | 1000000007 | -5 | -5 |
| 8 | 3 | 1000000008 | 10 | 5 |
| 9 | 3 | 1000000009 | 10 | 15 |
| 10 | 4 | 1000000010 | 5 | 5 |
+----+------------+------------+--------+---------+
The Customer table stores the current balance, and looks like:
+----+---------+
| id | balance |
+----+---------+
| 1 | -20 |
| 2 | 0 |
| 3 | 15 |
| 4 | 5 |
+----+---------+
I would like to add a balanceSignSince column, that would store the timestamp at which the balance sign last changed. Transitioning to and from positive, negative, or zero counts as a balance change.
After the update, based on the above data, the Customer table should contain:
+----+---------+------------------+
| id | balance | balanceSignSince |
+----+---------+------------------+
| 1 | -20 | 1000000002 |
| 2 | 0 | 1000000006 |
| 3 | 15 | 1000000008 |
| 4 | 5 | 1000000010 |
+----+---------+------------------+
How can I write a SQL query that updates every Customer with the last time the balance sign changed, based on the Transaction table?
I suspect I can't do this without a quite complex stored procedure, but am curious to see if any clever ideas come up.
This uses a simulated rank() function.
select customerId, min(tstamp) from
(
select tstamp,
if (#cust = customerId and sign(#bal) = sign(balance), #rn := #rn,
if (#cust = customerId and sign(#bal) <> sign(balance), #rn := #rn + 1, #rn := 0)) as rn,
#cust := customerId as customerId, #bal := balance as balance
from
(select #rn := 0) x,
(select id, #cust := customerId as customerId, tstamp, amount, #bal := balance as balance
from trans order by customerId, tstamp desc) y
) z
where rn = 0
group by customerId;
Check it: http://rextester.com/XJVKK61181
This script returns a table like this:
+------------+----+------------+---------+
| tstamp | rn | customerId | balance |
+------------+----+------------+---------+
| 1000000003 | 0 | 1 | -20 |
| 1000000002 | 0 | 1 | -10 |
| 1000000001 | 1 | 1 | 10 |
| 1000000006 | 0 | 2 | 0 |
| 1000000005 | 2 | 2 | -10 |
| 1000000004 | 2 | 2 | -5 |
| 1000000009 | 0 | 3 | 15 |
| 1000000008 | 2 | 3 | 5 |
| 1000000007 | 3 | 3 | -5 |
| 1000000010 | 0 | 4 | 5 |
+------------+----+------------+---------+
Then selecting min(timestamp) of files where rn = 0:
+------------+-------------+
| customerId | min(tstamp) |
+------------+-------------+
| 1 | 1000000002 |
+------------+-------------+
| 2 | 1000000006 |
+------------+-------------+
| 3 | 1000000009 |
+------------+-------------+
| 4 | 1000000010 |
+------------+-------------+
Updated answer with the restriction that this needs to work on the existing data
The following query should work for most cases, there is still an issue with customers having only a single transaction or no sign change. As this is a one time update, I would run the query below and then do a simple update for all users not having a timestamp set, for them it's going to be the timestamp of the first transaction:
# Find the smallest timestamp, e.g. the
# transaction which changed the signum.
SELECT
p.customerId as customerId,
MIN(t.timestamp) as balanceSignSince
FROM
transaction as t,
(
# find the latest timestamp having
# a different sign for each user.
# Here is the issue with users having
# only a single transaction or no sign
# changes.
SELECT
u.customerId as customerId,
MAX(t.timestamp) as balanceSignSince
FROM
transaction as t,
customer as c,
(
# find the timestamp of the very last
# transaction for every user.
SELECT
t.customerId as customerId,
MAX(t.timestamp) as lastTransaction
FROM
transaction as t
GROUP BY
t.customerId
) as u
WHERE
u.customerId = c.id
AND u.customerId = t.customerId
AND SIGN(c.balance) <> SIGN(t.balance)
GROUP BY
u.customerId
) as p
WHERE
p.customerId = t.customerId
AND p.balanceSignSince < t.timestamp
GROUP BY
p.customerId;
Fiddle: http://sqlfiddle.com/#!9/bd0760/13
Original Answer
This should work to get the timestamp of a sign change:
SELECT
c.id as id,
MAX(t.timestamp) as balanceSignSince
FROM
transaction as t,
customer as c
WHERE
t.customerId = c.id
AND SIGN(t.balance) <> SIGN(c.balance)
This needs to be executed before the customer table is updated with the new balance. If you have a trigger on transation:insert you should probably put the above into the query updating the customer table.

SUM from the results of a subquery of N results as max for each user

Let's suppose this schema:
CREATE TABLE test
(
test_Id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
user_Id INT NOT NULL,
date DATE,
result VARCHAR(255) NOT NULL,
) engine=innodb;
My goal is to pick up the last 5 results as maximum for each different user_Id, ordered from newest to oldest. Besides that, depending on result column I want to calculate a ratio of those last results, to be able to pick up the 3 users with best ratio.
So let's take this data as example:
test_Id | user_Id | date | result
1 | 1 |2016-09-05 | A
2 | 3 |2016-09-13 | A
3 | 3 |2016-09-30 | A
4 | 4 |2016-09-22 | A
5 | 4 |2016-09-11 | C
6 | 7 |2016-09-18 | D
7 | 4 |2016-09-08 | B
8 | 6 |2016-09-20 | E
9 | 7 |2016-09-16 | A
10 | 7 |2016-09-29 | E
11 | 7 |2016-09-23 | A
12 | 7 |2016-09-16 | B
13 | 4 |2016-09-15 | B
14 | 7 |2016-09-07 | C
15 | 7 |2016-09-09 | A
16 | 3 |2016-09-26 | A
17 | 4 |2016-09-11 | C
18 | 4 |2016-09-30 | E
What I have been able to achieve is this query:
SELECT p.user_Id, p.RowNumber, p.date, p.result,
SUM(CASE WHEN p.result='A' OR p.result='B'
THEN 1 ELSE 0 END) as avg
FROM (
SELECT #row_num := IF(#prev_value=user_Id,#row_num+1,1)
AS RowNumber, test_Id, user_Id, date, result,
#prev_value := user_Id
FROM test,
(SELECT #row_num := 1) x,
(SELECT #prev_value := '') y
WHERE #prev_value < 5
ORDER BY user_Id, YEAR(date) DESC, MONTH(date) DESC,
DAY(date) DESC
) p
WHERE p.RowNumber <=10
GROUP BY p.user_Id, p.test_Id
ORDER BY p.user_Id, p.RowNumber;
This query provides me this kind of output:
RowNumber |test_Id | user_Id | date | result | avg
1 | 1 | 1 |2016-09-05 | A | 1
1 | 3 | 3 |2016-09-30 | A | 1
2 | 16 | 3 |2016-09-26 | A | 1
3 | 2 | 3 |2016-09-13 | A | 1
1 | 18 | 4 |2016-09-30 | E | 0
2 | 4 | 4 |2016-09-22 | A | 1
3 | 13 | 4 |2016-09-15 | B | 1
4 | 5 | 4 |2016-09-11 | C | 0
5 | 17 | 4 |2016-09-11 | C | 0
1 | 8 | 6 |2016-09-20 | E | 0
1 | 10 | 7 |2016-09-29 | E | 0
2 | 11 | 7 |2016-09-23 | A | 1
3 | 6 | 7 |2016-09-18 | D | 0
4 | 9 | 7 |2016-09-16 | A | 1
5 | 12 | 7 |2016-09-16 | B | 1
What I was expecting is that in the avg column would get the total of the results for each user that match the condition (A or B value), to be able to calculate a ratio from the 5 results for each user_id. (0, 0.2, 0.4, 0.6, 0.8, 1).
Something like this:
RowNumber |test_Id | user_Id | date | result | avg
1 | 1 | 1 |2016-09-05 | A | 1
1 | 3 | 3 |2016-09-30 | A | 3
2 | 16 | 3 |2016-09-26 | A | 3
3 | 2 | 3 |2016-09-13 | A | 3
1 | 18 | 4 |2016-09-30 | E | 2
2 | 4 | 4 |2016-09-22 | A | 2
3 | 13 | 4 |2016-09-15 | B | 2
4 | 5 | 4 |2016-09-11 | C | 2
5 | 17 | 4 |2016-09-11 | C | 2
1 | 8 | 6 |2016-09-20 | E | 0
1 | 10 | 7 |2016-09-29 | E | 3
2 | 11 | 7 |2016-09-23 | A | 3
3 | 6 | 7 |2016-09-18 | D | 3
4 | 9 | 7 |2016-09-16 | A | 3
5 | 12 | 7 |2016-09-16 | B | 3
Am I being restricted by the GROUP BY p.user_Id, p.test_Id clause when doing the SUM? I tried the query with only user_Id as GROUP BY clause and only test_Id too as GROUP BY clause, without getting the expected results.
I think you need to calculate the avg and then join
select a.rn,a.test_id,a.user_id,a.date,a.result,u.avg from
(
select t1.*
, if (t1.user_id <> #p, #rn:=1,#rn:=#rn+1) rn
, #p:=t1.user_id p
from (select #rn:=0, #p:='') rn,test t1
order by t1.user_id, t1.date desc
) a
join
(
select s.user_id
, sum(case when s.result = 'A' or s.result = 'B' then 1 else 0 end) as avg
from
(
select t1.*
, if (t1.user_id <> #p, #rn:=1,#rn:=#rn+1) rn
, #p:=t1.user_id p
from (select #rn:=0, #p:='') rn,test t1
order by t1.user_id, t1.date desc
) s
where s.rn <= 5
group by s.user_id
) u on u.user_id = a.user_id
where a.rn <= 5

Is It Possible To Join Two Unrelated Tables In MySQL?

I have two unrelated tables but i want to join them into one query, is that possible?
This is how I did it using cross join but it did not work
table 1
| ID | Amount |
| 1 | 20 |
| 2 | 10 |
| 3 | 21 |
| 4 | 50 |
table 2
| ID | Paid Value |
| 011 | 5 |
| 052 | 2 |
//My tried Query
SELECT
a.`Amount`,
b.`Paid Value`
FROM
`table 1` a
CROSS JOIN
`table 2` b
This is what i get in return using the above query
| ID | Amount | Paid Value |
| 1 | 20 | 5 |
| 2 | 10 | 2 |
| 3 | 21 | 5 |
| 4 | 50 | 2 |
However this is my expected results
| ID | Amount | Paid Value |
| 1 | 20 | 5 |
| 2 | 10 | 2 |
| 3 | 21 | 0 |
| 4 | 50 | 0 |
You want to join by some implicit row number. Let me assume that this is based on the ordering of the ids. You can use variables to calculate the row number and then use that for the join:
select t1.id, t1.amount, coalesce(t2.paidvalue, 0)
from (select t1.*, (#rn := #rn + 1) as rn
from table1 t1 cross join
(select #rn := 0) vars
order by id
) t1 left join
(select t2.*, (#rn2 := #rn2 + 1) as rn
from table1 t2 cross join
(select #rn2 := 0) vars
order by id
) t2
on t1.rn = t2.rn;

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