2

I have an usual search form using HTML that will be extract the data from mysql (the connection is using PHP and mysqli). The function works well in searching 1 value, but I want to make user can search more than 1 value that will be separated by comma (,).

For example: Searching 1 value works well Searching 1 value works well.

But I want to search 2 or more values inside the search box, like: 42-7278954,53-1217544,07-2517487,...

I hope user can input something like in the pic below and the result will have 2 rows --> CN_no 42-7278954 and 53-1217544:

enter image description here

The query I have so far is:

$sql = "SELECT * FROM mock_data WHERE CN_no IN ('{$CN_no}') OR doc_no IN ('{$doc_no}')";

Notes: CN_no is "Shipment Code" and doc_no is "Reference No"

But well... it's obviously give me an error because of the incorrect syntax.

Please help me to revise it. Thank you.

======== update query based on vp_arth's answer ========

$cn = explode(',', $CN_no);
$incn = str_repeat('?, ', count($cn)-1).'?';
$doc = explode(',', $doc_no);
$indoc = str_repeat('?, ', count($doc)-1).'?';

$query = "SELECT * FROM mock_data WHERE CN_no IN ({$incn}) or doc_no IN ({$indoc})";
$result = $conn->query($query , array_merge($incn, $indoc));

But it give me an error

Philipp
  • 2,787
  • 2
  • 25
  • 27
