MYSQL Update rows with duplicate value but oldest date - mysql

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;

Related

UPDATE table JOIN table SET only updates unique id's

I want to update Table B with sum(aa) from Table A where A.id=B.id and into the column set by A.d (E.g. A:223, d1, 5 updates B: id 223, column d1 = d1 + 5).
TABLE A TABLE B
id d aa id d1 d4 d7
--- +-------+---- ----+-------+-------+-----
223 | d1 | 5 221 | 0 | 5 | 0 <
224 | d1 | 5 222 | 0 | 5 | 0 <
225 | d1 | 5 223 | 5 | 0 < | 0 <
226 | d1 | 5 224 | 5 | 0 < | 0 <
| | 225 | 5 | 0 < | 0 <
221 | d4 | 5 226 | 5 | 0 < | 0 <
222 | d4 | 5 < = missing
223 | d4 | 5
224 | d4 | 5
225 | d4 | 5
226 | d4 | 5
| | EXPECTED:
221 | d7 | 5 id d1 d4 d7
222 | d7 | 4 ----+-------+-------+------
223 | d7 | 5 221 | 0 | 5 | 5
224 | d7 | 5 222 | 0 | 5 | 5
224 | d7 | 5 223 | 5 | 5 | 5
225 | d7 | 5 224 | 5 | 5 | 10
226 | d7 | 5 225 | 5 | 5 | 5
226 | d7 | 5 226 | 5 | 5 | 15
226 | d7 | 5
Query: (As I can't use dynamic column names I need a CASE WHEN THEN)
UPDATE `TABLE_B` `B`
JOIN(
SELECT `id`,`d`,SUM(`aa`)`aa`
FROM `TABLE_A` GROUP BY `id`,`d`
) `A`
ON `A`.`id`=`B`.`id`
SET
`d1`= CASE `B`.`d` WHEN "d1" THEN `A`.`d1`+`B`.`aa` ELSE `A`.`d1` END,
`d2`= CASE `B`.`d` WHEN "d2" THEN `A`.`d2`+`B`.`aa` ELSE `A`.`d2` END,
`d3`= CASE `B`.`d` WHEN "d3" THEN `A`.`d3`+`B`.`aa` ELSE `A`.`d3` END,
`d4`= CASE `B`.`d` WHEN "d4" THEN `A`.`d4`+`B`.`aa` ELSE `A`.`d4` END,
`d5`= CASE `B`.`d` WHEN "d5" THEN `A`.`d5`+`B`.`aa` ELSE `A`.`d5` END,
`d6`= CASE `B`.`d` WHEN "d6" THEN `A`.`d6`+`B`.`aa` ELSE `A`.`d6` END,
`d7`= CASE `B`.`d` WHEN "d7" THEN `A`.`d7`+`B`.`aa` ELSE `A`.`d7` END
The problem is this only updates unique id's. It takes 223, 224, 225, 226 from d1, then only 221, 222 from d4 and nothing from d7, instead of updating 16 values (4x d1, 6x d4, 6x d7).
So where in the JOIN am I doing wrong?
Fiddle
I got a solution by using two subqueries to first make a full table with all columns in it and only one row per id:
SELECT
`id`,
CASE `d` WHEN "d1" THEN SUM(`aa`) ELSE 0 END AS `a1`,
CASE `d` WHEN "d4" THEN SUM(`aa`) ELSE 0 END AS `a4`,
CASE `d` WHEN "d7" THEN SUM(`aa`) ELSE 0 END AS `a7`
FROM `TABLE_A`
GROUP BY `id`,`d`
Giving (example only for id's 221 and 226):
id d1 d4 d7
----+---+---+----
221 | 0 | 5 | 0
221 | 0 | 0 | 5
226 | 5 | 0 | 0
226 | 0 | 5 | 0
226 | 0 | 0 | 15
Then make sure there is only one row for each id:
SELECT
`id`,
SUM(`a1`) `a1`,
SUM(`a4`) `a4`,
SUM(`a7`) `a7`
FROM(
.... previous select ...
)
GROUP BY `id`
Giving (example only for id's 221 and 226):
id d1 d4 d7
----+---+---+----
221 | 0 | 5 | 5
226 | 5 | 5 | 15
Now I could update Table B:
UPDATE `TABLE_B` `B`
JOIN(
SELECT
`id`,
SUM(`a1`) `a1`,
SUM(`a4`) `a4`,
SUM(`a7`) `a7`
FROM(
SELECT
`id`,
CASE `d` WHEN "d1" THEN SUM(`aa`) ELSE 0 END AS `a1`,
CASE `d` WHEN "d4" THEN SUM(`aa`) ELSE 0 END AS `a4`,
CASE `d` WHEN "d7" THEN SUM(`aa`) ELSE 0 END AS `a7`
FROM `TABLE_A`
GROUP BY `id`,`d`
) `C`
GROUP BY `id`
)`A`
ON `A`.`id`=`B`.`id`
SET
`d1`=`d1`+`a1`,
`d4`=`d4`+`a4`,
`d7`=`d7`+`a7`
Maybe there are better solutions, but this works.

Select where in() for each id return equal rows count

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;

MySQL summary table - evaluate and adjust

I have a very simple summary table that sums up 2 fields in a table where records are collected at 15 minute intervals. So;
SELECT timevalue, SUM(value1) AS sumvalue1, SUM(value2) AS sumvalue2
FROM table
GROUP BY timevalue
returns the results i would expect;
timevalue sumvalue1 sumvalue2
-------------------------------------
16/08/2016 08:30 3000 200
16/08/2016 08:45 3200 150
16/08/2016 09:00 3100 400
16/08/2016 09:15 3300 450
16/08/2016 09:30 3400 600
My question is, is there a way to check that each sum value is never less than the previous value? and if it is return the sum from the previous timevalue? (so therefore the sumvalues are always the same or greater than the previous timevalue).
The results table should then look like this;
timevalue sumvalue1 sumvalue2
-------------------------------------
16/08/2016 08:30 3000 200
16/08/2016 08:45 3200 200
16/08/2016 09:00 3200 400
16/08/2016 09:15 3300 450
16/08/2016 09:30 3400 600
I'm guessing i need some kind of if statement? Any ideas on how to achieve this?
Many Thanks
You can do this using the user defined variable and then doing some arithmetic something as
select timevalue,sumvalue_1 as sumvalue1, sumvalue_2 as sumvalue2 from
(
select
timevalue,
if(#prev_val1 = sumvalue1 or #prev_val1 > sumvalue1,#prev_val1,sumvalue1) as sumvalue_1,
if(#prev_val2 = sumvalue2 or #prev_val2 > sumvalue2,#prev_val2,sumvalue2) as sumvalue_2,
#prev_val1 := sumvalue1,
#prev_val2 := sumvalue2
from mytable,(select #prev_val1:=0,#prev_val2:=0)x
order by timevalue
)x
order by timevalue
Here is a demo
create table mytable (
timevalue datetime,
sumvalue1 int,
sumvalue2 int
);
insert into mytable values
('2016-08-16 08:30:00',3000,200),
('2016-08-16 08:45:00',3200,150),
('2016-08-16 09:00:00',3100,400),
('2016-08-16 09:15:00',3300,450),
('2016-08-16 09:30:00',3400,600);
mysql> select * from mytable;
+---------------------+-----------+-----------+
| timevalue | sumvalue1 | sumvalue2 |
+---------------------+-----------+-----------+
| 2016-08-16 08:30:00 | 3000 | 200 |
| 2016-08-16 08:45:00 | 3200 | 150 |
| 2016-08-16 09:00:00 | 3100 | 400 |
| 2016-08-16 09:15:00 | 3300 | 450 |
| 2016-08-16 09:30:00 | 3400 | 600 |
+---------------------+-----------+-----------+
Now with the query
mysql> select timevalue,sumvalue_1 as sumvalue1, sumvalue_2 as sumvalue2 from
-> (
-> select
-> timevalue,
-> if(#prev_val1 = sumvalue1 or #prev_val1 > sumvalue1,#prev_val1,sumvalue1) as sumvalue_1,
-> if(#prev_val2 = sumvalue2 or #prev_val2 > sumvalue2,#prev_val2,sumvalue2) as sumvalue_2,
-> #prev_val1 := sumvalue1,
-> #prev_val2 := sumvalue2
-> from mytable,(select #prev_val1:=0,#prev_val2:=0)x
-> order by timevalue
-> )x
-> order by timevalue;
+---------------------+-----------+-----------+
| timevalue | sumvalue1 | sumvalue2 |
+---------------------+-----------+-----------+
| 2016-08-16 08:30:00 | 3000 | 200 |
| 2016-08-16 08:45:00 | 3200 | 200 |
| 2016-08-16 09:00:00 | 3200 | 400 |
| 2016-08-16 09:15:00 | 3300 | 450 |
| 2016-08-16 09:30:00 | 3400 | 600 |
+---------------------+-----------+-----------+
Not the prettiest example but still should work.
INSERT INTO SecondTable VALUES (
NOW(),
GREATEST(
YOUR_SUM_1, (SELECT sumvalue1 FROM FirstTable ORDER BY timevalue DESC LIMIT 1)
),
GREATEST(
YOUR_SUM_2, (SELECT sumvalue2 FROM FirstTable ORDER BY timevalue DESC LIMIT 1)
)
);

Match column name to data in MYSQL

I have data like in table.
Item | 7/7/15 | 7/8/15 | 7/9/15
1 | 23 | 24 | 25
2 | 26 | 74 | 96
and
I have table which has,
Item | Date | Number
1 | 7/9/15 | 56
1 | 7/7/15 | 75
1 | 7/8/15 | 63
I want to find sum of Number from 7/7/15 to 7/8/15 from table 1 and sum of the number from second table.
My table should look like
Item | StartDate | EndDate | no. | TotalNumber
item 7/7/15 7/8/15 7/9/15
1 23 24 25
2 26 74 96
item date number
1 7/9/15 56
1 7/7/15 75
1 7/8/15 63
.
SELECT
i1.Item,
'7/7/15' AS "StartDate",
'7/8/15' AS "EndDate",
(SELECT SUM(`7/7/15`)+SUM(`7/8/15`) FROM table1 WHERE item=i1.item) AS no,
(SELECT SUM(number) FROM table2 WHERE item=i1.item) "TotalNumber"
FROM
table2 i2
RIGHT OUTER JOIN table1 i1 on i1.item=i2.item;
item startdate enddate no TotalNumber
1 7/7/15 7/8/15 47 194
1 7/7/15 7/8/15 47 194
1 7/7/15 7/8/15 47 194
2 7/7/15 7/8/15 100
.
It's working..

Still show the proper set of time even if there's no entry for that time

I have this query where it gets the average and group the values by 15 mins from 12 AM to 11:45 PM.
SELECT FROM_UNIXTIME(t_stamp/1000, '%m/%d/%Y %l:%i %p') as t_stamp,
ROUND(AVG(CASE WHEN id = '001' THEN value END),2) Value1,
ROUND(AVG(CASE WHEN id = '002' THEN value END),2) Value2,
ROUND(AVG(CASE WHEN id = '003' THEN value END),2) Value3
FROM table1
WHERE tagid IN ("001", "002", "003") and
date(from_unixtime(t_stamp/1000)) BETWEEN "2014-05-01" AND "2014-05-01"
GROUP BY DATE(from_unixtime(t_stamp/1000)), HOUR(from_unixtime(t_stamp/1000)), MINUTE(from_unixtime(t_stamp/1000)) DIV 15
The output looks like this
t_stamp | Value1 | Value2 | Value3
05/01/2014 12:00 AM | 199 | 99 | 100
05/01/2014 12:15 AM | 299 | 19 | 140
05/01/2014 12:30 AM | 399 | 59 | 106
05/01/2014 12:45 AM | 499 | 59 | 112
.
.
.
05/01/2014 11:00 PM | 149 | 199 | 100
05/01/2014 11:15 PM | 599 | 93 | 123
05/01/2014 11:30 PM | 129 | 56 | 150
05/01/2014 11:45 PM | 109 | 60 | 134
It works fine but I've noticed that sometimes if there's no entry for like the time 12:30 instead of showing
t_stamp | Value1 | Value2 | Value3
05/01/2014 12:00 AM | 199 | 99 | 100
05/01/2014 12:15 AM | 299 | 19 | 140
05/01/2014 12:30 AM | Null | Null | Null
05/01/2014 12:45 AM | 499 | 59 | 112
It will show the set of time like this:
t_stamp | Value1 | Value2 | Value3
05/01/2014 12:00 AM | 199 | 99 | 100
05/01/2014 12:15 AM | 299 | 19 | 140
05/01/2014 12:33 AM | 122 | 141 | 234
05/01/2014 12:45 AM | 499 | 59 | 112
What I would like to happen is when there's no time for that 15 min group it will still show the proper set of time and then just show null on the column values. The output I would like is like this:
t_stamp | Value1 | Value2 | Value3
05/01/2014 12:00 AM | 199 | 99 | 100
05/01/2014 12:15 AM | 299 | 19 | 140
05/01/2014 12:30 AM | Null | Null | Null
05/01/2014 12:45 AM | 499 | 59 | 112
How can I do this?
Thank You.
You need a table that's a source of cardinal numbers as a start for this. For the moment let's assume it exists, and it's called cardinal.
Then, you need to create a query (a virtual table) that will return rows with timestamps every fifteen minutes, starting with the earliest relevant timestamp and ending with the latest. Here's how to do that for your query.
SELECT '2014-05-01' + INTERVAL (cardinal.n * 15) MINUTE as t_stamp
FROM cardinal
WHERE cardinal.n <= 24*4
Then you need to JOIN that virtual table to your existing query, as follows
SELECT DATE_FORMAT(t_stamp.t_stamp, '%m/%d/%Y %l:%i %p') t_stamp,
ROUND(AVG(CASE WHEN id = '001' THEN value END),2) Value1,
ROUND(AVG(CASE WHEN id = '002' THEN value END),2) Value2,
ROUND(AVG(CASE WHEN id = '003' THEN value END),2) Value3
FROM table1 AS t
LEFT JOIN (
SELECT '2014-05-01' + INTERVAL (cardinal.n * 15) MINUTE as t_stamp
FROM cardinal
WHERE cardinal.n <= 24*4
) AS t_stamp
ON t_stamp.t_stamp = FROM_UNIXTIME(t.t_stamp/1000)
WHERE tagid IN ("001", "002", "003")
AND date(from_unixtime(t_stamp/1000)) BETWEEN "2014-05-01" AND "2014-05-01"
GROUP BY DATE(from_unixtime(t_stamp/1000)),
HOUR(from_unixtime(t_stamp/1000)),
MINUTE(from_unixtime(t_stamp/1000)) DIV 15
Notice that the LEFT JOIN makes sure the rows will NULL values from your original query get included in the result set.
Now, where does this magical cardinal table come from?
You can generate it as two views, like this. This particular view generates numbers from 0 to 100 000, which is more than enough for quarters of hours for a year.
CREATE OR REPLACE VIEW cardinal10 AS
SELECT 0 AS N UNION
SELECT 1 AS N UNION
SELECT 2 AS N UNION
SELECT 3 AS N UNION
SELECT 4 AS N UNION
SELECT 5 AS N UNION
SELECT 6 AS N UNION
SELECT 7 AS N UNION
SELECT 8 AS N UNION
SELECT 9 AS N;
CREATE OR REPLACE VIEW cardinal AS
SELECT A.N + 10*(B.N + 10*(C.N + 10*(D.N + 10*(E.N)))) AS N
FROM cardinal10 A,cardinal10 B,cardinal10 C,
cardinal10 D,cardinal10 E;
Here's a writeup on the topic.
http://www.plumislandmedia.net/mysql/filling-missing-data-sequences-cardinal-integers/