Fetching RAND() rows without ORDER BY RAND() in just one query - mysql

Using RAND() in MySQL to get a single random row out of a huge table is very slow:
SELECT quote FROM quotes ORDER BY RAND() LIMIT 1
Here is an article about this issue and why this is the case.
Their solution is to use two queries:
SELECT COUNT(*) AS cnt FROM quotes
- Use result to generate a number between 0 and COUNT(*)
SELECT quote FROM quotes LIMIT $generated_number, 1
I was wondering, whether this would be possible in just one query.
So my approach was:
SELECT * FROM quotes
LIMIT (
ROUND(
(SELECT COUNT(*) FROM quotes) * RAND()
)
), 1
But it seams MySQL does not allow any logic within Limit.
Though I can not find any information about this topic, whether this is true.
So my Questions:
How can I use RAND() within LIMIT?
Do you know of any other way to
solve this with just one query?

Is there a reason why a stored procedure cannot be used to create a prepared statement?
DELIMITER //
DROP PROCEDURE IF EXISTS rand_quote//
CREATE PROCEDURE rand_quote()
BEGIN
SET #rand := ROUND((SELECT COUNT(*) FROM quotes) * RAND());
SET #sql := CONCAT('SELECT * FROM quotes LIMIT ', #rand, ', 1');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END;
//
DELIMITER ;

I just figgured this one, that seams like a solution:
SELECT * FROM quotes
WHERE quotes_id = ROUND(
(SELECT COUNT(*) FROM quotes) * RAND()
)
LIMIT 1
But it will work only if quotes_id has no gaps.

I solved the problem by checking max id. Then i made php loop of rand(0, max_id) which checks if object exists. Done.
Much faster then previous ordering by rand.

Related

Using arithmetic operators and/or brackets in a LIMIT clause

I have the following small subquery in query in stored procedure.
(select f_cnt from results limit (i-1)*10,i*10)
But there is the syntax error:
"(" is not valid at this position, expecting an identifier
So, the question is: do I have a possibility to use brackets and/or arithmetic operators in LIMIT clause?
Documentation says I can use local variables in LIMIT clause within stored procedure. Do I really need to declare and set different variables for this case?
Just in case, link for the code of stored procedure.
You can't do arithmetic at that point
So do instead
SET #sql := CONCAT("SELECT * FROM TEsttable WHERE id In(select f_cnt from results limit ",(i-1) * 10,",",i*10,")");
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Which will produce
SELECT * FROM TEsttable WHERE id In(select f_cnt from results limit 90,100)

how to set the value of LIMIT using select count(id) from another table |MySQL

