Using variable in a LIMIT clause in MySQL - mysql

I am writing a stored procedure where I have an input parameter called my_size that is an INTEGER. I want to be able to use it in a LIMIT clause in a SELECT statement. Apparently this is not supported, is there a way to work around this?
# I want something like:
SELECT * FROM some_table LIMIT my_size;
# Instead of hardcoding a permanent limit:
SELECT * FROM some_table LIMIT 100;

For those, who cannot use MySQL 5.5.6+ and don't want to write a stored procedure, there is another variant. We can add where clause on a subselect with ROWNUM.
SET #limit = 10;
SELECT * FROM (
SELECT instances.*,
#rownum := #rownum + 1 AS rank
FROM instances,
(SELECT #rownum := 0) r
) d WHERE rank < #limit;

STORED PROCEDURE
DELIMITER $
CREATE PROCEDURE get_users(page_from INT, page_size INT)
BEGIN
SET #_page_from = page_from;
SET #_page_size = page_size;
PREPARE stmt FROM "select u.user_id, u.firstname, u.lastname from users u limit ?, ?;";
EXECUTE stmt USING #_page_from, #_page_size;
DEALLOCATE PREPARE stmt;
END$
DELIMITER ;
USAGE
In the following example it retrieves 10 records each time by providing start as 1 and 11. 1 and 11 could be your page number received as GET/POST parameter from pagination.
call get_users(1, 10);
call get_users(11, 10);

A search turned up this article. I've pasted the relevant text below.
Here's a forum post showing an example of prepared statements letting
you assign a variable value to the limit clause:
http://forums.mysql.com/read.php?98,126379,133966#msg-133966
However, I think this bug should get some attention because I can't
imagine that prepared statements within a procedure will allow for any
procedure-compile-time optimizations. I have a feeling that prepared
statements are compiled and executed at the runtime of the procedure,
which probaby has a negative impact on efficiency. If the limit
clause could accept normal procedure variables (say, a procedure
argument), then the database could still perform compile-time
optimizations on the rest of the query, within the procedure. This
would likely yield faster execution of the procedure. I'm no expert
though.

I know this answer has come late, but try SQL_SELECT_LIMIT.
Example:
Declare rowCount int;
Set rowCount = 100;
Set SQL_SELECT_LIMIT = rowCount;
Select blah blah
Set SQL_SELECT_LIMIT = Default;

This feature has been added to MySQL 5.5.6.
Check this link out.
I've upgraded to MySQL 5.5 just for this feature and works great.
5.5 also has a lot of performance upgrades in place and I totally recommend it.

Another way, the same as wrote "Pradeep Sanjaya", but using CONCAT:
CREATE PROCEDURE `some_func`(startIndex INT, countNum INT)
READS SQL DATA
COMMENT 'example'
BEGIN
SET #asd = CONCAT('SELECT `id` FROM `table` LIMIT ',startIndex,',',countNum);
PREPARE zxc FROM #asd;
EXECUTE zxc;
END;

As of MySQL version 5.5.6, you can specify LIMIT and OFFSET with variables / parameters.
For reference, see the 5.5 Manual, the 5.6 Manual and #Quassnoi's answer

I've faced the same problem using MySql 5.0 and wrote a procedure with the help of #ENargit's answer:
CREATE PROCEDURE SOME_PROCEDURE_NAME(IN _length INT, IN _start INT)
BEGIN
SET _start = (SELECT COALESCE(_start, 0));
SET _length = (SELECT COALESCE(_length, 999999)); -- USING ~0 GIVES OUT OF RANGE ERROR
SET #row_num_personalized_variable = 0;
SELECT
*,
#row_num_personalized_variable AS records_total
FROM(
SELECT
*,
(#row_num_personalized_variable := #row_num_personalized_variable + 1) AS row_num
FROM some_table
) tb
WHERE row_num > _start AND row_num <= (_start + _length);
END;
Also included the total rows obtained by the query with records_total.

you must DECLARE a variable and after that set it. then the LIMIt will work and put it in a StoredProcedure not sure if it works in normal query
like this:
DECLARE rowsNr INT DEFAULT 0;
SET rowsNr = 15;
SELECT * FROM Table WHERE ... LIMIT rowsNr;

Related

MYSQL trigger error (lots of function)

i can't seem to found any fault on my code to make a trigger. ( i usually code using oracle, but i convert to my sql in this project, checked all the function and convert those that aren't available in mysql already)
here's the code :
CREATE TRIGGER `transaction_before_insert` BEFORE INSERT ON `transaction` FOR EACH ROW BEGIN
DECLARE TEMPKODE VARCHAR(12);
DECLARE TEMP VARCHAR(5);
TEMP:= CONCAT('T',DATE_FORMAT(NOW(),'%Y'));
SELECT CONCAT(TEMP, LPAD(NVL(MAX(CAST(SUBSTR(TRANSACTION_ID,5,5) AS UNSIGNED))+1,1),5,0))
FROM TRANSACTION INTO TEMPKODE
WHERE SUBSTR(TRANSACTION_ID,1,4) = TEMP;
NEW.TRANSACTION_ID := TEMPKODE;
END
EDIT 1:
i'm coding it from heidisql if there's any code difference, since i heard if i do it on mysql work bench i should use
SET variables
instead of directly
variables :=
the desired result is forex: T201600001
//T for transaction, 2016 i got it from dateformat, and the rest is choosing the biggest data from the database
it's a software for production planning so i'm making the transaction code
NVL, is a function built for you?, Oracle NVL function does not exist in MySQL (find its equivalent in MySQL), see IFNULL.
DELIMITER $$
BEGIN
DECLARE TEMPKODE VARCHAR(12);
DECLARE TEMP VARCHAR(5) DEFAULT CONCAT('T',DATE_FORMAT(NOW(),'%Y'));
-- OR: SET TEMP := CONCAT('T',DATE_FORMAT(NOW(),'%Y'));
-- TEMP := CONCAT('T',DATE_FORMAT(NOW(),'%Y'));
/*
SELECT CONCAT(TEMP,LPAD(NVL(MAX(CAST(SUBSTR(TRANSACTION_ID,5,5) AS UNSIGNED))+1,1),5,0))
FROM TRANSACTION INTO TEMPKODE
WHERE SUBSTR(TRANSACTION_ID,1,4) = TEMP;
*/
SELECT CONCAT(TEMP,LPAD(COALESCE(MAX(CAST(SUBSTR(TRANSACTION_ID,5,5) AS UNSIGNED))+1,1),5,0))
FROM TRANSACTION
WHERE SUBSTR(TRANSACTION_ID,1,4) = TEMP INTO TEMPKODE;
-- NEW.TRANSACTION_ID := TEMPKODE;
SET NEW.TRANSACTION_ID := TEMPKODE;
END$$
DELIMITER ;
UPDATE
You can simplify with the answer of #GordonLinoff:
SET NEW.TRANSACTION_ID := CONCAT(...);
You really do not need temporary variables for this operation (in either Oracle or MySQL). I think the following is the same logic:
BEGIN
select new.transactionid := CONCAT('T', YEAR(now()),
LPAD(COALESCE(MAX(SUBSTR(TRANSACTION_ID, 5, 5) + 1
), 1
), 5, 0)
from transaction t
where TRANSACTION_ID LIKE CONCAT(YEAR(now()), '%')
END;

MySQL Syntax : "the right syntax to use near" - right in the beginning

I am a MySQL-noob and today I tried to setup a MySQL call which is more than 5 lines long. I keep getting syntax errors which I try to fix for hours, but I don't have a clue what the problem is. Here is the code:
USE myDatabase;
DELIMITER //
CREATE PROCEDURE MYPROC()
BEGIN
SET #ID = 1;
SET #maxID = 3;
CREATE TEMPORARY TABLE resultTable(v DOUBLE, ttc DOUBLE);
WHILE (#ID < #maxID) DO
INSERT partTable1.v, partTable2.ttc
INTO
resultTable
FROM
(SELECT * FROM
(((SELECT time_sec, v FROM speedTable WHERE (trip_id = #ID)) as partTable1)
INNER JOIN
((SELECT time_sec, ttc FROM sightsTable WHERE (trip_id = #ID)) as partTable2) ON
(0.04 > abs(partTable1.time_sec - partTable2.time_sec)))
);
SET #ID := #ID + 1;
END WHILE;
END //
DELIMITER;
CALL MYPROC();
SELECT * FROM resultTable LIMIT 100;
Is there anything obvious that needs to be corrected?
Update1: Added semicolon to the "CREATE.."-statement, now first three statements are OK.
Update2: Added 3 more semicolons!
Update3: Followed the suggestion to make it a function + separate function call. Error message changed!
Update4: I fixed the issues mentioned in the two answers. Still something wrong there. See updated code above and error message below.
Updated error message:
ERROR 1064 (42000) at line 4: You have an error in your SQL syntax; check the ma
nual that corresponds to your MySQL server version for the right syntax to use n
ear ' partTable2.ttc
INTO
resultTable
FROM
(SELECT * FROM
(((SELE' at line 11
Kind Regards,
Theo
Flow control statements, of which WHILE is one, can only be used within a stored procedure, but you are attempting to use it as a plain query via the console.
If you absolutely must take this path (using mysql instead of an application language), create a store procedure with the code you want, then call it.
Creating the procedure would look like this:
DELIMITER //
CREATE PROCEDURE MYPROC()
BEGIN
WHILE (#ID < #maxID) DO
SET #partTable1 = (SELECT time_sec, v FROM speedTable WHERE (trip_id = #ID));
SET #partTable2 = (SELECT time_sec, ttc FROM sightsTable WHERE (trip_id = #ID));
INSERT v, ttc INTO resultTable FROM
(#partTable1 INNER JOIN #partTable2 ON
(0.04 > abs(partTable1.time_sec - partTable2.time_sec)));
SET #ID := #ID + 1;
END WHILE;
END//
DELIMITER ;
Then to call it:
CALL MYPROC();
See this SQLFiddle of a simplified version of this working.
Note that you do have one syntax error:
#ID = #ID + 1; -- incorrect syntax
SET #ID := #ID + 1; -- correct
Still some syntactic problems and functionality problems...
You can't use WHILE in SQL scripts. You can use WHILE only in the body of a stored routine. See http://dev.mysql.com/doc/refman/5.6/en/flow-control-statements.html
You can't use SET to assign multiple columns to a scalar. MySQL doesn't support relation-valued variables, only scalar variables. See http://dev.mysql.com/doc/refman/5.6/en/set-statement.html
You can INSERT from the results of a query with a join, but the query must be introduced with SELECT. See http://dev.mysql.com/doc/refman/5.6/en/insert-select.html
You can't use session variables as the names of tables. You would have to use a prepared statement. See http://dev.mysql.com/doc/refman/5.6/en/prepare.html But that opens a whole different can of worms, and doing it wrong can be a security vulnerability (see http://xkcd.com/327). I wouldn't recommend you start using prepared statements as a self-described MySQL-noob.
This problem is probably simpler than you're making it. You don't need a temporary table, and you don't need to read the results one row at a time.
Here's an example that I think does what you intend:
USE myDatabase
SET #ID = 1;
SET #maxID = 3;
SELECT sp.v, si.ttc
FROM speedTable AS sp
INNER JOIN sightsTable AS si
ON (sp.trip_id = si.trip_id AND 0.04 > ABS(sp.time_sec - si.time_sec))
WHERE sp.trip_id BETWEEN #ID AND #maxID;

MySQL generates many new result-set

I've got MySQL procedure which I execute in MySQL Workbench. The problem is that it generates many new result-set, and GUI client show it (...many times) -
I found solution, which I can use instead just select clause to avoid generating result-sets. It looks like:
SELECT EXISTS(
SELECT ...
)
INTO #resultNm
Unfortunately it won't work with LIMIT ?,1 where ? is my variable 'i'. I also cant use just 'i' instead % because I am working on MySQL 5.1 (and limit clauses can't be done in other way). So my question - are other possibilities to hide result-sets?
CREATE PROCEDURE LOOPDOSSIERS(starter integer, finish integer)
BEGIN
DECLARE n INT DEFAULT 0;
DECLARE i INT DEFAULT 0;
DECLARE row_oid binary(16);
DECLARE row_str VARCHAR(256);
DECLARE row_old VARCHAR(256);
SET i=starter;
WHILE i<finish DO
-- SET row_str = ();
SET #row_str = 'select CAST(concat(d.prefixe_numero,d.numero) as CHAR) from courrier_concerne_dossier as x
join dossier as d on
d.oid = x.dossier_oid limit ?,1';
PREPARE stmt1 FROM #row_str;
EXECUTE stmt1 USING #i;
SET #row_oid = 'select x.courrier_oid
from courrier_concerne_dossier as x
join dossier as d on
d.oid = x.dossier_oid LIMIT ?,1';
PREPARE stmt2 FROM #row_oid;
EXECUTE stmt2 USING #i;
select dossiers_str from courrier_envoye where oid = row_oid into row_old;
update courrier_envoye set dossiers_str = CAST(CONCAT(row_str, " ", row_old) AS CHAR) where oid = row_oid;
SET i = i + 1;
END WHILE;
End;
;;
LIMIT without an ORDER BY clause doesn't have a well defined behavior. For your parameters to work in a sensible way, you'll need to order by something. The starter and finish variables aren't very meaningful at the moment, but it's currently not clear what they're intended to be.
I think you can probably accomplish this whole procedure in a single query using the syntax in this answer (also probably much faster). That probably won't work with LIMIT, but I'd highly recommend using a range of some kind in the where clause rather than a limit anyway.

How to rewrite query to avoid logging as log_queries_not_using_indexes

I have following stored procedure:
CREATE PROCEDURE testProc(IN p_idProject INTEGER)
BEGIN
DECLARE nowTime DATETIME;
SET #nowTime = NOW();
SELECT
idCustomer
FROM NextCalls WHERE #nowTime-nextCall =
(SELECT MAX(#nowTime - nextCall)
FROM NextCalls
WHERE idProject = p_idProject AND nextCall < #nowTime)
LIMIT 1;
END $$
There is index set on nextCall column. Unfortunately it's being logged into mysql-slow.log file as a query that is not using indexes properly. This procedure is used very, very often and I would be really happy to avoid bad indexing. Is it possible to rewrite to achieve that?
You are just selecting the oldest nextCall. There is no need to perform any calculations, you can just use ORDER BY:
SELECT idCustomer
FROM NextCalls
WHERE idProject = p_idProject AND nextCall < CURRENT_TIMESTAMP
ORDER BY nextCall
LIMIT 1

Using variables as OFFSET in SELECT statments inside mysql's stored functions

I'm quite new to subject of writting stored function for mySQL database, hence i'm not sure if what i'm trying to do here is possible at all.
I need a function that will return a column from random row from a table. I don't want to use ORDER BY RAND() method to do that, rather i would do this like this:
DECLARE MAX_COUNT INT DEFAULT 120000;
DECLARE rand_offset INT;
DECLARE str_rnd_word VARCHAR(255);
SET rand_offset = FLOOR((RAND() * MAX_COUNT));
SELECT word INTO str_rnd_word FROM all_words LIMIT 1 OFFSET rand_offset ;
RETURN str_rnd_word;
MySQL throws an error upon creating function with body like that. But when I use hard-coded number as OFFSET it works just fine.
Can someone shed some light on the subject please.
I'm running MySQL 5.0.45 on windows box.
Thanks
In MySQL before 5.5, you can't put a variable into the LIMIT clause in MySQL stored procedures. You have to interpolate it into a string and then execute the string as a dynamic query.
SET rand_offset = FLOOR(RAND() * (SELECT COUNT(*) FROM all_words));
SET #sql = CONCAT('SELECT word INTO str_rnd_word FROM all_words LIMIT 1 OFFSET ', rand_offset);
PREPARE stmt1 FROM #sql;
EXECUTE stmt1;