MYSQL Moving Average calculation with precedence unbounded - mysql

I have a table with three columns : id, A, B. I would like to populate a column E with the calculations below.
** Cols C and D are non-existent in my table, they are subqueries that i hope to create and put into my query that will help me populate col E. As it seems,C and D is a moving average but there is not much examples around that can help with that.
Table followers
id A B C D E
_ _ _ _ _ _
0 1 2 A0/(AVG(A0)) #1/1 B0/(AVG(B0)) (C0+D0)/2
1 2 2 A1/(AVG(A1,A0)) #2/((2+1)/2) B1/(AVG(B1,B0)) (C1+D1)/2
2 3 2 A2/(AVG(A1,A2,A0)) #3/((3+2+1)/3) B2/(AVG(B1,B2,B0)) (C2+D2)/2
How can i write a query for this? I believe i will need a left outer join to itself.
I was thinking set the calculations for column C as one variable, calculations for column D as another variable.
So for column C this is my approach:
update followers f left outer join
followers f2...
Not sure how to continue as i dont know how i can do
f.id=f2.id where f2.id between f.id and f2.id+1.
I got some help to use this query but it didnt work:
UPDATE
f,
(
SELECT AVG(f.A)
FROM (
SELECT f.A
FROM f
WHERE f.id <= f.id
) as t
) as temp,
(
SELECT AVG(f.B)
FROM (
SELECT f.B
FROM f
WHERE f.id <= f.id
) as t2
) as temp2
SET
f.C =((f.A/temp) + (f.B/temp2))/2;

I would use subqueries for this
SELECT id, A, B, C, D, (C+D)/2 E FROM (
SELECT id, A, B,
(A/(SELECT AVG(A) FROM table t2 WHERE t2.id <= t1.id)) C,
(B/(SELECT AVG(B) FROM table t2 WHERE t2.id <= t1.id)) D
FROM table t1 ) t3
This query assumes that AVG(A) and AVG(B) are always > 0. If that's not true you'll need to use CASE so you don't end up dividing by 0.
Another version using JOIN, probably faster than using subqueries
SELECT t3.id, t3.A, t3.B,
(t3.A/t4.AVG_A) C, (t3.B/t4.AVG_B) D,
((t3.A/t4.AVG_A) + (t3.B/t4.AVG_B))/2 E
FROM table t3
JOIN (SELECT t1.id, AVG(t2.A) AVG_A, AVG(t2.B) AVG_B
FROM table t1
JOIN table t2 ON t2.id <= t1.id
GROUP BY t1.id) t4 ON t3.id = t4.id

