PHP MySQL statment with AND and OR - php

I have a PHP MySQL statement and basically what I want is to check for that table element matches and then check a second table element matches or a third table element matches, sort of like this
if ref=ref (AND page=page OR allpages=1)
that means search for all pages with ref=ref and then in that recordset check if page=page or if all pages=1
so MySQL statement is this:
$sql=mysql_query("SELECT * FROM content WHERE ref='$ref' AND page_ref='$page_ref' OR allPages='1');
But it is taking records from the db that don’t match the ref but allpages=1
Is there some way of bracketing this or restructuring the statement?

Your if ref=ref (AND page=page OR allpages=1) was nearly right, but you want the AND out of the brackets:
if ref=ref AND (page=page OR allpages=1)
Implemented:
$sql=mysql_query("SELECT * FROM content WHERE ref='$ref' AND (page_ref='$page_ref' OR allPages='1')");
Note: you were missing a closing " from the query as well (though I suspect this was a copy error when creating the question)
Further improvement (concatenation and backticks):
$sql=mysql_query("SELECT * FROM `content` WHERE `ref`='".$ref."' AND (`page_ref`='".$page_ref."' OR `allPages`=1)");

Simply add brackets like this:
SELECT * FROM content
WHERE ref='ref'
AND (page_ref='page_ref' OR allPages='1')
-------^------------------------------------^----Add here
So your whole query should be:
$sql=mysql_query("SELECT * FROM content WHERE ref='$ref' AND (page_ref='$page_ref' OR allPages='1')");

User Operator Precedance.
The precedence of an operator specifies how "tightly" it binds two expressions together. For example, in the expression 1 + 5 * 3, the answer is 16 and not 18 because the multiplication ("*") operator has a higher precedence than the addition ("+") operator. Parentheses may be used to force precedence, if necessary. For instance: (1 + 5) * 3 evaluates to 18.
"SELECT * FROM content WHERE ref='".$ref."' AND (page_ref='".$page_ref."' OR allPages=1")

I think
SELECT * FROM content WHERE ref='$ref' AND (page_ref='$page_ref' OR allPages='1')
it is a priority matter. You can read about operators precedence in the corresponding manual page.
Also, remember, that mysql_* functions are officially deprecated, so use mysqli_ or PDO instead.

You can add brackets to your mySQL statement in just the same way as you add them to a PHP statement. Just make sure your expression is bracketed in the same way as your desired logic.
$sql=mysql_query("SELECT * FROM content WHERE ref='$ref' AND (page_ref='$page_ref' OR allPages='1')");

You should set braces around the OR comparison:
WHERE ref='$ref' AND ( page_ref='$page_ref' OR allPages='1' )

Related

mySQL more precision using LIKE function

I'm aware of the LIKE function in SQL, but I need to do something slightly more complex.
In my table people I have a text field called banned which stores a string of all banned names seperated with a #. So let's banned = Roger#Bobjob#Billy
Say I want to check if the name Bob appears
SELECT * FROM people WHERE banned LIKE '%$Bob%'
This would presumably find results because of the Bobjob in the string.
Is there any way I can make it so it only finds full names within the # delimiters?
Forget about using index in either of these.... but since you're using %var% i am assuming you expected that already.
We could concat a # at start and end of banned so that every name is surrounded by #'s then use the # in the name search. (Expects no name to have # in it.... and expects banned to not start nor end with a # (and if they do it really won't matter to the below))
Where concat('#', banned,'#') like '%#Bob#%'
Use find in set by converting the # to a , and if the result is > 1 then it was found.
find_in_set('Bob',replace(banned,','))>1
If you know the list is always composed of 2 or 3 names (as stated in your comment), the most straightforward way is to check for all 3 possible cases:
SELECT *
FROM people
WHERE list LIKE 'bob#%' /* name in first position */
OR list LIKE '%#bob#%' /* name in second position */
OR list LIKE '%#bob' /* name in third, final position */
You should use concat for build a proper like condition with vars and #
SELECT * FROM people WHERE banned LIKE concat('%#%', '$Bob','%#%')
This isn't an ideal data structure, but leaving that aside for the moment:
If you can change the delimiter to a comma, mysql has a built-in function for that: FIND_IN_SET
SELECT * FROM people WHERE FIND_IN_SET('Bob', list);
Otherwise, you can do a regular expression match.
SELECT * FROM people WHERE list REGEXP '(^|#)Bob($|#)';
Neither of these is necessarily very performant, so I wouldn't try it on large data sets.
It's probably not the best db architecture but you might use regular expressions for you purposes like so
SELECT * FROM people WHERE banned RLIKE '(^|#)Bob($|#)'

Dynamic PDO Query - With AND / OR

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

SQL Operators: AND and OR

I'm just wondering if you can use both of them in a PHP code. I thought something like this:
$sql2 = mysql_query("SELECT * FROM forum WHERE id='$topicsnumber' AND main='0' OR main='1' OR main='2'");
while($row=mysql_fetch_array($sql2)) {code in here}
So that it checks it like this WHERE id='$topicsnumber' AND (main='0' OR main='1' OR main='2').
Is this possible?
Yes, it's possible with parentheses around the conditions, but it's better written as
mysql_query("SELECT * FROM forum WHERE id='$topicsnumber' AND main IN ('0','1','2')");
That way you don't have to worry about operator precedence.
If main is a numeric data type, you can drop the apostrophes around the numbers in your query.

