How to "Fill Series" (Linear step value) in MySQL like in Excel? - mysql

In Excel, filling series with linear step value is simple. How do I do that in MySQL?
(1) SELECT * FROM blog_posts where postid = 5 ORDER BY rowid ASC
I get this query result from a huge table:
rowid postid Unix_TimeStamp
100 5 1000000000
135 5 1656885375
142 5 1885649882
208 5 1928211766
(2)Next, I I need to alter the values of Unix_TimeStamp. I want to leave the first row (rowid=100) alone, then every row's Unix_TimeStamp is 100 higher than the previous row's. The result would be:
rowid postid Unix_TimeStamp
100 5 1000000000
135 5 1000000100
142 5 1000000200
208 5 1000000300
Thanks a lot for generous replies.

In mysql 5.x you can do this like
In mysql 8 you have the window function rownumber
Schema (MySQL v5.7)
CREATE TABLE blog_posts (
`rowid` INTEGER,
`postid` INTEGER,
`Unix_TimeStamp` INTEGER
);
INSERT INTO blog_posts
(`rowid`, `postid`, `Unix_TimeStamp`)
VALUES
('100', '5', '1000000000'),
('135', '5', '1656885375'),
('142', '5', '1885649882'),
('208', '5', '1928211766');
Query #1
SELECT
`rowid`, `postid`
,(SELECT MIN(`Unix_TimeStamp`) FROM blog_posts where postid = 5 ) + #rn *100 `Unix_TimeStamp`
,#rn := #rn + 1 ronn
FROM blog_posts, (SELECT #rn := 0) a
where postid = 5
ORDER BY rowid ASC;
| rowid | postid | Unix_TimeStamp | ronn |
| ----- | ------ | -------------- | ---- |
| 100 | 5 | 1000000000 | 1 |
| 135 | 5 | 1000000100 | 2 |
| 142 | 5 | 1000000200 | 3 |
| 208 | 5 | 1000000300 | 4 |
UPDATE blog_posts bp INNER JOIN (SELECT
`rowid`, `postid`
,(SELECT MIN(`Unix_TimeStamp`) FROM blog_posts where postid = 5 ) + #rn *100 `Unix_TimeStamp`
,#rn := #rn + 1 ronn
FROM blog_posts, (SELECT #rn := 0) a
where postid = 5
ORDER BY rowid ASC) t1 ON bp.rowid = t1.rowid
SET bp.Unix_TimeStamp = t1.Unix_TimeStamp;
[View on DB Fiddle](https://www.db-fiddle.com/f/wUqVKNZy96RjR7hTk3md7o/4)

Related

mysql query to get sum of a field less than a limit grouped by another field

I have a table orders like:
order_id int ,type string ,weight double.
Example:
| 4 | type1 | 9.729999542236328 |
| 5 | type2 | 13.930000305175781 |
| 14 | type4 | 9.399999618530273 |
| 17 | type1 | 3.490000009536743 |
| 20 | type3 | 6.349999904632568 |
| 25 | type3 | 12.869999885559082 |
| 31 | type4 | 1.3700000047683716 |
| 40 | type5 | 20.079999923706055 |
| 42 | type2 | 9.0600004196167 |
| 45 | type2 | 15.390000343322754 |
I want to get rows grouped by id where the total weight is less than 500.
Example:
{order_ids: [1, 2, 3], total_weight: 450 }
{order_ids: [4, 5, 6], total_weight: 470 }
{order_ids: [7, 8, 9], total_weight: 400 }
I want to get the ids' of the orders and the total weight of them. I have 200k+ lines on the table so performance is a big focus for me. I haven't shared any query because I don't know where to start.
I am using golang with gorm and mysql 8.0.21.
I don't need to find the optimal solution it can be FIFO.
SELECT GROUP_CONCAT(order_id ORDER BY order_id) order_ids,
SUM(weight) total_weight
FROM (SELECT test.*,
#current_group := #current_group + (#current_weight + weight > #max_weight) group_number,
#current_weight := weight + #current_weight * (#current_weight + weight <= #max_weight) cumulative_weight
FROM test, (SELECT #current_weight := 0, #current_group := 0) variables
ORDER BY order_id) subquery
GROUP BY group_number;
fiddle
PS. Of course this query cannot find optimal "cutting stock problem" solution.
You can use a recursive CTE:
with tt as (
select tt.*,
row_number() over (order by rand()) as seqnum
from t
),
recursive cte (
select order_id, weight, weight as running_weight, 1 as grp
from tt
where seqnum = 1
union all
select tt.order_id, tt.weight,
(case when tt.weight + cte.running_weight >= 500
then tt.weight else tt.weight + cte.running_weight
end),
(case when tt.weight + cte.running_weight >= 500
then grp + 1 else grp
end)
from cte join
tt
on tt.seqnum = cte.seqnum + 1
)
select *
from cte;

Get user's highest score from a table

I have a feeling this is a very simple question but maybe i'm having brain fart right now and just can't seem to figure out how to go about it.
I have a MySQL table structure like below
+---------------------------------------------------+
| id | date | score | speed | user_id |
+---------------------------------------------------+
| 1 | 2016-11-17 | 2 | 133291 | 17 |
| 2 | 2016-11-17 | 6 | 82247 | 17 |
| 3 | 2016-11-17 | 6 | 21852 | 17 |
| 4 | 2016-11-17 | 1 | 109338 | 17 |
| 5 | 2016-11-17 | 7 | 64762 | 61 |
| 6 | 2016-11-17 | 8 | 49434 | 61 |
Now i can get a particular user's best performance by doing this
SELECT *
FROM performance
WHERE user_id = 17 AND date = '2016-11-17'
ORDER BY score desc,speed asc LIMIT 1
This should return the row with ID = 3. Now what I want is a single query to run to be able to return that 1 such row for each unique user_id in the table. So the resulting result would be something like this
+---------------------------------------------------+
| id | date | score | speed | user_id |
+---------------------------------------------------+
| 3 | 2016-11-17 | 6 | 21852 | 17 |
| 6 | 2016-11-17 | 8 | 49434 | 61 |
Also further more, can I have another question within this same query that would further sort this eventual resulting table by the same criteria of sort (score desc, speed asc). Thanks
A simple method uses a correlated subquery:
select p.*
from performance p
where p.date = '2016-11-17' and
p.id = (select p2.id
from performance p2
where p2.user_id = p.user_id and p2.date = p.date
order by score desc, speed asc
limit 1
);
This should be able to take advantage of an index on performance(date, user_id, score, speed).
Is easy using variable to emulate row_number() over (partition by Order by)
Explanation:
First create two variables in the subquery.
Order by user_id so when user change the #rn reset to 1
Order by score desc, speed asc so each row will have a row_number, and the one you want always will have rn = 1
#rn := you change #rn for each row
if you have a new user_id then #rn is set to 1
otherwise #rn is set to #rn+1
SQL Fiddle Demo
SELECT `id`, `date`, `score`, `speed`, `user_id`
FROM (
SELECT *,
#rn := if(#user_id = `user_id`,
#rn + 1 ,
if(#user_id := `user_id`,1,1)
) as rn
FROM Table1
CROSS JOIN (SELECT #user_id := 0, #rn := 0) as param
WHERE date = '2016-11-17'
ORDER BY `user_id`, `score` desc, `speed` asc
) T
where T.rn =1
OUTPUT
For mysql
You can try with a double in subselect and group by
select * from performance
where (user_id, score,speed ) in (
SELECT user_id, max_score, max(speed)
FROM performance
WHERE (user_id, score) in (select user_id, max(score) max_score
from performance
group by user_id)
group by user_id, max_score
);

MySQL top 2 records per group

Basically I need to get only the last 2 records for each user, considering the last created_datetime:
id | user_id | created_datetime
1 | 34 | '2015-09-10'
2 | 34 | '2015-10-11'
3 | 34 | '2015-05-23'
4 | 34 | '2015-09-13'
5 | 159 | '2015-10-01'
6 | 159 | '2015-10-02'
7 | 159 | '2015-10-03'
8 | 159 | '2015-10-06'
Returns (expected output):
2 | 34 | '2015-10-11'
1 | 34 | '2015-09-10'
7 | 159 | '2015-10-03'
8 | 159 | '2015-10-06'
I was trying with this idea:
select user_id, created_datetime,
$num := if($user_id = user_id, $num + 1, 1) as row_number,
$id := user_id as dummy
from logs group by user_id
having row_number <= 2
The idea is keep only these top 2 rows and remove all the others.
Any ideas?
Your idea is close. I think this will work better:
select u.*
from (select user_id, created_datetime,
$num := if(#user_id = user_id, #num + 1,
if(#user_id := id, 1, 1)
) as row_number
from logs cross join
(select #user_id := 0, #num := 0) params
order by user_id
) u
where row_number <= 2 ;
Here are the changes:
The variables are set in only one expression. MySQL does not guarantee the order of evaluation of expressions, so this is important.
The work is done in a subquery, which is then processed in the outer query.
The subquery uses order by, not group by.
The outer query uses where instead of having (actually, in MySQL having would work, but where is more appropriate).

Calculate delta(difference of current and previous row) mysql group by specific column

I have a table like : session is the name of the table for example
With columns: Id, sessionDate, user_id
What i need:
Delta should be a new calculated column
Id | sessionDate | user_id | Delta in days
------------------------------------------------------
1 | 2011-02-20 00:00:00 | 2 | NULL
2 | 2011-03-21 00:00:00 | 2 | NULL
3 | 2011-04-22 00:00:00 | 2 | NULL
4 | 2011-02-20 00:00:00 | 4 | NULL
5 | 2011-03-21 00:00:00 | 4 | NULL
6 | 2011-04-22 00:00:00 | 4 | NULL
Delta is the Difference between the timestamps
What i want is a result for Delta Timestamp (in Days) for the the previous row and the current row grouped by the user_id.
this should be the result:
Id | sessionDate | user_id | Delta in Days
------------------------------------------------------
1 | 2011-02-20 00:00:00 | 2 | NULL
2 | 2011-02-21 00:00:00 | 2 | 1
3 | 2011-02-22 00:00:00 | 2 | 1
4 | 2011-02-20 00:00:00 | 4 | NULL
5 | 2011-02-23 00:00:00 | 4 | 3
6 | 2011-02-25 00:00:00 | 4 | 2
I already have a solution for a specific user_id:
SELECT user_id, sessionDate,
abs(DATEDIFF((SELECT MAX(sessionDate) FROM session WHERE sessionDate < t.sessionDate and user_id = 1), sessionDate)) as Delta_in_days
FROM session AS t
WHERE t.user_id = 1 order by sessionDate asc
But for more user_ids i didnĀ“t find any solution
Hope somebody can help me.
Try this:
drop table a;
create table a( id integer not null primary key, d datetime, user_id integer );
insert into a values (1,now() + interval 0 day, 1 );
insert into a values (2,now() + interval 1 day, 1 );
insert into a values (3,now() + interval 2 day, 1 );
insert into a values (4,now() + interval 0 day, 2 );
insert into a values (5,now() + interval 1 day, 2 );
insert into a values (6,now() + interval 2 day, 2 );
select t1.user_id, t1.d, t2.d, datediff(t2.d,t1.d)
from a t1, a t2
where t1.user_id=t2.user_id
and t2.d = (select min(d) from a t3 where t1.user_id=t3.user_id and t3.d > t1.d)
Which means: join your table to itself on user_ids and adjacent datetime entries and compute the difference.
If id is really sequential (as in your sample data), the following should be quite efficient:
select t.id, t.sessionDate, t.user_id, datediff(t2.sessiondate, t.sessiondate)
from table t left outer join
table tprev
on t.user_id = tprev.user_id and
t.id = tprev.id + 1;
There is also another efficient method using variables. Something like this should work:
select t.id, t.sessionDate, t.user_id, datediff(prevsessiondate, sessiondate)
from (select t.*,
if(#user_id = user_id, #prev, NULL) as prevsessiondate,
#prev := sessiondate,
#user_id := user_id
from table t cross join
(select #user_id := 0, #prev := 0) vars
order by user_id, id
) t;
(There is a small issue with these queries where the variables in the select clause may not be evaluated in the order we expect them to. This is possible to fix, but it complicates the query and this will usually work.)
Although you have choosen an answer here is another way of achieving it
SELECT
t1.Id,
t1.sessionDate,
t1.user_id,
TIMESTAMPDIFF(DAY,t2.sessionDate,t1.sessionDate) as delta
from myTable t1
left join myTable t2
on t1.user_id = t2.user_id
AND t2.Id = (
select max(Id) from myTable t3
where t1.Id > t3.Id AND t1.user_id = t3.user_id
);
DEMO

Sql to find timediff between two rows based on ID

The subject of the question is not very explanatory, sorry for that.
Ya so the question follows:
I have a database structure as below where pk is primary key, id
is something which is multiple for many rows.
+------+------+---------------------+
| pk | id | value |
+------+------+---------------------+
| 99 | 1 | 2013-08-06 11:10:00 |
| 100 | 1 | 2013-08-06 11:15:00 |
| 101 | 1 | 2013-08-06 11:20:00 |
| 102 | 1 | 2013-08-06 11:25:00 |
| 103 | 2 | 2013-08-06 15:10:00 |
| 104 | 2 | 2013-08-06 15:15:00 |
| 105 | 2 | 2013-08-06 15:20:00 |
+------+------+---------------------+
What is really need to get is, value difference between first two rows (which is ordered by value) for each
group (where group is by id). So according to above structure I need
timediff(value100, value99) [ which is for id 1 group]
and timediff(value104, value103) [ which is for id 2 group]
i.e. value difference of time ordered by value for 1st two rows in each group.
One way i can think to do is by 3 self joins (or 3 sub queries) so as to find the
first two in 2 of them , and third query subtracting it. Any suggestions?
try this.. CTE is pretty powerfull!
WITH CTE AS (
SELECT
value, pk, id,
rnk = ROW_NUMBER() OVER ( PARTITION BY id order by id DESC)
, rownum = ROW_NUMBER() OVER (ORDER BY id, pk)
FROM test
)
SELECT
curr.rnk, prev.rnk, curr.rownum, prev.rownum, curr.pk, prev.pk, curr.id, prev.id, curr.value, prev.value, curr.value - prev.value
FROM CTE curr
INNER JOIN CTE prev on curr.rownum = prev.rownum -1 and curr.id = prev.id
and curr.rnk <=1
Looks a bit wierd... But you can try this way
SET #previous = 0;
SET #temp = 0;
SET #tempID = 0;
Above step may not be needed .. But just to make sure nothing goes wrong
SELECT pkid, id, diff, valtemp FROM (
SELECT IF(#previousID = id, #temp := #temp + 1, #temp := 1) occ, #previousID := id,
TIMEDIFF(`value`, #previous) diff, pk, id, `value`, #previous := `value`
FROM testtable) a WHERE occ = 2
Demo on sql fiddle