#1221 - Incorrect usage of UPDATE and ORDER BY - mysql

To bypass a problem I posted in a other thread. I tried an sql statement like this:
UPDATE user u JOIN (SELECT #i := 0) r
SET user_rank_planets = (#i := (#i + 1))
WHERE user_active=1
ORDER BY user_planets DESC
I got Error #1221. Without the order by clause, the statement works fine.
Is there someone who knows a solution for this issue?

You cannot use order by and limit in update statement in the case of multiple tables.
Quoting From MySQL Documentation:
For the multiple-table syntax, UPDATE updates rows in each table named
in table_references that satisfy the conditions. Each matching row is
updated once, even if it matches the conditions multiple times. For
multiple-table syntax, ORDER BY and LIMIT cannot be used.
UPDATE user u
INNER JOIN
(
SELECT
*,
(#i := (#i + 1)) AS row_number
FROM user u
CROSS JOIN (SELECT #i := 0) r
WHERE user_active=1
ORDER BY user_planets DESC
)AS t
ON u.Primary_key = t.primary_key
SET u.user_rank_planets = t.row_number.
Note: Replace u.Primary_key and t.primary_key by the primary key of user table.
Read first few paragraphs http://dev.mysql.com/doc/refman/5.7/en/update.html

The #1000111's answer doesn't work. I don't know the reason but MySQL just ignored the ORDER BY in the subquery and updated with the default order (by the Primary_key).
A silly solution is wrapping the subquery inside another subquery to create a temporary table.
UPDATE user u
INNER JOIN
(
SELECT * FROM (
SELECT *, (#i := (#i + 1)) AS row_number
FROM user u
CROSS JOIN (SELECT #i := 0) r
WHERE user_active=1
ORDER BY user_planets DESC
) AS t1
) AS t
ON u.<Primary_key> = t.<Primary_key>
SET u.user_rank_planets = t.row_number

Related

How to query a table created by another query?

So I have a table, users, with user Balances and IDs.
With the below query, I get the table I need – which sorts the users by their balance.
SET #row_num=0; SELECT (#row_num:=#row_num+1) AS serial_num, ID, Balance FROM users ORDER BY Balance DESC; - which returns the following table:
Resulting MYSQL table
How would I find the serial_num of a specific user from the above table by ID?
I've tried SELECT * FROM ( the query above ) WHERE ID = "..."; but I must be getting something wrong with the syntax and I don't quite understand how I would implement a sub-query here.
Cheers
You had actually just 1 like mistake which lead to an uninitialized variable. Replace
SET #row_num=0;
with
SET #row_num:=0;
A little shorter version which can be run in one query would be:
SELECT *
FROM
(
SELECT ID, Balance, #row := #row + 1 AS serial_num
FROM users
CROSS JOIN (SELECT #row := 0) r
ORDER BY Balance DESC
) tmp
WHERE serial_num = 2
SQLFiddle demo

SQL: A column in a subquery does not appear

There is a query in MySQL 5.7:
SELECT * FROM
(
SELECT (#rowNum:=#rowNum+1) AS rowNo,t.* FROM table_target t,(SELECT (#rowNum :=0)) AS b
WHERE p_d = '2020-11-08'
ORDER BY kills DESC
) t
WHERE t.uid= '8888'
Running this query, there is no exception but column B disappears and if using select b from in the outter query, it returns unknown column exception.
I have 2 questions:
Why the (SELECT (#rowNum :=0)) does not appear?
Is the (#rowNum:=#rowNum+1) equivelent to row_number() over () in Oracle? If so, how to understand it...
Thanks for your help in advance.
In addition, I just found if I put the (SELECT (#rowNum :=0) ) in the left:
...
SELECT (SELECT (#rowNum :=0) ) AS b, (#rowNum:=#rowNum+1) AS rowNo , t.* FROM table_target t
...
Then the row number column does not increase any more, why could this happen?
You have asked 3 questions here:
Question 1: Why the (SELECT (#rowNum :=0)) does not appear?
Answer: You have used (SELECT (#rowNum :=0)) as B as a table joining it but not calling it in column list after select. That's why it is not showing it in output. You have called it as (#rowNum:=#rowNum+1) which is showing the value after increment means starting from 1.
Question 2: Is the (#rowNum:=#rowNum+1) equivalent to row_number() over () in Oracle? If so, how to understand it
Answer: Yes, it is equivalent. MySql 8.0 and above also support this (known as window function). It works as:
At the time of initialization of the query (SELECT (#rowNum :=0)) variable #rowNum will be initialized with value 0.
When we are calling (#rowNum:=#rowNum+1) in select then it will add 1 in #rowNum and assign it to itself for every row returned by select query.
This is how it will print the row number.
Question 3: if I put the (SELECT (#rowNum :=0) ) in the left:
Answer: If you put the (SELECT (#rowNum :=0) ) as field list after select then it will initialize the value of #rownum to 0 in every row returned by select. This is why you will not get incremented value.
The column "disappears" because the value is NULL. MySQL does not guarantee the order of evaluation of expressions in the SELECT, the initialization might not work.
Second, you code does not do what you intend, even if that worked, because variables may not respect the ORDER BY. I think you intend:
select k.*
from (select (#rownum := #rownumn + 1) as rownum, k.*
from (select k.*
from kills k
where k.p_d = '2020-11-08'
order by kills desc
) k cross join
(select #rownum := 0) params
) k
where t.uid = '8888';
There are probably better ways to do what you want. But your question is specifically about variables and MySQL 5.7.

MySql Query to find out first 50% of records from a Table

I am trying to fetch first 50% of records from a MySQL Table User. I know we can use limit or top for finding them but the total number of records are not fixed so hard coding the actual number in the limit or top doesn't gives me first 50% of records. How can I achieve this?
If you are running MySQL 8.0, you can use window functions for this: ntile() does exactly what you ask for. Assuming that your ordering column is id:
select *
from (select t.*, ntile(2) over(order by id) nt from mytable) t
where nt = 1
In earlier versions, one option is a user variable and a join with an aggregate query:
select *
from (
select t.*, #rn := #rn + 1 rn
fom (select * from mytable order by id) t
cross join (select #rn := 0) x
cross join (select count(*) cnt from mytable) c
) t
where rn <= cnt / 2
Mysql directly not supports this. You can try with two queries or use subqueries
Something like this.
find the count of total records/2
that value has to be applied in the limit clause.
SET #count = (SELECT COUNT(*)/2 FROM table);
SET #sql = CONCAT('SELECT * FROM table LIMIT ', #count);
SELECT * FROM table name LIMIT (select COUNT(*)/2 from table name);

How can I select from in other select in MYSQL query?

How can I select from in other select in MYSQL query?
Something like this
SET #row_number = 0;
SELECT a.num FROM
(SELECT
(#row_number:=#row_number + 1) AS num, id
FROM
main) as a where a.id=6
I want to know the number of records where id=6 if it's the first row, second row or third one
If your query has the filter where a.id = 6, then the row with id = 6 will always be the first row of the result set.
I am interpreting your question to mean: "if I sorted by id ascending, what row number is the row with id = 6 going to be on". If so, you can use a simple aggregation:
SELECT COUNT(*)
FROM main m
WHERE m.id <= 6;
Your query seems inspired by enumerating all the rows. You could do this version as well:
select m.*
from (select m.*, (#rn := #rn + 1) as rn
from main m cross join
(select #rn := 0) params
order by id
) m
where id = 6;
The first version should be more efficient, particularly with an index on id.

Pre-ordering a GROUP BY statement

This is a follow-up to a previous question: GROUP BY ordering.
I have the following sql table and data: http://sqlfiddle.com/#!9/81c3b6/2/0.
The basic un-ordered SQL statement is:
SELECT territory_id, platform_type_id, store_url
FROM main_itemmaster
GROUP BY platform_type_id
I want to get a single entry for each platform_type_id that is in the table, with a preference for territory_id='US'. That is, if an entry exists where territory_id='US', I would like to grab that one in the GROUP BY statement.
What would be the correct SQL statement to return the GROUPed by statement with a preference for the US item first?
In MySQL, the safest way to do this probably involves variables:
select im.*
from (select im.*,
(#rn := if(#p = platform_type_id, #rn + 1,
if(#p := platform_type_id, 1, 1)
)
) as rn
from main_itemmaster im cross join
(select #rn := 0, #p := '') params
order by platform_type_id, (territory_id = 'US') desc
) im
where rn = 1;
The does not involve using the MySQL (mis)feature that permits columns in the SELECT of an aggregation query that are not aggregated and not in the GROUP BY.
Here is a SQL Fiddle showing it working.
EDIT:
On the subject of the order of evaluation of variables. From the documentation:
As a general rule, other than in SET statements, you should never
assign a value to a user variable and read the value within the same
statement. For example, to increment a variable, this is okay:
SET #a = #a + 1;
For other statements, such as SELECT, you might get the results you
expect, but this is not guaranteed. In the following statement, you
might think that MySQL will evaluate #a first and then do an
assignment second:
SELECT #a, #a:=#a+1, ...;
However, the order of evaluation for expressions involving user
variables is undefined.
The above code does, technically, read the variable in the same statement, but it is also in the same expression. The semantics of if() (and case which I sometimes also use) guarantee the order of evaluation of the expressions.
Try that :
SELECT * FROM main_itemmaster
WHERE territory_id LIKE 'US'
GROUP BY platform_type_id
UNION
SELECT * FROM main_itemmaster
WHERE platform_type_id NOT IN (
SELECT platform_type_id FROM main_itemmaster
WHERE territory_id LIKE 'US')
GROUP BY platform_type_id
This should work
SELECT territory_id, platform_type_id, store_url
FROM main_itemmaster
GROUP BY platform_type_id
ORDER by if(territory_id ='US',1,99) asc
Here is the sqlfiddle for the above