Alternative for Find_in_set which can use index - mysql

I have a my sql stored procedure where i am passing a list of numbers as a comma separated mediumtext field
For checking if my parameter matched i am using Find_in_set in my stored procedure as follows
SELECT SQL_CALC_FOUND_ROWS f1,f2,f3,f4 FROM `mytable`
WHERE FIND_IN_SET(f2,'1,2,4,5,6,6,7,8,8,4,9,7.......................') > 0
ORDER BY f1 DESC
LIMIT 25 OFFSET 0
;
SELECT FOUND_ROWS();
Now problems with Find_in_set i found out later is that it doesn't used index due to which My query is taking too long to complete.
Please suggest any improvement in the query
P.S.
Following is my complete stored procedure (including the changes as suggested in below answer)
DECLARE _calculated_offset INT;
SET _calculated_offset = _limit * (_pageNumber -1);
IF _calculated_offset < 0 THEN
SET _calculated_offset = 0;
END IF;
/*SELECT SQL_CALC_FOUND_ROWS f1,f2,f3,f4 FROM `mytable`
WHERE FIND_IN_SET(f1,_telcoIdList) > 0
AND FIND_IN_SET(f2,_msisdnList) > 0
ORDER BY f3 DESC
LIMIT _limit OFFSET _calculated_offset
;
SELECT FOUND_ROWS();*/
SET #query = CONCAT('SELECT SQL_CALC_FOUND_ROWS f1,f2,f3,f4
FROM `mytable`
WHERE f1 in (',_telcoIdList,')
AND f2 in (',_msisdnList,')
ORDER BY f3 DESC
LIMIT ', _limit,' OFFSET ',_calculated_offset,' ;
SELECT FOUND_ROWS();
');
PREPARE stmt FROM #query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

Use prepared statement and change to in clause
SET #query = CONCAT('SELECT SQL_CALC_FOUND_ROWS f1,f2,f3,f4 FROM mytable
WHERE f2 in (', myinputstr, ') ORDER BY f1 DESC LIMIT 25 OFFSET 0');
PREPARE stmt FROM #query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
This will allow the f2 index to be used.
EDIT : To include foundrows statement, use the following style
SET #query = CONCAT('SELECT SQL_CALC_FOUND_ROWS f1,f2,f3,f4
FROM `mytable`
WHERE f1 in (',_telcoIdList,')
AND f2 in (',_msisdnList,')
ORDER BY f3 DESC
LIMIT ', _limit,' OFFSET ',_calculated_offset);
PREPARE stmt FROM #query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SELECT FOUND_ROWS();
BTW, added SQL_CALC_FOUND_ROWS too in the query

Related

How to set the result of a query as a queryable table?

I am trying to use the result of a query as a table.
This query works fine:
SELECT date, number FROM `table_A`
The query below as well --> its result is table_B as a string of character not the table itself
SELECT nametable FROM `list_repository` WHERE id=1
But the combined one does not:
SELECT date, number FROM (SELECT nametable FROM `list_repository` WHERE id=1) A
I expect the resulting query to be
SELECT date, number FROM `table_B`
I tried to set a variable but it does not work either:
DECLARE x VARCHAR(150) ;
SET table=( SELECT `nametable` FROM `list_repository` WHERE id=1);
SELECT * from `table`. But it would not work
Thank you for your help!
Identifiers (db, table, column names etc) in SQL are static. Therefore you can't populate them at run-time. But you can build a query as a string and execute it via dynamic SQL. Something along the lines of
SET #sql = NULL;
SELECT CONCAT("SELECT * FROM ", nametable)
INTO #sql
FROM list_repository
WHERE id = 1;
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
An example of wrapping it up into a stored procedure
DELIMITER //
CREATE PROCEDURE sp1 (IN id INT)
BEGIN
SET #sql = NULL;
SELECT CONCAT("SELECT * FROM ", nametable)
INTO #sql
FROM list_repository
WHERE id = id;
SELECT #sql;
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END//
And then invoking it
CALL sp1(1);

mysql stored procedure multiple parameters

I am trying to add multiple dynamic columns in the select statement, (stored procedure) But it's not working at my end, Can you please help me on same.
BEGIN SET #col1 = col1;SET #col1 = col2;SET #getID = CONCAT('SELECT ' ,#col1 = col1, #col2 = col2,' FROM',tablename_In,' WHERE `type`= type ORDER BY id DESC LIMIT ?,?'); PREPARE stmt FROM #getID;SET #START =_START; SET #LIMIT = _LIMIT;EXECUTE stmt USING #START, #LIMIT; DEALLOCATE PREPARE stmt; END
Also, I have another stored procedure it's also not working with between query:
BEGIN SET #getID = CONCAT('SELECT count(id) as co FROM ',tablename_In,' WHERE',est_time,' BETWEEN',start_date,'AND',end_date);PREPARE stmt FROM #getID; EXECUTE stmt; DEALLOCATE PREPARE stmt; END

