how to detect peak of two columns in sql - mysql

I have two columns in mysql:
row A B
1 90 80
2 80 57
3 57 5
4 48 30
5 30 15
I need to compare the value of B and the next value of A, how could I detect a peak when B is 5 (row 3) and A is 48 (row 4)? New column can be added to say whether a peak is detected.
The result should be:
row A B peak_detection
1 90 80 0
2 80 57 0
3 57 5 0
4 48 30 1
5 30 15 0
Thank you

In steps:
How big is the difference:
SELECT
A,
B,
LAG(A) over (order by r)-B difference
FROM Table1
Select the max row:
SELECT r
FROM (
SELECT
r,
A,
B,
LAG(A) over (order by r)-B difference
FROM Table1
) t2
ORDER BY difference DESC
LIMIT 1
Add the column peak_detection:
SELECT
A,
B,
IF(Table1.r=t2.r,1,0) peak_detection
FROM Table1
LEFT JOIN (
SELECT r
FROM (
SELECT
r,
A,
B,
LAG(A) over (order by r)-B difference
FROM Table1
) t2
ORDER BY difference DESC
LIMIT 1
) t2 on t2.r=Table1.r
See: dbfiddle
output:
A
B
peak_detection
90
80
0
80
57
0
57
5
1
48
30
0
30
15
0
P.S. code improvement can be done (and might be needed) on the last query, if needed.

Related

MySQL join and group based on date ranges

I have table A
uid dt val_A
10 04/09/2012 34
10 08/09/2012 35
10 10/09/2012 36
100 04/09/2012 40
100 08/09/2012 41
and table B
uid date val_B
10 04/09/2012 1
10 05/09/2012 1
10 06/09/2012 2
10 07/09/2012 2
10 08/09/2012 1
100 07/09/2012 1
100 07/09/2012 3
I want to join them to get table C. I want to join them on uid. Furthermore I want to have a new column val_C which holds the average of val_B where date in B is greater or equal than the corresponding row-value dt in A AND less than the next higher dt value for this uid in table A. It means I want to aggregate the values in B based on date ranges defined in A. The joined table should look like this:
uid dt val_A val_C
10 04/09/2012 34 1.5
10 08/09/2012 35 1
10 10/09/2012 36 0
100 04/09/2012 40 2
100 08/09/2012 41 0
How can this be achieved?
//EDIT
How could a more generalized solution look like where all dates in B2 which are greater than the greatest date in A are being joined & aggregated to the greatest date in A. B2:
uid date val_B
10 04/09/2012 1
10 05/09/2012 1
10 06/09/2012 2
10 07/09/2012 2
10 08/09/2012 1
100 07/09/2012 1
100 07/09/2012 3
100 10/09/2012 4
100 11/09/2012 2
Desired output C2:
uid dt val_A val_C
10 04/09/2012 34 1.5
10 08/09/2012 35 1
10 10/09/2012 36 0
100 04/09/2012 40 2
100 08/09/2012 41 3
If you're on MySQL v8+ that supports LEAD() function, then you can try this:
WITH cte AS (
SELECT uid, dt, val_A,
IFNULL(LEAD(dt) OVER (PARTITION BY uid ORDER BY uid, dt),dt) dtRg
FROM tableA)
SELECT cte.uid, cte.dt, cte.val_A,
AVG(val_B) AS val_C
FROM cte
LEFT JOIN tableB tb1
ON cte.uid=tb1.uid
AND tb1.dt >= cte.dt
AND tb1.dt < cte.dtRg
GROUP BY cte.uid, cte.dt, cte.val_A
The query in common table expression (cte):
SELECT uid, dt, val_A,
IFNULL(LEAD(dt) OVER (PARTITION BY uid ORDER BY uid, dt),dt) dtRg
FROM tableA
will give you a result like this:
As you can see, the dtRg column is generated using LEAD() function which takes the next row dt value according to the ORDER BY. Read more about LEAD() here.
After that, join the cte with tableB on matching uid and where tableB.dt is the same or bigger than the existing tableA.dt - which is now as cte.dt, but lower than cte.dtRg - which is the next date in tableA that was generated by LEAD(). And finally adding AVG(val_B) AS val_C
Demo fiddle
On older MySQL version, you can try this:
SELECT tA.uid, tA.dt, tA.val_A,
AVG(val_B) AS val_C
FROM
(SELECT uid, dt, val_A,
(SELECT dt FROM tableA ta1
WHERE ta1.uid=ta2.uid
AND ta1.dt > ta2.dt LIMIT 1) AS dtRg
FROM tableA ta2) tA
LEFT JOIN tableB tB
ON tA.uid=tB.uid
AND tB.dt >= tA.dt
AND tB.dt < tA.dtRg
GROUP BY tA.uid, tA.dt, tA.val_A;
The difference are as following:
Instead of using LEAD(), it uses correlated subquery in SELECT to get the next dt value of next row in the same uid.
Instead of common table expression, it uses a derived table.
Fiddle for MySQL v5.7 version

