2009-10-19 11 views
63

Tengo un problema con PDO al que me gustaría obtener una respuesta después de haber estado plagado por ello durante bastante tiempo.Valores de enlace de PDO para la instrucción MySQL IN

Tome este ejemplo:

estoy vinculante una serie de identificaciones con un comunicado DOP para su uso en un MySQL en un comunicado.

La matriz sería decir: $ values ​​= array (1,2,3,4,5,6,7,8);

La variable de base de datos segura sería $ products = implode (',' $ values);

Así, productos $ entonces serían una CADENA con un valor de: '1,2,3,4,5,6,7,8'

La declaración se vería como:

SELECT users.id 
FROM users 
JOIN products 
ON products.user_id = users.id 
WHERE products IN (:products) 

Por supuesto, productos $ estarían obligados a la declaración como : productos.

Sin embargo, cuando la instrucción se compila y los valores obligado, que en realidad se vería así:

SELECT users.id 
FROM users 
JOIN products 
ON products.user_id = users.id 
WHERE products IN ('1,2,3,4,5,6,7,8') 

El problema se está ejecutando todo el interior de la declaración en la que una sola cadena, teniendo en cuenta que yo Lo preparé como valores separados por comas para enlazar a la declaración.

Lo que realmente necesito es:

SELECT users.id 
FROM users 
JOIN products 
ON products.user_id = users.id 
WHERE products IN (1,2,3,4,5,6,7,8) 

La única manera realmente puedo hacer esto es mediante la colocación de los valores dentro de la propia cadena sin ellos vinculante, sin embargo sé con certeza que tiene que haber una manera más fácil para hacer esto.

+0

