101

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

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

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

node-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 数组扩展为查询参数数组(在我的实际情况下,它还包含除 ids 列表之外的其他参数)似乎过于繁琐。在查询模板中硬编码 id 列表似乎也不可行,因为 node-postgres 不提供任何值转义方法。

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

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

4

7 回答 7

127

根据您对@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 }
于 2012-05-31T08:15:24.633 回答
66

我们之前在 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 参数化的转义。

于 2012-07-27T16:10:58.173 回答
32

我发现的最好的解决方案是将该ANY函数与 Postgres 的数组强制结合使用。这使您可以将列与任意值数组匹配,就好像您已经写出一样col IN (v1, v2, v3)。这是pero 的答案中的方法,但在这里我表明 的性能ANYIN.

询问

您的查询应如下所示:

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 行和一个名为“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[]))

根据表的大小、索引的位置和查询,您可能会看到不同的查询计划。但对于上述查询,ANY处理IN方式相同。

于 2014-12-30T23:19:45.940 回答
19

使用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])
于 2015-04-11T08:52:58.043 回答
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 功能的信息。

于 2015-03-26T12:03:57.417 回答
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
     });
});

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

于 2018-08-28T17:14:13.703 回答
-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) +')';
于 2018-11-30T22:55:12.147 回答