【问题标题】:Postgres equivalent of MySQL's SET?Postgres 相当于 MySQL 的 SET?
【发布时间】:2017-02-24 23:54:30
【问题描述】:

我正在尝试从 MySQL 迁移到 Postgres,但遇到了一些问题。我有一个表单,用户填写大约 40 个字段,这些值被插入到数据库中。使用 MySQL 我习惯这样做:

INSERT INTO table_name SET name="John Smith", email="jsmith@gmail.com", website="jsmith.org";

我还在使用带有 nodejs 的 mysql 模块,这就是我的代码目前的样子:

var data = {
  name: req.body.name,
  email: req.body.email,
  website: req.body.website,
  ...
  ...
}

var query = connection.query('INSERT INTO table_name SET ?', data)

由于SET 不是有效的SQL,如果我想使用Postgres,使用pg 模块的查询将如下所示:

client.query('INSERT INTO table_name (name, email, website) VALUES ($1, $2, $3)', req.body.name, req.body.email, req.body.website)

考虑到我有将近 40 个字段,这将变得非常乏味。

有没有更好的方法来做到这一点,还是我不得不手动编写查询?

谢谢。

【问题讨论】:

  • 很抱歉,该语法是 MySQL 特有的。您可以编写自己的包装器,但我认为这比重写所有查询需要更多时间。
  • 如果req.body只有相关的自己的属性,你可以使用Object.keys来构建( column_name [ , ... ] )列表并根据键的长度生成VALUES ($1, ...)表达式。然后再次使用该键列表形成参数列表并应用。但另一方面,如果某个随机键突然出现在您的 req.body 中,则手动编写查询的好处是不会崩溃。
  • @IljaEverilä 但是应该在某处(很可能来自 ORM)有一个列列表,因此您可以从该列表开始,删除通常的嫌疑人(id,时间戳,...),并留下一个列白名单来过滤req.body。一种临时解决方案,但也是支持迁移的合理桥梁。
  • @muistooshort 确实使用来自req.body 的列名白名单会更好。看起来原来的data 对象已经被列入白名单,所以可以使用。

标签: javascript mysql sql node.js postgresql


【解决方案1】:

因为你已经有一个包含相关数据的对象

var data = {
  name: req.body.name,
  email: req.body.email,
  website: req.body.website,
  ...
  ...
}

您可以编写一个查询函数来包装pg 客户端的query 方法并添加对象即值支持:

function insert(client, table, values) {
    const keys = Object.keys(values),
          // IMPORTANT: escape column identifiers. If `values` should come
          // from an uncontrolled source, naive concatenation would allow
          // SQL injection.
          columns = keys.map(k => client.escapeIdentifier(k)).join(', '),
          placeholders = keys.map((k, i) => '$' + (i + 1)).join(', ');

    return client.query(`INSERT INTO ${client.escapeIdentifier(table)} 
                         (${columns}) VALUES (${placeholders})`,
                        // According to docs `query` accepts an array of
                        // values, not values as positional arguments.
                        // If this is not true for your version, use the
                        // spread syntax (...args) to apply values.
                        keys.map(k => values[k]));
}

...

insert(client, 'table_name', data);

或者,您可能可以使用sql-template-strings 重写您的查询,这看起来相当不错。

【讨论】:

  • 感谢您加入 escapeIdentifier 电话。 String interpolation with backticks 可能比所有 +s 更干净,如果你假设 ES6+ 但这只是挑剔:)
  • @muistooshort 非常真实,值得编辑。我仍然忘记了这一点,尽管它是语言的强大补充。
【解决方案2】:

这在使用pg-promise时很容易实现,完整代码如下:

var pgp = require('pg-promise')();
var db = pgp(/*connection details*/);

app.get('/get', (req, res)=> {
    var insert = pgp.helpers.insert(req.body, null, {table: 'table_name'});
    db.none(insert)
        .then(()=> {
            // success
        })
        .catch(error=> {
            // error
        });
});

API:helpers.insertnone

方法helpers.insert 可以为您生成完整的INSERT 语句,格式正确。

它不仅是最简单的方法,而且也是最灵活的方法,因为您无需更改任何内容即可开始生成多插入语句,即如果insert 方法的第一个参数是一个数组对象,您将获得多插入查询。


如果您只需要请求中的特定属性,那么最好的方法是单独指定它们,作为请求之外的ColumnSet(可重复使用,以获得最佳性能):

var cs = new pgp.helpers.ColumnSet(['name', 'email', 'website'], {table: 'table_name'});

app.get('/get', (req, res)=> {
    var insert = pgp.helpers.insert(req.body, cs);
    db.none(insert)
        .then(()=> {
            // success
        })
        .catch(error=> {
            // error
        });
});

返回新 id-s 数组的示例

无转化

app.get('/get', (req, res)=> {
    var insert = pgp.helpers.insert(req.body, cs) + 'RETURNING id';
    db.many(insert)
        .then(data=> {
            // success
        })
        .catch(error=> {
            // error
        });
});

有转化

app.get('/get', (req, res)=> {
    var insert = pgp.helpers.insert(req.body, cs) + 'RETURNING id';
    db.map(insert, [], a=>a.id)
        .then(ids=> {
            // ids = array of id-s
        })
        .catch(error=> {
            // error
        });
});

【讨论】:

  • 我将如何做与ColumnSet相反的事情?我有一个我想从查询中忽略的字段(蜜罐),而不是写出我在查询中想要的所有字段。
  • 没关系...我会在使用完delete data.honeypot 后使用它,这样它就不会被插入。
  • @ipgof 目前ColumnSet不支持自动否定,只支持包含。但是,它可以接受要跳过的显式列,在名称前使用 ? 或设置属性 cnd - 请参阅 API。
  • 只是一个简单的问题,假设我想做你上面演示的插入查询,但也返回一个id,正如你在文档中显示的here。我该怎么做?
  • @ipgof 我已经为此添加了示例。
猜你喜欢
  • 1970-01-01
  • 2011-05-16
  • 2017-04-30
  • 2019-05-02
  • 1970-01-01
  • 2011-04-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多