Node-Postgres: Wie führe ich die Abfrage "WHERE col IN (<dynamische Werteliste>)" aus?


76

Ich versuche eine Abfrage wie folgt auszuführen:

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

Das Problem ist, dass die Liste der IDs, nach denen ich filtern möchte, nicht konstant ist und bei jeder Ausführung unterschiedlich sein muss. Ich müsste mich auch den IDs entziehen, da sie möglicherweise aus nicht vertrauenswürdigen Quellen stammen, obwohl ich mich tatsächlich allem entziehen würde, was in einer Abfrage enthalten ist, unabhängig von der Vertrauenswürdigkeit der Quelle.

Node-Postgres scheint ausschließlich mit gebundenen Parametern zu arbeiten : client.query('SELECT * FROM table WHERE id = $1', [ id ]); Dies funktioniert, wenn ich eine bekannte Anzahl von Werten ( client.query('SELECT * FROM table WHERE id IN ($1, $2, $3)', [ id1, id2, id3 ])) hatte, aber nicht direkt mit einem Array:, client.query('SELECT * FROM table WHERE id IN ($1)', [ arrayOfIds ])da es keine spezielle Behandlung von Array-Parametern zu geben scheint.

Das dynamische Erstellen der Abfragevorlage entsprechend der Anzahl der Elemente im Array und das Erweitern des IDs-Arrays in das Array mit den Abfrageparametern (das in meinem tatsächlichen Fall neben der Liste der IDs auch andere Parameter enthält) erscheint unangemessen aufwändig. Das Hardcodieren der Liste der IDs in der Abfragevorlage scheint ebenfalls nicht möglich zu sein, da Node-Postgres keine Methoden zum Entkommen von Werten bereitstellt.

Dies scheint ein sehr häufiger Anwendungsfall zu sein. Ich vermute also, dass ich tatsächlich etwas übersehen habe und nicht, dass es nicht möglich ist, den allgemeinen IN (values)SQL-Operator mit Node-Postgres zu verwenden.

Wenn jemand dieses Problem auf elegantere Weise gelöst hat als die oben aufgeführten, oder wenn mir wirklich etwas über Node-Postgres fehlt, helfen Sie bitte.


Ich habe dies selbst nicht versucht, aber es scheint, dass Sie ein verschachteltes Array als erstes (und in diesem Fall einziges) Element Ihres Substitutionsarrays übergeben möchten, da erwartet wird, dass jedes Element in diesem Parameter ein Substitutionswert ist. Beispiel: client.query ('SELECT * FROM Tabelle WHERE ID IN ($ 1)', [[arrayOfIds]]);
Ryan LaB

Nein, funktioniert nicht so (vorhersehbar). Anscheinend wird versucht, das Array [1, 2, 3] als Zeichenfolgenwert "1,2,3" darzustellen, und der Server gibt den Fehler "Ungültige Eingabesyntax für Ganzzahl" zurück.
Lanzz

Können Sie die vollständige Abfrage veröffentlichen, die ausgeführt werden soll, wenn das Array auf diese Weise übergeben wird? Ich bin mit Posgres nicht so vertraut wie MySQL, aber stellen Sie diese Abfrage nicht so dar? IN (1,2,3) sieht für mich richtig aus. Es sei denn, ich habe völlig falsch verstanden, was Sie versuchen zu tun.
Ryan LaB

Ich sehe keine Möglichkeit, die vollständige Abfrage zu erfassen, da die eigentliche Netzwerkprotokollnachricht die Abfragevorlage und die Liste der Parameter separat an den Postgres-Server übergibt und die endgültige Komposition auf der Serverseite erfolgt. Selbst im Postgres-Protokoll gibt es keine vollständige Abfrage: 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

Antworten:


50

Wir haben diese Frage schon einmal auf der Liste der Github-Probleme gesehen. Der richtige Weg ist, Ihre Liste von Parametern basierend auf dem Array dynamisch zu generieren. Etwas wie das:

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

Auf diese Weise erhalten Sie die parametrisierten Postgres-Escapezeichen.


9
Da wir uns in Node.js befinden, können Sie sicher native map () verwenden. Das vereinfacht den Code noch mehr: params = arr.map (Funktion (item, idx) {return '$' + idx});
Srigi

7
Ich wollte gerade einen blutigen Mord schreien, als ich Klartext in eine Abfrage einfügte! Es stellt sich heraus, dass es nur Dollarsymbole und Zahlen sind -_-'....
Lodewijk

7
@srigi Vorschlag hat einen Fehler. Es sollte sein: var params = arr.map (function (item, idx) {return '$' + (idx + 1);});
Heptic

26
Eine Aktualisierung dieser Antwort finden Sie jetzt in den FAQ zu Node-Postgres. Die folgenden Arbeiten: client.query("SELECT * FROM stooges WHERE name = ANY ($1)", [ ['larry', 'curly', 'moe'] ], ...);Siehe hier: github.com/brianc/node-postgres/wiki/…
Kavi Siegel

2
@SandeepSinghRana Ich sehe nicht, wie anfällig dies für SQL-Injection ist. Die erstellte Abfragezeichenfolge hat garantiert die Form, SELECT id ... IN ($1, $2, ... $N)die keine Möglichkeit der Injektion zulässt. Es handelt sich lediglich um eine $parametrisierte Abfrage, die zum Ausfüllen bereit ist. (Wir fügen der Zeichenfolge niemals tatsächliche Abfragewerte hinzu!) Dann erfolgt die Auffüllung des tatsächlichen Werts über die Bibliothek, die von Natur aus immun gegen SQL-Injection ist (es sei denn, es liegt ein schwerwiegender Fehler in der Bibliothek vor).
Apsiller

