Can't understand a mysql query - mysql

I had a question yesterday about ordering a mysql query by rand(). And I got a good answer here:
https://stackoverflow.com/a/16597706/2333744
THe code for the answer is below.
create temporary table results as
(Select *, #rn := #rn + 1 as rn, rand() as therand
from table1 inner join
table2
on table1.in = table2.in cross join
(select #rn := 0) const
where table1.T = A
);
select *
from results
where therand < 1000/#rn
order by therand
limit 500;
I understand everything except for
cross join (select #rn : = 0) const
I'm not sure what this is doing and if its important. When I remove it I get no performance change. Can anyone understand this part?

The User-Defined Variable #rn used in this case just for making a serial number column as explained in the answer of the previous question where you get this from.
The const is not used as a keyword here ... so don't be 'const-fused' by that. It is just a given name to (select #rn := 0) ... It could have been any other name like A, B, oops, aah, etc ... (see the second link below)
See example use in the folowing links to better understand the User-Defined Variables:
Create a Cumulative Sum Column in MySQL
MySql: Select Query- Make A Cumulative Sum Column

frankly, all this does is to reset the #rn variable. Is is "packed" into a select to avoid running 2 queries. The const means that it is constant, hence only evaluated once.
You could run into trouble when you remove it and add further queries into a single transaction.
Best regards
Zsolt

Related

MySQL 5.5.56 Temporary table always empty when used in a trigger but works when manually running the query

Can someone help or clear things for me. I've got this SQL code that I need to run on a trigger that doesn't work. But works when manually running the code on an SQL client.
SET #sr_id = NEW.purchase_id; /* SET #sr_id = 123456 when run manually */
SET #ndi = (SELECT COUNT(a.id) FROM purchase_rewards a LEFT JOIN item b ON b.id = a.item_id WHERE a.unit_id IS NOT NULL AND COALESCE(b.is_privileged,0) = 0 AND a.purchase_id = #sr_id);
SET #res = #ndi - CEIL(#ndi/2);
DROP TEMPORARY TABLE IF EXISTS for_removal;
CREATE TEMPORARY TABLE for_removal
SELECT ID FROM (
SELECT a.id, #rownum := #rownum + 1 AS `rank` FROM purchase_rewards a LEFT JOIN item b ON b.id = a.item_id
WHERE a.purchase_id = #sr_id AND COALESCE(b.is_privileged,0) = 0
) ft CROSS JOIN (SELECT #rownum := 0) r WHERE `rank` <= #res;
DELETE ta FROM purchase_rewards ta INNER JOIN for_removal tb ON ta.id = tb.id WHERE ta.purchase_id = #sr_id;
The code queries the purchased items that are not "privileged", putting a rank column on each and removing half of them. You only get rewarded for half of it, that's the point. The software was created by someone else with no source code so this is a piggy back system behind it.
I placed debug codes in between each to see if the connection changes or the results where empty but all is good except for the last part. Before the delete part I added a debug code:
SET #icount = (SELECT COUNT(ID) FROM for_removal);
INSERT INTO debug_log SET `log` = #icount;
and the result is that the table is always empty. I also tried converting the code into a stored procedure but I'm getting the same problem. Only running the code manually where it works.
I'm currently settling on CURSOR and loop-deletes which works, but it is slower when there are hundreds of items.
Sample Data: dbfiddle
Thanks!
Based on the comments above, the answer is to set the #rownum variable before the query.
SET #rownum = 0;
CREATE TEMPORARY TABLE ...
The reason is that you can't depend on the order of table evaluation in the CROSS JOIN. If the subquery is evaluated before the initialization of #rownum, then #rownum will be NULL, and any attempt to increment it with #rownum := #rownum + 1 will also yield NULL. So rank will have NULL on every row, and no rows will satisfy the WHERE clause.
As for why this works in the MySQL client but not in the trigger, I have a theory:
The session variable #rownum will keep its value if you test your query multiple times. So if you set it to some non-NULL value once in a session, then test the ranking query in the same session subsequently, it will increment.
But if you run it as part of a trigger, it will likely be a brand new session each time, and the value of #rownum will be initially NULL.

How does this reference with '#' work in mysql?

I'm new to mysql
I want to change all values in a column once a week. There is no condition.
I have this to do it, but I don't understand how this works.
Could someone please elucidate?
This works:
UPDATE maxscore, (select #row := 0) r SET max_score = (#row := 50);
This also works:
UPDATE maxscore SET max_score = (#row := 60);
Is this the best way to do this?
In my book there was an example which went like:
SELECT COUNT(*) FROM author WHERE
I tried like that, but I couldn't make it work.
The query that you are using works by cross-joining the maxscore table to a derived table that simply initialises #row to 0; then in the SET part of the query #row is set to 50 prior to being assigned to max_score. The second query also works because there's no need to initialise #row since it gets overwritten before being used. However, for what you're doing a simple
UPDATE maxscore
SET max_score = 50
is all you need; there is no need to use variables in the query at all.

MySQL limit work around

I need to limit records based on percentage but MYSQL does not allow that. I need 10 percent User Id of (count(User Id)/max(Total_Users_bynow)
My code is as follows:
select * from flavia.TableforThe_top_10percent_of_the_user where `User Id` in (select distinct(`User Id`) from flavia.TableforThe_top_10percent_of_the_user group by `User Id` having count(distinct(`User Id`)) <= round((count(`User Id`)/max(Total_Users_bynow))*0.1)*count(`User Id`));
Kindly help.
Consider splitting your problem in pieces. You can use user variables to get what you need. Quoting from this question's answers:
You don't have to solve every problem in a single query.
So... let's get this done. I'll not put your full query, but some examples:
-- Step 1. Get the total of the rows of your dataset
set #nrows = (select count(*) from (select ...) as a);
-- --------------------------------------^^^^^^^^^^
-- The full original query (or, if possible a simple version of it) goes here
-- Step 2. Calculate how many rows you want to retreive
-- You may use "round()", "ceiling()" or "floor()", whichever fits your needs
set #limrows = round(#nrows * 0.1);
-- Step 3. Run your query:
select ...
limit #limrows;
After checking, I found this post which says that my above approach won't work. There's, however, an alternative:
-- Step 1. Get the total of the rows of your dataset
set #nrows = (select count(*) from (select ...) as a);
-- --------------------------------------^^^^^^^^^^
-- The full original query (or, if possible a simple version of it) goes here
-- Step 2. Calculate how many rows you want to retreive
-- You may use "round()", "ceiling()" or "floor()", whichever fits your needs
set #limrows = round(#nrows * 0.1);
-- Step 3. (UPDATED) Run your query.
-- You'll need to add a "rownumber" column to make this work.
select *
from (select #rownum := #rownum+1 as rownumber
, ... -- The rest of your columns
from (select #rownum := 0) as init
, ... -- The rest of your FROM definition
order by ... -- Be sure to order your data
) as a
where rownumber <= #limrows
Hope this helps (I think it will work without a quirk this time)

MySQL: Incorrect usage of UPDATE and ORDER BY occuring at a position decoration update query

My Ruby plugin acts_as_list for sorting generates gaps in the position column when I insert Thing 2.0 at position 2 of a list from 1-3 and I want to get this list.
formatted_position;position;name
1;1;Thing 1
2;2;Thing 2.0;
3;4;Thing 2
4;5;Thing 3
So I tried ...
UPDATE user_ranking_items JOIN (SELECT #rownum := 0) r SET formatted_position = #rownum := #rownum + 1 WHERE ranking_id = 1 ORDER BY position ASC
But I got the MySQL exception Incorrect usage of UPDATE and ORDER BY.
How to handle this?
P.S.: Selecting works and returns the list from above ...
SELECT position, #rownum := #rownum + 1 AS formatted_position FROM user_ranking_items JOIN (SELECT #rownum := 0) r WHERE ranking_id = 1 ORDER BY position ASC;
The "trick" is to wrap your query that's working (with the user variable and the order by) as an inline view (MySQL calls it a derived table). Reference that whole view as a row source in the UPDATE statement.
Since you've already got the query that returns the result you want, you just need to have it return a unique id (e.g. the primary key) so you can do the JOIN between the inline view back to the original row in the target of the UPDATE.
Something like this:
UPDATE user_ranking_items t
JOIN (
-- your query here
) s
ON s.id = t.id
SET t.col = s.new_col_val
(This works because MySQL first materializes that inline view, as a temporary MyISAM table, and then the UPDATE references the temporary MyISAM table.)

Reorder a MYSQL table

I have a MySql table with a 'Order' field but when a record gets deleted a gap appears
how can i update my 'Order' field sequentially ?
If possible in one query 1 1
id.........order
1...........1
5...........2
4...........4
3...........6
5...........8
to
id.........order
1...........1
5...........2
4...........3
3...........4
5...........5
I could do this record by record
Getting a SELECT orderd by Order and row by row changing the Order field
but to be honest i don't like it.
thanks
Extra info :
I also would like to change it this way :
id.........order
1...........1
5...........2
4...........3
3...........3.5
5...........4
to
id.........order
1...........1
5...........2
4...........3
3...........4
5...........5
In MySQL you can do this:
update t join
(select t.*, (#rn := #rn + 1) as rn
from t cross join
(select #rn := 0) const
order by t.`order`
) torder
on t.id = torder.id
set `order` = torder.rn;
In most databases, you can also do this with a correlated subquery. But this might be a problem in MySQL because it doesn't allow the table being updated as a subquery:
update t
set `order` = (select count(*)
from t t2
where t2.`order` < t.`order` or
(t2.`order` = t.`order` and t2.id <= t.id)
);
There is no need to re-number or re-order. The table just gives you all your data. If you need it presented a certain way, that is the job of a query.
You don't even need to change the order value in the query either, just do:
SELECT * FROM MyTable WHERE mycolumn = 'MyCondition' ORDER BY order;
The above answer is excellent but it took me a while to grok it so I offer a slight rewrite which I hope brings clarity to others faster:
update
originalTable
join (select originalTable.ID,
(#newValue := #newValue + 10) as newValue
from originalTable
cross join (select #newValue := 0) newTable
order by originalTable.Sequence)
originalTable_reordered
on originalTable.ID = originalTable_reordered.ID
set originalTable.Sequence = originalTable_reordered.newValue;
Note that originalTable.* is NOT required - only the field used for the final join.
My example assumes the field to be updated is called Sequence (perhaps clearer in intent than order but mainly sidesteps the reserved keyword issue)
What took me a while to get was that "const" in the original answer was not a MySQL keyword. (I'm never a fan of abbreviations for that reason -- the can be interpreted many ways at times especially at these very when it is best they not be misinterpreted. Makes for verbose code I know but clarity always trumps convenience in my books.)
Not quite sure what the select #newValue := 0 is for but I think this is a side effect of having to express a variable before it can be used later on.
The value of this update is of course an atomic update to all the rows in question rather than doing a data pull and updating single rows one by one pragmatically.
My next question, which should not be difficult to ascertain, but I've learned that SQL can be a trick beast at the best of times, is to see if this can be safely done on a subset of data. (Where some originalTable.parentID is a set value).