node-postgres: how to execute “WHERE col IN ()” query?


node-postgres: how to execute “WHERE col IN (<dynamic value list>)” query?



I'm trying to execute a query like this:


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



The problem is that the list of ids I want to filter against is not constant and needs to be different at every execution. I would also need to escape the ids, because they might come from untrusted sources, though I would actually escape anything that goes in a query regardless of the trustworthiness of the source.



node-postgres appears to work exclusively with bound parameters: client.query('SELECT * FROM table WHERE id = $1', [ id ]); this will work if I had a known number of values (client.query('SELECT * FROM table WHERE id IN ($1, $2, $3)', [ id1, id2, id3 ])), but will not work with an array directly: client.query('SELECT * FROM table WHERE id IN ($1)', [ arrayOfIds ]), as there does not seem to be any special handling of array parameters.


client.query('SELECT * FROM table WHERE id = $1', [ id ])


client.query('SELECT * FROM table WHERE id IN ($1, $2, $3)', [ id1, id2, id3 ])


client.query('SELECT * FROM table WHERE id IN ($1)', [ arrayOfIds ])



Building the query template dynamically according to the number of items in the array and expanding the ids array into the query parameters array (which in my actual case also contains other parameters besides the list of ids) seems unreasonably burdensome. Hard-coding the list of ids in the query template seems not viable either, as node-postgres does not provide any value escaping methods.



This seems like a very common use-case, so my guess is that I'm actually overlooking something, and not that it is not possible to use the common IN (values) SQL operator with node-postgres.


IN (values)



If anybody has solved this problem in a more elegant manner than those I listed above, or if I'm really missing something about node-postgres, please help.





I haven't tried this myself, but it appears that you want to pass a nested array as the first (and in this case, only) element of your substitutions array, since it expects each element in that paramater to be a substitution value. Example: client.query('SELECT * FROM table WHERE id IN ($1)', [[arrayOfIds]]);
– Ryan LaBarre
May 24 '12 at 5:24






No, does not work that way (predictably). Apparently it tries to represent the array [1, 2, 3] as the string value "1,2,3", and the server returns an error "invalid input syntax for integer".
– lanzz
May 24 '12 at 6:15





Can you post the full query it tries to execute when passing the array this way? I'm not as familiar with Posgres as MySQL but isn't that how you represent that query? IN (1,2,3) looks correct to me. Unless I totally misunderstood what you're trying to do.
– Ryan LaBarre
May 25 '12 at 17:12





I don't see any way to capture the full query, as the actual network protocol message passes the query template and the list of parameters separately to the postgres server, and any final composition takes place on the server side. Even in the postgres log, there is no full query: 2012-05-26 20:31:08 EEST ERROR: invalid input syntax for integer: "1,3" 2012-05-26 20:31:08 EEST STATEMENT: SELECT * FROM users WHERE id IN ($1)
– lanzz
May 26 '12 at 17:32



2012-05-26 20:31:08 EEST ERROR: invalid input syntax for integer: "1,3" 2012-05-26 20:31:08 EEST STATEMENT: SELECT * FROM users WHERE id IN ($1)




5 Answers
5



We've seen this question before on the github issues list. The correct way is to dynamically generate your list of parameters based on the array. Something like this:


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) {
...
});



That way you get the postgres parameterized escaping.





Since we are in Node.js, you are safe to use native map(). That simplifies code even more: params = arr.map(function(item, idx) {return '$' + idx});
– srigi
Feb 10 '14 at 14:42






Was about to scream bloody murder at putting plaintext into a query! Turns out it's just dollar symbols and numbers -_-'....
– Lodewijk
Mar 3 '14 at 21:22





@srigi suggestion has a bug. It should be: var params = arr.map(function(item, idx) {return '$' + (idx+1);});
– Heptic
Mar 8 '14 at 5:59





This example will break, if any text element in the array contains single-quote symbols '.
– vitaly-t
Apr 15 '15 at 15:42





An update to this answer is now in the node-postgres FAQ. The following works: client.query("SELECT * FROM stooges WHERE name = ANY ($1)", [ ['larry', 'curly', 'moe'] ], ...); See here: github.com/brianc/node-postgres/wiki/…
– Kavi Siegel
Oct 14 '16 at 16:41


