SQLIte statement using an array inside instr() and IN - php

I am having issues structuring a sqlite statement. I am familiar with instr() and IN however is it possible to use them together? I have an array of multiple substrings that I would like to see if a column contains any of them.
Here is an example of what I am trying to accomplish with no luck
$array = array('-SVA[rev1]', '-KINGS[rev2]', '-TBS[rev3]');
$query2 = $db->query("SELECT * FROM BOT_Downloads WHERE instr( FileName, IN ('$array') ) AND SeriesTitle = 'The Simple Truth' ");
I know using LIKE '% %' instead of instr() would be another way however that one is over my head as well when combining it with IN

Your logic needs to be completely revised; IN is a SQL statement to determine if the contents of the parameter on the left of the IN statement is equal to any of the literal comma separated values in the statement's parenthesis, and is not a valid parameter for the INSTR function. You should be getting errors from SQLite when you try to execute that statement.
Your current code should produce a SQL query that looks like:
SELECT *
FROM BOT_Downloads
WHERE instr( FileName, IN ('-SVA[rev1],-KINGS[rev2],-TBS[rev3]') )
AND SeriesTitle = 'The Simple Truth';
I guarantee that is not what you want. You want to build a statement that looks more like this:
SELECT *
FROM BOT_Downloads
WHERE (
instr( FileName, '-SVA[rev1]') or
instr( FileName, '-KINGS[rev2]') or
instr( FileName, '-TBS[rev3]'))
AND SeriesTitle = 'The Simple Truth’;
As an aside the PHP array function is not going to surround array elements with single quotes, so your code will build a statement that looks like:
SELECT *
FROM BOT_Downloads
WHERE instr( FileName, IN ('-SVA[rev1],-KINGS[rev2],-TBS[rev3]'))
AND SeriesTitle = 'The Simple Truth’;
where you were probably shooting for something that looked more like this:
SELECT *
FROM BOT_Downloads
WHERE instr( FileName, IN ('-SVA[rev1]','-KINGS[rev2]','-TBS[rev3]'))
AND SeriesTitle = 'The Simple Truth’;
However as the IN clause is not a valid parameter to the Instr function then either way is wrong. The IN clause requires each string literal to have delimiters, otherwise it’s just going be an equality operator looking for the one string contained within the parenthesis. The PHP array method does not include those delimiters by default.
Continuation:
In continuation of my previous answer, I used the following SQL to create and populate a test table:
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS `BOT_Downloads` (
`FileName` TEXT,
`SeriesTitle` TEXT,
`Note` TEXT
);
INSERT INTO `BOT_Downloads` (FileName,SeriesTitle,Note) VALUES ('File-SVA[rev1].txt','The Simple Truth','matches filename and series title'),
('File-KINGS[rev2].txt','The Simple Truth','matches filename and series title'),
('File-TBS[rev3].txt','The Simple Truth','matches filename and series title'),
('File-WTH[rev1].txt','The Simple Truth','matches series title, doesn’t match filename'),
('File-KINGS[rev1].txt','The Simple Truth','matches series title, doesn’t match filename'),
('File-SVA[rev1].txt','War and Peace','matches filename, doesn’t match series title'),
('File-KINGS[rev2].txt','War and Peace','matches filename, doesn’t match series title'),
('File-TBS[rev3].txt','War and Peace','matches filename, doesn’t match series title'),
('File-WTH[rev1].txt','War and Peace','No matches'),
('File-KINGS[rev1].txt','War and Peace','No matches');
COMMIT;
I then ran the following query:
SELECT *
FROM BOT_Downloads
WHERE (
instr( FileName, '-SVA[rev1]') or
instr( FileName, '-KINGS[rev2]') or
instr( FileName, '-TBS[rev3]'))
AND SeriesTitle = 'The Simple Truth’;
and got results of the nature you’re asking for:
"File-SVA[rev1].txt" "The Simple Truth" "matches filename and series title"
"File-KINGS[rev2].txt" "The Simple Truth" "matches filename and series title"
"File-TBS[rev3].txt" "The Simple Truth" "matches filename and series title"
The logic you are pursuing, using PHP to create an array and then expanding that into a SQL statement will not work if the elements of the array are strings because PHP will not automatically add the required string delimiters to the array element; this is why you have to manually include the single tick marks in your query in the IN statement. This would work with integers but is a bad approach IMO. Since you are not putting string delimiters around each individual string element you’re testing for, it appears to the SQL engine that you are seeking all rows where the file name contains the literal string ‘-SVA[rev1],-KINGS[rev2],-TBS[rev3]’ which is not what you are seeking, and this is why you are getting no results even though there are no syntax errors.

