web-dev-qa-db-fra.com

node-postgres: comment exécuter la requête "WHERE col IN (<liste de valeurs dynamiques>)"?

J'essaie d'exécuter une requête comme celle-ci:

SELECT * FROM table WHERE id IN (1,2,3,4)

Le problème est que la liste des identifiants que je souhaite filtrer n’est pas constante et doit être différente à chaque exécution. J'aurais également besoin d'échapper aux identifiants, car ils pourraient provenir de sources non fiables, mais j'échapperais à tout élément de la requête, quelle que soit la fiabilité de la source.

node-postgres semble fonctionner exclusivement avec des paramètres liés: client.query('SELECT * FROM table WHERE id = $1', [ id ]); cela fonctionnera si j'ai un nombre connu de valeurs (client.query('SELECT * FROM table WHERE id IN ($1, $2, $3)', [ id1, id2, id3 ])), mais que je ne travaillerai pas directement avec un tableau: client.query('SELECT * FROM table WHERE id IN ($1)', [ arrayOfIds ]), car il ne semble pas y avoir de traitement spécial des paramètres du tableau.

Construire le modèle de requête dynamiquement en fonction du nombre d'éléments dans le tableau et développer le tableau ids dans le tableau de paramètres de requête (qui dans mon cas contient également d'autres paramètres en plus de la liste des identifiants) semble excessivement fastidieux. Le codage en dur de la liste des identifiants dans le modèle de requête ne semble pas non plus viable, car node-postgres ne fournit aucune méthode d'échappement de la valeur.

Cela ressemble à un cas d'utilisation très courant, donc je suppose que je suis en train d'oublier quelque chose, et non qu'il n'est pas possible d'utiliser l'opérateur SQL IN (values) commun avec node-postgres.

Si quelqu'un a résolu ce problème d'une manière plus élégante que celles que j'ai énumérées ci-dessus, ou s'il me manque quelque chose à propos de node-postgres, aidez-nous.

48
lanzz

Nous avons déjà vu cette question sur la liste des problèmes de github. La méthode correcte consiste à générer dynamiquement votre liste de paramètres en fonction du tableau. Quelque chose comme ça:

var arr = [1, 2, "hello"];
var params = [];
for(var i = 1; i <= arr.length; i++) {
  params.Push('$' + i);
}
var queryText = 'SELECT id FROM my_table WHERE something IN (' + params.join(',') + ')';
client.query(queryText, arr, function(err, cb) {
 ...
});

De cette façon, vous obtenez l'échappement paramétré pour postgres. 

35
brianc

Il semble que vous ayez été proche en raison de votre commentaire sur @ - ebohlman's answer . Vous pouvez utiliser WHERE id = ANY($1::int[]). PostgreSQL va convertir le tableau en type dans lequel le paramètre est converti en $1::int[]. Alors, voici un exemple artificiel qui fonctionne pour moi:

var ids = [1,3,4]; 

var q = client.query('SELECT Id FROM MyTable WHERE Id = ANY($1::int[])',[ids]);

q.on('row', function(row) {
  console.log(row);
})

// outputs: { id: 1 }
//          { id: 3 }
//          { id: 4 }
54
Pero P.

Avec pg-promise , cela fonctionne bien via le filtre CSV (valeurs séparées par des virgules):

const values = [1, 2, 3, 4];

db.any('SELECT * FROM table WHERE id IN ($1:csv)', [values])
    .then(data => {
        console.log(data);
    })
    .catch(error => {
        console.log(error);
    });

Et pour répondre aux préoccupations relatives aux différents types de données, le modificateur :csv sérialise le tableau en csv, tout en convertissant toutes les valeurs dans leur format PostgreSQL approprié, en fonction de leur type JavaScript, prenant même en charge le Formatage de type personnalisé .

Et si vous avez des valeurs de type mixte comme ceci: const values = [1, 'two', null, true], vous obtiendrez toujours le code SQL correctement échappé:

SELECT * FROM table WHERE id IN (1, 'two', null, true)

METTRE &AGRAVE; JOUR

À partir de la version 7.5.1, pg-promise a commencé à prendre en charge :list en tant qu'alias interchangeable pour le filtre :csv:

db.any('SELECT * FROM table WHERE id IN ($1:list)', [values])
15
vitaly-t

