programing

PDO 쿼리에서 배열을 IN() 조건에 바인드할 수 있습니까?

minecode 2022. 12. 30. 17:20
반응형

PDO 쿼리에서 배열을 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();
?>

어떤 것이 확실히 도움이 될까요? 하지만 제가 놓치고 있는 솔루션이 내장되어 있지 않은지 궁금해서요.

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(댓글)와 somebody introuble 모두 foreach-loop이...

(...)
// 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);

빠른 작업:

//$db = new PDO(...);
//$ids = array(...);

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

사용하는 것이 그렇게 중요합니까?IN스테이트먼트?사용해보십시오.FIND_IN_SETop.

예를 들어 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: 이 답변에 대한 댓글에서 지적된 바와 같이 설명해야 할 몇 가지 문제가 있습니다.

  1. FIND_IN_SET테이블에서 인덱스를 사용하지 않으며 아직 구현되지 않았습니다. MYSQL 버그 트래커의 이 레코드를 참조하십시오.통지해 주신 @BillKarwin님 감사합니다.
  2. 검색용 배열 값으로 쉼표가 포함된 문자열을 사용할 수 없습니다.의 해석은, 「이 문자열은 올바른 할 수 .implode쉼표 기호를 구분 기호로 사용하기 때문입니다.@VaL로 하다.

즉, 인덱스에 크게 의존하지 않고 검색에 쉼표가 있는 문자열을 사용하지 않는 경우 위에 나열된 솔루션보다 훨씬 쉽고 단순하며 빠릅니다.

동적 쿼리를 많이 하기 때문에 제가 만든 초심플 도우미 기능입니다.

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도 할 수 있어요.$bindArray쿼리를 실행할 때 필요한 바인딩의 수를 지정합니다.

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));

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();

저의 솔루션은 다음과 같습니다.

$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);

그리고 그것은 실패였다.

다른 파라미터가 있는 경우 다음과 같이 할 수 있습니다.

$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();

Stefs가 제안하는 것과 유사한 작업을 하기 위해 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();

저에게 있어 보다 섹시한 솔루션은 동적 연상 어레이를 구축하여 사용하는 것입니다.

// 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);

PDO: 사전 정의된 상수를 보면 PDO가 없습니다.필요한 PARAM_ARRAY는 PDOStatement에 기재되어 있습니다.-> bindParam