anakpanti
  • 319
  • 4
  • 17
  • 2
    Possible duplicate of [How to use php array with sql IN operator?](http://stackoverflow.com/questions/9618277/how-to-use-php-array-with-sql-in-operator) – Gurwinder Singh Dec 30 '16 at 07:08
  • I still don't understand how the query can detect comma (,) that inputted by user and put the value into array. – anakpanti Dec 30 '16 at 07:15
  • Not related, but be wary of [Sql Injection](https://en.wikipedia.org/wiki/SQL_injection) – gvmani Dec 30 '16 at 07:36
  • It's not confidential data, so it's okay. – anakpanti Dec 30 '16 at 07:45
  • 1
    Not working with confidential data doesn't mean you can ignore security. – Stefan Dec 30 '16 at 08:00
  • I'm not ignoring the security, but I'm trying to make my priority here. If someone can help me finding my answer for this question, then I will find a way to prevent SQL Injection if it needed. Thank you. – anakpanti Dec 30 '16 at 08:03
  • 1
    You have `IN ('42-7278954,53-1217544,07-2517487')` where should be `IN ('42-7278954', '53-1217544', '07-2517487')`. Try to learn about placeholders, then it will be just `IN (?, ?, ?)`. – vp_arth Dec 30 '16 at 08:08
  • yeah, I know... that's why I need help. I'm fully understand the syntax. **IN ('?','?','?')** but user won't be pleasant if they have to input '' as well. – anakpanti Dec 30 '16 at 08:12
  • We still have nothing to know how you interact with your database – vp_arth Dec 30 '16 at 08:16
  • As I explained above, the interaction to database is using php-mysqli and it works well because it success in searching 1 value already. – anakpanti Dec 30 '16 at 08:21
  • Please note, just because you're not storing confidential data, doesn't mean you can ignore security. For instance, what would happen if someone was to drop your tables? Truncate etc. You'll lose data, there is more at risk than leaking confidential information. Make that your priority before focusing on expansion – Daryl Gill Jan 03 '17 at 10:14
  • Can you post the error you are getting on this site? – Philipp Jan 03 '17 at 19:21
  • @Philipp Please see it in this link --> https://anakpanti.com/ab-cargo/controllers_searchshipment/. Thank you. – anakpanti Jan 04 '17 at 04:46
  • oh ok. What database are you actually using? And what API to access it, PDO? – Philipp Jan 04 '17 at 07:59
  • @Philipp I'm using mysql (phpmyadmin) --> mysqli – anakpanti Jan 04 '17 at 08:12
  • The accepted answer fails on avoiding SQL injection. Avoiding SQL injection is a **must** for a developer when inserting data to a database. By using prepared statements you let the driver do that for you. It is not about having to deal with confidential data or not. If confidential data was being stored by a developer to a database then he/she would have to both avoid SQL injection and encrypt data on database somehow. The accepted answer is not an answer really. –  Jan 09 '17 at 08:08

8 Answers8

2

This is a possible solution to use prepared statements with dynamic input in mysqli. The typing for the parameter binding is static though, in this case the parameters are strings.

/**
 * connecting to the database
 * defining in how manye columns you want to search (important to create the correct amount of arguments)
 */
$conn = new mysqli('localhost', 'root', '', 'test');
$columnsToSearch = 2;

/**
 * the numbers you want to search delimited by ","
 */
$CN_no = '42-7278954,53-1217544,07-2517487';
$cn = explode(',', $CN_no);

/**
 * writing the numbers to search into variables
 * putting the references of those variables into an array
 * 
 * the references will be used as arguments for the prepared statement
 */
$values = array();
for ($i = 0; $i < $columnsToSearch; $i++) {
    foreach ($cn as $k => $value) {
        $temp{$k}{$i} = $value;
        $values[] = &$temp{$k}{$i};
    }
}
/**
 * putting together the "types"-part for the binding of the prepared statement
 */
$types = array(str_repeat('s', count($cn) * $columnsToSearch - 1) . 's');
/**
 * merging types and references
 */
$argumentsArray = array_merge($types, $values);
/**
 * creating placeholder string for the query
 */
$placeholder = str_repeat('?, ', count($cn) - 1) . '?';

$stmt = $conn->prepare('SELECT CN_no, doc_no FROM mock_data WHERE CN_no IN (' . $placeholder . ') or doc_no IN (' . $placeholder . ')');

/**
 * check http://us3.php.net/manual/en/mysqli-stmt.bind-param.php#104073 to read what is happening here
 */
$ref = new ReflectionClass('mysqli_stmt');
$method = $ref->getMethod("bind_param");
$method->invokeArgs($stmt, $argumentsArray); 

$stmt->execute();

/**
 * fetching the result
 */
$stmt->bind_result($CN_no, $doc_no);
$row_set = array();
while ($row = $stmt->fetch()) {
    $row_set[] = array('CN_no' => $CN_no, 'doc_no' => $doc_no);
}
var_dump($row_set);
exit;

I adjusted the comment from http://us3.php.net/manual/en/mysqli-stmt.bind-param.php#104073 so it fits to your scenario.

Btw. With PDO as database-API this would be A LOT easier to write and to read. I might add an example for PDO later.

Philipp
  • 2,787
  • 2
  • 25
  • 27
2
$mysqli = new mysqli("localhost", "my_user", "my_password", "world");

/* check connection */
if (mysqli_connect_errno()) {
    printf("Connect failed: %s\n", mysqli_connect_error());
    exit();
}
// search input
$input = '42-7278954,53-1217544,07-2517487';
// create and filter your search array
$iArr = array_filter(array_map('trim', explode(",", $input)), 'strlen');
// create your search string from CN_no array 
$CN_no = implode("|",$iArr); 

/* create a prepared statement */
if ($stmt = $mysqli->prepare("SELECT * FROM `mock_data` WHERE `CN_no` RLIKE ?")) {

    /* bind parameters for search */
    $stmt->bind_param("s", $CN_no);

    /* execute query */
    $stmt->execute();
    // get all the rows returned
    // IMPORTANT
    // Use the below syntax in case you do use native mysqlnd driver.
    // If you don't have mysqlnd installed/loaded, you will get an error
    // that "mysqli_stmt_get_result()" method is undefined. 
    $result = $stmt->get_result()->fetch_all();
    // in case you don't have native mysqlnd loaded uncomment 
    // and try the below syntax to get the result
    /* $meta = $stmt->result_metadata(); 
    while ($field = $meta->fetch_field()) { 
        $params[] = &$row[$field->name]; 
    } 
    call_user_func_array(array($stmt, 'bind_result'), $params); 
    while ($stmt->fetch()) { 
        foreach($row as $key => $val) { 
            $c[$key] = $val; 
        } 
    $result[] = $c; 
    } */
    /* *********** */
    // print output
    print_r($result);

    /* close statement */
    $stmt->close();
}

/* close connection */
$mysqli->close();
  • Why don't you just use `$result = $stmt->get_result()` and stop there? OP should know how to use it. That would make your sollution look less comlicated. – Paul Spiegel Jan 07 '17 at 20:31
  • @PaulSpiegel you are right and have changed it accordingly thanks! –  Jan 08 '17 at 08:17
  • @PaulSpiegel i did a little more digging and tried the snippet in phpfiddle using `get_result` method and it returns method undefined. As specified in here http://php.net/manual/en/mysqli-stmt.get-result.php#refsect1-mysqli-stmt.get-result-mysqlnd this is the reason why. So i changed the answer in case of native mysqlnd or not. –  Jan 08 '17 at 09:35
0

Some abstract php code:

  $cn = explode(',', $input1);
  $incn = str_repeat('?, ', count($cn)-1).'?'
  $doc = explode(',', $input2);
  $indoc = str_repeat('?, ', count($doc)-1).'?'
  $sql = "SELECT ... WHERE cn_no IN ({$incn}) or doc_no IN ({$indoc})";
  $result = $db->rows($sql, array_merge($cn, $doc));
vp_arth
  • 14,461
  • 4
  • 37
  • 66
  • I think I have a problem with *$result = $db->rows($sql, array_merge($incn, $indoc));* is it not compatible with mysqli syntax? especially the array_merge part. I've edited my question to let you see my edited code so far and the error. – anakpanti Dec 30 '16 at 09:04
  • maybe because if it's not an array, it become error? (in case user only want to search 1 value) – anakpanti Dec 30 '16 at 09:12
  • Don't copy-paste a snippets, that marked as `abstract code` – vp_arth Dec 30 '16 at 10:26
  • @anakpanti, there was error in last line, it obviously should be merged array of data, not question mark strings. – vp_arth Jan 09 '17 at 12:03
  • Key part of this answer is to separate your data from query, it's only correct way to avoid sql injections. – vp_arth Jan 09 '17 at 12:08
0

Use FIND_IN_SET() function

Syntax : FIND_IN_SET (YOUR_INPUT_STRING_NAME, COLUMN_NAME);

YOUR_INPUT_STRING_NAME it may be 42-7278954,53-1217544,07-2517487,...

Let me know if it helps you.

Sam
  • 1,106
  • 10
  • 14
-1
$input = '42-7278954,53-1217544,07-2517487';
$inputs = explode(',', $input);

$new_inputs = array_map(function ($item){
    $item = trim($item);
    return "'" . $item . "'";
}, $inputs);

$string = implode(',', $new_inputs);

// string is  '42-7278954','53-1217544','07-2517487'

$sql = "SELECT * FROM mock_data WHERE CN_no IN ({$string})";
harry
  • 483
  • 2
  • 12
-1
$input = '42-7278954,53-1217544,07-2517487';

echo $strNew=str_replace(",","','",$input);

$sql = "SELECT * FROM mock_data WHERE CN_no IN ('".$strNew."')";
$row=mysqli_query($conn,$query)or die("not fire");

try this code

-1

It seems that in-clause in not getting generated correctly by your implementation.

To generate the in-clause correctly, you need to split the user input into array and then join back in-list. For example, if user have given 42-7278954,53-1217544,07-2517487 as input, in-list should be look like:

CN_No in ( '42-7278954' , '53-1217544' , '07-2517487');

Given that Shipment code and Reference code are string value, you have to enclose them in quotes ' '.

Following example may help you to generate in-list expression using implode and explode (php is very little known to me)

$in_list_cns = implode( "' , '", explode(",", $CN_no)); 
$sql = "SELECT * FROM mock_data WHERE CN_no IN ( '{$in_list_cns}')" 

Hope this help.

Also, as mentioned by other too, you may need to sanitize user input to prevent SQL-Injection attacks.

skadya
  • 4,330
  • 19
  • 27
-3

try

$cn = explode(',', $CN_no);
$incn = str_repeat('?, ', count($cn)-1).'?';
$doc = explode(',', $doc_no);
$indoc = str_repeat('?, ', count($doc)-1).'?';

$query = "SELECT * FROM mock_data WHERE CN_no IN ('.$incn.') or doc_no IN ({$indoc})";
$result = $conn->query($query , array_merge($incn, $indoc));
  • Welcome to StackOverflow. This is basically a code only answer. Please elaborate on your code example. – hotzst Jan 05 '17 at 15:28
  • That's a clever way to get the suitable number of "?" in the appropriate places. However, the quoting should be avoided since `query()` will add quotes. – Rick James Jan 07 '17 at 04:12
  • [mysqli::query()](http://php.net/manual/en/mysqli.query.php) will not accept an array as second parameter. Please read the [documentation](http://php.net/manual/en/mysqli.query.php). – Paul Spiegel Jan 07 '17 at 20:21