[php] PHP-IN 절 배열과 함께 PDO 사용

PDO를 IN사용하여 값에 배열을 사용하는 절이 있는 문을 실행 하고 있습니다.

$in_array = array(1, 2, 3);
$in_values = implode(',', $in_array);
$my_result = $wbdb->prepare("SELECT * FROM my_table WHERE my_value IN (".$in_values.")");
$my_result->execute();
$my_results = $my_result->fetchAll();

위의 코드는 완벽하게 작동하지만 내 질문은 이것이 왜 그렇지 않습니다.

 $in_array = array(1, 2, 3);
    $in_values = implode(',', $in_array);
    $my_result = $wbdb->prepare("SELECT * FROM my_table WHERE my_value IN (:in_values)");
    $my_result->execute(array(':in_values' => $in_values));
    $my_results = $my_result->fetchAll();

이 코드는 (1) my_value의 첫 번째 항목과 동일한 항목을 반환 $in_array하지만 배열의 나머지 항목 (2 및 3) 은 반환 하지 않습니다.



답변

PDO는 그런 것들에 좋지 않습니다. 플레이스 홀더가있는 문자열을 동적으로 생성하여 쿼리에 삽입하고 일반적인 방법으로 배열 값을 바인딩해야합니다. 위치 자리 표시자를 사용하면 다음과 같습니다.

$in  = str_repeat('?,', count($in_array) - 1) . '?';
$sql = "SELECT * FROM my_table WHERE my_value IN ($in)";
$stm = $db->prepare($sql);
$stm->execute($in_array);
$data = $stm->fetchAll();

쿼리에 다른 자리 표시자가있는 경우 다음 접근 방식을 사용할 수 있습니다 (코드는 내 PDO 자습서 에서 가져옴 ).

array_merge()함수를 사용 하여 모든 변수를 단일 배열로 조인하고 다른 변수를 쿼리에 나타나는 순서대로 배열 형식으로 추가 할 수 있습니다.

$arr = [1,2,3];
$in  = str_repeat('?,', count($arr) - 1) . '?';
$sql = "SELECT * FROM table WHERE foo=? AND column IN ($in) AND bar=? AND baz=?";
$stm = $db->prepare($sql);
$params = array_merge([$foo], $arr, [$bar, $baz]);
$stm->execute($params);
$data = $stm->fetchAll();