Query within a query to obtain certain value with multiple joins in MySQL Select

I have 3 tables that I have joined in the MySQL query below. All works well EXCEPT I would like the timeadj value with a 1 in column countingtime
to show from table 'data', not the first timeadj value the query finds.
I know this needs to be a query within a query but I am going around in circles and getting no where.
SELECT ttt_entries.tttid, ttt_teams.teamname, data.RacersInTeam,
ttt_entries.CoffeeClass, SEC_TO_TIME(data.timeadj),
COUNT(IF(data.division=5,1,NULL)) 'A+',
COUNT(IF(data.division=10,1,NULL)) A,
COUNT(IF(data.division=20,1,NULL)) B,
COUNT(IF(data.division=30,1,NULL)) C,
COUNT(IF(data.division=40,1,NULL)) D
FROM ttt_entries
INNER JOIN ttt_teams
ON ttt_entries.tttid = ttt_teams.tttid
INNER JOIN (SELECT * FROM data ORDER BY data.countingtime DESC) as data
ON ttt_entries.tttid = data.teamid
WHERE ttt_entries.eventDate = DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE()) >4, -5, 2) + DAYOFWEEK(CURDATE())) * -1 DAY) -- last Thursday
AND data.wtrlid = '22'
GROUP BY ttt_teams.tttid
ORDER BY data.timeadj ASC
For example.... In Team 1 (data.teamid=1) there are 8 time values in timeadj. In the adjacent column (countingtime) is a value either 1 or 0 but only ever 1x 1 per team.
Table ttt_entries
ID tttid CoffeeClass
1 23 Mocha
2 52 Espresso
3 6 Frappe
Table ttt_teams
tttid Name
6 Team A
23 Team 1
52 Team 2
Table 'data'
id wtrlid teamid timeadj countingtime division
1 22 23 3467.123 0 10
2 22 23 3467.125 0 20
3 22 23 3467.432 0 10
4 22 23 3469.000 1 10
5 22 23 3469.112 0 10
6 22 23 3468.987 0 5
My code brings back
tttid teamname RacersInTeam CoffeeClass Time A+ A B C D
23 Team 1 6 Mocha 3467.123 1 4 1 0 0
I need it to bring back the same data but a different time:
tttid teamname RacersInTeam CoffeeClass Time A+ A B C D
23 Team 1 6 Mocha 3469.000 1 4 1 0 0
You can try below way -
SELECT ttt_entries.tttid, ttt_teams.teamname, data.RacersInTeam,
ttt_entries.CoffeeClass, SEC_TO_TIME(max(case when countingtime=1 then data.timeadj end)),
COUNT(IF(data.division=5,1,NULL)) 'A+',
COUNT(IF(data.division=10,1,NULL)) A,
COUNT(IF(data.division=20,1,NULL)) B,
COUNT(IF(data.division=30,1,NULL)) C,
COUNT(IF(data.division=40,1,NULL)) D
FROM ttt_entries
INNER JOIN ttt_teams
ON ttt_entries.tttid = ttt_teams.tttid
INNER JOIN (SELECT * FROM data ORDER BY data.countingtime DESC) as data
ON ttt_entries.tttid = data.teamid
WHERE ttt_entries.eventDate = DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE()) >4, -5, 2) + DAYOFWEEK(CURDATE())) * -1 DAY) -- last Thursday
AND data.wtrlid = '22'
GROUP BY ttt_teams.tttid
ORDER BY data.timeadj ASC

MySQL SELECT record immediately prior to range and select range

