使用 node-postgres 更新数据的更简单方法?

Posted

技术标签:

【中文标题】使用 node-postgres 更新数据的更简单方法?【英文标题】:Easier way to update data with node-postgres? 【发布时间】:2014-03-12 15:54:13 【问题描述】:

我正在使用极好的插件 node-postgres,https://github.com/brianc/node-postgres

我有这个更新休息电话。我的表中有大约 30 列。有没有更简单的方法来更新这些?

/*
 Post /api/project/products/:pr_id HTTP/1.1
 */
exports.updateProduct = function(req, res)
  pg.connect(cs, function(err, client, done) 
    var query = "UPDATE products SET pr_title = ($1), pr_usercode = ($2) WHERE pr_id=($3)";
    client.query(query, [req.body.pr_title, req.body.pr_usercode, req.params.pr_id], function(err, result) 
      if (handleErr(err, done)) return;
      done();
      sendResponse(res, result.rows[0]);
    )
  );
;

我这里只有三列。当我写完所有 30 列时,它会变得凌乱且难以维护。必须是一种只用简单的一行更新 req.body 中所有列的方式吗?

有什么想法吗?

【问题讨论】:

【参考方案1】:

你总是可以推出这样的功能:

function updateProductByID (id, cols) 
  // Setup static beginning of query
  var query = ['UPDATE products'];
  query.push('SET');

  // Create another array storing each set command
  // and assigning a number value for parameterized query
  var set = [];
  Object.keys(cols).forEach(function (key, i) 
    set.push(key + ' = ($' + (i + 1) + ')'); 
  );
  query.push(set.join(', '));

  // Add the WHERE statement to look up by id
  query.push('WHERE pr_id = ' + id );

  // Return a complete query string
  return query.join(' ');

然后这样使用它:

/*
 Post /api/project/products/:pr_id HTTP/1.1
 */
exports.updateProduct = function(req, res)
  pg.connect(cs, function(err, client, done) 

    // Setup the query
    var query = updateProductByID(req.params.pr_id, req.body);

    // Turn req.body into an array of values
    var colValues = Object.keys(req.body).map(function (key) 
      return req.body[key];
    );

    client.query(query, colValues, function(err, result) 
      if (handleErr(err, done)) return;
      done();
      sendResponse(res, result.rows[0]);
    );
  );
;

或者,如果你需要一个 ORM,因为你会像上面那样做很多事情,你应该检查像 Knex.js 这样的模块

【讨论】:

请记住updateProductByID 中的id 应该是参数化值。不使用i + 1,而是使用i + 2,然后将产品ID值unshift为colValues,以确保不会通过插入恶意ID进行SQL注入。 过滤器函数返回布尔表达式,而不是值。您应该将“过滤器”替换为“地图”,这是返回值的正确函数 @NirO。好消息——这是用伪代码编写的,并没有实际执行。会更新。 updateProductById 函数只是构建一个字符串。函数名有误导性 @anolan23 这不是生产代码——命名很难。【参考方案2】:

已经给出了很好的答案,但是恕我直言,在一方面还不够好,它们都缺乏良好的抽象性。我将尝试使用node-postgres 提供更抽象的方式来更新postgres 中的数据。

遵循官方文档总是好的做法,以下代码结构取自node-postgres,您可以随意扩展它:

这是我的,这是您与数据库交互的地方

const  Pool  = require("pg");
const connection = require("./connection.json");
const pool = new Pool(connection);
const  insert, select, remove, update  = require("./helpers");


/**
 * The main mechanism to avoid SQL Injection is by escaping the input parameters.
 * Any good SQL library should have a way to achieve this.
 * PG library allows you to do this by placeholders `($1, $2)`
 */
module.exports = 
  query: (text, params, callback) => 
    const start = Date.now();

    return pool.query(text, params, (err, res) => 
      const duration = Date.now() - start;
      console.log("executed query",  text, duration, rows: res.rowCount );
      callback(err, res);
    );
  ,

  getClient: callback => 
    pool.connect((err, client, done) => 
      const query = client.query;
      // monkey patch the query method to keep track of the last query executed
      client.query = (...args) => 
        client.lastQuery = args;
        return query.apply(client, args);
      ;
      // set a timeout of 5 seconds, after which we will log this client's last query
      const timeout = setTimeout(() => 
        console.error("A client has been checked out for more than 5 seconds!");
        console.error(
          `The last executed query on this client was: $client.lastQuery`
        );
      , 5000);
      const release = err => 
        // call the actual 'done' method, returning this client to the pool
        done(err);
        // clear our timeout
        clearTimeout(timeout);
        // set the query method back to its old un-monkey-patched version
        client.query = query;
      ;
      callback(err, client, release);
    );
  ,

  /**
   * Updates data
   *
   * entity: table name, e.g, users 
   * conditions:  id: "some-unique-user-id", ... 
   * fields: list of desired columns to update  username: "Joe", ... 
   */
  updateOne: async (entity, conditions, fields) => 
    if (!entity) throw new Error("no entity table specified");
    if (Utils.isObjEmpty(conditions))
      throw new Error("no conditions specified");

    let resp;   
    const  text, values  = update(entity, conditions, fields);

    try 
      rs = await pool.query(text, values);
      resp = rs.rows[0];
     catch (err) 
      console.error(err);
      throw err;
    

    return resp;
  ,

  createOne: async (entity, data) => 
  ,

  deleteOne: async (entity, conditions, data) => 
  ,

  findAll: async (entity, conditions, fields) => 
  ,

  // ... other methods
;

这里是 CRUD 操作的辅助方法,它们会准备查询 带有准备值的文本:

