node-postgres: как выполнить запрос "WHERE col IN ()"?

Я пытаюсь выполнить такой запрос:

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

проблема в том, что список идентификаторов, которые я хочу фильтровать, не является постоянным и должен отличаться при каждом выполнении. Мне также нужно было бы избежать идентификаторов, потому что они могут исходить из ненадежных источников, хотя на самом деле я бы избегал всего, что входит в запрос, независимо от надежности источника.

node-postgres работает исключительно с связанными параметрами:client.query('SELECT * FROM table WHERE id = ', [ id ]); этот будет работать, если у меня было известное количество значений (client.query('SELECT * FROM table WHERE id IN (, , )', [ id1, id2, id3 ])), но не будет работать с массивом напрямую: client.query('SELECT * FROM table WHERE id IN ()', [ arrayOfIds ]), поскольку, похоже, нет никакой специальной обработки параметров массива.

построение шаблона запроса динамически в соответствии с количеством элементов в массиве и расширение массива ids в массив параметров запроса (который в моем фактическом случае также содержит другие параметры помимо списка ids) кажется неоправданно обременительным. Жесткое кодирование списка идентификаторов в запросе шаблон также кажется нежизнеспособным, поскольку node-postgres не предоставляет методов экранирования значений.

Это кажется очень распространенным случаем использования, поэтому я предполагаю, что я действительно что-то упускаю, а не то, что невозможно использовать общий IN (values) оператор SQL с node-postgres.

если кто-то решил эту проблему более элегантно, чем те, которые я перечислил выше, или если я действительно пропустил что-то о node-postgres, пожалуйста, помогите.

6 ответов


мы видели этот вопрос раньше в списке проблем github. Правильный способ, чтобы динамически генерировать список параметров на основе массива. Что-то вроде этого:--2-->

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.


похоже, вы, возможно, были близки на основе вашего комментария к @ebohlman's ответ. Вы можете использовать WHERE id = ANY(::int[]). PostgreSQL будет преобразование массив для типа, к которому приведен параметр в ::int[]. Вот надуманный пример, который работает для меня:

var ids = [1,3,4]; 

var q = client.query('SELECT Id FROM MyTable WHERE Id = ANY(::int[])',[ids]);

q.on('row', function(row) {
  console.log(row);
})

// outputs: { id: 1 }
//          { id: 3 }
//          { id: 4 }

лучшим решением, которое я нашел, было использование ANY функция с принуждением массива Postgres. Это позволяет сопоставить столбец с произвольным массивом значений, как если бы вы написали col IN (v1, v2, v3). Это подход в перо но здесь я показываю, что производительность ANY это то же самое, что IN.

запрос

ваш запрос должен выглядеть так:

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

этот бит в конце, который говорит ::int[] можно изменить на соответствует типу столбца" id". Например, если тип ваших идентификаторов uuid, ты вот пишешь ::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(::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 обрабатываются таким же образом.


используя pg-promise, это хорошо работает через CSV фильтр (разделенный запятыми):

const values = [1, 2, 3, 4];

db.any('SELECT * FROM table WHERE id IN (:csv)', [values])
    .then(data => {
        console.log(data);
    })
    .catch(error => {
        console.log(error);
    });

и для решения проблемы различных типов данных,:csv модификатор сериализует массив в csv, при этом Преобразуя все значения в их правильный формат PostgreSQL, в соответствии с их типом JavaScript, даже поддерживая Форматирование Пользовательского Типа.

и если у вас есть значения смешанного типа, такие как:const values = [1, 'two', null, true], вы все еще получит правильно экранированный SQL:

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

обновление

от V7.5.1, pg-promise поддержка :list как взаимозаменяемый псевдоним для :

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

другим возможным решением является использование


другое возможное решение, например, для REST API в узле 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() )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
     });
});

имейте в виду, что любой тип массива можно использовать