如何检查任何给定的查询是否在knex

时间:2018-01-09 14:08:57

标签: node.js knex.js

我有两个问题:

a)select id from ingredietns where name = my_param;

b)select word_id from synonyms where name = my_param;

两者都返回0或1行。如果需要,我还可以添加limit 1(或者在knex first()中)。

我可以将每个翻译成knex,如下所示:

knex("ingredients").select('id').where('name', my_param) //do we need first()?

knex("synonyms").select('word_id').where('name', my_param) //do we need first()?

我需要名为“ingredientGetOrCreate(my_param)”的功能。

这个功能

a)检查以上任何查询是否返回结果 b)如果其中任何一个返回,则返回ingredients.id或synonyms.word_id - 只能返回一个 c)如果记录在任何表中都没有,我需要做knex inesrt a并从函数中返回新添加的id

d)稍后我不确定我是否也理解如何调用这个新创建函数。 函数ingredientGetOrCreate稍后将用作单独的函数,或者在以下场景中使用(如“循环”),这对我来说也不起作用:

knex("products") // for each product
    .select("id", "name")
    .map(function (row) {
        var descriptionSplitByCommas = row.desc.split(",");
        Promise.all(descriptionSplitByCommas
            .map(function (my_param) {
                // here it comes - call method for each param and do insert
                ingredientGetOrCreate(my_param)
                  .then(function (id_of_ingredient) {
                          knex('ingredients_products').insert({ id_of_ingredient });                      

                  });
         ...

由于asynchronouse部分,我遇到knexPromise查询。请问有什么线索吗? 我虽然可以某种方式使用Promise.allPromise.some来调用这两个查询。

P.S。这是我第一天使用nodejs,Promise和knex。

1 个答案:

答案 0 :(得分:3)

就我解码你的问题而言,它由两部分组成:

(1)您需要实现upsert逻辑(get-or-create逻辑)。

(2)您的get部分要求不查询单个表,而是按特定顺序查询一对表。表名暗示这是应用程序内部的某种别名引擎。

让我们从(2)开始。这肯定可以通过两个查询来解决,就像你感觉到的一样。

function pick_name (rows)
{
    if (! rows.length) return null
    return rows[0].name
}

// you can sequence queries
function ingredient_get (name)
{
    return knex('ingredients')
    .select('id').where('name', name)
    .then(pick_name)
    .then(name =>
    {
        if (name) return name

        return knex('synonyms')
        .select('word_id').where('name', name)
        .then(pick_name)
    })
}

// or run em parallel
function ingredient_get (name)
{
    var q_ingredients = knex('ingredients')
    .select('id').where('name', name)
    .then(pick_name)

    var q_synonyms = knex('synonyms')
    .select('word_id').where('name', name)
    .then(pick_name)

    return Promise.all([ q_ingredients, q_synonyms ])
    .then(([name1, name2]) =>
    {
       return name1 || name2
    })
}

这里的重要概念:

  • 两种形式都运作良好并且首先出现或JS'null
  • 第一种形式优化了对DB的查询计数。
  • 第二种形式可以优化答案时间。

但是,您可以更深入地使用更多SQL。这个任务有一个特殊的工具叫做COALESCE。您可以参考SQL文档,这里是COLASCE of PostgreSQL 9COALESCE的主要思想是返回第一个非NULL参数,否则返回NULL。因此,您可以利用它来优化查询和回答时间。

function ingredient_get (name)
{
    // preparing but not executing both queries
    var q_ingredients = knex('ingredients')
    .select('id').where('name', name)

    var q_synonyms = knex('synonyms')
    .select('word_id').where('name', name)

    // put them in COALESCE
    return knex.raw('SELECT COALESCE(?, ?) AS name', [ q_ingredients, q_synonyms ])
    .then(pick_name)

此解决方案可确保单个查询,此外,数据库引擎可以以其认为合适的任何方式优化执行。

现在让我们解决(1):我们现在得到ingredient_get(name),返回Promise<string | null>。我们可以使用它的输出来激活create逻辑或返回我们的值。

function ingredient_get_or_create (name, data)
{
    return ingredient_get(name)
    .then(name =>
    {
       if (name) return name

       // …do your insert logic here
       return knex('ingredients').insert({ name, ...data }) 
       // guarantee homohenic output across get/create calls:
       .then(() => name)
    })
}

现在ingredient_get_or_create执行您想要的upsert逻辑。

<强> UPD1 : 我们已经获得了ingredient_get_or_create,它在任何场景中都会返回Promise<name>(包括获取或创建)。

a)如果您之后需要执行任何特定逻辑,则可以使用then

ingredient_get_or_create(…)
.then(() => knex('another_table').insert(…))
.then(/* another logic after all */)

在承诺语言中,这意味着“如果以前是正确的(then)»那就行动(ingredient_get_or_create)。在大多数情况下,这就是您所需要的。

b)要在promises中实现for循环,你会得到多种不同的习语:

// use some form of parallelism
var qs = [ 'name1', 'name2', 'name3' ]
.map(name =>
{
   return ingredient_get_or_create(name, data)
})

var q = Promise.all(qs)

请注意,这是一个激进的并行性,您将获得最大的并行查询,因为您的输入数组提供。

如果不需要,您需要限制并行性甚至按顺序运行任务。 Bluebird的Promise.map是一种运行map的方式,类似于上面的示例,但有concurrency选项可用。请仔细考虑文档。

还有Bluebird的Promise.mapSeries,它在概念上类似于for-loop但有承诺。这就像是顺序运行的地图。查看文档了解详细信息。

Promise.mapSeries([ 'name1', 'name2', 'name3' ],
(name) =>  ingredient_get_or_create(name, data))
.then(/* logic after all mapSeries are OK */)

我相信最后一个是你需要的。