복붙노트

배열을 IN () 조건에 바인딩 할 수 있습니까?

PHP

배열을 IN () 조건에 바인딩 할 수 있습니까?

PDO를 사용하여 값 배열을 자리 표시 자에 바인딩 할 수 있는지 궁금합니다. 여기서 사용 사례는 IN () 조건과 함께 사용할 값 배열을 전달하려고 시도하는 것입니다.

나는 이런 식으로 할 수 있기를 바란다 :

<?php
$ids=array(1,2,3,7,8,9);
$db = new PDO(...);
$stmt = $db->prepare(
    'SELECT *
     FROM table
     WHERE id IN(:an_array)'
);
$stmt->bindParam('an_array',$ids);
$stmt->execute();
?>

PDO가 배열의 모든 값을 바인드하고 인용하도록하십시오.

지금 내가하고있는 순간 :

<?php
$ids = array(1,2,3,7,8,9);
$db = new PDO(...);
foreach($ids as &$val)
    $val=$db->quote($val); //iterate through array and quote
$in = implode(',',$ids); //create comma separated list
$stmt = $db->prepare(
    'SELECT *
     FROM table
     WHERE id IN('.$in.')'
);
$stmt->execute();
?>

확실하게 그 일을하고 있지만, 솔루션이 내장되어 있는지 없는지 궁금합니다.

