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


76

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

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

问题是我要过滤的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的知识,请提供帮助。


我自己还没有尝试过,但是您似乎希望将嵌套数组作为替换数组的第一个(在这种情况下,仅)元素传递,因为它希望该参数中的每个元素都是一个替换值。示例:client.query('SELECT * FROM table WHERE id IN($ 1)',[[arrayOfIds]]));
瑞安·拉伯

不,这种方式无法正常工作(可预测)。显然,它尝试将数组[1、2、3]表示为字符串值“ 1、2、3”,并且服务器返回错误“整数的无效输入语法”。
lanzz

您可以以这种方式传递数组时,发布它尝试执行的完整查询吗?我不像MySQL那样熟悉Posgres,但这不是您表示查询的方式吗?IN(1,2,3)对我来说是正确的。除非我完全误解了您要做什么。
瑞安·拉伯

我看不到任何捕获完整查询的方法,因为实际的网络协议消息将查询模板和参数列表分别传递给postgres服务器,并且任何最终的组合都在服务器端进行。即使在postgres日志中,也没有完整的查询: 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 2012年

Answers:


50

我们之前在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) {
 ...
});

这样,您就可以将postgres参数化转义。


9
由于我们在Node.js中,因此可以安全地使用本机map()。这样可以进一步简化代码:params = arr.map(function(item,idx){return'$'+ idx});
srigi 2014年

7
正要在将纯文本查询时尖叫血腥谋杀!原来这只是美元符号和数字-_-'....
Lodewijk

7
@srigi建议存在错误。应该是:var params = arr.map(function(item,idx){return'$'+(idx + 1);});
无菌2014年

26
现在,在node-postgres常见问题解答中对此问题的更新。下面的工作:client.query("SELECT * FROM stooges WHERE name = ANY ($1)", [ ['larry', 'curly', 'moe'] ], ...);在这里看到:github.com/brianc/node-postgres/wiki/...
卡维西格尔

2
@SandeepSinghRana我看不到它如何容易受到SQL注入的攻击。确保正在构建的查询字符串采用SELECT id ... IN ($1, $2, ... $N)不容许注入的形式-它只是$准备填充的-参数化查询。(我们永远不会在字符串中添加实际查询值!)然后通过库进行实际值填充,这自然不受SQL注入的影响(除非库中存在严重的错误)。
49分

103

根据您对@ebohlman的回答的评论,您似乎已经关闭了。您可以使用WHERE id = ANY($1::int[])。PostgreSQL将转换数组为参数为in的类型$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 }

这缺少对中的值的引用arr,并且node-postgres不提供任何引用方法。我正在寻找解决此问题的“正确”方法,因此我不希望实现自己的SQL文字引用代码。此外,如果我朝这个方向前进,我宁愿将ID列表直接嵌入查询模板中,而不是准备一个数组文字以仅在服务器端再次对其进行解析。
lanzz

您能否进一步解释一下arr“请在其中缺少对值的引用”的意思?
Pero P. 2012年

这意味着,如果arr不包含整数而是包含逗号或大括号的字符串,则您的代码将失败或执行不正确。
lanzz

2
当然可以,这只是一个例子。当然,IN在准备执行语句之前,无论实现如何,都应该对子句参数进行清理。可能是我误解了您提出问题的理由。
PeroP。12年

6
因此,鉴于此答案使用的是参数化查询,因此在Postgres服务器上对参数进行了解析和转义,安全风险在哪里?如果有一个,那就存在更大的问题。
Pero P. 2014年

26

我发现最好的解决方案是将ANY函数与Postgres的数组强制一起使用。这样一来,您就可以将一列具有任意值的数组进行匹配,就好像您已经写了出来一样col IN (v1, v2, v3)。这是pero回答中的方法,但在这里我证明的性能ANY与相同IN

询问

您的查询应类似于:

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

末尾的那个位$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);
    }
  );
});

性能

了解SQL查询性能的最好方法之一就是查看数据库如何处理它。该样本表大约有400行,还有一个名为type的主键“ id” text

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

在这两种情况下,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[]))

您可能会看到不同的查询计划,具体取决于表的大小,有索引的位置以及查询。但对于查询像以上这样的,ANYIN被处理的方式相同。


请注意,虽然这对于带集合的ANY形式是正确的,但每个IN()和= ANY()都有另一种形式,并且它们并不完全等效。考虑:stackoverflow.com/questions/34627026/…–
乔纳斯·凯洛

17

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

为了解决对各种数据类型的担忧,:csv修饰符将数组序列化为csv,同时根据其JavaScript类型将所有值转换为适当的PostgreSQL格式,甚至支持“自定义类型格式”

而且,如果您有如下混合类型的值:const values = [1, 'two', null, true],您仍然会得到正确转义的SQL:

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

更新

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

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

1
喜欢这个图书馆
Quintin Botes,

0

另一个可能的解决方案是使用如下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);
    }
);

我已经在存储过程中使用了它,并且效果很好。相信它也可以在node-pg代码中使用。

您可以在此处阅读有关UNNEST函数的信息


1
id = ANY($1)解决方案相比,这似乎是一个巨大的杀伤力
lanzz

0

另一个可行的解决方案例如是NODE JS中的REST API:

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

请记住,可以使用任何类型的数组


-1

这个想法通常是:

var invals = [1,2,3,4], cols = [...fields];
var setvs = vs => vs.map(v=> '$'+ (values.push(v))  ).join();

var values = [];
var text = 'SELECT '+ setvs(cols) +' FROM table WHERE id IN (' + setvs(invals) +')';
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.