【问题标题】:node-postgres: how to execute "WHERE col IN (<dynamic value list>)" query?node-postgres:如何执行“WHERE col IN(<动态值列表>)”查询?
【发布时间】:2021-11-07 20:49:56
【问题描述】:

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

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

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

如果有人比上面列出的问题更优雅地解决了这个问题,或者我真的缺少有关节点孔的问题,请帮助。

【问题讨论】:

  • 我自己没有尝试过,但您似乎希望将嵌套数组作为替换数组的第一个(在本例中是唯一的)元素传递,因为它期望每个元素该参数是替代值。示例:client.query('SELECT * FROM table WHERE id IN ($1)', [[arrayOfIds]]);
  • 不,那样不行(可以预见)。显然,它试图将数组[1,2,3]表示为字符串值“ 1,2,3”,并且服务器返回一个错误“无效输入语法”。
  • 您可以发布它在以这种方式传递数组时尝试执行的完整查询吗?我对 Posgres 不像 MySQL 那样熟悉,但这不是您表示该查询的方式吗? 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)

标签: node.js node-postgres


【解决方案1】:

根据您对@ebohlman 的answer 的评论,您似乎很接近。您可以使用WHERE id = ANY($1::int[])。 PostgreSQL 会将convert 数组转换为$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 列表直接嵌入到查询模板中,而不是准备一个数组文字,只是为了让它在服务器端再次解析。
  • 您能否详细说明“这缺少对 arr 中值的任何引用”的意思?
  • 当然可以,这只是一个例子。当然,无论实现如何,您都会在准备语句之前清理IN 子句参数。可能是我误解了你的问题的理由。
  • “你肯定会在准备语句之前清理 IN 子句参数,而不管实现如何”。不,这是首先使用参数绑定的最重要原因之一。这个答案会给人们带来安全风险。
  • 因此,鉴于此答案是使用参数化查询,因此参数正在 postgres 服务器上进行解析和转义,安全风险在哪里?如果有,那么问题就更大了。
【解决方案2】:

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

【讨论】:

  • 由于我们在 Node.js 中,您可以安全地使用本机 map()。这进一步简化了代码: params = arr.map(function(item, idx) {return '$' + idx});
  • 在将明文放入查询中时正要尖叫血腥谋杀!原来它只是美元符号和数字 -_-'....
  • @srigi 建议有一个错误。应该是: var params = arr.map(function(item, idx) {return '$' + (idx+1);});
  • 这个答案的更新现在在 node-postgres 常见问题解答中。以下作品:client.query("SELECT * FROM stooges WHERE name = ANY ($1)", [ ['larry', 'curly', 'moe'] ], ...); 参见此处:github.com/brianc/node-postgres/wiki/…
  • @SandeepSinghRana 我看不出这如何容易受到 SQL 注入的攻击。正在构建的查询字符串保证是SELECT id ... IN ($1, $2, ... $N) 的形式,它承认没有注入的可能性——它只是一个准备填充的$-参数化查询。 (我们从不将实际查询值添加到字符串中!)然后通过库来填充实际值,这本质上不受 SQL 注入的影响(除非库中存在严重的错误)。
【解决方案3】:

我发现的最佳解决方案是使用 ANY 函数和 Postgres 的数组强制。这使您可以将列与任意值数组匹配,就好像您已经写出col IN (v1, v2, v3)。这是pero's answer 中的方法,但这里我展示了ANY 的性能与IN 相同。

查询

您的查询应如下所示:

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

可以更改末尾的 $1::int[] 以匹配您的“id”列的类型。例如,如果您的 ID 的类型是 uuid,您将编写 $1::uuid[] 以将参数强制转换为 UUID 数组。 See here for the list of Postgres datatypes.

这比编写代码来构造查询字符串更简单,并且可以安全地防止 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[]))

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

【讨论】:

  • 请注意,虽然这对于 ANY 取集合的形式是正确的,但对于每个 IN () 和 = ANY() 都有第二种形式,它们并不完全等价。考虑:stackoverflow.com/questions/34627026/…
【解决方案4】:

使用pg-promise,通过CSV Filter(逗号分隔值)效果很好:

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 格式,甚至支持Custom Type Formatting

如果你有这样的混合类型值: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])

【讨论】:

  • 爱这个图书馆
【解决方案5】:

另一种可能的解决方案是像这样使用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 函数here

【讨论】:

  • id = ANY($1) 解决方案相比,这似乎是一个巨大的矫枉过正
【解决方案6】:

另一种可能的解决方案是例如 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
     });
});

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

【讨论】:

    【解决方案7】:

    大致思路:

    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) +')';
    

    【讨论】:

      猜你喜欢
      • 2015-10-12
      • 2021-12-04
      • 2021-10-09
      • 1970-01-01
      • 2014-08-04
      • 2011-06-14
      • 1970-01-01
      • 2012-09-27
      相关资源
      最近更新 更多