Following is my sql query with included subquery. Essentially I'm looking to obtain the most recent meter readings for our equipment for a given month and year. I know it's in my subquery where the issue is coming up but I don't know how to properly fix it.
Following is result if I delete the subquery and just select equipment with meter readings for that month.
eid eqid name pid hours date
70 C1 KOMATSU WA250 3YD BUCKET 27 1176 2013-10-07
70 C1 KOMATSU WA250 3YD BUCKET 27 1195 2013-10-28
70 C1 KOMATSU WA250 3YD BUCKET 27 1178 2013-10-14
73 C11 KOMATSU PC200 EXCAVATOR 27 1080 2013-10-14
73 C11 KOMATSU PC200 EXCAVATOR 27 1099 2013-10-28
73 C11 KOMATSU PC200 EXCAVATOR 27 1078 2013-10-07
92 C4 CATERPILLAR 304D MINI EX 27 646 2013-10-14
92 C4 CATERPILLAR 304D MINI EX 27 645 2013-10-07
92 C4 CATERPILLAR 304D MINI EX 27 649 2013-10-28
58 E14 BOBCAT-ATV 2300 Utility 8 522 2013-10-31
61 E17 SKYTRAK FORKLIFT 40 943 2013-10-10
62 E18 SKYTRAK FORKLIFT 5 1790 2013-10-30
62 E18 SKYTRAK FORKLIFT 5 1789 2013-10-29
62 E18 SKYTRAK FORKLIFT 5 1777 2013-10-13
62 E18 SKYTRAK FORKLIFT 5 1772 2013-10-07
62 E18 SKYTRAK FORKLIFT 5 1777 2013-10-13
62 E18 SKYTRAK FORKLIFT 5 1772 2013-10-04
62 E18 SKYTRAK FORKLIFT 5 1772 2013-10-07
62 E18 SKYTRAK FORKLIFT 5 1772 2013-10-04
67 E23 BOBCAT SKID STEER 27 1178 2013-10-28
Following is result of full query including subquery.
92 C4 CATERPILLAR 304D MINI EX 27 649 2013-10-28
61 E17 SKYTRAK FORKLIFT 40 943 2013-10-10
62 E18 SKYTRAK FORKLIFT 5 1790 2013-10-30
Following is query I am using.
SELECT e.eid, e.eqid, e.name, m.pid, m.hours, m.date FROM meter m
JOIN equipment e ON m.eid = e.eid
WHERE MONTH(date) = $month
AND YEAR(date) = $year
AND m.date = (SELECT MAX(m2.date) FROM meter m2 WHERE m2.eid = m.eid)
ORDER BY e.eqid ASC
Any help is greatly appreciated.
EDIT***
I would have never gotten there Sebas. I had to change one thing and it worked perfectly.
SELECT DISTINCT e.eid, e.eqid, e.name, m.pid, m.hours, m.date
FROM equipment e
JOIN (
SELECT eid, MAX(date) as date
FROM meter
WHERE MONTH(date) = $month
AND YEAR(date) = $year
GROUP BY eid
) maxdate ON maxdate.eid = e.eid
JOIN meter m ON m.eid = e.eid AND m.date = maxdate.date
ORDER BY e.eqid ASC
Guessing out the fields of each table, I figured the following query:
SELECT DISTINCT e.eid, e.eqid, e.name, m.pid, m.hours, m.date
FROM
equipment e
JOIN (
SELECT eid, MAX(date) date
FROM meter
WHERE
MONTH(date) = $month
AND YEAR(date) = $year
GROUP BY eid
) maxdate ON maxdate.eid = e.eid
JOIN meter m ON m.eid = e.eid AND m.date = maxdate.date
ORDER BY e.eqid ASC
Try
SELECT e.eid, e.eqid, e.name, m.pid, m.hours, m.date
FROM
(
SELECT eid, MAX(date) date
FROM meter
WHERE date BETWEEN '2013-10-01' AND LAST_DAY('2013-10-01')
GROUP BY eid
) q JOIN meter m
ON q.eid = m.eid
AND q.date = m.date JOIN equipment e
ON q.eid = e.eid
ORDER BY e.eid
Note: make sure you have an index on date. Don't apply any functions (MONTH() and such) to this columns in WHERE clause because it prevents from using any index you might have on it effectively causing full table scan.
Sample output:
| EID | EQID | NAME | PID | HOURS | DATE |
|-----|------|--------------------------|-----|-------|------------|
| 58 | E14 | BOBCAT-ATV 2300 Utility | 8 | 522 | 2013-10-31 |
| 61 | E17 | SKYTRAK FORKLIFT | 40 | 943 | 2013-10-10 |
| 62 | E18 | SKYTRAK FORKLIFT | 5 | 1790 | 2013-10-30 |
| 67 | E23 | BOBCAT SKID STEER | 27 | 1178 | 2013-10-28 |
| 70 | C1 | KOMATSU WA250 3YD BUCKET | 27 | 1195 | 2013-10-28 |
| 73 | C11 | KOMATSU PC200 EXCAVATOR | 27 | 1099 | 2013-10-28 |
| 92 | C4 | CATERPILLAR 304D MINI EX | 27 | 649 | 2013-10-28 |
Here is SQLFiddle demo
Related
| recharge_table |
r_date
r_name
r_amount
01-01-2020
Phineas
120
01-02-2020
Phineas
130
01-03-2020
Phineas
199
01-04-2020
Candes
299
03-01-2020
Candes
149
03-02-2020
Ferb
149
03-03-2020
Platypus
349
05-08-2020
Ferb
459
09-11-2020
Candes
199
06-10-2020
Platypus
299
find last two amounts of each customer based on dates, and in ascending order of name.
output must be as below:-
| Candes | 199 | 299 |
| Ferb | 459 | 159 |
| Phineas | 199 | 130 |
| Platypus | 299 | 349 |
If Possible, also give explanation.
You can use row_number() and conditional aggregation:
select r_name,
max(case when seqnum = 1 then r_amount end),
max(case when seqnum = 2 then r_amount end)
from (select r.*,
row_number() over (partition by r_name order by r_date desc) as seqnum
from recharge_table r
) r
where seqnum <= 2
group by r_name;
Tried many suggestions to get this to work, difficult to explain so below is the data I have and the result I want to achieve.
I want to update the 'Active' Column to 0 if its not the MAX Ldate.
ID | SNumber | Ldate | Active
4804 188 2015-11-17 1
4806 189 2015-11-25 1
4807 190 2015-11-25 1
4808 191 2015-11-19 1
4809 192 2015-11-19 1
4820 193 2015-11-17 1
4821 193 2016-06-08 1
4830 194 2015-11-17 1
4831 194 2016-06-08 1
4828 195 2015-11-17 1
4829 195 2016-06-08 1
ID SNumber Ldate Active
4804 188 2015-11-17 1
4806 189 2015-11-25 1
4807 190 2015-11-25 1
4808 191 2015-11-19 1
4809 192 2015-11-19 1
4820 193 2015-11-17 0
4821 193 2016-06-08 1
4830 194 2015-11-17 0
4831 194 2016-06-08 1
4828 195 2015-11-17 0
4829 195 2016-06-08 1
I can get all rows with the MAX Ldate by "select ID, SNumber, Ldate from (select * from tbl order by SNumber, Ldate desc) x group by SNumber"
Thanks for taking the time to look!
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id SERIAL PRIMARY KEY
,SNumber INT NOT NULL
,Ldate DATE NOT NULL
);
INSERT INTO my_table VALUES
(4804,188,'2015-11-17'),
(4806,189,'2015-11-25'),
(4807,190,'2015-11-25'),
(4808,191,'2015-11-19'),
(4809,192,'2015-11-19'),
(4820,193,'2015-11-17'),
(4821,193,'2016-06-08'),
(4830,194,'2015-11-17'),
(4831,194,'2016-06-08'),
(4828,195,'2015-11-17'),
(4829,195,'2016-06-08');
SELECT x.*
, COALESCE(x.ldate = y.ldate,0) active
FROM my_table x
LEFT
JOIN
( SELECT snumber
, MAX(ldate) ldate
FROM my_table
GROUP
BY snumber
) y
ON y.snumber = x.snumber
AND y.ldate = x.ldate;
+------+---------+------------+--------+
| id | SNumber | Ldate | active |
+------+---------+------------+--------+
| 4804 | 188 | 2015-11-17 | 1 |
| 4806 | 189 | 2015-11-25 | 1 |
| 4807 | 190 | 2015-11-25 | 1 |
| 4808 | 191 | 2015-11-19 | 1 |
| 4809 | 192 | 2015-11-19 | 1 |
| 4820 | 193 | 2015-11-17 | 0 |
| 4821 | 193 | 2016-06-08 | 1 |
| 4830 | 194 | 2015-11-17 | 0 |
| 4831 | 194 | 2016-06-08 | 1 |
| 4828 | 195 | 2015-11-17 | 0 |
| 4829 | 195 | 2016-06-08 | 1 |
+------+---------+------------+--------+
I can't think why you'd store this, but it's easy enough to change the above to an UPDATE. It might look something like this (obviously, you'd need to alter the table design above first)...
UPDATE my_table x
LEFT
JOIN
( SELECT snumber
, MAX(ldate) ldate
FROM my_table
GROUP
BY snumber
) y
ON y.snumber = x.snumber
AND y.ldate = x.ldate
SET x.active = 0
WHERE y.snumber IS NULL;
But I think I would normally use an INNER JOIN for an UPDATE, in which case it might look like this (perhaps bound up inside a transaction)...
UPDATE my_table SET active = 0;
UPDATE my_table x
JOIN
( SELECT snumber
, MAX(ldate) ldate
FROM my_table
GROUP
BY snumber
) y
ON y.snumber = x.snumber
AND y.ldate = x.ldate
SET x.active = 1;
Here my table:
account
ac name
120 Tom
130 Jony
140 Jone
bread_sale
ac pcs amount date
120 12 60 2018-01-03
120 10 50 2018-01-04
140 8 40 2018-01-04
130 5 25 2018-01-05
water_sale
ac pcs amount date
130 2 30 2018-01-03
130 5 75 2018-01-04
140 3 45 2018-01-04
130 4 60 2018-01-05
120 5 75 2018-01-07
Here's the query that I have tried:
select account.ac,
account.name,
bread_sale.amount as BSAmount,
bread_sale.date as BSDate,
water_sale.amount as WSAmount,
water_sale.date as WSDate
from account left outer join bread_sale on account.ac = bread_sale.ac
left outer join water_sale on water_sale.ac = account.ac
order by account.ac
This is the result:
ac name BSAmount BSdate WSAmount WSdate
120 Tom 30 2018-01-03 75 2018-01-07
120 Tom 75 2018-01-04 75 2018-01-07
130 Jony 45 2018-01-05 30 2018-01-03
130 Jony 60 2018-01-05 75 2018-01-04
130 Jony 75 2018-01-05 60 2018-01-05
140 Jone 75 2018-01-04 45 2018-01-04
But I want to obtain something like this:
ac name BSAmount BSdate WSAmount WSdate
120 Tom 60 2018-01-03 75 2018-01-07
120 Tom 50 2018-01-04 0 2018-01-07
130 Jony 25 2018-01-05 30 2018-01-03
130 Jony 0 2018-01-05 75 2018-01-04
130 Jony 0 2018-01-05 60 2018-01-05
140 Jone 40 2018-01-04 45 2018-01-04
In 2018-01-07 Tom did not sale water but I get 75 amount.
Someone help me, please
It is achievable but messy and probably not very efficient. There is a relationship between the bread water and account on ac. It is possible to establish the maximum number of row numbers spanning bread and water and then rejoining on row number. Put another way bread and water are joined on a position basis (ie the order rows appear in the tables) The resulting query is horrible and parses the data more frequently than I would be comfortable with personally.
so
select * from
(
select c.ac cac,t.name tname,d.amount bsamount, d.dt ddt,
e.amount wsamount ,e.dt edt
,
case when d.dt is not null and d.dt < e.dt then d.dt
when d.dt is not null and e.dt is null then d.dt
else e.dt
end as sortorder
from
(
select *
from
(
select bs.ac,
if(bs.ac <> #p,#rn:=1,#rn:=#rn+1) rn,
#p:=bs.ac p
from bs
cross join (select #rn:=0,#p:=0) r
order by bs.ac,bs.dt
) a
union
(
select ac2,rn1,p1
from
(
select ws.ac ac2,
if(ws.ac <> #p1,#rn1:=1,#rn1:=#rn1+1) rn1,
#p1:=ws.ac p1
from ws
cross join (select #rn1:=0,#p1:=0) r
order by ws.ac,ws.dt
) b
)
) c
left join
(
select bs.ac,pcs,amount,dt,
if(bs.ac <> #p3,#rn3:=1,#rn3:=#rn3+1) rn3,
#p3:=bs.ac p
from bs
cross join (select #rn3:=0,#p3:=0) r
order by bs.ac,bs.dt
) d
on d.ac = c.ac and d.rn3 = c.rn
left join
(
select ws.ac,pcs,amount,dt,
if(ws.ac <> #p4,#rn4:=1,#rn4:=#rn4+1) rn4,
#p4:=ws.ac p
from ws
cross join (select #rn4:=0,#p4:=0) r
order by ws.ac,ws.dt
) e
on e.ac = c.ac and e.rn4 = c.rn
join t on t.ac = c.ac
) f
order by cac , sortorder;
+------+-------+----------+------------+----------+------------+------------+
| cac | tname | bsamount | ddt | wsamount | edt | sortorder |
+------+-------+----------+------------+----------+------------+------------+
| 120 | Tom | 60 | 2018-01-03 | 75 | 2018-01-07 | 2018-01-03 |
| 120 | Tom | 50 | 2018-01-04 | NULL | NULL | 2018-01-04 |
| 130 | Jony | 25 | 2018-01-05 | 30 | 2018-01-03 | 2018-01-03 |
| 130 | Jony | NULL | NULL | 75 | 2018-01-04 | 2018-01-04 |
| 130 | Jony | NULL | NULL | 60 | 2018-01-05 | 2018-01-05 |
| 140 | Jone | 40 | 2018-01-04 | 45 | 2018-01-04 | 2018-01-04 |
+------+-------+----------+------------+----------+------------+------------+
6 rows in set (0.00 sec)
select account.ac,
account.name,
bread_sale.amount as BSAmount,
bread_sale.date as BSDate,
water_sale.amount as WSAmount,
water_sale.date as WSDate
from account left outer join bread_sale on account.ac = bread_sale.ac
left outer join water_sale on water_sale.ac = bread_sale.ac
order by account.ac
This Query would produce this result. Your zeros will be replaced by correct value. I hope may be you would need this reuslt.
ac name BSAmount BSdate WSAmount WSdate
120 Tom 60 2018-01-03 75 2018-01-07
120 Tom 50 2018-01-04 75 2018-01-07
130 Jony 25 2018-01-05 30 2018-01-03
130 Jony 25 2018-01-05 75 2018-01-04
130 Jony 25 2018-01-05 60 2018-01-05
140 Jone 40 2018-01-04 45 2018-01-04
I have two columns coming from my sql query- month, value i.e. values are coming monthwise. My requirement is to club these months in the group of 3 months wise...and the values should come the average of these 3.
Ex.I have following data-
Month Values
Mar-14 50
Apr-14 51
May-14 52
Jun-14 53
Jul-14 54
Aug-14 55
Sep-14 56
Oct-14 57
Nov-14 58
Dec-14 59
Jan-15 60
Feb-15 61
Mar-15 62
Apr-15 63
May-15 64
Jun-15 65
Jul-15 66
Aug-15 67
Sep-15 68
Oct-15 69
Nov-15 70
Dec-15 71
Jan-16 72
Feb-16 73
Mar-16 74
Apr-16 75
May-16 76
Jun-16 77
Jul-16 78
Aug-16 79
Sep-16 80
Oct-16 81
Nov-16 82
Dec-16 83
Jan-17 84
Feb-17 85
Mar-17 86
How can I achieve following output in MySql-
3 Months Clubing Avg of Values
Mar-14 51
Jun-14 54
Sep-14 57
Dec-14 60
Mar-15 63
Jun-15 66
Sep-15 69
Dec-15 72
Mar-16 75
Jun-16 78
Sep-16 81
Thanks in Advance
A bit messy but you could use variables -assuming you have an incrementing id column (or soemthing you can order by)
drop table if exists t;
create table t(id int auto_increment primary key,Month varchar(10), Valus int);
insert into t (month,valus) values
('Mar-14', 50),
('Apr-14', 51),
('May-14', 52),
('Jun-14', 53),
('Jul-14', 54),
('Aug-14', 55),
('Sep-14', 56),
('Oct-14', 57),
('Nov-14', 58),
('Dec-14', 59);
select id,mth,rt
from
(
select id,month,valus,
#count:=#count+1 counter,
if(#count=1,#mth:=month,#mth:=#mth) mth,
if(#count=1,#block:=#block+1,#block:=#block) block,
if(#count<3,#sum:=#sum+valus,#sum:=(#sum+valus) / 3) rt,
if(#count=3,#count:=0,#count:=#count) creset,
if(#count=0,#sum:=0,#sum:=#sum) sumreset
from t
cross join (select #m ='',#count:=0,#sum:=0,#block:=0,#mth:='') s
order by id
)t
where counter = 3;
+----+--------+------+
| id | mth | rt |
+----+--------+------+
| 3 | Mar-14 | 51 |
| 6 | Jun-14 | 54 |
| 9 | Sep-14 | 57 |
+----+--------+------+
3 rows in set (0.03 sec)
Slightly less messy but using sql's avg function and using variables to fill down the first month in a 3 month block
select block,mth,avg(valus)
from
(
select id,month,valus,
#count:=#count+1 counter,
if(#count=1,#mth:=month,#mth:=#mth) mth,
if(#count=1,#block:=#block+1,#block:=#block) block,
if(#count=3,#count:=0,#count:=#count) creset
from t
cross join (select #block:=0,#count:=0,#mth:='') s
order by id
) t
group by block,mth
order by block,mth
+-------+--------+------------+
| block | mth | avg(valus) |
+-------+--------+------------+
| 1 | Mar-14 | 51.0000 |
| 2 | Jun-14 | 54.0000 |
| 3 | Sep-14 | 57.0000 |
| 4 | Dec-14 | 59.0000 |
+-------+--------+------------+
4 rows in set (0.05 sec)
Try this
create temporary table tab (month1 varchar(30), id int);
insert into tab (month1,id)
values('Mar-14' ,50),
('Apr-14' ,51),
('May-14' ,52),
('Jun-14' ,53),
('Jul-14' ,54),
('Aug-14' ,55),
('Sep-14' ,56),
('Oct-14' ,57),
('Nov-14' ,58),
('Dec-14' ,59),
('Jan-15' ,60),
('Feb-15' ,61),
('Mar-14' ,62);
set #row_number = 0;
select *
from tab where (#row_number := #row_number+1)%3= 1;
Result
month1 id
'Mar-14' '50'
'Jun-14' '53'
'Sep-14' '56'
'Dec-14' '59'
'Mar-14' '62'
How to select rows for each user_id equals select numbers of count for each user_id?
My example table:
mp3_id | user_id
--------------------
120 | 840
123 | 840
126 | 840
128 | 455
130 | 840
131 | 840
132 | 840
135 | 840
144 | 840
158 | 840
159 | 455
161 | 455
169 | 455
180 | 840
181 | 455
184 | 455
186 | 455
189 | 455
My simple query:
select mp3_id where user_id IN (840,455) limit 8
Return:
mp3_id | user_id
--------------------
120 | 840
123 | 840
126 | 840
128 | 455
130 | 840
131 | 840
132 | 840
135 | 840
But I want to this select:
mp3_id | user_id
--------------------
120 | 840
123 | 840
126 | 840
130 | 840
128 | 455
159 | 455
161 | 455
169 | 455
I want each user_id to return an equal row count. How to?
You could do this with a UNION:
select mp3_id where user_id = 840 limit 4
union all
select mp3_id where user_id = 455 limit 4
try this query:
select
yt.mp3_id,
e.user_id
from
(
select distinct user_id from your_table
) e
join your_table yt on true
where yt.mp3_id in (select tt.mp3_id from your_table tt where tt.user_id = e.user_id order by tt.mp3_id limit 4)
and tihs same query but with condition
select
yt.mp3_id,
e.user_id
from
(
select distinct user_id from your_table where user_id in (840,455)
) e
join your_table yt on true
where yt.mp3_id in (select tt.mp3_id from your_table tt where tt.user_id = e.user_id order by tt.mp3_id limit 4)
SELECT x.*
FROM my_table x
JOIN my_table y
ON y.user_id = x.user_id
AND y.mp3_id <= x.mp3_id
GROUP
BY x.mp3_id HAVING COUNT(*) <= 4
ORDER
BY user_id DESC
, mp3_id;
or faster
SELECT mp3_id, user_id FROM
(
SELECT x.*, CASE WHEN #prev = user_id THEN #i:=#i+1 ELSE #i:=1 END i, #prev:=user_id FROM my_table x, (SELECT #prev:=null,#i:=1) vars ORDER BY user_id DESC, mp3_id
) a
WHERE i<=4;