【问题标题】:Easier way to update data with node-postgres?使用 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 中所有列的方式吗?

有什么想法吗?

【问题讨论】:

    标签: node.js postgresql


    【解决方案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 查询

      【讨论】:

        猜你喜欢
        • 2017-08-14
        • 2015-12-03
        • 2021-12-01
        • 2011-06-14
        • 1970-01-01
        • 2019-04-24
        • 1970-01-01
        • 2019-10-16
        • 1970-01-01
        相关资源
        最近更新 更多