I've got a stored procedure in my MySQL database, and need to figure out how to limit the return to create some pagination.
Some pseudocode:
CREATE PROCEDURE `my_procedure`(IN member_id INT, IN start INT, IN end INT)
BEGIN
SELECT * FROM member_activity WHERE `member_id` = member_id
<if start is not null>
LIMIT start, end
<endif>
END;
If I pass a null value, how do I simply unlimit the query?
Passing my_procedure(1,null,null) returns an error.
I know I can just wrap the entire query in an IF statement, but I'd rather not, because there's several other variables that would be annoying to keep in sync. Is it possible to accomplish this without writing the entire query twice?
Thanks
As mentioned in the manual:
To retrieve all rows from a certain offset up to the end of the result set, you can use some large number for the second parameter. This statement retrieves all rows from the 96th row to the last:
SELECT * FROM tbl LIMIT 95,18446744073709551615;
Since, as you point out, one cannot use the IFNULL() function within the LIMIT clause, prior to your SELECT command you could do:
SET `start` := IFNULL(`start`, 0);
SET `end` := IFNULL(`end` , 18446744073709551615);
Related
I'm writing a stored function where I calculate the position of a cell, which value I need to select from a table. To do this, I decided to save this position in a variable, in order to use it as the offset of a LIMIT clause.
According to my research, the way of using values set into local variables with the LIMIT statement is through a prepared statement, but I also got that prepared statements (nor any dynamic SQL) are allowed in stored functions. Are there any alternatives to solve my problem?
A simplified example of my situation:
CREATE FUNCTION foo(a int) RETURNS decimal DETERMINISTIC
BEGIN
SET #var1 := (SELECT COUNT(*) FROM table);
SET #var2 := (ROUND(#var1 * a/5))
PREPARE STMT FROM 'RETURN (SELECT * FROM other_table LIMIT ?, ?)';
EXECUTE STMT USING #var2, #var1;
END
$$ DELIMITER ;
Ideally, this would get me the result I need, where I need it. But, of course, I get Error Code 1336 saying "Dynamic SQL is not allowed in stored function or trigger"
You don't need dynamic SQL for this stored function. You don't need to use dynamic SQL for a LIMIT clause. You just need to make sure the variables are INT type, not strings.
Here's a quick demo:
create function foo(a int) returns int reads sql data
begin
return (select x from test limit 1 offset a);
end
Notice several other things:
Use READS SQL DATA instead of DETERMINISTIC. Your function is not deterministic. You should read the manual page on create function to understand these options better.
Don't use SELECT *. A function can only return a single scalar value, not a set of columns. The table you are querying might in fact have one column, but it's a good habit to make queries be more clear.
Using LIMIT with no ORDER BY may surprise you later, because it doesn't guarantee which order it will use for determining the offset. It's best if you use ORDER BY explicitly.
When using LIMIT, I think it's more clear to use LIMIT <count> OFFSET <offset> instead of LIMIT <offset>, <count>. They do the same thing, but it's easier to remember which argument is which.
Your LIMIT query appears to be selecting many rows. You need the query to select exactly one column and one row, or else it's not valid to return from a stored function.
I am trying to get some records from the table and count of all the records in the table. I am fetching all the records via select statement and count via #var. My sproc in mysql is as below. When I run it I am getting Error in Query String : Commands out of sync; you can't run this command now
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `getallenquiries`(IN `lim` BIGINT, IN `pgid` BIGINT, IN `uid` BIGINT, OUT `totalrec` BIGINT)
NO SQL
BEGIN
SELECT * from admin_enquiry order by enq_createdon desc limit lim offset pgid;
select count(enq_id) into totalrec from admin_enquiry;
END$$
DELIMITER ;
I am calling as "CALL getallenquiries(3,2,1,#totalrec)"
When I run it in phpmyadmin i am able to get both the result sets. Thank you in advance for your help.
I am not sure if I understand this correctly, but MySQL already has a function that will paginate (limit) and then another that will return the total count, as if the limit was not applied: SQL_CALC_FOUND_ROWS
mysql> SELECT SQL_CALC_FOUND_ROWS * FROM tbl_name
-> WHERE id > 100 LIMIT 10;
mysql> SELECT FOUND_ROWS();
First query returns a limit of 10 results and with the selected fields (* in this case).
The next SQL query returns the size of the results for the previous query if a limit was not applied.
I mean something like:
create table Measures (
id_user int,
date timestamp,
measure_1 double default 'select measure_1 from Measures where data = '**/**/****'',
measure_2 double default 'select measure_1 from Measures where data = '**/**/****'');
In this way I insert the value of the last measure saved in the db..
Is it possible?
Not directly:
11.7 Data Type Default Values
... the default value must be a constant; it cannot be a function or an expression.
You'll have to do this on application level, or in a trigger as suggested by #Timekiller.
You can do that via a before-insert trigger.
Check if NEW.measure_1 is null, and if it is, then perform select and store results.
UPD:
Right, I was in a bit of a hurry yesterday, and forgot to give an example later. Trigger is a good replacement for complex default value - it will work transparently, will look just like the default value from database user standpoint, and you won't have to do anything on the application level, since triggers are stored in the database itself. It will look something like this:
CREATE TRIGGER `measures_bi_trigger` BEFORE INSERT ON `Measures`
FOR EACH ROW BEGIN
if NEW.measure_1 is null then
SET NEW.measure_1 = (select measure_1 from Measures where ... limit 1);
end if;
if NEW.measure_2 is null then
SET NEW.measure_2 = (select measure_2 from Measures where ... limit 1);
end if;
END
It's not exactly clear what should be in your where condition, so you'll have to substitute ... yourself. Note that your query should return exactly one row, so either use an aggregate function like MAX or order by ... limit 1. If your query returns no rows, NULL will be inserted.
I am trying to create an sql trigger statement using phpmyadmin trigger interface.
Trying to do something for table 1 as shown below :
BEGIN
declare #valid_number int ;
select id into #valid_number from table 2 ;
if 10 does not exist in #valid_number then
{do something here}
end if;
END
how to achieve it?
First: a variable in a stored routine can't store multiple values, just a single one. Your statement
select id into #valid_number from table 2 ;
will only work, if the query returns exactly one row. An error will occur, if the query returns multiple rows, a warning, if the query returns no row at all, see the manual page to SELECT ... INTO:
The INTO clause can name a list of one or more variables, which can be
user-defined variables, stored procedure or function parameters, or
stored program local variables. [...]
The selected values are assigned to the variables. The number of
variables must match the number of columns. The query should return a
single row. If the query returns no rows, a warning with error code
1329 occurs (No data), and the variable values remain unchanged. If
the query returns multiple rows, error 1172 occurs (Result consisted
of more than one row).
Solution:
It's not difficult to create a statement that gives you the desired answer in exact one row, i.e.
SELECT COUNT(*) into valid_number FROM example WHERE id = 10;
This query will return 0, if the id 10 does not exists in column id and the count of occurences else. Of course there are several ways to achieve this, this is just one of them. You could rewrite your stored routine to:
BEGIN
-- prefer local variables, don't use user defined, if not needed.
DECLARE valid_number int;
SELECT COUNT(*) into valid_number FROM example WHERE id = 10;
IF valid_number = 0 THEN
-- do something here
END IF;
SELECT result;
END
Note
You could use a cursor to traverse the result of a query, but most times one wants to avoid a cursor. To use a cursor under similar conditions as of this question would not be the SQL way to do it and most times very inefficient.
I am trying to combine these two queries in twisted python:
SELECT * FROM table WHERE group_id = 1013 and time > 100;
and:
UPDATE table SET time = 0 WHERE group_id = 1013 and time > 100
into a single query. Is it possible to do so?
I tried putting the SELECT in a sub query, but I don't think the whole query returns me what I want.
Is there a way to do this? (even better, without a sub query)
Or do I just have to stick with two queries?
Thank You,
Quan
Apparently mysql does have something that might be of use, especially if you are only updating one row.
This example is from: http://lists.mysql.com/mysql/219882
UPDATE mytable SET
mycolumn = #mycolumn := mycolumn + 1
WHERE mykey = 'dante';
SELECT #mycolumn;
I've never tried this though, but do let me know how you get on.
This is really late to the party, but I had this same problem, and the solution I found most helpful was the following:
SET #uids := null;
UPDATE footable
SET foo = 'bar'
WHERE fooid > 5
AND ( SELECT #uids := CONCAT_WS(',', fooid, #uids) );
SELECT #uids;
from https://gist.github.com/PieterScheffers/189cad9510d304118c33135965e9cddb
You can't combine these queries directly. But you can write a stored procedure that executes both queries. example:
delimiter |
create procedure upd_select(IN group INT, IN time INT)
begin
UPDATE table SET time = 0 WHERE group_id = #group and time > #time;
SELECT * FROM table WHERE group_id = #group and time > #time;
end;
|
delimiter ;
So what you're trying to do is reset time to zero whenever you access a row -- sort of like a trigger, but MySQL cannot do triggers after SELECT.
Probably the best way to do it with one server request from the app is to write a stored procedure that updates and then returns the row. If it's very important to have the two occur together, wrap the two statements in a transaction.
There is a faster version of the return of updated rows, and more correct when dealing with highly loaded system asks for the execution of the query at the same time on the same database server
update table_name WITH (UPDLOCK, READPAST)
SET state = 1
OUTPUT inserted.
UPDATE tab SET column=value RETURNING column1,column2...