bool PDOStatement : : bindParam ( mixed $ parameter , mixed & $ variable [ , int $data _ type [ , int $length [ , mixed $driver _ options ] )

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

저도 이 스레드가 오래되었다는 것을 알지만, 곧 폐지될 mysql 드라이버를 PDO 드라이버로 변환하는 동안 동일한 파라미터 어레이에서 일반 파라미터와 IN을 동적으로 빌드할 수 있는 기능을 만들어야 하는 독특한 문제가 있었습니다.그래서 저는 이것을 재빨리 만들었습니다.

/**
 * 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;
}

그것은 아직 검증되지 않았지만 논리는 거기에 있는 것처럼 보인다.

같은 처지에 있는 사람에게 도움이 되길 바랍니다.

편집: 몇 가지 테스트 결과:

  • PDO는 그들의 이름에서 ''를 좋아하지 않는다.(내게는 좀 바보같다)
  • bindParam은 잘못된 함수이고 bindValue는 올바른 함수입니다.

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

슈날레 코드에 대한 약간의 편집

<?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

어떤 데이터베이스를 사용하고 있습니까?포스트그레SQL은 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();
?>

유감스럽게도 이것은 휴대할 수 없습니다.

다른 데이터베이스에서는 다른 사용자가 언급한 것처럼 자신만의 마법을 만들어야 합니다.물론 이 논리를 클래스/함수에 넣어 프로그램 전체에서 재사용할 수 있도록 해야 합니다.mysql_query페이지를 표시합니다.이 시나리오의 주제와 예에 대한 자세한 내용은 NET을 참조하십시오.

열에 정수만 포함할 수 있는 경우, 플레이스 홀더 없이 이 작업을 수행할 수 있으며 ID를 쿼리에 직접 넣을 수 있습니다.배열의 모든 값을 정수로 지정하기만 하면 됩니다.다음과 같이 합니다.

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

SQL 주입에 취약해서는 안 됩니다.

배열을 PDO 스테이트먼트에 바인드할 가능성은 없는 것으로 알고 있습니다.

단, 2가지 일반적인 솔루션이 있습니다.

  1. 위치 표시자(?,?,?) 또는 명명된 자리 표시자(:id1,:id2,:id3)를 사용합니다.

    $whereIn = innode", , array_fill(0, counts-ids), '?';

  2. 어레이에 대한 이전 견적

    $whereIn = array_map(array_db, 'disc'), $ids;

두 가지 옵션 모두 안전합니다.두 번째 것이 더 짧고 필요에 따라 파라미터를 var_dump할 수 있기 때문에 두 번째 것이 좋습니다.자리 표시자를 사용하여 값을 바인딩해야 하며, 결국 SQL 코드는 동일합니다.

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

마지막으로 중요한 것은 "바인드 변수의 수가 토큰 수와 일치하지 않는다"는 오류를 피하는 것입니다.

원칙: 들어오는 매개변수에 대한 내부 제어가 있기 때문에 위치 표시자를 사용하는 좋은 예입니다.

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 그 가 있습니다(Object를 합니다

는 더 .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();

...등등

따라서 혼합값 배열을 사용하는 경우 type 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 ));

하지만 나는 이것을 테스트하지 않았다.

여기 내 해결책이 있다.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'];
}

당신의 의견을 저에게 알려주십시오.

와 MySQL PDO를 할 수 .JSON_CONTAINS()(https://dev.mysql.com/doc/refman/8.0/en/json-search-functions.html#function_json-contains) 를 참조해 주세요.

$ids = [123, 234, 345, 456]; // Array of users I search
$ids = json_encode($ids); // JSON conversion

$sql = <<<SQL
    SELECT ALL user_id, user_login
    FROM users
    -- Cast is mandatory beaucause JSON_CONTAINS() waits JSON doc candidate
    WHERE JSON_CONTAINS(:ids, CAST(user_id AS JSON))
    SQL;

$search = $pdo->prepare($sql);
$search->execute([':ids' => $ids]);
$users = $search->fetchAll();

또, 사용할 수 있는 것은JSON_TABLE()(더 복잡한 사례 및 JSON 데이터 탐색에 대해서는 https://dev.mysql.com/doc/refman/8.0/en/json-table-functions.html#function_json-table)를 참조하십시오.

$users = [
    ['id' => 123, 'bday' => ..., 'address' => ...],
    ['id' => 234, 'bday' => ..., 'address' => ...],
    ['id' => 345, 'bday' => ..., 'address' => ...],
]; // I'd like to know their login

$users = json_encode($users);

$sql = <<<SQL
    SELECT ALL user_id, user_login
    FROM users
    WHERE user_id IN (
        SELECT ALL user_id
        FROM JSON_TABLE(:users, '$[*]' COLUMNS (
            -- Data exploration...
            -- (if needed I can explore really deeply with NESTED kword)
            user_id INT PATH '$.id',
            -- I could skip these :
            user_bday DATE PATH '$.bday',
            user_address TINYTEXT PATH '$.address'
        )) AS _
    )
    SQL;

$search = $pdo->prepare($sql);
$search->execute([':users' => $users]);
...

저는 좀 더 나아가서 자리 표시자를 사용하여 파라메스를 묶는 원래의 질문에 더 가까운 답을 얻었습니다.

이 답변은 쿼리에서 사용할 어레이를 통해 2개의 루프를 만들어야 합니다.그러나 더 선별적인 쿼리를 위해 다른 열 자리 표시자를 갖는 문제는 해결됩니다.

//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();

먼저 쿼리에서 "?" 번호를 설정한 후 다음과 같은 "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);

언급URL : https://stackoverflow.com/questions/920353/can-i-bind-an-array-to-an-in-condition-in-a-pdo-query

반응형