using row number in update query - MySQL - mysql

I have this DB2 query which I want to make MySQL compliant :
UPDATE
(
SELECT x.name, row_number() over () as rown from XYZ x where x.id = '123' and
x.div='abc')A
SET
A.name = 'name_1'
where
A.rown<= ( select count(*) -1 from XYZ where id='123' and div='abc');
Now, I tried writing this I MySQL:
UPDATE
(
select x.name, (#row_number := #row_number +1) as rown
from XYZ x, (Select #row_number := 0)as t
where x.id='123' and x.div='abc'
) A
Set
A.name = 'name_1'
where
A.rown<= ( select count(*) -1 from XYZ where id='123' and div='abc');
However, it gives me the error: The target table A of the UPDATE is not updatable
I have tried multiple ways but all in vain. Where am I going wrong?
Also if the DB2 query can be made into MySql in any other way, since Mysql doesn't support
row_number()

You can't update a derived table. You need to join with the real table so you can update it.
UPDATE XYZ AS x
JOIN (
select x.id, (#row_number := #row_number +1) as rown
from XYZ x, (Select #row_number := 0) as t
where x.id='123' and x.div='abc'
) AS A ON x.id = A.id
Set X.name = 'name_1'
where A.rown <= ( select count(*) from XYZ where id='123' and div='abc');
I'm not sure if this will do the same thing as the DB2 query, though. It seems to assume some inherent ordering in the table, and perhaps DB2 provides such a thing, but MySQL doesn't make any guarantees about ordering when you don't use ORDER BY. If you add ORDER BY x.id in the subquery, maybe that will do what you want.

In DB2 you can do it :
update XYZ f1
set f1.name='name_1'
where f1.id='123' and f1.div='abc'
and rrn(f1) not in
(
select max(rrn(f2)) from XYZ f2 where f2.id='123' and f2.div='abc'
)

Related

MySQL convert duplicate field entries to duplicate incrementing value

I currently have a table that has a parent_id field for multiple entries, ie the same ID number for 4 entries (as per the screenshot below). I would like to convert the parent_id to run from 9000 upwards, so in the screenshot 000004 would become 9000 in 4 entries, 000007 would become 9001 in 4 entries and so on (as shown in the example outcome). Does anyone know of an easy way to implement this please as I'd rather not have to manually change 2224 entries!?
Thanks in advance guys!
Table screenshot:
Example outcome:
You seem to want an update. If so:
update t join
(select t.*, row_number() over (order by id) as seqnum
from t
) tt
on t.id = tt.id
set t.parent_id = 9000 + floor( (seqnum - 1) / 4);
Note that this ignores the current parent_id, assigning the same value to groups of 4 rows based on the id.
EDIT:
In older versions of MySQL:
update t join
(select t.*, (#rn := #rn + 1) as seqnum
from (select t.* from t order by id) t cross join
(select #rn := 0) params
) tt
on t.id = tt.id
set t.parent_id = 9000 + floor( (seqnum - 1) / 4);
If you are runnig MySQL 8.0, you can use dense_rank() for this:
select
t.*,
8999 + dense_rank() over(order by parent_id) new_parent_id
from mytable t
On earlier versions, one (less efficient) option uses a correlated subquery:
select
t.*,
9000 + (select count(distinct t1.parent_i) from mytable t1 where t1.parent_id < t.parent_id) new_parent_id
from mytable t

MySQL rank query. Get the position of a specific member

I have the following table on my DataBase:
TimeRank(user-id, name, time)
I would like to order the table by time and get the position of an specific ID on the table, for example:
The user nÂș 68 is on the 3rd position.
I only need to do a query that returns the position of the user.
MySQL don't have the function row_number, so I don't know how to do it.
SELECT x.user-id,
x.name,
x.time,
x.position
FROM (SELECT t.user-id,
t.name,
t.time,
#rownum := #rownum + 1 AS position
FROM TABLE TimeRank t
JOIN (SELECT #rownum := 0) r
ORDER BY t.time) x
WHERE x.user-id = 123
Alternative:
SELECT user-id,
(SELECT COUNT(*) FROM TimeRank WHERE time <= (SELECT time FROM TimeRank WHERE user-id = 123)) AS position,
time,
name
FROM TimeRank
WHERE user-id = 123
You can generate a position column with a variable
set #pos=0;
select pos,user_id
from (select #pos:=#pos+1 pos,user_id from TimeRank order by time) s
where user_id=68;
If indexing is a concern, you can add a column to your table and update it with
set #pos=0;
update TimeRank set position=(#pos:=#pos+1) order by time;

How to eliminate only continuous duplicates but not all duplicates in a select query (MySQL)?

I have a table like this:
01-Jul-17 100
02-Jul-17 100
03-Jul-17 300
04-Jul-17 300
05-Jul-17 500
06-Jul-17 500
07-Jul-17 300
08-Jul-17 400
09-Jul-17 100
10-Jul-17 100
What I want to output is (in this order) by eliminating the continuous duplicates but not all duplicates:
100
300
500
300
400
100
I cannot select Distinct, as it will eliminate the second instances of 300, 100. Is there a way to achieve this result in MySQL?
Thanks!
You want to get the previous value. If the dates really have no gaps or duplicates, just do:
select t.*
from t left join
t tprev
on t.col1 = date_add(tprev.col1, interval 1 day)
where tprev.col2 is null or tprev.col2 <> t.col2;
EDIT:
If the dates don't meet these conditions, then you can use variables:
select t.*
from (select t.*,
(#rn := if(#v = col2, #rn + 1,
if(#v := col2, 1, 1)
)
) as rn
from t cross join
(select #v := 0, #rn := 0) params
order by t.col1
) t
where rn = 1;
Note that MySQL does not guarantee the order of evaluation of expressions in the SELECT. So variables should not be assigned in one expression and then used in another -- they should be assigned in a single expression.
One way to handle this problem is by using session variables to track the changes of the values as ordered by your date column. In the query below, we keep track of the value, ordered by date, and assign a row number to each group of identical value. Then, only the first value in each group is retained. Note that this approach is robust to any number of duplicates. It is also robust with respect to there being gaps in your dates, so long as each record can be ordered by date.
SET #rn = 1;
SET #val = NULL;
SELECT t.val
FROM
(
SELECT
#rn:=CASE WHEN #val = val THEN #rn+1 ELSE 1 END rn,
#val:=val AS val,
dt
FROM yourTable
ORDER BY dt
) t
WHERE t.rn = 1
ORDER BY t.dt;
Output:
Demo here:
Rextester
You can make use of lag and lead functions.
select y from (select y , lag(y,1,0) over (order by x) as prev_y from t1) where y <> prev_y;

SQL find rows where value is not increasing

I have a table with columns like this:
id | timestamp | ...
and I am looking for rows where the timestamp decreased since the previous row.
I tried a statement like this:
SELECT count(a.id)
FROM tbl AS a INNER JOIN tbl AS b ON a.id+1=b.id
WHERE a.timestamp<b.timestamp;
but it appears not to have worked. I get zero results even though I expect some. Any suggestions what is wrong?
I would also appreciate any ideas on a better way to write this query.
I am using MySQL.
You can get the previous value using a correlated subquery, and then use that for the comparison:
select t.*
from (select t.*,
(select t2.timestamp from tbl t2 where t2.id < t.id order by t2.id desc limit 1
) as prevts
from tbl t
) t
where timestamp < prevts;
The problem with your query is probably that the ids have gaps in them.
EDIT:
You can do this with variables. The challenge is getting the variable comparison and assignment in a single expression. This is needed because MySQL does not guarantee the order of evaluation of expressions in a select statement.
The following assigns a value to IsDecreasing and assigns the values:
select t.*
from (select t.*,
if(#prev > timestamp, if(#prev := timestamp, 1, 1),
if(#prev := timestamp, 0, 0)
) IsDecreasing
from tbl t cross join
(select #prev := -1) vars
order by id
) t
where IsDecreasing = 1;
This should be faster than the previous method -- probably even when you have the right index.

Getting latest rows in MySQL based on date (grouped by another column)

This type of question is asked every now and then. The queries provided works, but it affects performance.
I have tried the JOIN method:
SELECT *
FROM nbk_tabl
INNER JOIN (
SELECT ITEM_NO, MAX(REF_DATE) as LDATE
FROM nbk_tabl
GROUP BY ITEM_NO) nbk2
ON nbk_tabl.REF_DATE = nbk2.LDATE
AND nbk_tabl.ITEM_NO = nbk2.ITEM_NO
And the tuple one (way slower):
SELECT *
FROM nbk_tabl
WHERE REF_DATE IN (
SELECT MAX(REF_DATE)
FROM nbk_tabl
GROUP BY ITEM_NO
)
Is there any other performance friendly way of doing this?
EDIT: To be clear, I'm applying this to a table with thousands of rows.
Yes, there is a faster way.
select *
from nbk_table
order by ref_date desc
limit <n>
Where is the number of rows that you want to return.
Hold on. I see you are trying to do this for a particular item. You might try this:
select *
from nbk_table n
where ref_date = (select max(ref_date) from nbk_table n2 where n.item_no = n2.item_no)
It might optimize better than the "in" version.
Also in MySQL you can use user variables (Suppose nbk_tabl.Item_no<>0):
select *
from (
select nbk_tabl.*,
#i := if(#ITEM_NO = ITEM_NO, #i + 1, 1) as row_num,
#ITEM_NO := ITEM_NO as t_itemNo
from nbk_tabl,(select #i := 0, #ITEM_NO := 0) t
order by Item_no, REF_DATE DESC
) as x where x.row_num = 1;