I have a MySQL table of states for three things, a,b and c
id a b c time
--------------------------
1 0 1 1 78
2 1 1 0 89
3 1 0 0 105
4 0 0 0 107
5 1 0 1 122
6 0 0 1 134
7 0 1 0 167
8 1 1 1 168
9 0 1 0 177
10 0 0 0 180
As an example, the bounds of time are chosen by the user as time>100
AND time<170
But I need to know the value of ‘a’ immediately prior to the 1st returned record. (where id=2)
I’m trying to find the most efficient way of creating this query, without resorting to 2 separate queries.
SELECT a, time FROM states WHERE time<100 order by time DESC limit 1
AND
SELECT a, time FROM states WHERE time>100 AND time<170 ORDER BY time ASC
To return a result set of ...
a time
1 89
1 105
0 107
1 122
0 134
0 167
0 168
Any advice would be gratefully received, thanks!
One method uses LEAD():
SELECT a, time
FROM (SELECT s.*, LEAD(time) OVER (ORDER BY time) as next_time
FROM states s
) s
WHERE next_time > 100 AND time < 170;
You can also use:
select s.*
from states s
where s.time >= (select s2.time from states s2 where s2.time <= 100 order by s2.time desc limit 1) and
s.time < 170;
This, alas, doesn't work when the subquery returns no values. That can be fixed, but it complicates the query.
However, your solution is actually fine (with union all):
(SELECT a, time
FROM states
WHERE time <= 100
ORDER BY time DESC
LIMIT 1
) UNION ALL
(SELECT a, time
FROM states
WHERE time > 100 AND time < 170
)
ORDER BY time ASC;
From a performance perspective, this should be okay if you have an index on time. This also readily handles the problem when there are no values 100 or less.

MySQL-how to make query for selecting opening recd issued closingbalance rate from two different table one is purchase and another is issued [duplicate]