La meilleure solution que j'ai trouvée consiste à utiliser la fonction ANY avec la contrainte sur les tableaux de Postgres. Cela vous permet de faire correspondre une colonne avec un tableau arbitraire de valeurs comme si vous aviez écrit col IN (v1, v2, v3). C'est l'approche dans la réponse de pero mais ici je montre que la performance de ANY est la même que IN.

Question

Votre requête devrait ressembler à:

SELECT * FROM table WHERE id = ANY($1::int[])

Ce bit à la fin qui dit $1::int[] peut être modifié pour correspondre au type de votre colonne "id". Par exemple, si le type de vos identifiants est uuid, vous écrivez $1::uuid[] pour contraindre l'argument à un tableau d'UUID. Voir ici pour la liste des types de données Postgres .

Cela est plus simple que d’écrire du code pour construire une chaîne de requête et est sûr contre les injections SQL.

Exemple

Avec node-postgres, un exemple complet en JavaScript ressemble à ceci:

var pg = require('pg');

var client = new pg.Client('postgres://username:password@localhost/database');
client.connect(function(err) {
  if (err) {
    throw err;
  }

  var ids = [23, 65, 73, 99, 102];
  client.query(
    'SELECT * FROM table WHERE id = ANY($1::int[])',
    [ids],  // array of query arguments
    function(err, result) {
      console.log(result.rows);
    }
  );
});

Performance

L'un des meilleurs moyens de comprendre les performances d'une requête SQL consiste à examiner le traitement de la base de données. La table exemple contient environ 400 lignes et une clé primaire appelée "id" de type text.

EXPLAIN SELECT * FROM tests WHERE id = ANY('{"test-a", "test-b"}');
EXPLAIN SELECT * FROM tests WHERE id IN ('test-a', 'test-b');

Dans les deux cas, Postgres a signalé le même plan de requête:

Bitmap Heap Scan on tests  (cost=8.56..14.03 rows=2 width=79)
  Recheck Cond: (id = ANY ('{test-a,test-b}'::text[]))
  ->  Bitmap Index Scan on tests_pkey  (cost=0.00..8.56 rows=2 width=0)
        Index Cond: (id = ANY ('{test-a,test-b}'::text[]))

Vous pouvez voir un plan de requête différent en fonction de la taille de votre table, de l'index et de votre requête. Mais pour les requêtes comme celles ci-dessus, ANY et IN sont traités de la même manière.

12
ide

Une autre solution possible consiste à utiliser la fonction UNNEST comme ceci: 

 var ids = [23, 65, 73, 99, 102];
 var strs = ['bar', 'tar', 'far']
 client.query(
   'SELECT * FROM table WHERE id IN(SELECT(UNNEST($1))',
    [ids],  // array of query arguments
    function(err, result) {
       console.log(result.rows);
    }
);
client.query(
   'SELECT * FROM table WHERE id IN(SELECT(UNNEST($1))',
    [strs],  // array of query arguments
    function(err, result) {
       console.log(result.rows);
    }
);

Je l'ai utilisé dans une procédure stockée et cela fonctionne bien. Croyez que cela devrait fonctionner aussi à partir du code node-pg.

Vous pouvez en savoir plus sur la fonction UNNEST ici .

0
Yaki Klein

Une autre solution possible consiste par exemple pour l’API REST dans NODE JS:

var name = req.body;//Body is a objetc that has properties for example provinces
var databaseRB = "DATABASENAME"
var conStringRB = "postgres://"+username+":"+password+"@"+Host+"/"+databaseRB; 

var filter_query = "SELECT row_to_json(fc) FROM ( SELECT 'FeatureCollection' As type, array_to_json(array_agg(f)) As features FROM (SELECT 'Feature' As type, ST_AsGeoJSON(lg.geom)::json As geometry, row_to_json((parameters) As properties FROM radiobases As lg WHERE lg.parameter= ANY($1) )As f) As fc";

var client = new pg.Client(conStringRB);
client.connect();
var query = client.query(new Query(filter_query,[name.provinces]));
query.on("row", function (row, result) {
  result.addRow(row);
});
query.on("end", function (result) {
 var data = result.rows[0].row_to_json
   res.json({
     title: "Express API",
     jsonData: data
     });
});

Gardez à l'esprit que tout type de tableau peut être utilisé

0
C. Marca