client.query("SELECT * FROM stooges WHERE name = ANY ($1)", [ ['larry', 'curly', 'moe'] ], ...);



It looks like you may have been close based on your comment to @ebohlman's answer. You can use WHERE id = ANY($1::int). PostgreSQL will convert the array to the type the parameter is cast to in $1::int. So here's a contrived example that works for me:


WHERE id = ANY($1::int)


$1::int


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 }





This lacks any quoting of the values in arr, and node-postgres does not provide any quoting methods. I'm looking for the "proper" way to approach this issue, so I'm disinclined to implement my own SQL literal quoting code. Besides, if I proceed in that direction, I would rather embed the list of ids directly in the query template, instead of preparing an array literal only to have it parsed again on the server-side.
– lanzz
May 31 '12 at 8:20


arr


node-postgres





Can you expand on what you mean by "this lacks any quoting of the values in arr" please?
– Pero P.
May 31 '12 at 8:27


arr





It means that if arr does not contain integers but, say, strings that include commas or braces, your code would fail or will perform incorrectly.
– lanzz
May 31 '12 at 8:35



arr





Of course it would, this is just an example. Surely you would sanitize the IN clause parameters prior to preparing the statement regardless of implementation. May be I've misunderstood the grounds of your question.
– Pero P.
May 31 '12 at 8:43


IN





So given this answer is using a parameterized query and therefore the parameters are being parsed and escaped on the postgres server, where is the security risk? If there is one then there are bigger problems.
– Pero P.
Feb 8 '14 at 3:50




The best solution I've found has been to use the ANY function with Postgres' array coercion. This lets you match a column with an arbitrary array of values as if you had written out col IN (v1, v2, v3). This is the approach in pero's answer but here I show that the performance of ANY is the same as IN.


ANY


col IN (v1, v2, v3)


ANY


IN



Your query should look like:


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



That bit at the end that says $1::int can be changed to match the type of your "id" column. For example, if the type of your IDs is uuid, you'd write $1::uuid to coerce the argument to an array of UUIDs. See here for the list of Postgres datatypes.


$1::int


uuid


$1::uuid



This is simpler than writing code to construct a query string and is safe against SQL injections.



With node-postgres, a complete JavaScript example looks like:


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



One of the best ways to understand the performance of a SQL query is to look at how the database processes it. The sample table has about 400 rows and a primary key called "id" of type text.


text


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



In both cases, Postgres reported the same query plan:


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



You might see a different query plan depending on the size of your table, where there's an index, and your query. But for queries like the ones above, ANY and IN are processed the same way.


ANY


IN





Amazing! This should be the correct answer.
– Sam Davies
Sep 21 '16 at 17:55





Note that while this is true for the form of ANY taking a set, there is a second form for each IN () and = ANY() and those are not completely equivalent. Consider: stackoverflow.com/questions/34627026/…
– Jonas Kello
Feb 27 at 10:23




Using pg-promise, this works well via the CSV Filter (comma-separated values):


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



And to address the concern about various data types, :csv modifier serializes the array into csv, while converting all values into their proper PostgreSQL format, according to their JavaScript type, even supporting the Custom Type Formatting.


:csv



And if you have mixed-type values like this: const values = [1, 'two', null, true], you still will get the correctly escaped SQL:


const values = [1, 'two', null, true]


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



UPDATE



From v7.5.1, pg-promise started supporting :list as an interchangeable alias for the :csv filter:


:list


:csv


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





Love this library
– Quintin Botes
May 3 at 11:48



Another possible solution is to use the UNNEST function like this:


UNNEST


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



I've used this in a stored procedure and it works fine. Believe it should work also from node-pg code.



You can read about the UNNEST function here.





This seems like a huge overkill, compared to the id = ANY($1) solutions
– lanzz
Mar 26 '15 at 12:11


id = ANY($1)






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

api-platform.com Unable to generate an IRI for the item of type

PHP contact form sending but not receiving emails

Do graphics cards have individual ID by which single devices can be distinguished?