¿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í intenta pasar una matriz de valores para usar con una condición IN() .
No soy muy bueno para explicar, así que aquí hay un psuedocode para demostrar. Me gustaría poder hacer algo como esto:

 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:

 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?

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

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

arreglar: 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, por lo que el bucle foreach y $stmt->execute podrían reemplazarse por …

 execute($ids); ?> 

(nuevamente, no lo probé)

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

¿Es tan importante usar la statement IN ? Intenta 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 utiliza el índice en una tabla, y aún no está implementado; vea 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 dicha cadena de la manera correcta después de la implode ya que utiliza el símbolo de coma como separador. Gracias a @VaL por la nota.

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

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ás cuando sea el momento de ejecutar tu consulta. ¡Fácil!

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

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

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

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.

En cuanto a PDO: constantes predefinidas, no hay PDO :: PARAM_ARRAY que necesitaría como se indica en PDOStatement-> bindParam

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

Entonces no creo que sea alcanzable.

También me doy cuenta de que este hilo es antiguo, pero tuve un problema único en el que, al convertir el controlador mysql en desuso futuro al controlador PDO, tuve que hacer una función que pudiera construir, dinámicamente, tanto params normales como IN de la misma param array. Así que rápidamente construí esto:

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

Todavía no se ha probado, sin embargo, la lógica parece estar allí.

Espero que ayude a alguien en la misma posición,

Editar: Después de algunas pruebas descubrí:

  • A PDO no le gusta '.' en sus nombres (lo cual es un poco estúpido si me preguntas)
  • bindParam es la función incorrecta, bindValue es la función correcta.

Código editado a la versión de trabajo.

Cuando tienes otro parámetro, puedes hacer esto:

 $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; $iprepare($query); $stmt->bindValue(':x', 123); // some value for($i=0; $ibindValue(':p'.$i, $ids[$i]); } $stmt->execute(); 

Una pequeña edición sobre el código de Schnalle

 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 

¿Qué base de datos estas usando? En PostgreSQL me gusta usar CUALQUIER (matriz). Para reutilizar tu ejemplo:

 prepare( 'SELECT * FROM table WHERE id = ANY (:an_array)' ); $stmt->bindParam('an_array',$ids); $stmt->execute(); ?> 

Lamentablemente, esto es bastante no portátil.

En otras bases de datos necesitarás inventar tu propia magia como otros mencionaron. Querrá poner esa lógica en una clase / función para que sea reutilizable en todo su progtwig, por supuesto. Eche un vistazo a los comentarios en la página mysql_query en PHP.NET para obtener más ideas sobre el tema y ejemplos de este escenario.

Después de pasar por el mismo problema, fui a una solución más simple (aunque todavía no tan elegante como sería un PDO::PARAM_ARRAY ):

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

… y así

Entonces, si está usando una matriz de valores mixtos, necesitará más código para probar sus valores antes de asignar el tipo 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 )); 

Pero no he probado este.

Como sé, no hay ninguna posibilidad de vincular una matriz a la instrucción PDO.

Pero existe 2 soluciones comunes:

  1. Utilice marcadores de posición posicionales (?,?,?,?) O marcadores de posición con nombre (: id1,: id2,: id3)

    $ whereIn = implode (‘,’, array_fill (0, count ($ ids), ‘?’));

  2. Citar matriz anterior

    $ whereIn = array_map (array ($ db, ‘quote’), $ ids);

Ambas opciones son buenas y seguras. Prefiero el segundo porque es más corto y puedo var_dump parámetros si lo necesito. Al usar marcadores de posición, debe vincular valores y, al final, su código SQL será el mismo.

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

Y lo último e importante para mí es evitar el error “el número de variables vinculadas no coincide con el número de tokens”

Doctrine es un gran ejemplo del uso de marcadores de posición posicionales, solo porque tiene control interno sobre los parámetros entrantes.

Para mí, la solución más atractiva es construir una matriz asociativa dinámica y usarla

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

aquí está mi solución. También extendí la clase 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); } } } 

Aquí hay un uso de muestra para el código anterior:

 $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']; } 

Déjame saber lo que piensas

Si la columna solo puede contener números enteros, probablemente podría hacer esto sin marcadores de posición y simplemente colocar los identificadores en la consulta directamente. Solo tiene que convertir todos los valores de la matriz en enteros. Me gusta esto:

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

Esto no debería ser vulnerable a ninguna inyección SQL.

Me llevé un poco más para obtener la respuesta más cerca de la pregunta original de usar marcadores de posición para unir los parámetros.

Esta respuesta tendrá que hacer dos bucles a través de la matriz para usar en la consulta. Pero resuelve el problema de tener otros marcadores de posición de columna para consultas más selectivas.

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

primero establece el número de “?” en la consulta y luego por un “para” enviar parámetros como este:

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