MySQL prepared statements with order by clause

I am using prepared statements in MySQL with 'order by' conditional clause. Using '?' and variables don't work with such things like ordering so I've decided to make it in other way. I set if conditions, but it's much more code. Maybe there is other option to reduce code and just changing 'order by' arguments?
IF sorting_column_index = 1 and sorting_column_mode = 0
THEN PREPARE STMT FROM 'SELECT a.oid as \'oid\',
...
FROM table as a
...
order by numero_annee desc LIMIT ?, ?';
EXECUTE STMT USING #skip, #ROWS;
END IF;
IF sorting_column_index = 2 and sorting_column_mode = 1
THEN PREPARE STMT FROM 'SELECT a.oid as \'oid\',
...
FROM table as a
...
order by numero_ordre asc LIMIT ?, ?';
EXECUTE STMT USING #skip, #ROWS;
END IF;
...
Try something like this, not using parameters for asc/desc but building your query string:
SET #sort_order = 'desc';
SET #my_limit = 5;
SET #sql = CONCAT('SELECT whatever FROM whatever ORDER BY col1 ', #sort_order, ' LIMIT ?;');
PREPARE stmt FROM #sql;
EXECUTE stmt USING #my_limit;
DEALLOCATE PREPARE stmt;

How to create a user defined variable that can be used in a set expression?

I would like to do this
set #a =(1,2,3);
select * from mytable where somefield in #a;
But mysql does not like it.
How can I do this?
One way to go about this
SET #a = '1,2,3';
SELECT *
FROM mytable
WHERE FIND_IN_SET(somefield, #a) > 0;
Note: This will effectively cause a full scan.
IMHO you better off without user variables containing strings
SELECT *
FROM mytable t JOIN
(
SELECT 1 somefield UNION ALL
SELECT 2 UNION ALL
SELECT 3
) q
ON t.somefield = q.somefield;
One more option is to leverage dynamic SQL
SET #a = '1,2,3';
SET #sql = CONCAT('SELECT * FROM mytable WHERE somefield IN(', #a, ')');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Here is SQLFiddle demo for all queries
You can do this using temporary tables, as in this question:
How can I simulate an array variable in MySQL?
Or you can use find_in_set() as in this question:
how to set an array as a mysql user variable
It could be done but with proper syntax and prepare statement, likethis:
set #a =1,2,3;
select * from mytable where somefield in (#a);
set #crt=concat("select * from mytable where somefield in ",concat('(',#a,')'),";");
prepare smnt from #crt;
EXECUTE smnt;
DEALLOCATE PREPARE smnt;
Try this
SET #a = '1,2,3';
SET #sql = CONCAT('SELECT * FROM test WHERE `user` IN(', #a, ')');
PREPARE stmt FROM #sql;
EXECUTE stmt;
See Fiddle Demo

procedure for comparing results of two sql statements

1)Below is the code I'm trying for a procedure where I need to compare the count from two different tables. (tbl1 and tbl2 are in parameters to be passed).
But in the above code the values for 'a' and 'b' are set as statements. I need the resultant value(count) from the two statements to compare in the 'IF' condition.
declare a integer;
declare b integer;
set #a:=concat('select count(*) from ', tbl1);
/*PREPARE stmt1 FROM #a;
execute stmt1;
deallocate PREPARE stmt1;*/
set #b:= concat('select count(*) from ', tbl2);
/*PREPARE stmt2 FROM #b;
execute stmt2;
deallocate PREPARE stmt2;*/
if
#a=#b
then
select 1;
else
select 2;
end if;
2) Actually where I am facing a problem to set 'tbl1' and 'tbl2' as parameters in procedure.
The below code works fine if the table names are given directly, but i need them as parameters.
CREATE PROCEDURE myproc (in tbl1 varchar(50), in tbl2 varchar(50))
declare a integer;
declare b integer;
set #a:=(select count(*) from tbl1);
/*PREPARE stmt1 FROM #a;
execute stmt1;
deallocate PREPARE stmt1;*/
set #b:= (select count(*) from tbl2);
/*PREPARE stmt2 FROM #b;
execute stmt2;
deallocate PREPARE stmt2;*/
if
#a=#b
then
select 1;
else
select 2;
end if;
Hope anyone out there can help me with the solution.
As mentioned in the comments above, it is likely that you shouldn't be doing this at all... but, for what it's worth:
SET #sql := CONCAT('SELECT (
SELECT COUNT(*) FROM `',REPLACE('`','``',tbl1),'`
) = (
SELECT COUNT(*) FROM `',REPLACE('`','``',tbl2),'`
);');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #sql := NULL;
REPLACE() has been used to try and avoid SQL injection (it's somewhat crude, but it's the best one can do without further information).