web-dev-qa-db-fra.com

Valeurs de liaison PDO pour l'instruction MySQL IN

J'ai un problème avec le PDO pour lequel j'aimerais vraiment obtenir une réponse après avoir été en proie à cela pendant un bon bout de temps.

Prenons cet exemple:

Je lie un tableau d'identifiants à une instruction PDO pour une utilisation dans une instruction MySQL IN.

Le tableau serait par exemple: $ values ​​= array (1,2,3,4,5,6,7,8);

La variable de base de données sécurisée serait $ products = implode (',' $ values);

Ainsi, $ produits serait alors une chaîne de valeur: '1,2,3,4,5,6,7,8'

La déclaration ressemblerait à ceci:

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

Bien entendu, $ products serait lié à la déclaration en tant que: products .

Cependant, lorsque l'instruction est compilée et que les valeurs sont liées, elle ressemble en réalité à ceci:

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

Le problème est qu’il exécute tout ce qui se trouve à l’intérieur de l’instruction IN en tant que chaîne unique, étant donné que je l’ai préparée sous forme de valeurs séparées par des virgules pour la lier à l’instruction.

Ce dont j'ai réellement besoin, c'est:

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

Le seul moyen de le faire est de placer les valeurs dans la chaîne elle-même sans les lier, mais je sais avec certitude qu'il doit y avoir un moyen plus facile de le faire.

69
hd82

C'est la même chose que celle posée dans cette question: Puis-je lier un tableau à une condition IN ()?

La réponse à cela était que, pour une liste de taille variable dans la clause in, vous devez construire la requête vous-même.

Cependant, vous pouvez utiliser la liste citée, séparée par des virgules, avec find_in_set , bien que cela puisse avoir un impact considérable sur les performances, car chaque valeur de la table doit être convertie en un type .

Par exemple:

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

Ou, comme troisième option, vous pourriez créer une fonction définie par l'utilisateur qui scinde pour vous la liste séparée par des virgules (cf. http://www.slickdev.com/2008/09/15/mysql- query-real-values-from-delimiter-separ-string-ids/ ). C’est probablement la meilleure option parmi les trois, en particulier si vous avez beaucoup de requêtes qui reposent sur des clauses in(...).

37
James McNellis

Un bon moyen de gérer cette situation consiste à utiliser str_pad pour placer un ? pour chaque valeur de la requête SQL. Ensuite, vous pouvez passer le tableau de valeurs (dans votre cas $values) comme argument à exécuter:

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

De cette façon, vous tirez davantage parti des instructions préparées plutôt que d’insérer les valeurs directement dans le code SQL.

PS - Les résultats renverront des ID utilisateur en double si les produits portant les ID donnés partagent des ID utilisateur. Si vous souhaitez uniquement des identifiants utilisateur uniques, je vous suggère de modifier la première ligne de la requête en SELECT DISTINCT users.id

26
ghbarratt

La déclaration la mieux préparée que vous pourriez probablement trouver dans une situation comme celle-ci ressemble à ceci:

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

Vous pouvez ensuite parcourir vos valeurs et les lier à l'instruction préparée en vous assurant qu'il existe le même nombre de points d'interrogation que les valeurs que vous liez.

7
Asaph

vous devez fournir le même nombre de? s inDANSque le nombre de valeurs dans votre $ values ​​array 

cela peut être fait facilement en créant un tableau de? s comme 

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

et utiliser ce tableau $ in dans votre clause IN 

Cela vous fournira dynamiquement un tableau sur mesure de? S selon votre tableau changiing $ values

5
ashsam786

Si l'expression est basée sur une entrée utilisateur sans lier les valeurs avec bindValue (), SQL expérimental n'est peut-être pas un très bon choix. Mais , vous pouvez le rendre sûr en vérifiant la syntaxe de l'entrée avec REGEXP de MySQL.

Par exemple:

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

Vous pouvez le faire très facilement . Si vous avez un tableau de valeurs pour votre instruction IN () EG:

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

Vous pouvez simplement faire

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

Voici un exemple de liaison d'un nombre inconnu de colonnes d'enregistrement à des valeurs pour une insertion.

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]'));
0
Xeoncross