searching between dates in MYSQL in this format 03/17/10.11:22:45

I have a script that automatically populates a mysql database with data every hour. It populates the date field like 03/17/10.12:34:11 and so on.
I'm working on pulling data based on 1 day at a time from a search script.
If i use
SELECT *
FROM call_logs
WHERE call_initiated between '03/17/10.12:00:00' and '03/17/10.13:00:00'
it works, but when I try to add the rest of the search params, it ignores the call_initiated field.
SELECT *
FROM call_logs
WHERE caller_dn = '2x9xxx0000' OR called_dn = '2x9xxx0000'
AND call_initiated between '03/17/10.12:00:00' and '03/17/10.13:00:00'
^-- I x'd out a couple of the numbers. I've also tried without the between function, and used >= <= to pull the records, but have the same results. Im sure its an oversight, thanks in advance.
try using parentheses around your OR statement
... where (caller_dn='2x9xxx0000' OR called_dn='2x9xxx0000') AND call_initiated between '03/17/10.12:00:00' and '03/17/10.13:00:00'
use the IN operator instead of OR
... where caller_dn IN('2x9xxx0000','2y9yyyy000') AND call_initiated between '03/17/10.12:00:00' and '03/17/10.13:00:00'
The OR statement is more than likely the issue, since OR evaluates the left side, sees that its true, it doesn't care what is on the right site, because as long as one of the statements is true, it considiers the entire statement to be true.
Read up on the OR operator at the MYSQL page
Your Statement should look like
SELECT * FROM `call_logs` WHERE (caller_dn='2x9xxx0000' OR called_dn='2x9xxx0000') AND call_initiated BETWEEN '03/17/10.12:00:00' and '03/17/10.13:00:00';

"SELECT * FROM users WHERE id IN ( )" == FAIL

I have a function that I use called sqlf(), it emulates prepared statements. For instance I can do things like:
$sql = sqlf("SELECT * FROM Users WHERE name= :1 AND email= :2",'Big "John"','bj#example.com') ;
For various reasons, I cannot use prepared statements, but I would like to emulate them. The problem that I run into is with queries like
$sql = sqlf("SELECT * FROM Users WHERE id IN (:1)",array(1,2,3) );
My code works, but it fails with empty arrays, e.g. the following throws a mysql error:
SELECT * FROM Users WHERE id IN ();
Does anyone have any suggestions? How should I translate and empty array into sql that can be injected into an IN clause? Substituting NULL will not work.
Null is the only value that you can guarantee is not in the set. How come it is not an option? Anything else can be seen as part of the potential set, they are all values.
I would say that passing an empty array as argument for an IN() clause is an error. You have control over the syntax of the query when calling this function, so you should also be responsible for the inputs. I suggest checking for emptiness of the argument before calling the function.
Is there a possibility that you could detect empty arrays withing sqlf and change the SQL to not have the IN clause?
Alteratively, you could postprocess the SQL before passing it to the "real" SQL executor so that "IN ()" sections are removed although you'd have to do all sorts of trickery to see what other elements had to be removed so that:
SELECT * FROM Users WHERE id IN ();
SELECT * FROM Users WHERE a = 7 AND id IN ();
SELECT * FROM Users WHERE id IN () OR a = 9;
would become:
SELECT * FROM Users;
SELECT * FROM Users WHERE a = 7;
SELECT * FROM Users WHERE a = 9;
That could get tricky depending on the complexity of your SQL - you'd basically need a full SQL language interpreter.
If your prepare-like function simply replaces :1 with the equivalent argument, you might try having your query contain something like (':1'), so that if :1 is empty, it resolves to (''), which will not cause a parse error (however it may cause undesirable behavior, if that field can have blank values -- although if it's an int, this isn't a problem). It's not a very clean solution, however, and you're better off detecting whether the array is empty and simply using an alternate version of the query that lacks the "IN (:1)" component. (If that's the only logic in the WHERE clause, then presumably you don't want to select everything, so you would simply not execute the query.)
I would use zero, assuming your "id" column is a pseudokey that is assigned numbers automatically.
As far as I know, automatic key generators in most brands of database begin at 1. This is a convention, not a requirement (auto-numbered fields are not defined in standard SQL). But this convention is common enough that you can probably rely on it.
Since zero probably never appears in your "id" column, you can use this value in the IN() predicate when your input array is empty, and it'll never match.
The only way I can think to do it would be to make your sqlf() function scan to see if a particular substitution comes soon after an "IN (" and then if the passed variable is an empty array, put in something which you know for certain won't be in that column: "m,znmzcb~~1", for example. It's a hack, for sure but it would work.
If you wanted to take it even further, could you change your function so that there are different types of substitutions? It looks like your function scans for a colon followed by a number. Why not add another type, like an # followed by a number, which will be smart to empty arrays (this saves you from having to scan and guess if the variable is supposed to be an array).

Categories