node-postgres:如何执行"WHERE col IN(<动态值列表>)"查询?

lan*_*nzz 55 node.js node-postgres

我正在尝试执行这样的查询:

SELECT * FROM table WHERE id IN (1,2,3,4)
Run Code Online (Sandbox Code Playgroud)

问题是我要过滤的ID列表不是常量,每次执行时都需要不同.我还需要逃避id,因为它们可能来自不受信任的来源,尽管我实际上会逃避查询中的任何内容,无论源的可信度如何.

节点的Postgres似乎与绑定参数专门工作: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 ])因为似乎没有任何特殊的数组参数处理.

根据数组中项目的数量动态构建查询模板,并将ids数组扩展到查询参数数组(在我的实际情况下,除了id列表之外还包含其他参数)似乎是不合理的负担.在查询模板中对id列表进行硬编码似乎也不可行,因为node-postgres不提供任何值转义方法.

这似乎是一个非常常见的用例,所以我的猜测是我实际上忽略了某些东西,而不是不可能将常见的IN (values)SQL运算符与node-postgres一起使用.

如果有人以比我上面列出的方式更优雅的方式解决了这个问题,或者如果我真的错过了关于node-postgres的内容,请帮忙.

Per*_* P. 71

根据您对@ ebohlman的回答的评论,您可能已经接近了.你可以用WHERE id = ANY($1::int[]).PostgreSQL会将数组转换为参数转换为的类型$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 }
Run Code Online (Sandbox Code Playgroud)

  • 所以鉴于这个答案是使用参数化查询,因此在postgres服务器上解析和转义参数,安全风险在哪里?如果有,那么就有更大的问题. (5认同)
  • 当然会,这只是一个例子。当然,无论实现如何,您都会在准备语句之前清理 `IN` 子句参数。可能是我误解了你问题的理由。 (2认同)
  • “当然,无论实现如何,您都会在准备语句之前清理 IN 子句参数”。不。这是首先使用参数绑定的最重要原因之一。这个答案给人们带来了安全风险。 (2认同)

小智 41

我们之前在github问题列表上看过这个问题.正确的方法是根据数组动态生成参数列表.像这样的东西:

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) {
 ...
});
Run Code Online (Sandbox Code Playgroud)

这样你就可以获得postgres参数化转义.

  • 这个答案的更新现在在node-postgres FAQ中.以下工作:`client.query("SELECT*FROM stooges WHERE name = ANY($ 1)",[['larry','curly','moe']],...);`见这里:https: //github.com/brianc/node-postgres/wiki/FAQ#11-how-do-i-build-a-where-foo-in--query-to-find-rows-matching-an-array-of - 值 (11认同)
  • 由于我们在Node.js中,因此您可以安全地使用本机map().这简化了代码:params = arr.map(function(item,idx){return'$'+ idx}); (7认同)
  • @srigi建议有一个错误.它应该是:var params = arr.map(function(item,idx){return'$'+(idx + 1);}); (6认同)
  • 准备将明文放入查询时尖叫着血腥谋杀!原来它只是美元符号和数字-_-'.... (4认同)
  • @SandeepSinghRana 我不明白这如何容易受到 SQL 注入的攻击。正在构建的查询字符串保证采用“SELECT id ... IN ($1, $2, ... $N)”的形式,它不允许注入——它只是一个“$”参数化查询被填充。(我们从不将实际查询值添加到字符串中!)然后通过库进行实际值的填充,这本质上不受 SQL 注入的影响(除非库中存在严重错误)。 (4认同)

ide*_*ide 21

我发现的最好的解决方案是使用ANYPostgres的阵列强制功能.这使您可以将列与任意值的数组匹配,就像您已写出一样col IN (v1, v2, v3).这是pero答案中的方法,但在这里我表明它的表现ANY是相同的IN.

询问

您的查询应如下所示:

SELECT * FROM table WHERE id = ANY($1::int[])
Run Code Online (Sandbox Code Playgroud)

最后说的那一点$1::int[]可以更改为与"id"列的类型相匹配.例如,如果您的ID类型是uuid,您将写入$1::uuid[]以强制参数为UUID数组.有关Postgres数据类型的列表,请参见此处.

这比编写代码来构造查询字符串更简单,并且可以安全地防止SQL注入.

使用node-postgres,一个完整的JavaScript示例如下所示:

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);
    }
  );
});
Run Code Online (Sandbox Code Playgroud)

性能

理解SQL查询性能的最佳方法之一是查看数据库如何处理它.示例表有大约400行和一个名为"id"的主键text.

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

在这两种情况下,Postgres报告了相同的查询计划:

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[]))
Run Code Online (Sandbox Code Playgroud)

您可能会看到不同的查询计划,具体取决于表的大小,索引和查询.但对于像上面的查询,ANYIN以相同的方式处理.


vit*_*y-t 16

使用pg-promise,这可以通过CSV过滤器(逗号分隔值)很好地工作:

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);
    });
Run Code Online (Sandbox Code Playgroud)

为了解决对各种数据类型的关注,:csv修饰符将数组序列化为csv,同时根据其JavaScript类型将所有值转换为正确的PostgreSQL格式,甚至支持自定义类型格式.

如果您有这样的混合类型值:const values = [1, 'two', null, true],您仍将获得正确转义的SQL:

SELECT * FROM table WHERE id IN (1, 'two', null, true)
Run Code Online (Sandbox Code Playgroud)

UPDATE

从v7.5.1 开始,pg-promise开始支持过滤器:list的可互换别名:csv:

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

  • 喜欢这个图书馆 (2认同)