web-dev-qa-db-ja.com

node-postgres:「WHERE col IN(<動的値リスト>)」クエリの実行方法

私はこのようなクエリを実行しようとしています:

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

問題は、フィルタリングしたいidのリストが一定ではなく、実行ごとに異なる必要があることです。 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のリスト以外の他のパラメータも含む)に展開するのは、不当に面倒です。 node-postgresは値をエスケープするメソッドを提供しないため、クエリテンプレート内のIDのリストをハードコーディングすることも実行可能ではないようです。

これは非常に一般的なユースケースのように思えるので、実際には何かを見落としているのではなく、node-postgresで一般的なIN (values) SQL演算子を使用することは不可能だと推測します。

誰かが上記のリストよりもエレガントな方法でこの問題を解決した場合、またはnode-postgresについて本当に何かが足りない場合は、助けてください。

54
lanzz

この質問は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エスケープを取得できます。

39
brianc

@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 }
68
Pero P.

私が見つけた最良の解決策は、Postgresの配列強制でANY関数を使用することです。これにより、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クエリのパフォーマンスを理解する最良の方法の1つは、データベースがそれを処理する方法を調べることです。サンプルテーブルには、約400行と、text型の "id"と呼ばれる主キーがあります。

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は同じ方法で処理されます。

19
ide

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もサポートしますフォーマット

そして、次のような混合型の値がある場合:const values = [1, 'two', null, true]、まだ正しくエスケープされたSQLを取得します:

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

[〜#〜] update [〜#〜]

V7.5.1から、 pg-promise:listフィルターの交換可能なエイリアスとして:csvのサポートを開始しました。

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

別の可能な解決策は、次のように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

0
Yaki Klein

別の可能な解決策は、たとえばREST NODE JSの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
     });
});

任意のタイプの配列を使用できることに注意してください

0
C. Marca