MySql - Join tables, count "distict" the id occurences and caluclate average - mysql

I need some help, please, with joining two queries.
I have two (or more) tables (same structure), as follows:
table_a
+-----------------+-------+
| id | date | value |
+----+------------+-------+
| 1 | 01-05-2018 | 8 |
| 1 | 03-05-2018 | 1 |
| 1 | 04-05-2018 | 1 |
| 1 | 05-05-2018 | 2 |
+----+------------+-------+
table_b
+-----------------+-------+
| id | date | value |
+----+------------+-------+
| 2 | 01-05-2018 | 10 |
| 2 | 03-05-2018 | 5 |
| 2 | 04-05-2018 | 10 |
| 2 | 05-05-2018 | 6 |
+----+------------+-------+
I'm "joining" them and calculating the AVG value row by row, date by date. This is how I DON'T WANT my results:
Result
+-----------------+--------+--------+
| cnt | id | date | average|
+----+------------+--------+--------+
| 1 | 1 | 01-05-2018 | 8 | <-- (8)
| 2 | 1 | 03-05-2018 | 4.5 | <-- (8+1=9/2)
| 3 | 1 | 04-05-2018 | 3.3 | <-- (8+1+1=10/3)
| 4 | 1 | 05-05-2018 | 3 | <-- (8+1+1+2=12/4)
| 5 | 2 | 01-05-2018 | 4.4 | <-- (8+1+1+2+10=22/5)
| 6 | 2 | 03-05-2018 | 4.5 | <-- (8+1+1+2+10+5=27/6)
| 7 | 2 | 04-05-2018 | 5.2 | <-- (8+1+1+2+10+5+10=37/7)
| 8 | 2 | 05-05-2018 | 5.3 | <-- (8+1+1+2+10+5+10+6=37/8)
+------+----+--------------+--------+
This is how it should be:
Result
+-----------------+-------+---------+
| cnt | id | date | average |
+----+------------+-------+---------+
| 1 | 1 | 01-05-2018 | 8 | <-- (8)
| 2 | 1 | 03-05-2018 | 4.5 | <-- (8+1=9/2)
| 3 | 1 | 04-05-2018 | 3.3 | <-- (8+1+1=10/3)
| 4 | 1 | 05-05-2018 | 3 | <-- (8+1+1+2=12/4)
| 1 | 2 | 01-05-2018 | 10 | <-- (10)
| 2 | 2 | 03-05-2018 | 7.5 | <-- (10+5=15/2)
| 3 | 2 | 04-05-2018 | 8.3 | <-- (10+5+10=25/3)
| 4 | 2 | 05-05-2018 | 7.7 | <-- (10+5+10+6=25/4)
+------+----+--------------+--------+
I'm playing around with this query:
select t.cnt,t.id,t.date, (average / cnt) as average
from (select t.*, (#rn := #rn + 1) as cnt, (#s := #s + value) as average
from table_a t
cross join (select #rn := 0, #s := 0) params) t
UNION ALL
select t.cnt,t.id,t.date, (average / cnt) as average
from (select t.*, (#rn := #rn + 1) as cnt, (#s := #s + value) as average
from table_b t
cross join (select #rn := 0, #s := 0) params) t
Basically, I need to join the tables, count distinct id occurences and calculate the average of the value, row by row (date) for each id. How can I adjust my query, please? Thanks in advance.

Your query seems to do the job you want. Just use different variable names for the two subqueries:
select t.cnt,t.id,t.date, (average / cnt) as average
from (select t.*, (#rn1 := #rn1 + 1) as cnt, (#s1 := #s1 + value) as average
from table_a t
cross join (select #rn1 := 0, #s1 := 0) params) t
UNION ALL
select t.cnt,t.id,t.date, (average / cnt) as average
from (select t.*, (#rn2 := #rn2 + 1) as cnt, (#s2 := #s2 + value) as average
from table_b t
cross join (select #rn2 := 0, #s2 := 0) params) t
Demo here

Related

mysql sequence number by value coloumn (query UPDATE)

example:
I have a table with the columns
______________________
|field_id|Code|seq_num|
| 1 | a | 1 |
| 1 | a | 2 |
| 1 | a | 3 |
| 2 | a | 4 |
| 2 | a | 5 |
| 3 | a | 6 |
| 3 | a | 7 |
| 3 | a | 8 |
how to query it, so sequence number look like this
_____________________
|field_id|Code|seq_num|
| 1 | a | 1 |
| 1 | a | 2 |
| 1 | a | 3 |
| 2 | a | 1 |
| 2 | a | 2 |
| 3 | a | 1 |
| 3 | a | 2 |
| 3 | a | 3 |
please help!!
One method is to get the minimum sequence for the field:
select t.field_id, t.code,
(seq_num - min_seqnum + 1) as seqnum
from t join
(select field_id, min(seq_num) as min_seq_num
from t
group by field_id
) f
on t.field_id = f.field_id;
You can also do this using variables, if you don't trust the current sequence numbers to have no gaps:
select . . .,
(#rn := if(#f = field_id, #rn + 1,
if(#f := field_id, 1, 1)
)
) as seq_no
from (select t.*
from t
order by field_id, seq_no
) t cross join
(select #f := '', #rn := 0) params;

Order by logic and get mixed results?

I have this table structure
id | status
-----+------+
1 | a |
2 | b |
3 | b |
4 | b |
5 | a |
6 | a |
7 | b |
8 | a |
I have two statuses "a" and "b".
I need to order this by logic that says this: for each two "a", show one "b".
So something like this (the "a" is more important, so if there are a lot of "b"s they will just be left in the end)
id | status
-----+------+
1 | a |
5 | a |
2 | b |
6 | a |
8 | a |
3 | b |
4 | b |
7 | b |
Is there a way to do this?
select id
,status
from (select status
,id
,#prev_status := #status
,#status := status
,#rn := case when #prev_status = status
then #rn + 1
else 1
end as rn
from mytable t1,(select #status:=null,#rn:=0) x
order by status
,id
) t
order by floor((rn-1) / case status when 'a' then 2 else 1 end)
,case status when 'a' then 1 else 2 end
,rn
;
+----+--------+
| id | status |
+----+--------+
| 1 | a |
| 5 | a |
| 2 | b |
| 6 | a |
| 8 | a |
| 3 | b |
| 4 | b |
| 7 | b |
+----+--------+
This will help you to understand the solution:
(group_id = floor((rn-1) / case status when 'a' then 2 else 1 end))
+--------+----+----+----------+
| status | id | rn | group_id |
+--------+----+----+----------+
| a | 1 | 1 | 0 |
| a | 5 | 2 | 0 |
| a | 6 | 3 | 1 |
| a | 8 | 4 | 1 |
| b | 2 | 1 | 0 |
| b | 3 | 2 | 1 |
| b | 4 | 3 | 2 |
| b | 7 | 4 | 3 |
+--------+----+----+----------+
Use variables to enumerate the values and then some simple logic:
select id, status
from (select t.*,
(#rn := if(#s = status, #rn + 1,
if(#s := status, 1, 1)
)
) as rn
from t cross join
(select #rn := 0, #s := '') params
where status in ('a', 'b')
order by status, id
) ab
order by (case when status = a then floor( (rn - 1) / 2) else (rn - 1) end),
status, id;

select sum of max values from multiple incrementing sequences

I want to calculate a sum of the max values from sequence of increment values.
for this data set:
time_stamp count
1467820429 6 *
1467820428 5
1467820427 4
1467820426 3
1467820416 2
1467820415 1
1467820413 0
1467820412 3 *
1467820411 2
1467820409 1
1467820408 0
1467820405 1 *
1467820404 0
1467820400 5 *
answer = 6 + 3 + 1 + 5 = 15
how can i write a MySQL compatible SQL statement to acheve this
As I mentioned in comments there is no efficient way to do this in Mysql atleast to my knowledge
Try this
SELECT Sum(CASE
WHEN `count` >= prev_cnt THEN `count`
ELSE 0
END)
FROM (SELECT *,
IFnull((SELECT `count`
FROM yourtable b
WHERE a.`time_stamp` < b.`time_stamp`
ORDER BY `time_stamp` LIMIT 1), `count`) AS prev_cnt
FROM yourtable a) c
you can get it in following method
mysql> select time_stamp,count,if (count=0,#curRank :=0,#curRank := #curRank + 1) as rank from ff,(SELECT #curRank := 0) r;
+------------+-------+------+
| time_stamp | count | rank |
+------------+-------+------+
| 1467820429 | 6 | 1 |
| 1467820428 | 5 | 2 |
| 1467820427 | 4 | 3 |
| 1467820426 | 3 | 4 |
| 1467820415 | 2 | 5 |
| 1467820415 | 1 | 6 |
| 1467820413 | 0 | 0 |
| 1467820412 | 3 | 1 |
| 1467820411 | 2 | 2 |
| 1467820409 | 1 | 3 |
| 1467820408 | 0 | 0 |
| 1467820405 | 1 | 1 |
| 1467820404 | 0 | 0 |
| 1467820408 | 5 | 1 |
+------------+-------+------+
14 rows in set (0.00 sec)
mysql> SELECT * FROM (select time_stamp,count,if (count=0,#curRank :=0,#curRank := #curRank + 1) as rank from ff,(SELECT #curRank := 0) r) t WHERE rank=1;
+------------+-------+------+
| time_stamp | count | rank |
+------------+-------+------+
| 1467820408 | 5 | 1 |
| 1467820412 | 3 | 1 |
| 1467820429 | 6 | 1 |
| 1467820405 | 1 | 1 |
+------------+-------+------+
4 rows in set (0.00 sec)
mysql> SELECT sum(count) as total FROM
(select time_stamp,count,if (count=0,#curRank :=0,#curRank := #curRank + 1) as rank from ff,
(SELECT #curRank := 0) r) t WHERE rank=1;
+-------+
| total |
+-------+
| 15 |
+-------+
1 row in set (0.00 sec)
you can get it with simple inner query
SELECT SUM(a.cnt)
FROM
( SELECT x.*
, MIN(y.time_stamp) next
FROM my_table x
LEFT
JOIN my_table y
ON y.time_stamp > x.time_stamp
GROUP
BY x.time_stamp
) a
LEFT
JOIN my_table b
ON b.time_stamp = a.next
AND b.cnt > a.cnt
WHERE b.cnt IS NULL;
You need to identify when a value shifts. One way to get the previous value uses variables:
select sum(count)
from (select t.*,
(if((#old_c := #c) is null, 0, -- never happens
if((#c := count) is not null, #old_c, #old_c)
)
) as prev_count
from t cross join
(select #c := -1) params
order by time_stamp
) t
where prev_count >= count;
The expression for getting the previous count is a bit complicated. MySQL does not guarantee the order of evaluation of expressions, so the assignment of the new value of count and returning the old value needs to be in a single expression.
You need GROUP BY and HAVING, like this:
select sum ( count )
from table
group by time_stamp
having count = max(count)
Very simple solution :
1- take the lag of the column time_stamp
2- take the difference of orif time_stamp column and the lag column
3- sum the values of count after filtering out the records for -1
+------------+-------+------+-----------------+-------+
| a | b | c | d | a-d |
| time_stamp | count | flag | lag_time_stamp | diff |
| 1467820429 | 6 | * | nulll | null |
| 1467820428 | 5 | | 1467820429 | -1 |
| 1467820427 | 4 | | 1467820428 | -1 |
| 1467820426 | 3 | | 1467820427 | -1 |
| 1467820416 | 2 | * | 1467820426 | -10 |
| 1467820415 | 1 | | 1467820416 | -1 |
| 1467820413 | 3 | * | 1467820415 | -2 |
| 1467820412 | 3 | | 1467820413 | -1 |
| 1467820411 | 2 | | 1467820412 | -1 |
| 1467820409 | 1 | * | 1467820411 | -2 |
| 1467820408 | 0 | | 1467820409 | -1 |
| 1467820405 | 1 | * | 1467820408 | -3 |
| 1467820404 | 0 | | 1467820405 | -1 |
| 1467820400 | 5 | * | 1467820404 | -4 |
+------------+-------+------+-----------------+-------+
--sum the values of the table that we got after filtering the records for -1
+------------+-------+
| time_stamp | count |
+------------+-------+
| 1467820429 | 6 |
| 1467820416 | 2 |
| 1467820413 | 3 |
| 1467820409 | 1 |
| 1467820405 | 1 |
| 1467820400 | 5 |
+------------+-------+

How to get top n records group by wildcards

Here is my table example:
+--------+-------------+
| id | city_detail |
+--------+-------------+
| 1 | 12_hyd_test |
| 2 | 13_blr_test |
| 3 | 15_blr_test |
| 4 | 18_hyd_test |
| 5 | 17_coi_test |
| 6 | 22_coi_test |
| 7 | 62_hyd_test |
| 8 | 72_blr_test |
| 9 | 92_blr_test |
| 10 | 42_hyd_test |
| 11 | 21_coi_test |
| 12 | 82_coi_test |
+--------+-------+-----+
From this table, how to use like condition with group by to select like this
+--------+-------------+
| id | city_detail |
+--------+-------------+
| 12 | 82_coi_test |
| 11 | 21_coi_test |
| 10 | 42_hyd_test |
| 7 | 62_hyd_test |
| 9 | 92_blr_test |
| 8 | 72_blr_test |
+--------+-------+-----+
In each city show only two result (%coi% or %hyd% or '%blr%') order by id DESC
Probably the simplest method is to use variables:
select e.*
from (select e.*,
(#rn := if(#c = substr(city_detail, 4), #rn + 1,
if(#c := substr(city_detail, 4), 1, 1
)
) as seqnum
from example e cross join
(select #c := '', #rn := 0) params
order by substr(city_detail, 4)
) e
where rn <= 2;

mysql split row to multiple columns and rows

I have a table with the following structure and these values in column assoc.
| id | assoc |
| 1 | |3|-1|107|-4|146|-6| |
| 2 | |19|-3|107|-5| |
| 3 | |42|-1| |
You can see it here
This is a wrong mysql table structure. So I think that the right structure it must be:
| id | assoc | attrib | order |
| 1 | 3 | 1 | 1 |
| 1 | 107 | 4 | 2 |
| 1 | 146 | 6 | 3 |
| 2 | 19 | 3 | 1 |
| 2 | 107 | 5 | 2 |
| 3 | 42 | 1 | 1 |
Is possible to do with a mysql script on phpmyadmin?
SET #prev := null;
SET #cnt := 0;
SELECT id,blah,mah,IF(#prev <> id, #cnt := 1, #cnt := #cnt + 1) AS rank, #prev := id
FROM(
SELECT id,REPLACE(SUBSTRING_INDEX(assoc,'|',2),'|','')*1 as blah,
SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(assoc,'-0'),'|',3),'-',-1)as mah FROM table1
UNION ALL
SELECT id,
SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(assoc,'|0'),'|',4),'|',-1)*1 as blah,
SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(assoc,'-0'),'|',5),'-',-1)as mah FROM table1
UNION ALL
SELECT id,
SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(assoc,'|0'),'|',6),'|',-1)*1 as blah,
SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(assoc,'-0'),'|',7),'-',-1)as mah FROM table1
)x
WHERE x.mah !='0'
ORDER BY x.id ,x.blah
FIDDLE