0

I'm building a search page in PHP, using multiple select options and, passing the values from one page to another via GET method.

But I'm get stuck with the WHERE clause where unpassed values won't fill my query and ultimately I'll always receive No records found. I tried few approached detailed on some related posts on this subject like: SQL WHERE column = everything and Why would someone use WHERE 1=1 AND in a SQL clause? where WHERE column=column (1=1) is suggested, but as is described on the first link, it's not a working solution.

A suggested working solution is using IF cases, but if I think about my 15 options and all type of combinations of it, (when some are selected and some are unselected with 'NULL'/any/all value, I instantly get a headache.

So, I thought about asking if is possible to build this WHERE condition based on the passed parameters and than apply to the query.


Edit 1.

@Flewz my filters looks like:

these ones

and through GET I get like this: get 2]get1 my database it's like as follows: database

and my where clause is:

$stmt = $conn->prepare("SELECT * FROM Mydatabase WHERE mushtype='$mushtype'AND capsurface='$capsurface' AND capform='$capform')

I don't iterate here all 15 filters which in time probably will be more, but you got the idea.

So, a user don't need to use all filters to identify it's mushroom, it will use only the needed one, but in my sentence if a column is not used, return me a NO RECORDS FOUND message and I want to avoid this situation. I'm open to any suggestion.


Edit 2.

Thank you to @Flewz for such a beautiful guide, which for my surprise, I where able to understand it without any programming skills and adapt it to my needs. Taking baby steps and print_r($.........); (everything). I where able to see what’s happening in every line of the code and follow it through till the end. I where needed to alter a bit this guide at here:

if($get_var_size == 0){
  // no point in doing anything, 
  // echo error message
  echo 'Please select at least one filtering condition'; // echo 'I am a teapot';
  exit();
}

For the reason that I don’t want to return an error message if nothing is selected, because that would mean a user want to list all available records and that is not necessary an error. Second section is related to the above, here:

// in addition we can check if we have all get params and adjust query
if($get_var_size == count($all_get_keys)){
    // execute
}

Because if $get_var_size == count($all_get_keys) means a user filled out every possible condition, so my query $sql = 'SELECT * FROM some_table'; is not this one, but a fully customized query built from the ELSE branch. So, I cutted out this IF branch, considering it not needed.

So far so good, but I'm not done, I still have an issue. Rewinding and summarizing it, I have an index page with this code (using 3 filters for a shorter code example):

<form name="search_form" role="form" method="GET" id="search_form" action="SearchResults.php">
  <?php 
    try {
      $conn = new PDO('sqlite:db/Ciupercomania.db');
      $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
   ?>

<select id="mushtype" name="mushtype" style="width: 30vw;">
  <option value="" selected disabled hidden>Choose</option>
    <?php 
      $stmt = $conn->prepare("SELECT * FROM attributes WHERE attributename='mushtype' ORDER by attributevalueEN ASC");
      $stmt->execute();
      $data = $stmt->fetchAll(PDO::FETCH_ASSOC);
        foreach($data as $row): ?>
  <option value="<?php echo $row['idattributevalue']; ?>"><?php echo $row['attributevalueEN']; ?></option>
        <?php endforeach; ?>
</select>

<select id="capform" name="capform" style="width: 30vw;">
  <option value="" selected disabled hidden>Choose</option>
    <?php 
      $stmt = $conn->prepare("SELECT * FROM attributes WHERE attributename='capform' ORDER by attributevalueEN ASC");
      $stmt->execute();
      $data = $stmt->fetchAll(PDO::FETCH_ASSOC);
        foreach($data as $row): ?>
  <option value="<?php echo $row['idattributevalue']; ?>"><?php echo $row['attributevalueEN']; ?></option>
        <?php endforeach; ?>
</select>

<select id="capsurface" name="capsurface" style="width: 30vw;">
  <option value="" selected disabled hidden>Choose</option>
    <?php 
      $stmt = $conn->prepare("SELECT * FROM attributes WHERE attributename='capsurface' ORDER by attributevalueEN ASC");
      $stmt->execute();
      $data = $stmt->fetchAll(PDO::FETCH_ASSOC);
        foreach($data as $row): ?>
  <option value="<?php echo $row['idattributevalue']; ?>"><?php echo $row['attributevalueEN']; ?></option>
        <?php endforeach; ?>
</select><br><br>

  <?php 
    } catch(PDOException $e) {
    echo "Error: " . $e->getMessage();
    }
    $conn = null;
  ?>

<input type="submit" value="Find Your Mushroom">
</form>

and a Results page with

<?php 
  // declaring all parameters I expect to pass through GET method
  $all_get_keys = ['mushtype', 'capform', 'capsurface'];

  // we will use this to make our sql statement
  $get_key_config = [
    'mushtype'    => [
      'db_col'    => 'mushtype',
      'assoc_var' => 'mushtype'
    ], 
    'capform'     => [
      'db_col'    => 'capform',
      'assoc_var' => 'capform'
    ],
    'capsurface'     => [
      'db_col'    => 'capsurface',
      'assoc_var' => 'capsurface'
    ]
  ];

  // for parsed $_GET
  $get_params = [];

  // go over all possible keys
  foreach ($all_get_keys as $key) {
    // if we have it, push to array
    if (isset($_GET[$key])) {
      $get_params[] = [
        'key'   => $key,
        'value' => $_GET[$key]
      ];
    }
  }

  // how many we got
  $get_var_size = count($get_params);

try {
  $conn      =  new PDO('sqlite:db/Ciupercomania.db');
  $conn      -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  $stmt_data = [];
  $sql       = 'SELECT IDmush, genus, species, img1 FROM mushroomdata ';

  // adjusting the query
  if ($get_var_size == 0) {
  } else {
    $sql .= ' WHERE ';

    for ($i = 0; $i < $get_var_size; $i++) {

      // get config for our get key
      $cfg = $get_key_config[$get_params[$i]['key']];

      // append to sql query
      $sql .= $cfg['db_col'] . '=:' . $cfg['assoc_var'];

      // don't forget on variable
      $stmt_data[$cfg['assoc_var']] = $get_params[$i]['value'];

      // don't add AND if its our last
      if ($i < $get_var_size - 1) {
          $sql .= ' AND ';
      }
    }
  }

  $stmt = $conn->prepare($sql);
  $stmt -> execute();
  $data = $stmt->fetchAll(PDO::FETCH_ASSOC);
  echo '<ul class="no-bullets">';
  if ( !empty($data) ) {
    foreach ( $data as $row ){
       echo '<a target="blank" href="Datasheet.php?IDmush='.$row['IDmush'].'">' .
              '<li>'.$row['genus'].'&nbsp;'.$row['species'].'</li>'.
            '</a>';
    }
  } else {
       echo "No records found.";
  }
  echo '</ul>';
} catch(PDOException $e) {
  echo "Error: " . $e->getMessage();
}

// temporary messages
  echo "<pre>"; 
    echo "all-get-keys<br/>"; print_r($all_get_keys); echo '<br/>';
    echo "stmt_data<br/>"; print_r($stmt_data); echo '<br/>';
    echo "sql statement<br/>"; print_r($sql); echo '<br/>';
  echo "</pre>";

$conn = null;
?>  

Running this code and applying some filtering parameters I receive again this unexpected No records found result

Since the SQL query is correctly built, thanks to Flewz and can be seen on the screen capture, I'm sure is my fault in making the execute command, but I don't know where. The unsorted list part is also correct, I'm using it already on other pages. Please help me identify my error making the execute command. Thanks.

  • Please don't tag conflicting technologies. I've removed them, please (re)tag any that were appropriate. – Thom A Dec 20 '22 at 08:42
  • 2
    Yes, it's possible to build query dynamically. But `SELECT * FROM tbl WHERE column = any_value` would mean same as `SELECT * FROM tbl` (without any WHERE) – Justinas Dec 20 '22 at 08:47
  • the where clause is needed, but not for all 15 select boxes, so I need a method to skip the unselected ones – Szabolcs Horvath Dec 20 '22 at 12:33
  • Your code is open to SQL Injection. Do not concatenate strings with values coming from the UI. Use Dynamic SQL to assemble the final query, and that will include only the selected filters; the non-selected filters will be removed automatcally by Dynamic SQL. – The Impaler Dec 20 '22 at 18:55
  • 1
    @The Impaler - i'm just a hobbiest and only understand half of what you saying, that's why i asked in first place, because i don't know how to do it, but i'm open to learn it if is not something out of ordinary. I'm an accountant with a hobby :) and i'm ready to fight to reach my goal. – Szabolcs Horvath Dec 20 '22 at 20:38
  • 1
    @SzabolcsHorvath I hear you. A workaround I've seen sometimes is to use `column IS NOT NULL` to interpret that as your desired `column = *`. That would work if your database values are not null. As you see that expression does not have the equal sign anymore, but could do what you want. – The Impaler Dec 20 '22 at 21:10
  • @The Impaler, in other words I use my query as it is and add an additional condition with column IS NOT NULL, respectively, for default SELECT valuea I use NULL ('')? – Szabolcs Horvath Dec 21 '22 at 05:58
  • @The Impaler `WHERE columns IS NOT NULL ` by itself ignores all for me, `WHERE columns IS NOT NULL OR column='column'` still ignores for me all selected values and `WHERE columns IS NOT NULL AND column='column' ` is exactly from where I started. I tried also the `WHERE NOT columns='' AND/OR column='column'` but still not working, probably I should focus on the IF/ELSE cases as it is proposed by Flewz – Szabolcs Horvath Dec 21 '22 at 13:26
  • @SzabolcsHorvath I think you are right. A series of IF/ELSE should do the trick. I would say it's the simplest solution in the end. That's exactly what ORMs do automatically behind the scenes, so they save you the effort of doing it and they are not error prone. However, that's a whole different ball game, and a few IF/ELSE should work perfectly well. – The Impaler Dec 21 '22 at 15:28
  • 1
    The line `$stmt -> execute();` is missing the variables. To be more precise the variable `$stmt_data`. If you `var_dump()` `$sql` and `$stmt_data` at the end you will notice that it holds your associated variables. Correctly its `$stmt -> execute($stmt_data);`. I am more surprised sql doesn't return a error for missing variables. – Flewz Feb 24 '23 at 12:30
  • @Flewz, as I saw this line `` gives different result if there is disabled present or not or if I play around with the `value=''` giving values to it (NULL, 0, '', ...), the most convenable form for me is as I inserted above. Probably other versions return the error you expected – Szabolcs Horvath Feb 24 '23 at 13:02
  • @Flewz, your method can be applied as it is to text/search inputs as well? How do I prevent empty text/search input **?name1=** to be submitted? I tried [this](https://stackoverflow.com/questions/8029532/how-to-prevent-submitting-the-html-forms-input-field-value-if-it-empty) method but don't seems to work with GET – Szabolcs Horvath Feb 28 '23 at 06:35
  • @SzabolcsHorvath you can extend the `if(isset($_GET[$key])){` if sentence and inside of it check for empty value and only push it in if its not empty. There is a php function `empty()` but i wouldn't recomment it. It evaluates 0 to true when 0 can be a value we are interested in. Find out what happens when nothing is send over with `vardump()` and check against it and add `if(my_var_empty) continue;` inside of the `isset()` if. – Flewz Feb 28 '23 at 06:42
  • SQL can also be iffy with no value or null values. In SQL `col=NULL` sometimes doesnt want to work and we have to use `col IS NULL` so your `AND` sentences is the most simple form in SQL go from `AND col=:col` to `AND (col=:col OR col IS NULL)`, ofc that will pull a NULL col everytime. – Flewz Feb 28 '23 at 06:45
  • I don't want to add it into SQL query if input is empty, just exactly as above. I want to only consider it when there is some text and then use [WHERE column LIKE patern](https://www.sqlitetutorial.net/sqlite-like/) – Szabolcs Horvath Feb 28 '23 at 06:50
  • @SzabolcsHorvath Then simply check the value of `$_GET` inside of `if (isset($_GET[$key]))` and if it doesn't satisfy condition skip over it. something like `isset($_GET[$key]) ? ($_GET[$key] == null ? false : true) : false` – Flewz Feb 28 '23 at 07:00

2 Answers2

1

The WHERE clause is there to filter your data. So if you don't need filtering (want to see all records), just skip the WHERE clause completely. It is not needed.

eilenart
  • 11
  • 2
  • the where clause is needed for **min 1** and **max 15** filter condition, I cannot skip it, that's how I do the search – Szabolcs Horvath Dec 20 '22 at 12:29
  • @SzabolcsHorvath Would be nice if you add your query into a question. Or a part of it so that we are able to see how its constructed and what it does. Now I dont know what kind of min and max condition you have but it sounds like a `OFFSET` and `LIMIT` clauses. – Flewz Dec 20 '22 at 12:46
  • @SzabolcsHorvath your code would be probably more manageable if you did not try to squeeze everything into a single WHERE clause. IF/ELSE statements would be useful in this case. – eilenart Dec 20 '22 at 13:55
  • @flewz, I'm updated my question with some printscreens, maybe it clears out what I want. – Szabolcs Horvath Dec 20 '22 at 20:39
1

Since you have updated your question lets start by some safe practices.
You are using prepared statements which is good, just that you are doing it the wrong way.

Since you are using php we have a option for named parameters. See example bellow.

$sql = 'SELECT name FROM pets WHERE species=:species;';
$stmt = $con->prepare($sql);
$stmt->execute([ 'species' => 'cat' ]);
$err = $stmt->errorInfo();

if($err[0] != '00000'){
    // handle error, code is in $err[2]
}

while($row = $stmt->fetch(PDO::FETCH_ASSOC)){
    // do something
}

WHERE species=:species :species is our variable in prepared statement, we fill it out with passing a array with keys to execute method. This is the only safe way of doing it.

For your problem at hand easiest to understand would be lots of if statements. Can be done with configuration object and a for loop.

Untested, might not work.

// we need to figure out which GET parameters came trough
$all_get_keys = [ 'mushtype' ];
// we will use this to make our sql statement
$get_key_config = [
    'mushtype'      => [
        'db_col'    => 'mtype',
        'assoc_var' => 'mtype'
    ]
];
// for parsed $_GET
$get_params = [];

// go over all possible keys
foreach($all_get_keys as $key){
    // if we have it, push to array
    if(isset($_GET[$key])){
        $get_params[] = [
            'key'   => $key,
            'value' => $_GET[$key]
        ];
    }
}

// how many we got
$get_var_size = count($get_params);

if($get_var_size == 0){
    // no point in doing anything
    // echo error message
    echo 'I am a teapot';
    exit();
}

$stmt_data = [];
$sql = 'SELECT * FROM some_table';

// in addition we can check if we have all get params and adjust query
if($get_var_size == count($all_get_keys)){
    // execute
}else{
    $sql .= ' WHERE ';

    for($i = 0; $i < $get_var_size; $i++){
        // get config for our get key
        $cfg = $get_key_config[$get_params[$i]['key']];

        // append to sql query
        $sql .= $cfg['db_col'] . '=:' . $cfg['assoc_var'];
        // don't forget on variable
        $stmt_data[$cfg['assoc_var']] = $get_params[$i]['value'];

        // don't add AND if its our last
        if($i < $get_var_size - 1){
            $sql .= ' AND ';
        }
    }

    //execute
}

// check for error
// do data operation

The only difference between if and for loop is that the options from config would be typed as if statements.

Flewz
  • 343
  • 9
  • it's possible to work, but probably will need some fine tunings. are you willing to lend me some of your time and continue it in a private conversation (email, chat, whatever you prefer) since here probably will be a very long post?,Thanks. – Szabolcs Horvath Dec 21 '22 at 07:51
  • @SzabolcsHorvath I think the provided example is good enough to point you into the right direction. – Flewz Dec 22 '22 at 10:49
  • as noob as I( am, i'm sure you are right, but still .. it can happen to need some intervention here and there – Szabolcs Horvath Dec 22 '22 at 11:51
  • as I thought, I need your help if you please. Thank you. – Szabolcs Horvath Feb 24 '23 at 12:01