If you can concatenate the array items to this:
'-SVA[rev1],-KINGS[rev2],-TBS[rev3]'
then:
SELECT * FROM BOT_Downloads
WHERE
',' || '-SVA[rev1],-KINGS[rev2],-TBS[rev3]' || ',' LIKE '%,' || FileName || ',%'
AND
SeriesTitle = 'The Simple Truth'

Related

searching datas in the database with prepare statement

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!

How to make this nested SQL 'group by' query work?

The following SQL-Query is not working and results in an error. How it should be modified to work as expected?
mysql_query("SELECT * from
(SELECT * from dist WHERE Date='$_POST[date]' and Time='$_POST[time]'
group by Part, Subject, Room)
WHERE Room='$ss2a[Room]'
");
There were a few issues in the sql - most notably the use of array variables within the statement - these were not being accessed correctly within a quoted string. Also, the final where clause appeared to have a constant though I suspect that should have been a string - on closer inspection, all $_POST[var] instances failed to use quoted names ~ should be $_POST[$var] or $_POST['var']
To access array variables within a quoted string ( or certain other types of data also ) encapsulate within curly braces.
Also, field names such as Date and Time are not really valid - so you should encapsulate those in backticks.
mysql_query("select * from (
select * from dist where `Date`='{$_POST['date']}' and `Time`='{$_POST['time']}' group by `Part`, `Subject`, `Room`
) tbl where `Room`='{$ss2a['Room']}';");
Though I'm not sure I can see a reason why you need the nested select statement

MySQL IN clause - String and INT comparison

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.

PHP implode function explanation

I am relatively new to PHP. The textbook im working from covers PHP5.2 and mentions nothing of implode. The information im getting from PHP manual is a bit unclear to me. Im looking for a short clear explanation and example on implode
What exactly is its purpose?
Why would you use it?
Is it secure?
I know implode returns a string of the elements of its array but how is the following 2 exmples different, and why would you use one over the other:
example 1
$query = 'SELECT `name`, `position`
FROM `player_info`
WHERE `player_id` IN (' . implode(',', $player_ids) . ')';
example 2
$result2 = mysql_query("SELECT `fixture_id`, `opponents`
FROM `fixtures` ") or die (mysql_error());
Thank you
I'm going to ignore the fact that you're using mysql_query() in your code - as that is a more vulnerable library in the PHP reportoire. When you get more comfortable, or if you can right away, use PDO.
To understand implode() you need to understand arrays(). I'm going with the assumption that you know what arrays are.
In SQL, when you use IN() - it's equivalent to multiple "OR"s.
e.g.
SELECT * FROM table t WHERE t.id IN ( 1,2,3 )
is essentially the same as:
SELECT * FROM table t WHERE t.id = '1' OR t.id = '2' OR t.id = '3' OR t.id = '3'
When you have an array in PHP - it will look like this:
$array = array ( 1 , 2 , 3 );
If you wanted to dump it into the SQL statement, it'll fail, because a query like:
SELECT * FROM table t WHERE t.id IN ( $array )
would output:
SELECT * FROM table t WHERE t.id IN ( Array )
What you want are the values within the array. That's where implode() comes in. Implode would create delimiter - which is equivalent to a consistent value that goes between each value in your array (in your example, a comma) and will output the necessary string that you need in SQL.
$array = array ( 1 , 2 , 3 );
$query = "SELECT * FROM table t WHERE t.id IN ( ".implode("," , $array)." ) ";
Is the same as:
$query = "SELECT * FROM table t WHERE t.id IN ( 1,2,3 ) ";
Hope that helps
Implode can be particularly useful for lists. Say you have an array of ingredients, you might echo implode("\n",$ingredients) to show them on their own lines.
In your first example, you need to be very careful. It will only work properly if $player_ids is an array of numbers. implode() by itself is perfectly secure, however improper use can leave gaping holes in your security. A more "correct" version of that code might look like this:
$query = "SELECT `name`, `position` FROM `player_info`
WHERE `player_id` IN ('".implode("','",array_map("mysql_real_escape_string",$player_ids))."')";
The above code also handles the case where $player_ids is empty. In your code, it will result in a MySQL syntax error (IN () is not valid), whereas in this code it will simply look for IN (''), which will match no rows.
Another use for implode might be when packing numbers. Say you have an array of digits, you might implode("",$digits) to pack them into a single string (and str_split($string) to unpack them).
In short, there are many potential uses for this function, and generally it will be obvious when the function applies. Try not to think too hard about it ;)
Example 1 extracts players information of the players who's ID's are $player_ids. Second example extracts two columns without WHERE condition.
What is your question exactly? As long as you are 100% sure the $player_ids is an array of integers! then it is safe.
Implode will take an array and transform it in a string where each element of original array will be separated by "first parameter" of implode function.
What do you mean with "secure"? I don't understand this question but I suppose that you are asking it because you seen implode() used into a db query (in the example). implode() has nothing to do with db query so no sql injection (*) and so on.
We should use it instead of loop over the whole array
(*) obviously you should pay attention of what array to implode is
implode creates a string from an array.
You must have some basic understanding of array, before you start working with it.
Thus, if you have a set of same-type data you store it in arrays. The first parameter in array is a delimiter. Second - is the array.
$animals = ("cats", "dogs", "cows");
echo implode(" &", $animals) . " are animals" ;
Will produce:
cats & dogs & cows are animals
In your first example, there is IN construction which can accept several parameters. Arrays are just suitable to work with it.
$player_ids = array(1,2,3);
"IN (".implode(", ", $player_ids).")" will result in IN (1, 2, 3)

Converting varchar into an int just for the search query?

What I've been trying to do is to select a row from a table while treating the varchar cells as int ones,
Here's a little explanation:
I have a table of phone numbers, some have "-" in them, some don't.
I wanted to select a number from the database, without including those "-" in the query.
So I used this preg_replace function:
$number = preg_replace("/[^0-9]/","",$number); //that leaves only the numbers in the variable
and then I run the following query:
"SELECT * FROM `contacts` WHERE `phone` = '{$number}'"
Now, of course it won't match sometimes since the number Im searching may have "-" in the database, so I tried to look for a solution,
on solution is just converting the cells into int's, but I'm not interested in doing that,
So after looking around, I found a MySQL function named CAST, used like : CAST(phone AS UNSIGNED)
I tried to mess with it, but it didn't seem to work.
Edit:
I kept looking around for a solution, and eventually used MySQL's REPLACE function for that.
"SELECT * FROM `contacts` WHERE REPLACE(phone,'-','') = '{$number}'"
Thank you all for your help.
MySQL doesn’t support extraction of regex matches.
You could try writing a stored function to handle it, but your best bet is to convert the data to ints so that all the numbers are uniform. I know you said you don't want to do that, but if you can, then it’s the best thing to do. Otherwise, you could do something like:
"SELECT * FROM `contacts` WHERE `phone` = '{$number}' OR `phone` = '{$number_with_dashes}'"
That is, search for the plain number OR the number with dashes.
1.
The easiest way to do it might be by using the REPLACE operator.
SELECT * FROM `contacts` WHERE REPLACE(REPLACE(`phone`, '-', ''), ' ', '') = '5550100';
What it does is simpy replacing all whitespaces and dashes with nothing, namely removing all spaces and dashes.
2.
Another alternative to solve the problem would be to use LIKE. If the phone numbers with a dash always are formatted the same way like 555-0100 and 555-0199 you can simple insert a %-sign instead of the dash. If your number may be formatted in different ways you can insert a %-between every character. It's not a beautiful solution but it does the trick.
SELECT * FROM `contacts` WHERE `phone` LIKE '555%0100';
or
SELECT * FROM `contacts` WHERE `phone` LIKE '5%5%5%0%1%0%0';
3.
You can use regular expressions. Since MySQL doesn't implement regex replace functions you need to use user defined functions. Have a look at https://launchpad.net/mysql-udf-regexp. It supports REGEXP_LIKE, REGEXP_SUBSTR, REGEXP_INSTR and REGEXP_REPLACE.
Edit: Removed my first answer and added some other alternatives.
I kept looking around for a solution, and eventually used MySQL's REPLACE function for that.
"SELECT * FROM `contacts` WHERE REPLACE(phone,'-','') = '{$number}'"

Categories