Pregunta ¿Puedo vincular una matriz a una condición IN ()?


Tengo curiosidad por saber si es posible vincular una matriz de valores a un marcador de posición con PDO. El caso de uso aquí está intentando pasar una matriz de valores para usar con un IN() condición.

Me gustaría poder hacer algo como esto:

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

Y haga que PDO enlace y cite todos los valores en la matriz.

En este momento estoy haciendo:

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

Lo cual ciertamente hace el trabajo, ¿pero me pregunto si me falta una solución integrada?


513
2018-05-28 11:17


origen


Respuestas:


creo que soulmerge tiene razón. Tendrás que construir la cadena de consulta.

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

fijar: Dan, tenías razón. corrigió el código (aunque no lo probó)

editar: ambos chris (comentarios) y alguien es insoportable sugirieron que el foreach-loop ...

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

$stmt->execute();

... podría ser redundante, entonces el foreach lazo y el $stmt->execute podría ser reemplazado por solo ...

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

(nuevamente, no lo probé)


241
2018-05-28 12:02



Para algo rápido:

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

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

157
2018-05-23 15:10



¿Es tan importante usar IN ¿declaración? Tratar de usar FIND_IN_SET op.

Por ejemplo, hay una consulta en PDO como esa

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

Entonces solo necesitas vincular una matriz de valores implosionados con coma, como este

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

y está hecho.

UPD: Como algunas personas señalaron en los comentarios a esta respuesta, hay algunos problemas que deben mencionarse explícitamente.

  1. FIND_IN_SET no usa el índice en una tabla, y todavía no está implementado - ver este registro en el rastreador de errores MYSQL. Gracias a @BillKarwin por el aviso.
  2. No puede usar una cadena con una coma dentro como valor de la matriz para búsqueda. Es imposible analizar esa cadena de la manera correcta después implode ya que usa el símbolo de coma como separador. Gracias a @VaL por la nota.

En definitiva, si no depende mucho de los índices y no usa cadenas con coma para búsqueda, mi solución será mucho más fácil, más simple y más rápida que las soluciones mencionadas anteriormente.


41
2017-10-02 23:22



Como realizo muchas consultas dinámicas, esta es una función auxiliar muy simple que hice.

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

Úselo así:

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

Devuelve una cadena :id1,:id2,:id3 y también actualiza tu $bindArray de enlaces que necesitará cuando sea el momento de ejecutar su consulta. ¡Fácil!


27
2018-03-26 14:10



La solución de EvilRygy no funcionó para mí. En Postgres puedes hacer otra solución:


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

16
2018-04-15 13:07



una manera muy limpia para Postgres es usar el 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));

14
2018-02-18 22:00



Extendí PDO para hacer algo similar a lo que Stefs sugiere, y fue más fácil para mí en el largo plazo:

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

Puedes usarlo así:

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

12
2018-01-21 12:15



Aquí está mi solución:

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

Tenga en cuenta el uso de array_values. Esto puede solucionar problemas de pedidos clave.

Estaba fusionando matrices de identificadores y luego eliminando elementos duplicados. Tenía algo así como:

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

Y eso estaba fallando.


11
2018-05-28 11:48



Mirando a PDO: constantes predefinidas no hay PDO :: PARAM_ARRAY que necesitaría tal como figura en la lista PDOStatement-> bindParam 

bool PDOStatement :: bindParam (mixed $ parameter, mixed y $ variable [, int $ data_type [, int $ length [, mixed $ driver_options]]])

Entonces no creo que sea alcanzable.


10
2018-03-18 19:16