103

Es sieht so aus, als wären Sie aufgrund Ihres Kommentars zu @ ebohlmans Antwort nah dran gewesen . Sie können verwenden WHERE id = ANY($1::int[]). PostgreSQL konvertiert das Array in den Typ, in den der Parameter umgewandelt wird $1::int[]. Hier ist ein erfundenes Beispiel, das für mich funktioniert:

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 }

Dies enthält keine Anführungszeichen für die Werte in arrund node-postgresbietet keine Anführungsmethoden. Ich bin auf der Suche nach dem "richtigen" Weg, um dieses Problem anzugehen, und bin daher nicht geneigt, meinen eigenen SQL-Literal-Anführungscode zu implementieren. Wenn ich in diese Richtung gehe, würde ich die Liste der IDs lieber direkt in die Abfragevorlage einbetten, anstatt ein Array-Literal vorzubereiten, nur um es erneut auf der Serverseite zu analysieren.
Lanzz

Können Sie bitte erläutern, was Sie unter "hier fehlt die Angabe der Werte in arr" verstehen?
Pero P.

Dies bedeutet, dass arrIhr Code fehlschlägt oder eine falsche Leistung erbringt , wenn er keine Ganzzahlen enthält, sondern beispielsweise Zeichenfolgen, die Kommas oder geschweifte Klammern enthalten.
Lanzz

2
Natürlich wäre dies nur ein Beispiel. Sicherlich würden Sie die Klauselparameter INvor der Erstellung der Anweisung unabhängig von der Implementierung bereinigen. Vielleicht habe ich die Gründe Ihrer Frage falsch verstanden.
Pero P.

6
Wenn diese Antwort eine parametrisierte Abfrage verwendet und daher die Parameter analysiert und auf dem Postgres-Server maskiert werden, wo liegt das Sicherheitsrisiko? Wenn es eines gibt, gibt es größere Probleme.
Pero P.

26

Die beste Lösung, die ich gefunden habe, war die Verwendung der ANYFunktion mit dem Array-Zwang von Postgres. Auf diese Weise können Sie eine Spalte mit einem beliebigen Array von Werten abgleichen, als hätten Sie ausgeschrieben col IN (v1, v2, v3). Dies ist der Ansatz in Peros Antwort, aber hier zeige ich, dass die Leistung von ANYdie gleiche ist wie IN.

Abfrage

Ihre Anfrage sollte folgendermaßen aussehen:

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

Das Bit am Ende, das besagt, $1::int[]kann geändert werden, um dem Typ Ihrer "ID" -Spalte zu entsprechen. Wenn der Typ Ihrer IDs beispielsweise lautet uuid, würden Sie schreiben $1::uuid[], um das Argument in ein Array von UUIDs zu zwingen. Hier finden Sie eine Liste der Postgres-Datentypen .

Dies ist einfacher als das Schreiben von Code zum Erstellen einer Abfragezeichenfolge und vor SQL-Injektionen geschützt.

Beispiel

Mit Node-Postgres sieht ein vollständiges JavaScript-Beispiel folgendermaßen aus:

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

Performance

Eine der besten Möglichkeiten, die Leistung einer SQL-Abfrage zu verstehen, besteht darin, zu untersuchen, wie die Datenbank sie verarbeitet. Die Beispieltabelle enthält ungefähr 400 Zeilen und einen Primärschlüssel namens "id" vom Typ text.

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

In beiden Fällen meldete Postgres denselben Abfrageplan:

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[]))

Abhängig von der Größe Ihrer Tabelle, in der sich ein Index befindet, und Ihrer Abfrage wird möglicherweise ein anderer Abfrageplan angezeigt. Aber für Anfragen wie die oben genannten ANYund INwerden auf die gleiche Weise verarbeitet.


Beachten Sie, dass dies zwar für die Form von ANY gilt, die eine Menge nimmt, es jedoch für jedes IN () und = ANY () eine zweite Form gibt, die nicht vollständig äquivalent ist. Bedenken Sie: stackoverflow.com/questions/34627026/…
Jonas Kello

17

Mit pg-versprechen funktioniert dies gut über den CSV-Filter (durch Kommas getrennte Werte):

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

Um die Bedenken hinsichtlich verschiedener Datentypen auszuräumen, :csvserialisiert der Modifikator das Array in csv und konvertiert alle Werte entsprechend ihrem JavaScript-Typ in das richtige PostgreSQL-Format, wobei sogar die benutzerdefinierte Typformatierung unterstützt wird .

Und wenn Sie Werte vom gemischten Typ wie diese haben: erhalten const values = [1, 'two', null, true]Sie immer noch das korrekt maskierte SQL:

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

AKTUALISIEREN

Von V7.5.1, pg-Versprechen begann die Unterstützung :listfür die als Wechsel Alias - :csvFilter:

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

1
Ich

0

Eine andere mögliche Lösung besteht darin, die UNNESTFunktion wie folgt zu verwenden :

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

Ich habe dies in einer gespeicherten Prozedur verwendet und es funktioniert gut. Glaube, es sollte auch mit Node-Pg-Code funktionieren.

Über die UNNEST-Funktion können Sie hier lesen .


1
Dies scheint ein großer Overkill im Vergleich zu den id = ANY($1)Lösungen
Lanzz

0

Eine andere mögliche Lösung ist beispielsweise für die REST-API in NODE JS:

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

Beachten Sie, dass jede Art von Array verwendet werden kann


-1

Die Idee allgemein:

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) +')';
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.