해결법

  1. ==============================

    1.

    나는 soulmerge가 옳다고 생각해. query-string을 생성해야합니다.

    <?php
    $ids     = array(1, 2, 3, 7, 8, 9);
    $inQuery = implode(',', array_fill(0, count($ids), '?'));
    
    $db = new PDO(...);
    $stmt = $db->prepare(
        'SELECT *
         FROM table
         WHERE id IN(' . $inQuery . ')'
    );
    
    // bindvalue is 1-indexed, so $k+1
    foreach ($ids as $k => $id)
        $stmt->bindValue(($k+1), $id);
    
    $stmt->execute();
    ?>
    

    고칠 : 댄, 네가 옳았다. 고정 코드 (비록 그것을 테스트하지 않았다)

    편집 : chris (의견) 및 somebodyisintrouble 제안 foreach 루프 ...

    (...)
    // bindvalue is 1-indexed, so $k+1
    foreach ($ids as $k => $id)
        $stmt->bindValue(($k+1), $id);
    
    $stmt->execute();
    

    ... 중복 될 수 있으므로 foreach 루프와 $ stmt-> execute는 ...로 대체 될 수 있습니다.

    <?php 
      (...)
      $stmt->execute($ids);
    ?>
    

    (다시, 나는 그것을 시험하지 않았다)

  2. ==============================

    2.

    빠른 것을 위해 :

    //$db = new PDO(...);
    //$ids = array(...);
    
    $qMarks = str_repeat('?,', count($ids) - 1) . '?';
    $sth = $db->prepare("SELECT * FROM myTable WHERE id IN ($qMarks)");
    $sth->execute($ids);
    
  3. ==============================

    3.

    IN 문을 사용하는 것이 중요합니까? FIND_IN_SET op를 사용해보십시오.

    예를 들어 PDO에는 다음과 같은 쿼리가 있습니다.

    SELECT * FROM table WHERE FIND_IN_SET(id, :array)
    

    다음과 같이 쉼표로 내포 된 값 배열을 바인딩해야합니다.

    $ids_string = implode(',', $array_of_smth); // WITHOUT WHITESPACES BEFORE AND AFTER THE COMMA
    $stmt->bindParam('array', $ids_string);
    

    끝났어.

    UPD : 어떤 사람들은이 대답에 대한 논평에서 지적했듯이 명시 적으로 언급해야 할 몇 가지 이슈가 있습니다.

    인덱스에 크게 의존하지 않고 검색에 쉼표가 포함 된 문자열을 사용하지 않는다면 위의 솔루션보다 훨씬 쉽고 빠르며 빠릅니다.

  4. ==============================

    4.

    동적 쿼리가 많기 때문에 이것은 내가 작성한 수퍼 단순 도우미 함수입니다.

    public static function bindParamArray($prefix, $values, &$bindArray)
    {
        $str = "";
        foreach($values as $index => $value){
            $str .= ":".$prefix.$index.",";
            $bindArray[$prefix.$index] = $value;
        }
        return rtrim($str,",");     
    }
    

    다음과 같이 사용하십시오.

    $bindString = helper::bindParamArray("id", $_GET['ids'], $bindArray);
    $userConditions .= " AND users.id IN($bindString)";
    

    id1, id2, id3 문자열을 반환하고 쿼리를 실행할 때 필요한 $ bindingArray 바인딩을 업데이트합니다. 쉬운!

  5. ==============================

    5.

    EvilRygy의 솔루션이 저에게 효과적이지 않았습니다. Postgres에서 다른 해결 방법을 사용할 수 있습니다 :

    
    $ids = array(1,2,3,7,8,9);
    $db = new PDO(...);
    $stmt = $db->prepare(
        'SELECT *
         FROM table
         WHERE id = ANY (string_to_array(:an_array, ','))'
    );
    $stmt->bindParam(':an_array', implode(',', $ids));
    $stmt->execute();
    
  6. ==============================

    6.

    postgres를위한 매우 깨끗한 방법은 postgres-array ( "{}")를 사용하는 것입니다 :

    $ids = array(1,4,7,9,45);
    $param = "{".implode(', ',$ids)."}";
    $cmd = $db->prepare("SELECT * FROM table WHERE id = ANY (?)");
    $result = $cmd->execute(array($param));
    
  7. ==============================

    7.

    나는 stef가 제안한 것과 비슷한 것을하기 위해 PDO를 확장 시켰고, 장기적으로 나에게는 더 쉬웠다.

    class Array_Capable_PDO extends PDO {
        /**
         * Both prepare a statement and bind array values to it
         * @param string $statement mysql query with colon-prefixed tokens
         * @param array $arrays associatve array with string tokens as keys and integer-indexed data arrays as values 
         * @param array $driver_options see php documention
         * @return PDOStatement with given array values already bound 
         */
        public function prepare_with_arrays($statement, array $arrays, $driver_options = array()) {
    
            $replace_strings = array();
            $x = 0;
            foreach($arrays as $token => $data) {
                // just for testing...
                //// tokens should be legit
                //assert('is_string($token)');
                //assert('$token !== ""');
                //// a given token shouldn't appear more than once in the query
                //assert('substr_count($statement, $token) === 1');
                //// there should be an array of values for each token
                //assert('is_array($data)');
                //// empty data arrays aren't okay, they're a SQL syntax error
                //assert('count($data) > 0');
    
                // replace array tokens with a list of value tokens
                $replace_string_pieces = array();
                foreach($data as $y => $value) {
                    //// the data arrays have to be integer-indexed
                    //assert('is_int($y)');
                    $replace_string_pieces[] = ":{$x}_{$y}";
                }
                $replace_strings[] = '('.implode(', ', $replace_string_pieces).')';
                $x++;
            }
            $statement = str_replace(array_keys($arrays), $replace_strings, $statement);
            $prepared_statement = $this->prepare($statement, $driver_options);
    
            // bind values to the value tokens
            $x = 0;
            foreach($arrays as $token => $data) {
                foreach($data as $y => $value) {
                    $prepared_statement->bindValue(":{$x}_{$y}", $value);
                }
                $x++;
            }
    
            return $prepared_statement;
        }
    }
    

    다음과 같이 사용할 수 있습니다.

    $db_link = new Array_Capable_PDO($dsn, $username, $password);
    
    $query = '
        SELECT     *
        FROM       test
        WHERE      field1 IN :array1
         OR        field2 IN :array2
         OR        field3 = :value
    ';
    
    $pdo_query = $db_link->prepare_with_arrays(
        $query,
        array(
            ':array1' => array(1,2,3),
            ':array2' => array(7,8,9)
        )
    );
    
    $pdo_query->bindValue(':value', '10');
    
    $pdo_query->execute();
    
  8. ==============================

    8.

    여기 내 해결책은 :

    $total_items = count($array_of_items);
    $question_marks = array_fill(0, $total_items, '?');
    $sql = 'SELECT * FROM foo WHERE bar IN (' . implode(',', $question_marks ). ')';
    
    $stmt = $dbh->prepare($sql);
    $stmt->execute(array_values($array_of_items));
    

    array_values의 사용에 유의하십시오. 이로 인해 주요 주문 문제가 해결 될 수 있습니다.

    ID의 배열을 병합 한 다음 중복 된 항목을 제거했습니다. 나는 다음과 같은 것을 가지고 있었다 :

    $ids = array(0 => 23, 1 => 47, 3 => 17);
    

    그리고 그것은 실패했습니다.

  9. ==============================

    9.

    PDO : 미리 정의 된 상수를 살펴보면 PDOStatement-> bindParam에 나열된대로 필요한 PDO :: PARAM_ARRAY가 없습니다.

    그래서 나는 그것이 달성 가능하다고 생각하지 않는다.

  10. ==============================

    10.

    다른 매개 변수가 있으면 다음과 같이 할 수 있습니다.

    $ids = array(1,2,3,7,8,9);
    $db = new PDO(...);
    $query = 'SELECT *
                FROM table
               WHERE X = :x
                 AND id IN(';
    $comma = '';
    for($i=0; $i<count($ids); $i++){
      $query .= $comma.':p'.$i;       // :p0, :p1, ...
      $comma = ',';
    }
    $query .= ')';
    
    $stmt = $db->prepare($query);
    $stmt->bindValue(':x', 123);  // some value
    for($i=0; $i<count($ids); $i++){
      $stmt->bindValue(':p'.$i, $ids[$i]);
    }
    $stmt->execute();
    
  11. ==============================

    11.

    나는 또한이 스레드가 오래되었음을 깨닫는다. 그러나 PDO 드라이버로 곧 나올 deprecated mysql 드라이버를 변환하는 동안 동적으로 빌드 할 수있는 함수를 만들어야하는 동시에 고유 한 문제가 발생했다. param 배열 그래서 저는 이것을 신속하게 만들었습니다 :

    /**
     * mysql::pdo_query('SELECT * FROM TBL_WHOOP WHERE type_of_whoop IN :param AND siz_of_whoop = :size', array(':param' => array(1,2,3), ':size' => 3))
     *
     * @param $query
     * @param $params
     */
    function pdo_query($query, $params = array()){
    
        if(!$query)
            trigger_error('Could not query nothing');
    
        // Lets get our IN fields first
        $in_fields = array();
        foreach($params as $field => $value){
            if(is_array($value)){
                for($i=0,$size=sizeof($value);$i<$size;$i++)
                    $in_array[] = $field.$i;
    
                $query = str_replace($field, "(".implode(',', $in_array).")", $query); // Lets replace the position in the query string with the full version
                $in_fields[$field] = $value; // Lets add this field to an array for use later
                unset($params[$field]); // Lets unset so we don't bind the param later down the line
            }
        }
    
        $query_obj = $this->pdo_link->prepare($query);
        $query_obj->setFetchMode(PDO::FETCH_ASSOC);
    
        // Now lets bind normal params.
        foreach($params as $field => $value) $query_obj->bindValue($field, $value);
    
        // Now lets bind the IN params
        foreach($in_fields as $field => $value){
            for($i=0,$size=sizeof($value);$i<$size;$i++)
                $query_obj->bindValue($field.$i, $value[$i]); // Both the named param index and this index are based off the array index which has not changed...hopefully
        }
    
        $query_obj->execute();
    
        if($query_obj->rowCount() <= 0)
            return null;
    
        return $query_obj;
    }
    

    아직 테스트되지는 않았지만 로직이있는 것처럼 보입니다.

    그것이 같은 위치에있는 누군가를 돕기를 바랍니다.

    편집 : 일부 테스트 후 발견 :

    코드가 작업중인 버전으로 편집되었습니다.

  12. ==============================

    12.

    Schnalle의 코드에 대한 약간의 편집

    <?php
    $ids     = array(1, 2, 3, 7, 8, 9);
    $inQuery = implode(',', array_fill(0, count($ids)-1, '?'));
    
    $db   = new PDO(...);
    $stmt = $db->prepare(
        'SELECT *
         FROM table
         WHERE id IN(' . $inQuery . ')'
    );
    
    foreach ($ids as $k => $id)
        $stmt->bindValue(($k+1), $id);
    
    $stmt->execute();
    ?>
    
    //implode(',', array_fill(0, count($ids)-1), '?')); 
    //'?' this should be inside the array_fill
    //$stmt->bindValue(($k+1), $in); 
    // instead of $in, it should be $id
    
  13. ==============================

    13.

    어떤 데이터베이스를 사용하고 있습니까? PostgreSQL에서는 ANY (배열)을 사용하는 것을 좋아합니다. 예를 재사용하려면 다음을 수행하십시오.

    <?php
    $ids=array(1,2,3,7,8,9);
    $db = new PDO(...);
    $stmt = $db->prepare(
        'SELECT *
         FROM table
         WHERE id = ANY (:an_array)'
    );
    $stmt->bindParam('an_array',$ids);
    $stmt->execute();
    ?>
    

    불행히도 이것은 꽤 휴대하기 어렵습니다.

    다른 데이터베이스에서는 다른 사람들이 언급 한 것처럼 자신 만의 마법을 만들어야합니다. 그 논리를 클래스 / 함수에 넣으면 프로그램 전체에서 재사용 할 수 있습니다. PHP.NET의 mysql_query 페이지에서이 시나리오의 주제와 예제에 대한 더 많은 의견을 얻으십시오.

  14. ==============================

    14.

    나를 위해 더 멋진 해결책은 동적 연관 배열을 만들고 그것을 사용하는 것입니다.

    // a dirty array sent by user
    $dirtyArray = ['Cecile', 'Gilles', 'Andre', 'Claude'];
    
    // we construct an associative array like this
    // [ ':name_0' => 'Cecile', ... , ':name_3' => 'Claude' ]
    $params = array_combine(
        array_map(
            // construct param name according to array index
            function ($v) {return ":name_{$v}";},
            // get values of users
            array_keys($dirtyArray)
        ),
        $dirtyArray
    );
    
    // construct the query like `.. WHERE name IN ( :name_1, .. , :name_3 )`
    $query = "SELECT * FROM user WHERE name IN( " . implode(",", array_keys($params)) . " )";
    // here we go
    $stmt  = $db->prepare($query);
    $stmt->execute($params);
    
  15. ==============================

    15.

    같은 문제를 겪은 후에, 나는 더 간단한 솔루션으로 갔다. (여전히 PDO만큼 우아하지는 않지만 : PARAM_ARRAY가 될 것이다.)

    주어진 배열 $ ids = array (2, 4, 32) :

    $newparams = array();
    foreach ($ids as $n => $val){ $newparams[] = ":id_$n"; }
    
    try {
        $stmt = $conn->prepare("DELETE FROM $table WHERE ($table.id IN (" . implode(", ",$newparams). "))");
        foreach ($ids as $n => $val){
            $stmt->bindParam(":id_$n", intval($val), PDO::PARAM_INT);
        }
        $stmt->execute();
    

    ... 등등

    따라서 혼합 값 배열을 사용하는 경우 형식 param을 할당하기 전에 값을 테스트하는 데 더 많은 코드가 필요합니다.

    // inside second foreach..
    
    $valuevar = (is_float($val) ? floatval($val) : is_int($val) ? intval($val) :  is_string($val) ? strval($val) : $val );
    $stmt->bindParam(":id_$n", $valuevar, (is_int($val) ? PDO::PARAM_INT :  is_string($val) ? PDO::PARAM_STR : NULL ));
    

    그러나 나는 이것을 시험하지 않았다.

  16. ==============================

    16.

    PDO 선언문에 배열을 바인딩 할 가능성은 없습니다.

    그러나 두 가지 공통적 인 해결책이 존재합니다 :

    두 옵션 모두 안전하고 안전합니다. 내가 더 짧기 때문에 두 번째를 선호하고, 필요하다면 매개 변수를 var_dump 할 수 있습니다. 자리 표시자를 사용하면 값을 바인드해야하며 결국 SQL 코드가 동일하게됩니다.

    $sql = "SELECT * FROM table WHERE id IN ($whereIn)";
    

    그리고 마지막으로 중요하게 생각하는 것은 "바인딩 된 변수의 수가 토큰의 수와 일치하지 않습니다"라는 오류를 피하는 것입니다.

    Doctrine은 들어오는 매개 변수를 내부적으로 제어 할 수 있기 때문에 위치 고정 자리 표시자를 사용하는 좋은 예입니다.

  17. ==============================

    17.

    여기 내 해결책이다. PDO 클래스를 확장했습니다.

    class Db extends PDO
    {
    
        /**
         * SELECT ... WHERE fieldName IN (:paramName) workaround
         *
         * @param array  $array
         * @param string $prefix
         *
         * @return string
         */
        public function CreateArrayBindParamNames(array $array, $prefix = 'id_')
        {
            $newparams = [];
            foreach ($array as $n => $val)
            {
                $newparams[] = ":".$prefix.$n;
            }
            return implode(", ", $newparams);
        }
    
        /**
         * Bind every array element to the proper named parameter
         *
         * @param PDOStatement $stmt
         * @param array        $array
         * @param string       $prefix
         */
        public function BindArrayParam(PDOStatement &$stmt, array $array, $prefix = 'id_')
        {
            foreach($array as $n => $val)
            {
                $val = intval($val);
                $stmt -> bindParam(":".$prefix.$n, $val, PDO::PARAM_INT);
            }
        }
    }
    

    다음은 위의 코드에 대한 샘플 사용법입니다.

    $idList = [1, 2, 3, 4];
    $stmt = $this -> db -> prepare("
      SELECT
        `Name`
      FROM
        `User`
      WHERE
        (`ID` IN (".$this -> db -> CreateArrayBindParamNames($idList)."))");
    $this -> db -> BindArrayParam($stmt, $idList);
    $stmt -> execute();
    foreach($stmt as $row)
    {
        echo $row['Name'];
    }
    

    당신이 무슨 생각을하는지 제게 알려주세요

  18. ==============================

    18.

    열에 정수만 포함될 수 있다면 자리 표시 자없이이 작업을 수행하고 ID를 직접 쿼리에 넣을 수 있습니다. 배열의 모든 값을 정수로 변환하면됩니다. 이렇게 :

    $listOfIds = implode(',',array_map('intval', $ids));
    $stmt = $db->prepare(
        "SELECT *
         FROM table
         WHERE id IN($listOfIds)"
    );
    $stmt->execute();
    

    이것은 SQL 주입에 취약하지 않아야합니다.

  19. ==============================

    19.

    PDO에서 이와 같은 배열을 사용할 수 없습니다.

    다음과 같이 각 값에 대해 매개 변수가있는 문자열을 작성해야합니다 (또는?).

    : an_array_0, : an_array_1, : an_array_2, : an_array_3, : an_array_4, : an_array_5

    다음은 그 예입니다.

    <?php
    $ids = array(1,2,3,7,8,9);
    $sqlAnArray = join(
        ', ',
        array_map(
            function($index) {
                return ":an_array_$index";
            },
            array_keys($ids)
        )
    );
    $db = new PDO(
        'mysql:dbname=mydb;host=localhost',
        'user',
        'passwd'
    );
    $stmt = $db->prepare(
        'SELECT *
         FROM table
         WHERE id IN('.$sqlAnArray.')'
    );
    foreach ($ids as $index => $id) {
        $stmt->bindValue("an_array_$index", $id);
    }
    

    bindParam을 계속 사용하려면 다음과 같이하십시오.

    foreach ($ids as $index => $id) {
        $stmt->bindParam("an_array_$index", $ids[$id]);
    }
    

    당신이 원한다면? 자리 표시자를 사용하면 다음과 같이 할 수 있습니다.

    <?php
    $ids = array(1,2,3,7,8,9);
    $sqlAnArray = '?' . str_repeat(', ?', count($ids)-1);
    $db = new PDO(
        'mysql:dbname=dbname;host=localhost',
        'user',
        'passwd'
    );
    $stmt = $db->prepare(
        'SELECT *
         FROM phone_number_lookup
         WHERE country_code IN('.$sqlAnArray.')'
    );
    $stmt->execute($ids);
    

    $ ids가 비어 있는지 여부를 모르는 경우이를 테스트하고 적절하게 처리해야합니다 (빈 배열을 반환하거나 Null 객체를 반환하거나 예외를 throw하는 등).

  20. ==============================

    20.

    매개 변수를 바인딩하기 위해 자리 표시자를 사용한다는 원래의 질문에 더 가까운 답을 얻으 려했습니다.

    이 대답은 쿼리에서 사용할 배열을 통해 두 개의 루프를 만들어야합니다. 그러나 더 많은 선택 쿼리를 위해 다른 열 자리 표시 자의 문제를 해결합니다.

    //builds placeholders to insert in IN()
    foreach($array as $key=>$value) {
        $in_query = $in_query . ' :val_' . $key . ', ';
    }
    
    //gets rid of trailing comma and space
    $in_query = substr($in_query, 0, -2);
    
    $stmt = $db->prepare(
        "SELECT *
         WHERE id IN($in_query)";
    
    //pind params for your placeholders.
    foreach ($array as $key=>$value) {
        $stmt->bindParam(":val_" . $key, $array[$key])
    }
    
    $stmt->execute();
    
  21. ==============================

    21.

    당신은 처음에 "?" 쿼리에서 "for"매개 변수로 보내기 이렇게 :

    require 'dbConnect.php';
    $db=new dbConnect();
    $array=[];
    array_push($array,'value1');
    array_push($array,'value2');
    $query="SELECT * FROM sites WHERE kind IN (";
    
    foreach ($array as $field){
        $query.="?,";
    }
    $query=substr($query,0,strlen($query)-1);
    $query.=")";
    $tbl=$db->connection->prepare($query);
    for($i=1;$i<=count($array);$i++)
        $tbl->bindParam($i,$array[$i-1],PDO::PARAM_STR);
    $tbl->execute();
    $row=$tbl->fetchAll(PDO::FETCH_OBJ);
    var_dump($row);
    
  22. from https://stackoverflow.com/questions/920353/can-i-bind-an-array-to-an-in-condition by cc-by-sa and MIT lisence