Многорядная вставка с pg-обещанием

Я хотел бы вставить несколько строк с одним запросом INSERT, например:

INSERT INTO tmp(col_a,col_b) VALUES('a1','b1'),('a2','b2')...

Есть ли способ сделать это легко, желательно для массива таких объектов:

[{col_a:'a1',col_b:'b1'},{col_a:'a2',col_b:'b2'}]

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

До сих пор я мог сделать это только для одного объекта:

INSERT INTO tmp(col_a,col_b) VALUES(${col_a},${col_b})

В качестве побочного вопроса: включены ли вставки с использованием ${} нотации, защищенной от SQL-инъекций?

Ответы

Ответ 1

Я - автор pg-обещания.

В старых версиях библиотеки это было описано на упрощенных примерах в статье Performance Boost, которая по-прежнему важна для написания высокопроизводительных приложений баз данных.

Более новый подход заключается в использовании пространства имен помощников, которое в конечном итоге является гибким и оптимизировано для повышения производительности.

const pgp = require('pg-promise')({
    /* initialization options */
    capSQL: true // capitalize all generated SQL
});
const db = pgp(/*connection*/);

// our set of columns, to be created only once, and then shared/reused,
// to let it cache up its formatting templates for high performance:
const cs = new pgp.helpers.ColumnSet(['col_a', 'col_b'], {table: 'tmp'});

// data input values:
const values = [{col_a: 'a1', col_b: 'b1'}, {col_a: 'a2', col_b: 'b2'}];

// generating a multi-row insert query:
const query = pgp.helpers.insert(values, cs);
//=> INSERT INTO "tmp"("col_a","col_b") VALUES('a1','b1'),('a2','b2')

// executing the query:
db.none(query);
    .then(data => {
        // success, data = null
    })
    .catch(error => {
        // error;
    });

См. API: ColumnSet, insert.

Такая вставка даже не требует транзакции, потому что, если один набор значений не может быть вставлен, ни один не будет вставлен.

И вы можете использовать тот же подход для генерации любого из следующих запросов:

  • однорядный INSERT
  • многорядный INSERT
  • однорядный UPDATE
  • многорядный UPDATE

Защищены ли вставки с использованием нотации $ {} от внедрения SQL?

Да, но не один. Если вы вставляете имена схем/таблиц/столбцов динамически, важно использовать имена SQL, что в сочетании защитит ваш код от внедрения SQL.


Смежный вопрос: Многострочные обновления PostgreSQL в Node.js


дополнительные

Q: Как получить id каждой новой записи одновременно?

A: Просто добавьте RETURNING id к вашему запросу и выполните его методом many:

const query = pgp.helpers.insert(values, cs) + 'RETURNING id';

const res = await db.many(query);
//=> [{id: 1}, {id: 2}, ...]

или, что еще лучше, получите идентификаторы и преобразуйте результат в массив целых чисел, используя метод map:

const res = await db.map(query, [], a => +a.id);
//=> [1, 2, ...]

Чтобы понять, почему мы использовали +, см. pg-обещание возвращает целые числа в виде строк.

UPDATE-1

Для вставки огромного количества записей см. Импорт данных.

ОБНОВЛЕНИЕ 2-

Используя v8.2.1 и новее, вы можете заключить статическую генерацию запроса в функцию, чтобы ее можно было генерировать в методе запроса, чтобы отклонить при сбое генерации запроса:

// generating a multi-row insert query inside a function:
const query = () => pgp.helpers.insert(values, cs);
//=> INSERT INTO "tmp"("col_a","col_b") VALUES('a1','b1'),('a2','b2')

// executing the query as a function that generates the query:
db.none(query)
    .then(data => {
        // success;
    })
    .catch(error => {
        // error;
        // will get here, even if the query generation fails
    });