I have a stored procedure which takes in a single String parameter - the value passed into this parameter is a comma separated list of ID's from PHP - something like 2,3,4,5
`DECLARE tags_in VARCHAR(255);`
Within the Stored procedure I would like to select the rows which have ids corresponding to the ids in the parameter - the query would be like
`SELECT * from tags WHERE tag_id IN (tags_in)`
I pass in the values from PHP to MySQL using the following statement binding the value as a string
`$stmt->bindParam(':tags', '2,3,4', PDO::PARAM_STR);`
Problem - the actual query being executed by MySQL is as below - where the parameters passed in are considered as one string
`SELECT * from tags WHERE tag_id IN ('2,3,4')`
When the query I want executed is as below where the parameters are considered as individual integers
`SELECT * from tags WHERE tag_id IN (2,3,4)`
Any suggestions on I can accomplish this?
SQL placeholders can represent only SINGLE values. If you pass in some comma separated values, they won't be seen as multiple individual values with commas, they'll just be treated like a monolithic string.
e.g.
... WHERE foo IN (:bar)
... WHERE foo = :bar
are functionally identical as far as the SQL parser are concerned, and it won't make allowances for passing in your CSV values. Both will execute the same way:
... WHERE foo IN ('1,2,3')
... WHERE foo = '1,2,3'
You'll either have to limit yourself to only as many values as you have placeholders, or dynamically build your SQL and put in a placeholder for each individual value you're trying to put into the IN clause.
e.g.
$placeholders = array_fill(0, count($values_to_check) -1, '?');
$in_clause = implode(',', $placeholders);
/// builds ?,?,?,?,?,....?
$sql = "SELECT ... WHERE foo IN ($in_clause)";
$stmt = $dbh->prepare($sql);
$stmt->execute($values_to_check);
This is one place where prepared statements fall flat on their faces, and you have to fall back to good old "build some sql dynamically".
There is sometimes another way to accomplish the desired result by casting the integer you're trying to compare as a string surrounded by commas and checking if the result is contained in your list of possible values (with added commas on either side as well). It's not the most efficient for performance maybe, but it allows you to do what you want in a single procedure or query.
For example (in your case) something like this might work:
SELECT * from tags WHERE INSTR (CONCAT(',', tags_in, ','), CONCAT(',', tag_id, ',') );
MySql is a little bit weird in that it does the conversion from int to char within the CONCAT function, some other databases require explicit casting.
Related
I'm working on syncing two PostgreSQL databases using a PHP script. I am not able to query the entire table so I have to use an id column to grab records in batches.
The id column is a string column, not numeric. However, there are numerical ids in the column. This is where I'm having an issue.
When I prepare the SQL statement in PHP, when I happen to get an id that is numeric, when I bind it to the statement, it doesn't put quotes around the value because it thinks its an int, not a string.
How do I force it to be a string and always put single quotes around the id??
If I put the quotes around the ? in the query it treats it as text and the parameter doesn't get bound to the statement.
As you can see in the code I also tried casting the $start variable as a string. $start contains the starting id.
Here is the code:
$sql = "select id from properties where id > ? order by id limit ?";
$params = [(string) $start, 50000];
$rows = $this->wolfnet->select($sql, $params);
I have a variable that may contain a comma separated list. In my database, I also have a column with a column separated list.
I know I can find a single value in that db column by using FIND_IN_SET(needle, haystack)
However, if my variable contains a list such as "a,b,c" how can I check if at least one item in the list matches at least one item in the column? Is this possible?
SELECT `column_A` REGEXP CONCAT('(,|^)(', REPLACE( `column_B` ,',','|'), ')(,|$)');
SELECT '123,456,789' REGEXP CONCAT('(,|^)(', REPLACE( '1234,456,6789' ,',','|'), ')(,|$)');
This is my solution.
It sounds like you probably need some linking tables in your database. Storing comma separated lists in columns to compare against other comma separated lists is going to hurt your performance if you get to any sort of scale.
I'd highly suggest you read more about linking tables (associative entities) to perhaps convince you to change your database design somewhat:
https://en.wikipedia.org/wiki/Associative_entity
To answer your question about how you would use FIND_IN_SET to perform multiple searches in a single query, you would need to build your query dynamically.
Here is a basic example to simply show you how to build a query dynamically. Please take proper steps to prevent SQL injection (http://php.net/manual/en/security.database.sql-injection.php).
// This is the list you want to search against - for your actual implementation you would use your column name
$haystack_str = '2,4,6,8,10';
// This is the comma separated list you want to use as your needle
$search_str = '1,2,3';
// Break the string apart into separate values
$search_array = explode(',', $search_str);
// Store your query fragments
$query_array = array();
// Loop through each string value you want to use as your needle
foreach ($search_array as $needle) {
// PLEASE TAKE PRECAUTIONS AGAINST SQL INJECTION !!!
$query_array[] = sprintf('FIND_IN_SET("%s","%s")', $needle, $haystack_str);
}
// Join all pieces together using OR
$query_str = implode(' OR ', $query_array);
// Show your example query
echo 'SELECT ' . $query_str . ';';
Example: https://eval.in/867963
This produces the following query:
SELECT FIND_IN_SET("1","2,4,6,8,10") OR FIND_IN_SET("2","2,4,6,8,10") OR FIND_IN_SET("3","2,4,6,8,10");
Returns a value in the range of 1 to N if the string str is in the string list strlist consisting of N substrings.
Returns 0 if str is not in strlist or if strlist is the empty string.
https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_find-in-set
The sample query will produce 1 which indicates one of your search values is in your set.
I'm trying to include a list of strings to be used in an "in" expression in a sql statement for example:
select * from poop where id in ('asd','sas','ser')
I want to pass the in parameter from a variable. The quoting is really screwing me up. Should I be passing this as a string which I have been trying to no avail by making a comma seperated string that looks like this:
282366381A,240506808A,244154247A,491404349A,242443808B,328409296A,239723812A,383423679M
or "282366381A","240506808A","244154247A","491404349A","242443808B","328409296A"
or
'282366381A','240506808A','244154247A','491404349A','242443808B','328409296A'
None of these work or is there a different way using an array of values?
This is the statement I'm using with the string:
$cernerResults = $this->cernerdb->exec( "select
pat as HICN,
from pat
where
HICN in ( ? )", $hicsString );
Edit:
I was able to get around this by constructing the entire query as a string like this:
$query = "select pat as HICN from pat where HICN in (".$hicsString.")";
$hicsString has single quotes around each item like so:
'282366381A','240506808A','244154247A','491404349A','242443808B','328409296A'
The problem is that providing the string to the exec would result in no results. When looking at the freetds log file the in expression values would be double quoted as a whole or each one would be double single quoted and if i used no quotes they would not be quoted at all.
All of these would make the statement return no results. I should also mention that this is a Sybase database.
I think your problem may come from the fact that PDO parser needs to have one value per question mark so it is able to validate it. So your "hack" with one question mark which is assigned to more than one value is where it fails IMHO.
This is how I handle case like that:
$values = ['asd','sas','ser'];
$count = count($values);
$results = $db->exec(
"select * from poop where id in ( ?".str_repeat(", ?", $count-1).")",
$values
);
In general I would advice you using data mappers instead of running the queries on a DB object. It is easier to iterate through them and it is more secure.
I pass two different values into the file, one which the user entered and the other which is selected from a predefined set of values in a drop down menu, which is the one i'm having trouble with.
When using a single placeholder for the query it works,for example:
$result = pg_query_params($con, "SELECT * FROM chemsub WHERE name like $1", array("%".$_REQUEST['term']."%"));
I want to alter the query so the user can change which column they are searching i can't seem to get it to work, here is what i have
$result = pg_query_params($con, "SELECT * FROM chemsub WHERE $1 like $2", array($_REQUEST['dropdown'],"%".$_REQUEST['term']."%"));
I know the correct value is being passed into the file with the correct spelling matching a column name in the database but for some reason it returns no rows.
Any help would be much appreciated.
You can't have params in place of identifiers. If you want to have a dynamic column being queried again you can either prepare the query text in php or have the sql look like ($1 = 'foo' AND foo LIKE $2) OR ($1 = 'bar' ANd bar LIKE $2.`
$zips = array('10583','06890','06854');
$list = implode("','",$zips);
$q = "SELECT site_id FROM site_zipcodes WHERE zipcode IN ('%s')";
$result = db_query($q, $list);
This query returns no results.
However, a sprintf with the same parameters returns
SELECT site_id FROM site_zipcodes WHERE zipcode IN ('10583','06890','06854')
and when I put that query into Sequel Pro, I get three results (the expected behavior).
When I use one zipcode in the IN statement, db_query works just fine.
I can't for the life of me figure out why this is happening.
Without a definition of the db_query function, it's not possible to tell why this isn't working. For debugging this, the db_query function could echo (or var_dump) the actual SQL text being sent to the database.
But likely, the SQL produced by the db_query function does not include the comma separators in the IN list. The comma separators must be part of the SQL text. Any commas that are passed in as part of a value of a bind parameter are considered to be part of the value.
If we use a prepared statement, like this:
SELECT * FROM foo WHERE bar IN ( ? )
And we supply 'fee','fi','fo','fum' as the value of the bind parameter, that's equivalent to running statement like this:
SELECT * FROM foo WHERE bar IN ( '''fee'',''fi'',''fo'',''fum''' )
Such that the query will only return rows where bar is equal to that single string. Effectively equivalent to:
SELECT * FROM foo WHERE bar = '''fee'',''fi'',''fo'',''fum'''
To search for rows that match one of the values in that list, using a statement with bind parameters, the SQL text would need to be of the form:
SELECT * FROM foo WHERE bar IN ( ? , ? , ? , ? )
The commas need to be part of the actual SQL text. And we'd supply a separate value for each of the four bind parameters.
But again, I'm just guessing at what that db_query function is doing.