posible duplicado de [PHP PDO: ¿Puedo vincular una matriz a una condición IN()?] (Http: // stackoverflow.com/questions/920353/php-pdo-can-i-bind-un-array-to-an-in-condition) – PhoneixS

Respuesta

33

Esto es lo mismo que se le pidió en esta pregunta: Can I bind an array to an IN() condition?

La respuesta fue que no, para obtener una lista de tamaño variable en la cláusula in, que necesita para construir la consulta a sí mismo.

Sin embargo, puede utilizar la lista citada, separada por comas utilizando find_in_set, aunque para grandes conjuntos de datos, esto tendría un considerable impacto en el rendimiento, ya que cada valor de la tabla tiene que ser echado a un tipo char.

Por ejemplo:

select users.id 
from users 
join products 
on products.user_id = users.id 
where find_in_set(cast(products.id as char), :products) 

O, como tercera opción, puede crear una función definida por el usuario que se divide la lista separada por comas para usted (cf. http://www.slickdev.com/2008/09/15/mysql-query-real-values-from-delimiter-separated-string-ids/). Esta es probablemente la mejor opción de las tres, especialmente si tiene muchas consultas que se basan en las cláusulas in(...).

+63

Caray, PDO es una biblioteca tan ordenada pero extraña algunas de las funcionalidades más básicas. Es una verdadera lástima. – hd82

+0

Si tienes un pequeño conjunto de datos, puedes salirte con find_in_set (actualicé la respuesta con un ejemplo), o puedes probar una función definida por el usuario para analizar la coma. lista separada para ti. (Perdón por seguir agregando ideas, ja, ja; después de publicar el enlace a la otra pregunta, pensé: "tiene que haber una forma de hacerlo ...") –

+1

Gracias por el FIND_IN_SET mano a mano. Realmente salvó mi día. Pero descubrí que funciona sin tener que hacer un CAST, por lo que find_in_set (products.id,: products) es suficiente. – Andy

7

La mejor declaración preparada que probablemente se podría llegar a una situación como esta es algo parecido a lo siguiente:

SELECT users.id 
FROM users 
JOIN products 
ON products.user_id = users.id 
WHERE products IN (?,?,?,?,?,?,?,?)

A continuación, bucle a través de sus valores y los unirá a la declaración preparada asegurándose de que no son la misma cantidad de signos de interrogación que los valores que está vinculando.

+6

Solución lógica cuando sabes cuántas variables enlazar, pero se vuelve un poco complicado cuando tienes un indefinido cantidad de parámetros – hd82

+0

@ hd82: Bueno, entonces tienes que combinar inteligentemente sprintf y contar. – Boldewyn

1

Puede hacerlo muy fácilmente. Si tiene una matriz de valores para su IN() Declaración Ejem:

$test = array(1,2,3); 

Se puede hacer simplemente

$test = array(1,2,3); 
$values = count($test); 
$criteria = sprintf("?%s", str_repeat(",?", ($values ? $values-1 : 0))); 
//Returns ?,?,? 
$sql = sprintf("DELETE FROM table where column NOT IN(%s)", $criteria); 
//Returns DELETE FROM table where column NOT IN(?,?,?) 
$pdo->sth = prepare($sql); 
$pdo->sth->execute($test); 
+2

Esto ya se mencionó en la respuesta de Asaph anterior, y no funciona si está usando parámetros con nombre, ya que no puede mezclarlos. – cincodenada

26

Una buena manera de manejar esta situación es el uso de str_pad para colocar una ? para cada valor en la consulta SQL. A continuación, puede pasar a la matriz de valores (en su caso $values) como argumento para ejecutar:

$sql = ' 
SELECT users.id 
FROM users 
JOIN products 
ON products.user_id = users.id 
WHERE products.id IN ('.str_pad('',count($values)*2-1,'?,').') 
'; 

$sth = $dbh->prepare($sql); 
$sth->execute($values); 
$user_ids = $sth->fetchAll(); 

esta manera se obtiene un mayor beneficio de usar comandos preparados en vez de insertar los valores directamente en el SQL.

PS - Los resultados devolverán identificadores de usuario duplicados si los productos con los identificadores proporcionados comparten identificadores de usuario. Si solo desea identificadores de usuario únicos, le sugiero cambiar la primera línea de la consulta a SELECT DISTINCT users.id

+0

hombre, eso es genio! :-D –

+0

¿Por qué es necesario hacer '* 2-1' en lugar de simplemente' -1' en 'count()'? – Nalum

+1

@Nalum: Porque el segundo parámetro de str_pad es la "longitud del panel" que está en caracteres. Dado que desea un signo de interrogación para cada valor y una coma entre ellos, resulta ser 'count ($ values) * 2-1'. Alternativamente, puede usar algo como 'str_repeat ('?,', Count ($ values) -1). '?'' Si lo desea. – ghbarratt

1

Si la expresión se basa en la entrada del usuario sin vincular los valores con bindValue(), SQL experimental podría no ser una gran opción. Pero, puede hacerlo seguro comprobando la sintaxis de la entrada con REGEXP de MySQL.

Por ejemplo:

SELECT * 
FROM table 
WHERE id IN (3,156) 
AND '3,156' REGEXP '^([[:digit:]]+,?)+$' 
0

Aquí es un ejemplo de la unión de un número desconocido de columnas de registro a los valores para una inserción.

public function insert($table, array $data) 
{ 
    $sql = "INSERT INTO $table (" . join(',', array_keys($data)) . ') VALUES (' 
     . str_repeat('?,', count($data) - 1). '?)'; 

    if($sth = $this->db->prepare($sql)) 
    { 
     $sth->execute(array_values($data)); 
     return $this->db->lastInsertId(); 
    } 
} 

$id = $db->insert('user', array('name' => 'Bob', 'email' => '[email protected]')); 
-1

Inténtelo así:

WHERE products IN(REPLACE(:products, '\'', '')) 

Saludos

+3

Esta pregunta fue hecha y respondida hace 5 años, y esta respuesta no resuelve el problema ... – OGHaza

4

es necesario proporcionar mismo número de s en EN como el número de valores en su $ valores? matriz

esto se puede hacer fácilmente b y la creación de una serie de? s como

$in = join(',', array_fill(0, count($values), '?')); 

y utilizar estos $ en la matriz en su cláusula IN

THis dinámicamente le proporcionará a medida variedad de ? s como por su changiing valores de $ array

Cuestiones relacionadas