명명 된 자리 표시자를 사용하는 경우 명명 된 자리 표시 자의 시퀀스를 만들어야하므로 코드가 조금 더 복잡해집니다 (예 : :id0,:id1,:id2. 따라서 코드는 다음과 같습니다.

// other parameters that are going into query
$params = ["foo" => "foo", "bar" => "bar"];

$ids = [1,2,3];
$in = "";
$i = 0;// we are using an external counter 
// because the actual array keys could be dangerous
foreach ($ids as $item)
{
    $key = ":id".$i++;
    $in .= "$key,";
    $in_params[$key] = $item; // collecting values into a key-value array
}
$in = rtrim($in,","); // :id0,:id1,:id2

$sql = "SELECT * FROM table WHERE foo=:foo AND id IN ($in) AND bar=:bar";
$stm = $db->prepare($sql);
$stm->execute(array_merge($params,$in_params)); // just merge two arrays
$data = $stm->fetchAll();

다행히도 명명 된 자리 표시 자의 경우 엄격한 순서를 따를 필요가 없으므로 배열을 어떤 순서로든 병합 할 수 있습니다.


답변

PDO 준비 명령문의 변수 대체는 배열을 지원하지 않습니다. 그것은 하나입니다.

배열의 길이에 따라 필요한 자리 표시 자 수를 생성하여이 문제를 해결할 수 있습니다.

$variables = array ('1', '2', '3');
$placeholders = str_repeat ('?, ',  count ($variables) - 1) . '?';

$query = $pdo -> prepare ("SELECT * FROM table WHERE column IN($placeholders)");
if ($query -> execute ($variables)) {
    // ...
}


답변

PDO가 좋은 솔루션을 제공하지 않는 것 같으므로 대부분 PDO의 API를 따르지만 유용한 기능 http://docs.doctrine-project.org/projects/doctrine-dbal/을 추가하는 DBAL 사용을 고려하는 것이 좋습니다. en / latest / reference / data-retrieval-and-manipulation.html # list-of-parameters-conversion

$stmt = $conn->executeQuery('SELECT * FROM articles WHERE id IN (?)',
    array(array(1, 2, 3, 4, 5, 6)),
    array(\Doctrine\DBAL\Connection::PARAM_INT_ARRAY)
);

복잡성을 추가하지 않고 데이터베이스와의 상호 작용을 가리지 않는 (대부분의 ORM처럼) 다른 패키지가있을 수 있지만 동시에 작은 일반적인 작업을 조금 더 쉽게 만듭니다.


답변

클로저를 사용하는 PHP 망상 (@ your-common-sense)의 대체 버전 :

$filter      = ["min_price" => "1.98"];
$editions    = [1,2,10];

$editions = array_combine(
    array_map(function($i){ return ':id'.$i; }, array_keys($editions)),
    $editions
);
$in_placeholders = implode(',', array_keys($editions));
$sql = "SELECT * FROM books WHERE price >= :min_price AND edition IN ($in_placeholders)";
$stm = $pdo->prepare($sql);
$stm->execute(array_merge($filter,$editions));
$data = $stm->fetchAll();


답변

내가 이해하는 것은 PDO가 $ in_values ​​내용을 단일 항목으로 취급하고 그에 따라 꽤 할 것이기 때문입니다. PDO는 1,2,3을 단일 문자열로 인식하므로 쿼리는 다음과 같습니다.

SELECT * FROM table WHERE my_value IN ( “1,2,3”)

따옴표와 쉼표를 갖도록 내파를 변경하면 문제가 해결 될 것이라고 생각할 수 있지만 그렇지 않습니다. PDO는 따옴표를보고 문자열을 따옴표로 묶는 방법을 변경합니다.

귀하의 쿼리가 첫 번째 값과 일치하는 이유에 대해서는 설명이 없습니다.


답변

나는 방금이 문제에 맞서 작은 래퍼를 코딩했습니다. 내가 확신하는 가장 예쁘거나 최고의 코드는 아니지만 누군가에게 도움이 될 수 있으므로 여기에 있습니다.

function runQuery(PDO $PDO, string $sql, array $params = [])
{
    if (!count($params)) {
        return $PDO->query($sql);
    }

    foreach ($params as $key => $values) {
        if (is_array($values)) {
            // get placeholder from array, e.g. ids => [7,12,3] would be ':ids'
            $oldPlaceholder  = ':'.$key;
            $newPlaceholders = '';
            $newParams = [];
            // loop through array to create new placeholders & new named parameters
            for($i = 1; $i <= count($values); $i++) {
                // this gives us :ids1, :ids2, :ids3 etc
                $newKey = $oldPlaceholder.$i;
                $newPlaceholders .= $newKey.', ';
                // this builds an associative array of the new named parameters
                $newParams[$newKey] = $values[$i - 1];
            }
            //trim off the trailing comma and space
            $newPlaceholders = rtrim($newPlaceholders, ', ');

            // remove the old parameter
            unset($params[$key]);

            // and replace with the new ones
            $params = array_merge($params, $newParams);

            // amend the query
            $sql = str_replace($oldPlaceholder, $newPlaceholders, $sql);
        }
    }

    $statement = $PDO->prepare($sql);
    $statement->execute($params);
    return $statement;
}

예를 들어 다음을 전달합니다.

SELECT * FROM users WHERE userId IN (:ids)

array(1) {
  ["ids"]=>
  array(3) {
    [0]=>
    int(1)
    [1]=>
    int(2)
    [2]=>
    int(3)
  }
}

된다 :

SELECT * FROM users WHERE userId IN (:ids1, :ids2, :ids3)

array(3) {
  [":ids1"]=>
  int(1)
  [":ids2"]=>
  int(2)
  [":ids3"]=>
  int(3)
}

방탄은 아니지만 내 필요에 대한 유일한 개발자로서 어쨌든 지금까지 괜찮습니다.


답변

다음은 이름없는 자리 표시 자 (?)에 대한 솔루션입니다. “A =? AND B IN (?)” 과 같은 물음표와 함께 $ sql을 전달 하고 일부 요소가 [1, [1,2,3]]과 같은 배열 인 $ args를 전달하면 적절한 숫자의 SQL 문자열이 반환됩니다. 자리 표시 자 수- “A =? AND B IN (?,?,?)” . $ args 매개 변수는 배열 요소와 필요한 자리 표시 자 수를 찾는 데만 필요합니다. 쿼리를 실행할이 메서드를 사용하여 작은 PDO 확장 클래스를 찾을 수 있습니다.
https://github.com/vicF/pdo/blob/master/src/PDO.php

public function replaceArrayPlaceholders($sql, $args)
{
    $num = 0;
    preg_match_all('/\?/', $sql, $matches, PREG_OFFSET_CAPTURE);  // Captures positions of placeholders
    //echo $matches[0][1][1];
    $replacements = [];
    foreach($args as $arg) {
        if(is_array($arg)) {
            $replacements[$matches[0][$num][1]] = implode(',',array_fill(0, count($arg), '?')); // Create placeholders string
        }
        $num++;
    }
    krsort($replacements);
    foreach($replacements as $position => $placeholders) {
        $sql = substr($sql, 0, $position).$placeholders.substr($sql, $position+1); // Replace single placeholder with multiple
    }
    return $sql;
}