I have a scenario where the result must be limited depends on counting ids in another table.
Suppose i have these two tables counter and dispanser,
i want to select the last records in the table counter and limit the selection by counting the number of records in dispanser table.
something like this
select * from counter limit (select count(dispID) from dispanser)
You can't do this without using prepared statements or a stored procedure. From the manual:
LIMIT takes one or two numeric arguments, which must both be nonnegative integer constants
In a stored procedure you could do something like this. COUNT(dispID) is stored into cnt and that variable is then used as the parameter to LIMIT. This is the exception to the above-mentioned rule.
DELIMITER //
CREATE PROCEDURE select_counter()
BEGIN
DECLARE cnt INT;
SELECT COUNT(dispID) INTO cnt FROM dispanser;
SELECT * FROM counter LIMIT cnt;
END //
DELIMITER ;
DBFiddle
Based on conversations in the comments, it sounds like what you're trying to do is get the count from counter for each record in dispanser - if this is wrong, please comment, and I can adjust my response. The best way to accomplish what you're looking for is through joining a subquery with the GROUP BY syntax. Something like this might could work, depending on your schema:
SELECT
d.*,
c.total
FROM
dispanser as d
INNER JOIN (
SELECT
COUNT(*) as 'total',
dispID
FROM
counter
GROUP BY
dispID
) as c
ON c.dispID = d.id
You can try to use dynamic SQL.
Set a variable #num to get an amount from dispanser table.
prepared your SQL statement CONCAT('select * from counter limit ', #num ).
Final use EXECUTE to execute SQL dynamically.
SET #sql = CONCAT('select * from counter order by counterID desc limit ', (SELECT count(dispID) from dispanser));
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
sqlfiddle

Can I use MySQL functions in the LIMIT offset

Can I use MySQL functions in the LIMIT offset?
Like:
SELECT * FROM sites WHERE ... LIMIT FLOOR(1 + RAND() * (SELECT COUNT(*) FROM sites)) , 1
No, you can't do that directly. LIMIT and OFFSET values must be constants.
Citation from the MySQL docs:
The LIMIT clause can be used to constrain the number of rows returned by the SELECT statement. LIMIT takes one or two numeric arguments, which must both be nonnegative integer constants (except when using prepared statements).
You can use prepared statements and variables, though:
SELECT #offset:=FLOOR(1 + RAND() * COUNT(*)) FROM sites;
PREPARE STMT FROM 'SELECT * FROM sites WHERE ... LIMIT ?, 1';
EXECUTE STMT USING #offset;

Issue with using MAX()

I was answering a question on SO that encountered this issue.
Why I can't use MAX() within a LIMIT?
SELECT *
FROM table
ORDER BY id DESC
LIMIT 0, MAX(id)
Or
SELECT *, MAX(id) AS m
FROM table
ORDER BY id DESC
LIMIT 0, m
Both give a similar syntax error:
1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'MAX(id)' at line 4
Wanted query:
SELECT *
FROM table
ORDER BY id DESC
LIMIT 0, MAX(id)-5
MAX() is an aggregate function over the result rows, but LIMIT is a clause that constrains the number of result rows. In short, you cannot use a function that depends on the result rows while you are still determining which rows will be in the result -- that simply doesn't make any sense.
According to the documentation, LIMIT arguments must either be integer constants or parameters of a prepared query (emphasis mine):
LIMIT takes one or two numeric arguments, which must both be nonnegative integer constants (except when using prepared statements).
The syntax specification simply does not allow a column or function to be used. You would have to pre-compute the value and then use it in a prepared query (or by string substitution, though I would avoid that).
Based on the query you gave in your question:
SELECT *
FROM table
ORDER BY id DESC
LIMIT 0, MAX(id)-5
I suspect that this is the query you actually want:
SELECT *
FROM table
WHERE id <= (SELECT MAX(id) FROM table) - 5
ORDER BY id DESC
Even if it were valid, the first query you gave will not do what you expect if there are gaps in the sequence of the id column (for example, if a row was deleted).
An alternative if you want to get your desired result is to create a dynamic sql.
SET #maxID = (SELECT MAX(ID) FROM tableName);
SET #sql = CONCAT('SELECT *
FROM tableName
ORDER BY ID DESC
LIMIT 0,', #maxID);
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
The function MAX cannot be used on the limit.
Here is the documentation:
http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_max
The count argument in the limit should be a value so you cannot execute that in one step. Here is the select documentation:
http://dev.mysql.com/doc/refman/5.0/en/select.html
You can do is:
SET #a=(SELECT MAX(ID) FROM table);
PREPARE STMT FROM 'SELECT * FROM table ORDER BY id DESC LIMIT 0, ?';
EXECUTE STMT USING #a;
So it is a tree step here. Get max in a variable. Prepare a statement and than execute it applying the variable to the statement.
You can use the HAVING clause with MAX(id) to obtain your desired result
SELECT *
FROM table1
HAVING id<((SELECT Max(id) FROM table1)-5)
ORDER BY id DESC
Fiddle http://sqlfiddle.com/#!2/16e50/3
ISSUE IS WIth your query::
Select
*
from
myTable,
(SELECT MAX(id) as n
FROM table) temp
ORDER BY id DESC
LIMIT 0, temp.n

SQL / Limit (another query)

I have 2 tables. One of them holds the limit numbers.
And i am trying to run an sql like
SELECT * FROM table WHERE x='1' LIMIT (another query)
The Select TOP clause limit the number of rows. You can put an expression in it
http://msdn.microsoft.com/en-us/library/ms189463.aspx
In MySQL you can't do that using regular querys.
LIMIT clause does not allow variables. LIMIT clause only accepts constant numbers.
You have to use Stored Procedures instead regular querys.
Try use something like this inside an SP code:
DECLARE offset bigint
SELECT your_field INTO offset FROM your_table where your_conditions
SELECT * FROM other_table LIMIT offset;
Use a prepared statement:
PREPARE stmt FROM "SELECT * FROM table WHERE x='1' LIMIT ?";
SET #limit = (another query);
EXECUTE stmt USING #limit;
\You do what I have suggested to several people is generate SQL from a select statement and then execute that. Here is my solution:
SELECT "select * from other_table LIMIT ", your_field
FROM your_table where your_conditions
Then execute this new SQL. Of course this works best in UNIX/Linux and with a scripting language.