/**
 * tableName: `users`
 * conditions:  id: 'joe-unique-id', ... 
 * data:  username: 'Joe', age: 28, status: 'active', ... 
 *
 *  "UPDATE users SET field_1 = $1, field_2 = $2, field_3 = $3, ... ( WHERE ...) RETURNING *";
 */
exports.update = (tableName, conditions = , data = ) => 
  const dKeys = Object.keys(data);
  const dataTuples = dKeys.map((k, index) => `$k = $$index + 1`);
  const updates = dataTuples.join(", ");
  const len = Object.keys(data).length;

  let text = `UPDATE $tableName SET $updates `;

  if (!Utils.isObjEmpty(conditions)) 
    const keys = Object.keys(conditions);
    const condTuples = keys.map((k, index) => `$k = $$index + 1 + len `);
    const condPlaceholders = condTuples.join(" AND ");

    text += ` WHERE $condPlaceholders RETURNING *`;
  

  const values = [];
  Object.keys(data).forEach(key => 
    values.push(data[key]);
  );
  Object.keys(conditions).forEach(key => 
    values.push(conditions[key]);
  );

  return  text, values ;
;

exports.select = (tableName, conditions = , data = ["*"]) => ...
exports.insert = (tableName, conditions = ) => ...
exports.remove = (tableName, conditions = , data = []) => ...

最后,您可以在路由处理程序中使用它而不会造成混乱 你的代码库:

const db = require("../db");

/**
 *
 */
exports.updateUser = async (req, res) => 
  try 
    console.log("[PUT] api/v1/users");
    const fields = 
      name: req.body.name,
      description: req.body.description,
      info: req.body.info
    ;
    const userId = req.params.id;

    const conditions =  id: userId ;
    const updatedUser = await db.updateOne("users", conditions, fields);

    if (updatedUser) 
      console.log(`team $updatedUser.name updated successfully`);
      return res.json(updatedUser);
    
    res.status(404).json( msg: "Bad request" );
   catch (err) 
    console.error(err);
    res.status(500).send( msg: "Server error" );
  
;

方便的实用程序:

const Utils = ;
Utils.isObject = x => x !== null && typeof x === "object";
Utils.isObjEmpty = obj => Utils.isObject(obj) && Object.keys(obj).length === 0;

【讨论】:

很好的答案。你能解释一下$k = $$index + 1 是如何不受sql 注入影响的吗?如果发送的请求带有一个带有恶意 sql 键的对象怎么办? @Chano,它发生在抽象层并返回 textvalues 作为辅助方法的结果,但最终它们最终成为 pool.query(text, values) 的参数,此方法 @ 987654332@ 由pg 提供,负责所有清理和其他可能的恶意内容【参考方案3】:

我喜欢使用knexjs,它适用于 postgre。它也是一种有趣的 javascript 编写查询的方式(没有那些讨厌的 SQL 字符串操作)。

以这个方法为例,它存储了一些联系信息。该联系信息的 JSON 模式在其他地方定义(在我验证时也很有用)。结果是代码生成的查询,其中仅包含传入的列。

function saveContactInfo( inputs, callback ) 
  var setObj = ;
  for( var property in inputs.contact )
  
    //assumes properties are same as DB columns, otherwise need to use some string-mapping lookup.
    setObj[ property ] = inputs.contact[property];
  
  setObj[ "LastModified" ] = new Date();

  var query = knex( "tblContact" ).update( setObj ).where( "contactId", inputs.contact.contactId );
  //log.debug("contactDao.saveContactInfo: " + query.toString());
  query.exec( function(err, results )
    if(err) return callback(err);
    //Return from DB is usually an array, so return the object, not the array.
    callback( null, results[0] );
  );    

Knexjs 也有一些漂亮的 postgre-only 选项(如果我没有使用 mysql,这对我很有用)

【讨论】:

答案也很好。我批准了另一个,因为他的解决方案没有插件。但我一定会尝试 knexjs。好像很方便Thx!【参考方案4】:

我的简单例子:

async update(objectToSave) 
    const dbID = objectToSave.id;

    const args = Object.values(objectToSave);
    const keys = Object.keys(objectToSave).join(',');
    const argKeys = Object.keys(objectToSave).map((obj,index) =>  
      return "$"+(index+1) 
    ).join(',');        

    const query = "UPDATE table SET ("+keys+") = ("+argKeys+") WHERE id = "+dbID;

    try 
        const res = await client.query(query, args)
        return true;
     catch (err) 
        console.log(err.stack)
        return false;
    

【讨论】:

【参考方案5】:

创建插入查询

exports.createInsertQuery = (tablename, obj) => 
    let insert = 'insert into ' + tablename;
    let keys = Object.keys(obj);
    let dollar = keys.map(function (item, idx)  return '$' + (idx + 1); );
    let values = Object.keys(obj).map(function (k)  return obj[k]; );
    return 
        query: insert + '(' + keys + ')' + ' values(' + dollar + ')',
        params: values
    

用法

let data = firstname : 'hie' , lastname : 'jack', age : 4
let yo = createInsertQuery('user',data) 

client.query(yo.query, yo.params ,(err,res) =>
 console.log(res)
)

同样,您可以创建 updatedelete 查询

【讨论】:

以上是关于使用 node-postgres 更新数据的更简单方法?的主要内容,如果未能解决你的问题,请参考以下文章

将 Async/Await 与 node-postgres 一起使用

为啥我不能使用 node-postgres 从数据库中删除?

使用 node-postgres 在 postgres 中存储文件

pg(node-postgres)是不是自动清理数据

具有大量查询的 node-postgres

使用 node-postgres 监听查询超时?