This question already has answers here:
What's the most efficient way to select the last n rows in a table without changing the table's structure?
(8 answers)
Closed 4 years ago.
I want to make a query that retrieve data from two table i.e one is purchase and another is issue.
both tables have same fields i.e icode,qty,rate,purdate and issuedate.
query of purchase is:-SELECT Dry_Purchase.Icode, Sum(Dry_Purchase.Qty) AS SumOfQty, Dry_Purchase.Rate
FROM Dry_Purchase
WHERE (((Dry_Purchase.PurDate) Between DateSerial(Year(Date()),(Month(Date())-1),21) And DateSerial(Year(Date()),Month(Date()),20)))
GROUP BY Dry_Purchase.Icode, Dry_Purchase.Rate;
output of purchase query is:
Icode SumOfQty Rate
11 10 13.5
11 39.5 14
19 75 79.75
19 22 80
21 54 87.45
23 15 218
24 10.5 650
8 79 33.25
8 13 34
query of issue is :- SELECT Dry_Expense.Icode, Sum(Dry_Expense.Qty) AS SumOfQty, Dry_Expense.Rate
FROM Dry_Expense
WHERE (((Dry_Expense.ExpDate) Between DateSerial(Year(Date()),(Month(Date())-1),21) And DateSerial(Year(Date()),Month(Date()),20)))
GROUP BY Dry_Expense.Icode, Dry_Expense.Rate;
output of this query is
Icode SumOfQty Rate
11 11.55 13
11 8.55 13.5
11 10.8 14
19 2.35 80
21 54 87.45
8 15.9 33.25
after combining above both query the output should like this
rptdate icode opening recd issued closingbal rate
19/09/18 11 0 10 8.550 1.450 13.50
19/09/18 11 0 39.5 10.800 28.700 14.00
19/09/18 19 0 75 0.000 75 79.75
19/09/18 19 0 22 2.350 72.650 80.00
19/09/18 21 0 54 54 0 87.45
19/09/18 23 0 15 0 15 218.00
19/09/18 24 0 10.5 0 10.500 650.00
19/09/18 8 0 79.0 15.900 63.100 33.25
19/09/18 8 0 13.0 0 13.000 34.00
19/09/18 8 11.550 0 11.550 0 13.00
please help me how to make query for this output
i am trying this query
SELECT A.icode,A.qty,A.rate,A.recd as recd,B.Issued as Issue
FROM (SELECT icode,rate,purdate,SUM(Abs(qty)) AS recd
FROM Dry_Purchase GROUP BY icde,rate ) A,
(SELECT icode,rate,expdate,(SUM(Abs(qty)) AS Issue
FROM Dry_Expense GROUP BY icode,rate) B
WHERE A.icode=B.icode AND A.rate=B.rate AND
(A.purdate Between DateSerial((Year(Date()),(Month(Date())-1),21)) And DateSerial(Year(Date()),Month(Date()),20))
AND B.expdate Between DateSerial((Year(Date()),(Month(Date())-1),21) And DateSerial(Year(Date()),Month(Date()),20));
please help me
Use a sub-select to locate the minimum date, then join to the to get the row matching that date.
SELECT
a.`Purdate` AS a.`Date1`, a.`Qty`, a.`Rate`
FROM `TableName` a
JOIN (SELECT MIN(`PurDate`) as `minDate`
FROM `TableName`
WHERE `Icode` = '1') b
ON b.`minDate` = a.`PurDate`
WHERE a.`Icode` = '1'
Since you don't need grouping you can just order by the date column and get the first two rows.
SELECT PurDate, Qty, Rate FROM TableName WHERE Icode = '1' ORDER BY PurDate LIMIT 2
use corelated sub query and union
select A.* from
(
select * from tablename t1 #1st min date will return
where t1.purdate in
(select min(purdate) from
tablename t2 where t2.icode=t1.icode
)
union
select t1.* from tablename t1 inner join
(SELECT
Icode
, Purdate
FROM (
SELECT
#row_num :=IF(#prev_value=Icode,#row_num+1,1) AS rn
, mp.Icode
, mp.Purdate
, #prev_value := Icode
FROM tablename mp
CROSS JOIN (SELECT #row_num :=1, #prev_value :='') vars
ORDER BY
mp.Icode
, mp.Purdate DESC
) d
WHERE rn = 2
) t2
on t1.Icode=t2.Icode and t1.Purdate=t2.Purdate
) as A where A.Icode in (......)

change multi rows to one row-multi column

For some reason i have to do this. i have a query that have result like this :
limit usage tariff total
0 10 10 700 7000
11 20 10 900 9000
21 30 10 1800 18000
31 > 11 2700 29700
the query return 4 rows maximum (like above) or sometime just 3 rows.
I want to change the rows to just one row and multi column like this (the list below just one row):
limit1 usage1 tariff1 total1 limit2 usage2 tariff2 total2
0 10 10 700 7000 11 20 10 900 9000
limit3 usage3 tariff3 total3 limit4 usage4 tariff4 total4
21 30 10 1800 18000 31 > 11 2700 29700
if the query return just 3 rows, the values in column limit4 until total4 will be empty. I dont know how to do like that.
EDITED
I add one ID column so the list will be :
ID limit usage tariff total
1 0 10 10 700 7000
2 11 20 10 900 9000
3 21 30 10 1800 18000
4 31 > 11 2700 29700
I try to make it one row like this :
SELECT e.*,f.id AS id4,f.limit AS limit4,f.usage AS usage4,f.tariff AS tariff4,f.total AS total4
FROM
(SELECT c.*,d.id AS id3,d.limit AS limit3,d.usage AS usage3,d.tariff AS tariff3,d.total AS total3
FROM
(SELECT b.id AS id1,b.limit AS limit1,b.usage AS usage1,b.tariff AS tariff1,b.total AS total1,
a.id AS id2,a.limit AS limit2,a.usage AS usage2,a.tariff AS tariff2,a.total AS total2
FROM testtariff a
INNER JOIN testtariff b ON a.id!=b.id
LIMIT 1) c INNER JOIN testtariff d ON c.id1 != d.id AND c.id2 != d.id
LIMIT 1) e INNER JOIN testtariff f ON e.id1 != f.id AND e.id2 != f.id
AND e.id3 != f.id
LIMIT 1
it work as i expected for 4 rows but not work for 3 rows. should i use cursor ?
This is generally called a "pivot". Here's how you do it:
select
'0 10' as limit1,
sum(limit between 0 and 10 * usage) as usage1,
sum(limit between 0 and 10 * tariff) as tariff1,
sum(limit between 0 and 10 * usage * tariff) as total1,
'11 20' as limit2,
sum(limit between 11 and 20 * usage) as usage2,
sum(limit between 11 and 20 * tariff) as tariff2,
sum(limit between 11 and 20 * usage * tariff) as total2,
-- etc
from mytable
group by 1,5 -- etc
This works because limit between x and x is 1 if true and 0 if false, so using multiplying by this is a simple way to filter the results into different groups.