Get condition of WHERE clause from table - mysql

I have table1 which has only 2 columns: id and condition. For example:
id condition
--------------------
100 caption LIKE "%xyz%"
200 tag=5
300 color>153
...
The user sends an id to the server, and a specific select query must be run on table2 based on the condition of that id. For example, if id 100 is sent to server, then this query must be run:
SELECT * FROM table2 WHERE caption LIKE "%xyz%"
How to get the condition from table1 and run the query with that condition? I have already tried this:
SELECT * FROM table2 INNER JOIN table1 AS t1 ON t1.id=... WHERE t1.condition
However, I get the following warning with no result.
Warning: #1292 Truncated incorrect DOUBLE value: 'caption LIKE "%xyz%"'

This is possible in MySQL 5+ using prepared statements. You can create a procedure with a condition id as an argument:
DELIMITER //
CREATE PROCEDURE get_from_table2_by_condition_id(IN conditionId bigint)
BEGIN
SET #condition = NULL;
SELECT cond
INTO #condition
FROM table1
WHERE id = conditionId;
SET #sql = CONCAT('SELECT * FROM table2 WHERE ', COALESCE(#condition, 'FALSE'));
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END//
DELIMITER ;
CALL get_from_table2_by_condition_id(100);
See a working example in this fiddle

I don't particularly recommend storing SQL code as you are describing.
But you have a client and a server. The solution is simply to query table1 for the condition and then construct the query that you want from that. This requires two queries.
You could set up a stored procedure inside the database that uses dynamic SQL for the same purpose.
There are several reasons why this is not desirable:
The code can introduce syntax errors which are quite hard to debug.
The code is subject to SQL injection, depending on the security features around table1.
Changes to the underlying tables might invalidate the conditions.
What is an alternative? One possibility is to create separate views for the different conditions. Or, just create one query and pass in parameters:
select t2.*
from table2 t2
where (caption like :caption or :caption is null) and
(tag = :tag or :tag is null) and
(color > :color or :color is null);

That is not possible, and if it where possible that would be a huge stored SQL injection security hole. To be able to do this, you will need something that will parse the stored condition and evaluate the condition on the fly. As far as I'm aware, MySQL has nothing built in for this.

Related

MySql "view", "prepared statement" and "Prepared statement needs to be re-prepared"

I've a problem with MySql, here is the details :
I created a new schema/database, executed (only) this queries :
create table mytable (
id varchar(50) not null,
name varchar(50) null default '',
primary key (id));
create view myview as
select id,name from mytable;
insert into mytable values ('1','aaa');
insert into mytable values ('2','bbb');
insert into mytable values ('3','ccc');
and then, if I run these queries :
select * from mytable;
select * from myview;
prepare cmd from 'select id,name from mytable where id=?';
set #param1 = '2';
execute cmd using #param1;
the queries give the correct result (3 rows,3 rows,1 row).
but, the problem exists if I run this query:
prepare cmd from 'select id,name from myview where id=?';
set #param1 = '2';
execute cmd using #param1;
ERROR: #1615 - Prepared statement needs to be re-prepared
I've done some research and found that the increment of configurations below "may" solve the problem :
increase table_open_cache_instances value
increase table_open_cache value
increase table_definition_cache value
As far as I know, the queries above are the common and standard MySql queries, so I think there is no problem with the syntax.
I'm on a shared webhosting and using MySql version is 5.6.22
But the things that make me confused is, it only contain 1 schema/database, with 1 table with 3 short records and 1 view,
and I executed a common and standard MySql select query,
does the increment of values above really needed?
is there anyone with the same problem had increase the values and really solve the problem?
or, perhaps do you have any other solution which you think may or will works to solve this problem?
ps: it does not happen once or twice in a day (which assumed caused by some backup or related), but in all day (24 hours).
Thank you.
Do you do this after each execute?
deallocate prepare cmd;
The closest guess until now is some other shared members on the server dont write their code quite well (because it is a shared webhosting), either doing large alter while doing the large select, or dont deallocate the prepared statement after using it, like Rick James said. (want to make the post usefull, but I dont have the reputation, sorry Rick)
I can not make sure if the increment of "table_definition_cache" will works because the system administrator still wont change the value until now, but incase you having the same problem and you can modify it, it worth to try.
My current solution is I change all my views in my query strings into non-view or subqueries, it works for me, but the problem is still in the air.
eg. from
select myview.id, myview.name
from myview
inner join other_table on ...
where myview.id=?
into
select x.id, x.name
from (select id,name from mytable) x
inner join other_table on ...
where x.id=?

using mysql variable to hold comma separated value to be used for where in clause

