I'm creating a very simple PHP file that searches a table in my SQLite database.
I want it to filter my table according to 2 parameters, min and max, but I want to be able to specify one parameter, both or even none. How do I translate that into a SQL query?
<?php
$db = new PDO('sqlite:database.db');
$min = $_GET['min'];
$max = $_GET['max'];
if($min == '') $min = -1;
if($max == '') $max = -1;
$res = $db->prepare('SELECT * FROM Roteiro WHERE ...');
$res->execute(array(...));
$test = $res->fetchAll();
print_r($test);
?>
where (? > a or ? is null)
and (? = foo or ? is null)
-- etc...
The trick is let the condition be satisfied if the value is null. Make sure you pass type null, not some empty string or other falsy value.
The query optimizer should optimize these extra conditions away no problem because its easy for it to see they never vary per row.
an equivalent way to say it
where (? is not null and ? > a)
and (? is not null and ? = foo)
but I prefer the first way.
Youll need to build the statement dynamically:
$db = new PDO('sqlite:database.db');
$min = isset($_GET['min']) && !empty($_GET['min']) ? (integer) $_GET['min'] : null;
$max = isset($_GET['max']) && !empty($_GET['max']) ? (integer) $_GET['max'] : null;
$base = 'SELECT * FROM Roteiro';
$params = array();
if(null != $min && null != $max) {
// im using BETWEEN here just for simplicity int he example,
// but you could (should?) use separate statements with
// typical <,> comparison ops.
$base .= ' WHERE the_column BETWEEN :min AND :max';
$params[':min'] = $min;
$params[':max'] = $max;
} elseif(null !== $max) {
$base .= ' WHERE the_column < :max';
$params[':max'] = $max;
} elseif(null != $min) {
$base .= ' WHERE the_column > :min';
$params[':min'] = $min;
}
$db->prepare($base);
$db->execute($params);
You'll want to dynamically create the query from the information you have.
So, lets say you want to filter some table by a optional minimal and an optional maximal value.
SELECT some_row
FROM some_table
WHERE filter_row < [max]
AND filter_row > [min]
An easy way to do this would be to simply store the part of your SQL-query that filters by maximum and minimum and append it to the query when needed/defined:
$sql = "SELECT some_row
FROM some_table ";
if (isset($max) && isset($min)){
$sql .= "WHERE filter_row < ?
AND filter_row > ?";
} else if (isset($max) ){
$sql .= "WHERE filter_row < ?";
} else if (isset($min) ){
$sql .= "WHERE filter_row > ?";
}
if ($stmt = $mysqli->prepare($sql)) {
// bind parameters according to the given/available
// parameters and execute
}
I'm not sure if the query will work since I have nothing to test it here, but the idea should be clear.
This is just a very simple solution which will get very large and ugly with a growing list of optional parameters. So, if you need it more often and with more parameters, consider using a "Query Builder".
Related
i need to write a search engine. And i had occurred some prbolems with it.
so for now my query looks like this.
SELECT * FROM umowy WHERE keywords LIKE ('%propanek%' OR '%seba%')
And i making this, with this example of code. (two words for now)
$szukaj = $_POST['szukaj'];
$szukaj_trim = trim($szukaj);
$szukaj_array = explode(" ",$szukaj_trim);
$szukaj_length = count($szukaj_array);
if ($szukaj_length === 1 ) {
$constr = "('%${szukaj_array[0]}%')";
}
else{
for($i = 0; $i <= $szukaj_length; $i++){
if($i == 0){
$constr = $constr."('%${szukaj_array[${i}]}%'";
}
if($i > 0 && $i < ($szukaj_length-1)){
$constr = $constr." OR '%${szukaj_array[${i}]}%'";
}
if ($i == ($szukaj_length-1)) {
$constr = $constr." OR '%${szukaj_array[${i}]}%')";
}
}
}
$query="SELECT * FROM umowy WHERE keywords LIKE $constr";
Is there way to reduce a result output by adding more words in search input?
Now it's working like "more words, more content to show". But i need something opposite, more words, the more accurate result is.
(sorry for my not perfect english)
I would like to select those movies which have similar titles.
I found this, but this way it dosn't work, it gives nothing. I would like to give toy story 2, toy story 3 and others with similar title like toy soldielrs, etc.
$title = "Toy Story";
$query = mysql_query("SELECT title, year, poster, LEVENSHTEIN_RATIO( ".$title.", title ) as textDiff FROM movies HAVING textDiff > 60");
I can compare strings in PHP with this function:
static public function string_compare($str_a, $str_b)
{
$length = strlen($str_a);
$length_b = strlen($str_b);
$i = 0;
$segmentcount = 0;
$segmentsinfo = array();
$segment = '';
while ($i < $length)
{
$char = substr($str_a, $i, 1);
if (strpos($str_b, $char) !== FALSE)
{
$segment = $segment.$char;
if (strpos($str_b, $segment) !== FALSE)
{
$segmentpos_a = $i - strlen($segment) + 1;
$segmentpos_b = strpos($str_b, $segment);
$positiondiff = abs($segmentpos_a - $segmentpos_b);
$posfactor = ($length - $positiondiff) / $length_b;
$lengthfactor = strlen($segment)/$length;
$segmentsinfo[$segmentcount] = array( 'segment' => $segment, 'score' => ($posfactor * $lengthfactor));
}
else
{
$segment = '';
$i--;
$segmentcount++;
}
}
else
{
$segment = '';
$segmentcount++;
}
$i++;
}
// PHP 5.3 lambda in array_map
$totalscore = array_sum(array_map(function($v) { return $v['score']; }, $segmentsinfo));
return $totalscore;
}
But how can I compare in a SELECT query or any other way?
You can use like queries for that:
Following example will return all the records from table customer for which customer name ends with kh
select * from customer where name like '%kh'
Following example will return all the records from table customer for which customer name start with kh
select * from customer where name like 'kh%'
Following example will return all the records from table customer for which the middle world of customer name is kh
select * from customer where name like 'kh%'
if you want more specific record then add some and/or condition in your query
I recommend you to read this
http://dev.mysql.com/doc/refman/5.7/en/string-comparison-functions.html#operator_like
I think you might need to define how similar things need to be to be considered a match.
But if you just wanna search for containing words, you could split your search string by whitespaces and use it in a REGEXP in your query
$search_array = explode(" ", "Toy story");
$query = "SELECT title, year, poster FROM movies WHERE title REGEXP '".implode("|", $search_array)."'";
This would probably match a lot rows, but you could make a more restrictive regular expression.
I am trying the use refine tools for a search on my website. The bit i'm stuck with is search by start letter. For example i could use a wildcard '%X%' but his would return anything that contained the letter 'x'.
I read on few sites that SUBSTRING can be used in mysql queries
http://dev.mysql.com/
http://www.kirupa.com/
https://stackoverflow.com/questions/6302027
This is what I have so far but returns nothing. There is data in the database that should return with the query.
public function refineUsersFollowers($user_id,$q){
if($this->databaseConnection()){
// get the users followers
$state = array(1,2);
$stmt = $this->db_connection->prepare("SELECT * FROM friends WHERE id_2 = :1 AND Friend_Request_State = :2 OR id_2 = :3 AND Friend_Request_State = :4");
$stmt->bindParam(':1', $user_id);
$stmt->bindParam(':2', $state[0]);
$stmt->bindParam(':3', $user_id);
$stmt->bindParam(':4', $state[1]);
$stmt->execute();
// format the SQL OR statements
$sql = '';
$ids = [];
while($rows = $stmt->fetch(\PDO::FETCH_ASSOC)){
array_push($ids,$rows['id_1']);
}
for($x = 0; $x < count($ids); $x++){
if(count($ids) == 1){
//if there is one result
$sql.= ' user_id = :'.$x." AND SUBSTRING('first_name',0,1) = :".$x.$x;
}else if($x == (count($ids) - 1)){
// last entry
$sql.= ' user_id = :'.$x." AND SUBSTRING('first_name',0,1) = :".$x.$x;
}else{
//continue loop
$sql.= ' user_id = :'.$x." AND SUBSTRING('first_name',0,1) = :".$x.$x." OR";
}
}
$stmt = $this->db_connection->prepare("SELECT * FROM account WHERE ".$sql);
for($x = 0; $x < count($ids); $x++){
$stmt->bindParam(':'.$x,$ids[$x]);
$insert = $x.$x.'';
$stmt->bindParam(':'.$insert,$q);
}
$stmt->execute();
$results = $stmt->fetch(\PDO::FETCH_ASSOC);
print_r($results);
// check for followers that start with letter
}
}
The first part of the function is fine, this gets an array of id's which is then placed together as an SQL string. Is the SQL not returning results because SUBSTRING is not supported in this way?
If so is there a way of producing a query like this or would it be easier to pull every result from the database then check them in a different function?
You have two issues with this expression:
SUBSTRING('first_name', 0, 1) = :".$x.$x;
First, substr() in SQL (in general) starts counting with 1 and not 0. So, the first argument should be 1.
Second, you have the first argument in single quotes. So, at best, this would return the letter 'f'. Here is a simple rule: Only use single quotes for string and date constants. Never use single quotes to refer to column names.
There are several way to write what you want. Here are three:
SUBSTRING(first_name, 1, 1) = $x
LEFT(first_name, 1) = $x
first_name like '$x%'
You query can be greatly simplified with the LIKE operator. This:
"AND SUBSTRING('first_name',0,1) = :".$x.$x;
can become this:
"AND first_name LIKE '".$x.$x."%'";
I'm not sure what the $x.$x is for, so I just left it in for illustrative purposes.
I have searched around on forums however all answers don't seem to work for my I am guessing it's more user error.
What I am trying to do:
Retrieve the data set from MySQL
Count the total number of rows
Work out specifically how many of them have the value "Y" in the metSLA column
Work out specifically how many of them have the value "N" in the metSLA column
Convert each of these metSLA values to a percentage
**The MySQL query works for sure and its stored in variable $result for reference.
*
//sla and total case count and percentages
$sla_met_rows = 0;
$sla_not_met_rows = 0;
$total_cases = mysql_num_rows($result);
while ($row = mysql_fetch_array($result))
{
if `metSLA` = "Y"
{
$sla_met_rows ++;
} else if `metSLA` = "N"
{
$sla_not_met_num_rows ++;
}
}
$met_percentage = 100 / $total_cases * $sla_met_rows;
$not_met_percentage = 100 / $total_cases * $sla_not_met_num_rows;
You can use a single MySQL query to get the percentage result:
SELECT COUNT( CASE WHEN `metSLA` = "Y" THEN 1 ELSE NULL END ) AS `Yes`,
COUNT( CASE WHEN `metSLA` = "N" THEN 1 ELSE NULL END ) AS `No`,
COUNT(1) AS `total`
FROM `TableName`
In your PHP, it'll be referenced as:
$result = mysql_query( <<The query above is here>> );
$row = mysql_fetch_array( $result );
$met_precentage = $row['Yes'] * 100 / $row['total'];
$not_met_precentage = $row['No'] * 100 / $row['total'];
Change
if `metSLA` = "Y"
{
$sla_met_rows ++;
} else if `metSLA` = "N"
{
$sla_not_met_num_rows ++;
}
To:
if ($row['metSLA'] == "Y")
{
$sla_met_rows ++;
}
else if ($row['metSLA'] == "N")
{
$sla_not_met_num_rows ++;
}
What you have has three problems:
You're missing the brackets around the conditions,
You're assigning (=) rather than comparing (==), and
You're running a shell command rather than getting the value from the database row.
I got a few queries built dynamically by my scripts. They usually fit the following template:
SELECT ...
FROM ...
JOIN ...
WHERE ... (lots of filters, search conditions, etc. here) ...
ORDER BY (optional) ...
LIMIT (optional) ... OFFSET (optional)
I want to remove the LIMIT and OFFSET parts from the query. I used
$sql_entitati = implode("LIMIT", explode("LIMIT", $sql_entitati, -1));
to do it but then it hit me: what if there's no LIMIT in the query and what if the only LIMIT is somewhere in the where clauses?
So my question to you is: How can I safely remove everything after the LIMIT key word, without screwing it up if there's no LIMIT and/or there's a "LIMIT" somewhere in the where clause? All this done in php.
A bit of an edit for clarity:
the algorithm i use:
$sql = implode("LIMIT", explode("LIMIT", $sql, -1));
Will work on 99% of the cases. The problem occurs when the "LIMIT" key word at the end is missing, AND there is "LIMIT" written somewhere in the conditions. for example:
SELECT * FROM table WHERE bla = 'SPEED LIMIT' ORDER BY table.a
this is the problem i need to tackle.
Solved using the following algorithm (Credit to techfoobar):
$p = strrpos($sql, "LIMIT");
if($p !== false) {
$q = strpos($sql, ")", $p);
$r = strpos($sql, "'", $p);
$s = strpos($sql, "\"", $p);
if($q === false && $r === false && $s === false)
$sql = substr($sql, 0, $p);
}
You should do something like:
Get the position of the last "LIMIT" - store this position in say, p
Ensure that you do not have a ")" character after p - if so, it is part of some inner query inside a condition etc..
Ensure that you do not have a "'" character after p - if so, it is part of some user input string
If steps 2 and 3 are passed, strip off everything after p
More hints:
For step 1 - use strrpos for the last occurrence of LIMIT
For steps 2 and 3, use strpos with p as the search start offset
For step4, use substr to strip off everything after p
Get the position of LIMIT, check it exists with !==FALSE then substring.
$str = "SELECT X FROM Y JOIN WHERE CONDITIONS ORDER BY ORDERS LIMIT OFFSET";
$pos = strrpos($str,"LIMIT");
if ($pos!==false)
{
$newStr = substr($str,0,$pos);
echo $newStr;
}
Try this aproach
$query = '';
$query .= 'SELECT...';
$query .= 'FROM ...';
$query .= 'JOIN ...';
$query .= 'WHERE...';
if ($limit)
{
$query .= " LIMIT $limit";
}
This may help you...
$str = " SELECT * FROM table WHERE bla = 'SPEED LIMIT' ORDER BY table.a";
$pos = strrpos($str,"LIMIT");
if ($pos!==false)
{
$ok = 1;
if(substr_count($str, '"', $pos) % 2 != 0)
{
$ok = 0;
}
if(substr_count($str, "'", $pos) % 2 != 0)
{
$ok = 0;
}
if(substr_count($str, ")", $pos) > 0)
{
$ok = 0;
}
if($ok == 1)
$str = substr($str,0,$pos);
}
echo $str;