Trying to build a searchpage for an equipment database, and I have a prepared statement which takes an equipment 'tag' (always required) and searches a keyword in six different data fields where the 'tag' matches user input.
The problem is that no results are found if the keyword is IN one of these possibly long fields, so many intended results are missing.
In regular SQL I'd just to a table join and add a LIKE query to an IN query; but with mysqli I'm using a prepared statement with the ? placeholder and not sure what can be done about using this mark more than once for each variable.
The statement I have is this:
if (!($stmt = $conn->prepare("SELECT * FROM equipment
WHERE EQUIP_CND LIKE ?
AND (EQUIP_TYP LIKE ? OR ((EQUIP_SER LIKE ? OR EQUIP_PNO LIKE ?)
OR (EQUIP_LOC LIKE ? OR EQUIP_CMT LIKE ?)));"))) {
echo "Prepare failed: (" . $conn->errno . ") " . $conn->error;
}
And I want to just replace LIKE with LIKE IN. How to achieve this?
The like requires wildcards to have loose matching.
e.g.
select * from table where a like 'b'
is the same as:
select * from table where a = 'b'
so a record of b would be found but abc would not.
From the manual:
With LIKE you can use the following two wildcard characters in the pattern:
% matches any number of characters, even zero characters.
_ matches exactly one character.
So to find abc you'd use:
select * from table where a like '%b%'
For prepared statements the wildcards get appended to the variable, or in the binding, NOT in the query itself. Example 6 on the PDO manual page shows this. http://php.net/manual/en/pdo.prepared-statements.php#example-991 (after the comment // placeholder must be used in the place of the whole value)
Related
a quick question :), I wrote this because someone said that my codes are vulnerable to mysql injection and it is a requirement to learn prepared statement in web programming to avoid any user putting malicious data or statement into the database..What I have is a search function that search data from the database, if you type in a string like this "torres" then i search for torres but if you just put "tor" it won't search for datas that contain "tor" in their name..I don't know the correct format while using prepared statement, If you have advice I'm very happy to take it :)
<?php
if (isset($_POST['search'])) {
$box = $_POST['box'];
$box = preg_replace("#[^0-9a-z]#i","",$box);
$grade =$_POST['grade'];
$section = $_POST['section'];
$strand = $_POST['strand'];
$sql = "SELECT * FROM student WHERE fname LIKE ? or lname LIKE ? or mname LIKE ? or grade = ? or track = ? or section = ?";
$stmt = mysqli_stmt_init($conn);
if (!mysqli_stmt_prepare($stmt, $sql)){
echo "SQL FAILED";
}
else {
//bind the parameter place holder
mysqli_stmt_bind_param($stmt, "ssssss",$box, $box, $box, $grade, $strand, $section);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
while($row = mysqli_fetch_assoc($result))
{
echo "<tr>";
echo "<td>".$row['lname']."</td>";
echo "<td>".$row['fname']."</td>";
echo "<td>".$row['mname']."</td>";
echo "<td>".$row['grade']."</td>";
echo "<td>".$row['track']."</td>";
echo "<td>".$row['section']."</td>";
echo "</tr>";
}
}
As requested:
#ArtisticPhoenix I clearly prefer the king's way [compound full text index]. This should be your primary answer showing an example/explaination.
First make a full text index that includes all three fields (this is in PHPmyAdmin, it's a bit easier to explain with an image)
Then do a query like this:
#PDO version SELECT * FROM `temp` WHERE MATCH(fname,mname,lname)AGAINST(:fullname IN BOOLEAN MODE)
#MySqli version SELECT * FROM `temp` WHERE MATCH(fname,mname,lname)AGAINST(? IN BOOLEAN MODE)
SELECT * FROM `temp` WHERE MATCH(fname,mname,lname)AGAINST('edward' IN BOOLEAN MODE)
It seems simple but there are some things with full text to be aware of Min char count which is 3 (I think) anything smaller than that is not searched on. This can be changed but it requires repairing the DB and restarting MySql.
Stop words, these are things like and, the etc. These can also be configured in my.cnf.
Punctuation is ignored. This might not seem a big deal on names but think of hyphenated last names.
Usually I reduce the word min to 2 and point the stopwords to an empty file (disabling them).
The match against syntax is quite different, it's pretty powerful but it's not really used outside of full text. An example is: this is the wild card * and you use '"' double quotes for exact phrase match '"match exactly"', and + is logical AND, such as word+ word+ (default is or), - is do not match this etc... If I remember right, I used it a bunch a few years ago but haven't had to use it recently.
For example doing "begins with" on a partial word
SELECT * FROM `temp` WHERE MATCH(fname,mname,lname)AGAINST('edwar*' IN BOOLEAN MODE)
Same result matches one row. The obvious benefit is searching all 3 fields at the same time, but the full text syntax itself can be quite useful too.
For more information:
https://dev.mysql.com/doc/refman/8.0/en/fulltext-boolean.html
PS. I might add that using OR in a query can really kill performance, I've went as far as to replace simple OR with a UNION because of how bad the performance is on a large table. Logically the DB optimizer has to rescan the entire table for an OR, unlike AND where it can use the result of the previous expression to reduce the next expressions data set (or that is how I understand it). I can say the performance difference is very noticeable using OR vs UNION.
This is true for a compound full text index vs doing OR on each field separately. By default fulltext is faster, but it's even faster this way.
To fix your current query (for the sake of completeness)
You need whats known as an exclusive or, like this:
SELECT * FROM student WHERE ( fname LIKE ? OR lname LIKE ? OR mname LIKE ? ) AND grade = ? AND track = ? AND section = ?
What this does is group the OR's together so that they evalute as one expression to the "next level up" ( outside the parenthesis ). Basically order of operations. In English, you would have to match at least 1 of these columns fname, lname, mname AND you would also have to match all of the rest of the columns as well, to get a result returned for any given row.
If you use all OR (as you are now) and any single field matches, then the query comes back as true with matches. Which is the behaviour you are experiencing now.
If you simply change everything outside of the name fields to AND, Basically remove the parenthesis
Like this:
#this is wrong don't use it.
SELECT * FROM student WHERE fname LIKE ? OR lname LIKE ? OR mname LIKE ? AND grade = ? AND track = ? AND section = ?
Then you have to match this way.
(grade AND track AND section AND mname) OR lname OR fname
So if the last or first name match you get results regardless of any of the other fields. But the mname field you would find has to match with all the rest of the fields to get a result (but you would not likely notice this). Because, it would seem that the query works how you want but only when the mname is a match.
I hope that makes sense. It may be helpful to think of the WHERE clause as an IF condition the same logic rules apply.
Cheers!
I try to make SQL to search some string in database.
In this spesification, The SQL must be dont display one string in database.
my sql like this :
$query = "SELECT * FROM `chatuser` WHERE CONCAT( `fullname`,`image`) LIKE '%".$search_string."%' NOT (`$string is not be displayed`) " ;
is that possible ?
Thanks for help
The correct syntax of LIKE and NOT LIKE as two conditions would be:
SELECT * FROM chatuser
WHERE CONCAT(CustomerName,ContactName) LIKE '%t%'
AND CONCAT(CustomerName,ContactName) NOT LIKE '%m%';
You miss AND Between conditions. Also you have to repeat CONCAT(CustomerName,ContactName).
In the example above we are looking for all CustomerName+ContactName with a t in any place but if it doesn't have an m in any place.
From the docs found at https://www.w3resource.com/mysql/comparision-functions-and-operators/not-like.php
Example: MySQL NOT LIKE operator with (%) percent
The following MySQL statement excludes those rows from the table author, having the 1st character of aut_name ‘W’.
Code:
SELECT aut_name, country
FROM author
WHERE aut_name NOT LIKE 'W%';
And so it seems would work in your situation.
I have a table filter feature in PHP club membership webpage. I made it so the user can filter the table and choose which members to display in a table. For example, he can choose the country or state where the member is from then hit display. I am using a prepared statement.
The problem is, I need to use wildcards to make the coding easier. How do I use a wildcard in PHP MySQL query? I will use wildcards for example if the user does NOT want specific country but instead he wants to display all members from all countries.
I know not specifying the WHERE country= will automatically select any countries but I already constructed it so each controls like the SELECT control for country already has a value like "CA" or "NY" and "*" if the user leaves that control under "All Countries". This value when submitted is then added to the query like:
$SelectedCountry = $_POST["country"];
sql .= " WHERE country=" . $SelectedCountry;
But the problem is using WHERE country=* doesn't seem to work. No errors, just doesn't work. Is "*" the wildcard in PHP MySQL?
The * is not a wildcard in SQL when comparing with the = operator. You can use the like operator and pass a % to allow for anything.
When doing this the % should be the only thing going to the bind. $Bind_country = "'%'"; is incorrect because the driver is already going to quote the value and escape the quotes. So your query would come out as:
WHERE country ='\'%\''
The = also needs to be a like. So you want
$bind_country = '%';
and then the query should be:
$sql = 'select * from table where country like ?';
If this were my application I would build the where part dynamically.
Using * in WHERE clause is not right. You can only give legit value. For example:
// looking for an exact value
SELECT * FROM table WHERE column = 'value'
// you can also do this when looking for an exact value
// it works even if your $_POST[] has no value
SELECT * FROM table WHERE column = 'value' OR '$_POST["country"]' = ''
// looking for a specific or not exact value
// you can place % anywhere in value's place
// % denotes the unknown characters of the value
// it works also even if your $_POST[] has no value
// results will not be the same when you're using AND or OR clause
SELECT * FROM table WHERE column LIKE '%val%'
I think below link can solve your problem.
Just have a look and choose what you need.
Thanks.
http://www.w3schools.com/sql/sql_wildcards.asp
$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.
I have two issues, the first as the title states is that I need to have dynamic query with AND/OR in it. I fully understand the AND part (I've done a bunch of these) however, the OR part is very confusing to me because looking at this following sql :
$sql = SELECT * FROM table WHERE 1
then if you add an OR statement if a condition is met :
if(isset($_POST['OR'])){
$sql. = " OR peaches = :good";
}
then the query will return WHERE 1 OR peaches = :good
Again I understand the part with the AND, but I do not understand how to set up the OR part.
This is how I have set up the AND / OR selection (and this works)
The second issue I am facing is this code snippet from the same script (please read code comments) :
$sql .= " GROUP BY anum"; // I always group BY anum no matter what
if ($count !== "") { // if COUNT is not ""
$sql .= " HAVING COUNT(session.anum) :count"; // Then I want the user to be able to choose the operator (> < => =< =) and the dynamic number for it to use
$placeholder[':count'] = $count; // Then add the key :count to an array with the value of $count
}
$dynamic = $this->db->conn_id->prepare($sql);
$dynamic->execute($placeholder);
So as you notice I give the named parameter (:count) the value of $count, however this does "not work".
Is it possible to do what I am trying to do ($sql .= " HAVING COUNT(session.anum) :count";)
If not then I could just do : $sql .= " HAVING COUNT(session.anum) $count";
but that would defeat the purpose of PDO.
Any help would be great
Problem 1:
The reason that some developers use WHERE 1 when they have optional search terms is that an expression like TRUE AND <condition> is always equal to <condition>. This is basic boolean algebra.
But this is not the case for OR expressions. TRUE OR <condition> is always simply TRUE. You could modify your base query to use WHERE 0 so that when you append an OR term it comes out as WHERE 0 OR <condition>. Any expression like FALSE OR <condition> is always equal to the <condition>.
If you need to support both AND and OR in the same SQL query, you need to start putting parentheses around terms so they evaluate in the way you intend. I'm not going to explain boolean algebra and MySQL's operator precedence in this StackOverflow answer. But suffice to say that simply appending terms with .= isn't going to work when you have a mix of AND and OR terms.
Problem 2:
Parameters are very useful, but they don't solve every case of dynamic SQL. You can use an SQL parameter in place of a single literal value, but nothing else.
Not table names
Not column names
Not lists of values (like an IN( ) predicate)
Not SQL keywords
Not expressions
Not operators
You have to use string interpolation to include a user-chosen operator in your HAVING clause.
It's recommended to use whitelisting to avoid risk of SQL injection when you need to interpolate dynamic content and can't use a parameter.
For the first issue, what is the problem exactly?
For the second, MySQL manual says that you can't use functions on having clauses.
You can do like this:
SELECT *, COUNT(session.anum) AS total GROUP BY session.anum HAVING total > :count