I would approach this using variables.
select t.id, t.A, t.B,
(case when (#n := #n + 1) is null then null
when (#c := #c + a) is null then null
else t.A / (#c / #n)
end) as C,
(case when (#d := #d + b) is null then null
else t.B / (#d / #n)
end) as D,
(t.A / (#c / #n)) + (t.B / (#d / #n)) as E
from table t cross join
(select #c := 0, #d := 0, #n := 1) vars
order by id;
You can put this in an update statement using a join.
update table t join
(select t.id, t.A, t.B,
(case when (#n := #n + 1) is null then null
when (#c := #c + a) is null then null
else t.A / (#c / #n)
end) as C,
(case when (#d := #d + b) is null then null
else t.B / (#d / #n)
end) as D,
(t.A / (#c / #n)) + (t.B / (#d / #n)) as E
from table t cross join
(select #c := 0, #d := 0, #e := 0, #n := 1) vars
order by id
) newvals
on t.id = newvals.id
set t.C = newvals.C,
t.D = newvals.D,
t.E = newvals.E;

Related

How to find median value with group by (MySQL)

Need to find median value of time difference between sent date and click date (in seconds) for each type of emails. I found solution just for all data:
SET #rowindex := -1;
SELECT g.type, g.time_diff
FROM
(SELECT #rowindex:=#rowindex + 1 AS rowindex,
TIMESTAMPDIFF(SECOND, emails_sent.date_sent, emails_clicks.date_click) AS time_diff,
emails_sent.id_type AS type
FROM emails_sent inner join emails_clicks on emails_sent.id = emails_clicks.id_email
ORDER BY time_diff) AS g
WHERE g.rowindex IN (FLOOR(#rowindex / 2) , CEIL(#rowindex / 2));
Is it possible to add group by id_type statement?
Thanks!
First, you need to enumerate the rows for each type. Using variables, this code looks like:
select sc.*,
(#rn := if(#t = id_type, #rn + 1,
if(#t := id_type, 1, 1)
)
) as seqnum
from (select timestampdiff(second, s.date_sent, c.date_click) as time_diff,
s.id_type,
from emails_sent s inner join
emails_clicks c
on s.id = c.id_email
order by time_diff
) sc cross join
(select #t := -1, #rn := 0) as params;
Then, you need to bring in the total number for each type and do the calculation for the median:
select sc.id_type, avg(time_diff)
from (select sc.*,
(#rn := if(#t = id_type, #rn + 1,
if(#t := id_type, 1, 1)
)
) as seqnum
from (select timestampdiff(second, s.date_sent, c.date_click) as time_diff,
s.id_type,
from emails_sent s inner join
emails_clicks c
on s.id = c.id_email
order by time_diff
) sc cross join
(select #t := -1, #rn := 0) as params
) sc join
(select id_type, count(*) as cnt
from emails_sent s inner join
emails_clicks c
on s.id = c.id_email
group by id_type
) n
where 2 * seqnum in (n.cnt, n.cnt, n.cnt + 1, n.cnt + 2)
group by sc.id_type;

MySql - Join not giving expected result

trying to run below query, expecting to find a match. Since my two sub-queries are same, i am expecting to find a match, which is not happening here.
select TAB_1.RUL_IDD, TAB_2.RUL_IDD
FROM
( select aaa.RUL_ID RUL_IDD from TEST_1 aaa
inner join
(
select dt,#curRank := #curRank + 1 AS rank
from (
select distinct DATE(BTCH_RUN_DTTM) dt
FROM TEST_1
where ALRT_FLG_IND = 'Y'
and PASS_IND = 'N'
and SCH_RUN_IND = 'Y'
order by DATE(BTCH_RUN_DTTM) desc
) p,
(SELECT #curRank := 0) r
order by dt desc
) zz
where DATE(aaa.BTCH_RUN_DTTM) = zz.dt
and zz.rank=1
and aaa.ALRT_FLG_IND = 'Y'
and aaa.PASS_IND = 'N'
and aaa.SCH_RUN_IND = 'Y'
) TAB_1
left outer JOIN
(
select bbb.RUL_ID as RUL_IDD from TEST_1 bbb
inner join
(
select dt,#curRank := #curRank + 1 AS rank
from (
select distinct DATE(BTCH_RUN_DTTM) dt
FROM TEST_1
where ALRT_FLG_IND = 'Y'
and PASS_IND = 'N'
and SCH_RUN_IND = 'Y'
order by DATE(BTCH_RUN_DTTM) desc
) p,
(SELECT #curRank := 0) r
order by dt desc
) zzz
on DATE(bbb.BTCH_RUN_DTTM) = zzz.dt
and zzz.rank=1
and bbb.ALRT_FLG_IND = 'Y'
and bbb.PASS_IND = 'N'
and bbb.SCH_RUN_IND = 'Y'
) TAB_2
on TAB_2.RUL_IDD = TAB_1.RUL_IDD;
Result:
enter image description here
Not getting why its not giving the expected result. TAB_2.RUL_IDD should also have same value as TAB_1.RUL_IDD. Can experts help me over here?

Update duplicate rows

I have a table:
id name
1 a
2 a
3 a
4 b
5 b
6 c
I am looking for an update statement that will update name column to:
id name
1 a
2 a-2
3 a-3
4 b
5 b-2
6 c
In SQL Server I would use:
;with cte as(select *, row_number() over(partition by name order by id) rn from table)
update cte set name = name + '-' + cast(rn as varchar(10))
where rn <> 1
I am not strong in MySQL nonstandard queries.
Can I do something like this in MySQL?
You can do this:
UPDATE YourTable p
JOIN(SELECT t.id,t.name,count(*) as rnk
FROM YourTable t
INNER JOIN YourTable s on(t.name = s.name and t.id <= s.id)
GROUP BY t.id,t.name) f
ON(p.id = f.id)
SET p.name = concat(p.name,'-',f.rnk)
WHERE rnk > 1
This will basically use join and count to get the same as ROW_NUMBER() , and update only those who have more then 1 result(meaning the second,third ETC excluding the first)
In MySQL you can use variables in order to simulate ROW_NUMBER window function:
SELECT id, CONCAT(name, IF(rn = 1, '', CONCAT('-', rn))) AS name
FROM (
SELECT id, name,
#rn := IF(name = #n, #rn + 1,
IF(#n := name, 1, 1)) AS rn
FROM mytable
CROSS JOIN (SELECT #rn := 0, #n := '') AS vars
ORDER BY name, id) AS t
To UPDATE you can use:
UPDATE mytable AS t1
SET name = (
SELECT CONCAT(name, IF(rn = 1, '', CONCAT('-', rn))) AS name
FROM (
SELECT id, name,
#rn := IF(name = #n, #rn + 1,
IF(#n := name, 1, 1)) AS rn
FROM mytable
CROSS JOIN (SELECT #rn := 0, #n := '') AS vars
ORDER BY name, id) AS t2
WHERE t1.id = t2.id)
Demo here
You can also use UPDATE with JOIN syntax:
UPDATE mytable AS t1
JOIN (
SELECT id, rn, CONCAT(name, IF(rn = 1, '', CONCAT('-', rn))) AS name
FROM (
SELECT id, name,
#rn := IF(name = #n, #rn + 1,
IF(#n := name, 1, 1)) AS rn
FROM mytable
CROSS JOIN (SELECT #rn := 0, #n := '') AS vars
ORDER BY name, id) AS x
) AS t2 ON t2.rn <> 1 AND t1.id = t2.id
SET t1.name = t2.name;
The latter is probably faster than the former because it performs less UPDATE operations.
The next query will do it with less effort for the database:
UPDATE
tab AS tu
INNER JOIN
-- result set containing only duplicate rows that must to be updated
(
SELECT
t.id,
COUNT(*) AS cnt
FROM
tab AS t
-- join the same table by smaller id and equal value. That way you will exclude rows that are not duplicated
INNER JOIN
tab AS tp
ON
tp.name = t.name
AND
tp.id < t.id
GROUP BY
t.id
) AS tc
ON
tu.id = tc.id
SET
tu.name = CONCAT(tu.name, '-', tc.cnt + 1)

MySQL - Parallel merge two unrelated queries with same # of rows

I have two tables:
exam_outline_items:
jml_quiz_pool:
Of all the things I've tried, this got me the closest:
select t1.sequence, t1.title, t2.q_cat, t2.q_count
from student_pl.exam_outline_items t1
cross join pe_joomla.jml_quiz_pool t2
where t1.exam_outline_id = 5 and t1.chapter_num > 0
and t2.q_id = 1109 and t2.q_count > 0
group by title
Which produces this result:
I just need those q_cat values to be different, like they are in the 2nd query.
Thanks in advance for your help.
You have to have something to connect them with. If you don't have such a column, you can simulate one by creating a rownumber with variables.
select sequence, title, q_cat, q_count from (
select t1.sequence, t1.title, #r1 := #r1 + 1 as rownumber
from student_pl.exam_outline_items t1
, (select #r1 := 0) var_init
where t1.exam_outline_id = 5 and t1.chapter_num > 0
order by t1.sequence
) a
inner join
(
select t2.q_cat, t2.q_count, #r2 := #r2 + 1 as rownumber
from pe_joomla.jml_quiz_pool t2
, (select #r2 := 0) var_init
where t2.q_id = 1109 and t2.q_count > 0
order by t2.q_cat
) b on a.rownumber = b.rownumber;
Also note, that I used order by in those queries. In a database you have no sort order unless you explicitly set it with order by.

how to get upper trend with avg mysql

I have a Mysql Database like below:
id , name , col1
and i want to find all rows that: value of col1 of the row is greater than avrage of maximom 5 rows past
for example if I have 50 rows , and if the row #20 has gotten , the avrage of value of col1 of rows #20,#19,#18,#17,#16 should be less than the value of col1 of row #20 , and so on...
Thank you in advance.
What you seem to want here is running average of past M records starting from current record and we need to select the current record if current record's column value is greater than the running average.
Here is my attempt to it:
SET #M := 2;
SELECT * FROM
(
SELECT (#rownumber:= #rownumber + 1) AS rn, yt.*
FROM your_table yt,(SELECT #rownumber:= 0) nums
ORDER BY name, id
) a
WHERE a.var1 >
(
SELECT avg(b.var1)
FROM
(
SELECT (#rownumber:= #rownumber + 1) AS rn, yt.*
FROM your_table yt,(SELECT #rownumber:= 0) nums
ORDER BY name, id
) b
WHERE b.rn > a.rn - #M AND b.rn <= a.rn
)
#M is count of past records to be considered for finding running average.
Here is the code at SQL Fiddle
[EDIT]:
Here is another solution which according to me should be more efficient than correlated query.
SET #M := 2;
SELECT a.* FROM
(
SELECT (#rownumber:= #rownumber + 1) AS rn, yt.*
FROM your_table yt,(SELECT #rownumber:= 0) nums
ORDER BY name, id
) a
JOIN
(
SELECT b.name, b.rn, AVG(c.var1) AS av
FROM
(
SELECT (#rownumber1:= #rownumber1 + 1) AS rn, yt.*
FROM your_table yt,(SELECT #rownumber1:= 0) nums
ORDER BY name, id
) b
JOIN
(
SELECT (#rownumber2:= #rownumber2 + 1) AS rn, yt.*
FROM your_table yt,(SELECT #rownumber2:= 0) nums
ORDER BY name, id
) c
ON b.name = c.name
AND c.rn > (b.rn - #M) AND c.rn <= b.rn
GROUP BY b.name,b.rn
) runningavg
ON a.name = runningavg.name
AND a.rn = runningavg.rn
AND a.var1 > runningavg.av
Here I have used simple inner join to calculate running average and again with inner join have selected rows which have column value greater than average.
Here is the code at SQL Fiddle
Let me know did it prove to be efficient.