I have to run a query like this (query 1) -
select something from sometable where someId in (1,2,3)
I would like to keep a variable for the IDs part, like this (query 2) -
set #myIds = "1,2,3";
select something from sometable where someId in (#myIds);
But this does not give the expected result (gives an empty result set), and no query error as well.
I checked that if I wrap the comma separated IDs inside quotes, the query results an empty result set (query 3) -
select something from sometable where someId in ("1,2,3");
I guess when I am using variable #myIds like I showed above (query 2), it is evaluating to the above query (query 3).
You need to have a dynamic sql on this,
SET #myIds = '1,2,3';
SET #sql = CONCAT('select something from sometable where someId in (',#myIds,')');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SQLFiddle Demo
The proper (and also more complicated) way to do that would be a temp table:
DROP TEMPORARY TABLE IF EXISTS `some_tmp_table`
CREATE TEMPORARY TABLE `some_tmp_table` (
`id` INT(10) UNSIGNED NOT NULL
) ENGINE=MEMORY #memory engine is optional...
Insert your ID's the temp table
INSERT INTO some_tmp_table VALUES (1),(2),(3)...
and then use a JOIN instead of IN().
SELECT something
FROM sometable s
JOIN some_tmp_table ts ON ts.id = s.someId
The other way is to use dynamic SQL as the other answer suggests. It might be simpler for you to generate the dynamic SQL in your app, but you can do it in MySQL too.

Use of FInd_IN_SET vs IN clause MYSQL stored procedure

I have a stored procedure that is similar to below
SELECT *
FROM Table1
WHERE Tag IN (ids)
here Tag is an Integer column.
I tired to pass in comma separated values as string into the stored procedure but it does not work. Then I used stored procedure like below
SELECT *
FROM Table1
WHERE FIND_IN_SET(Tag, ids)
This works very well, the only problem is my table is very big - millions of rows and using FIND_IN_SET takes too long compared to IN when running a direct SQL statement.
What would be the best performance optimized option to use?
Is there a split function that can convert the ids into integer and parse it ready for IN clause? I think that would be the best option. Any suggestions or ideas?
You can prepare a statement and then execute it:
set #sql = concat('select * from table1 where tag in (', ids, ')');
PREPARE q FROM #sql;
execute q;
This constructs the string for each execution, so you can use in. The resulting execute should be able to use an index on tag, which should speed things up considerably.

getting table name from a table and using the table name to access data

I want to create a database where there is a list of table names stored in a table. Now with the help of this list I can access the other tables.
Ex :-
Table name :- table_list (2 column i.e. table_name,table_id)
table_list attributes
authentication 1
basic_info 2
contact 3
I can directly access these tables using select statement but I want to access them using the table_list table preferably using select statement.
I tried
select * from (select table_name as x from table_list where id=2) as y
But could not get the proper output.
It is called Prepared Statements and their only use is when you want to implement your mentioned need in one request. Otherwise you can easily retrieve table names in a programming language and create your next statement using the data in hand. Here's how Prepared Statements work:
SELECT table_name INTO #tbl FROM my_tables WHERE id = 1 LIMIT 1;
SET #sql := CONCAT('SELECT * FROM ', #tbl);
PREPARE stmt1 FROM #sql;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
TRY THIS
select * from (SELECT TABLE_NAME FROM TABLE_LIST WHERE ID=2)as y
The table name (an "identifier") must be a static part of the SQL text issed to the database; the identifier can't be supplied "on the fly", either as a parameter or as a result from another SQL query.
To do what you want to do, you will need a two step approach. You can use one (or more) SQL statements to obtain the identifiers you need (table name, column names, etc.), and then use that to dynamically create a second SQL statement, as a string.
The identifiers (table names, column anmes) can not be provided as parameters or "bind variables", they must be a static part of the SQL text.
For example, to generate the statement:
SELECT CONCAT('SELECT * FROM `',table_name,'` ORDER BY 1') AS stmt
FROM table_list
WHERE id = 2
(The coding details are dependent on what language you are using.)
Since you are sure that the table name you want to access is x, just check whether such a table exists using a query and use x for future purpose.

selecting first column from all tables in a mysql database

i have a column named "name" which is present in all tables in mysql database.
I wanted to list all the names in all tables so i used the following query
select name from (SELECT table_name FROM information_schema.tables WHERE table_type='BASE TABLE') as abc
But it did not work for me and instead it returned me the table_name column alone.
Then i used show tables and stored the output in another table called table_list then i executed the following query
select name from (select table_name from table_list) as abc
This also returned me the same result i.e. all table names.
Can i know what is that i am doing wrong and what is the right way to do it?
I am using MySQL 5.4 and i want to either write a subquery or a procedure or a function purely in mysql.
There is PREPARE and EXECUTE which can run a sql statement from inside a user variable, so could probably use something similar to (untested!)
SET #a = "";
SELECT #a := CONCAT('(select name from ',GROUP_CONCAT(table_name SEPARATOR ') UNION (select name from '),')') FROM information_schema.tables WHERE table_type='BASE TABLE' GROUP BY 1;
PREPARE stmt FROM #a;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
What you need is a way to have variables in SQL (so you can say select * from $name where $name in (select ...)). SQL doesn't allow that.
My suggestion is to split the process up:
First run this query:
select 'select distinct name from ' || table_name || ' union'
from select table_name from table_list
That'll give you the selects to run. Put them into a small script, remove the last "union" and run that script.
[EDIT] If MySQL supports an "eval" operator in stored procedures (i.e. where you can build SQL from parts and then run it), you could do this. I checked the docs and it doesn't look like there is an eval. You could also write an extension in C (see chapter 22) that either implements the lookup or an "eval" function.
But my guess is that your database won't change all the time. So the most simple solution would be to write a small SQL script that creates the code for a view (that is a string; it doesn't actually create the view ). Every time the DB changes, you simply run the script to recreate the view and afterwards, you can run the query against the view to get a list of all names.