You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

解决Knex/PostgreSQL中Upsert方法报错update is not a function

问题:KnexJS种子文件Upsert逻辑报错原因解析

环境信息

  • Knex版本:knex@2.3.0
  • PostgreSQL客户端:pg@8.8.0
  • 需求:编写种子文件实现Upsert逻辑,首次运行插入新数据,重复运行时更新已有数据

报错的Upsert实现代码

/**
 * @param { import("knex").Knex } knex
 * @returns { Promise<void> }
 */
exports.seed = async function (knex) {
  // Inserts a new questionnaire or updates an existing one
  const [questionnaire] = await knex('questionnaires')
    .insert({
      title: 'User Consent Questionnaire',
      description:
        'This questionnaire is used to obtain user consent to the privacy',
      eligibility_criteria: '[{"privacy_policy":"yes"}]',
      created_by: '0',
      type: 'USER_CONSENT',
      // locale: 'en_US', by default is already as en_US
      status: 'ACTIVE',
      // study_id: '',  by default is NULL
    })
    .onConflict(['type'])
    // Running the seed again the same row will be updated but with new values
    // The values can be edited right now are used as a starting point 
    // to ensure the correct functionality of the seed
    .update({
      title: 'Privacy Consent', 
      description: 'The user consenting to the privacy agreement',
      // eligibility_criteria: '[{}]'
      // created_by: '0',
      // locale: 'en_US',
      // study_id: '',  by default is NULL
    })
    .returning('id');

  const questionnaire_id = questionnaire.id;

  // Inserts a new question or updates an existing one
  await knex('questions')
    .insert({
      questionnaire_id,
      question: 'Do you consent to the privacy requirements?',
      description: 'privacy consent',
      type: 'yesno',
      key: 'privacy_policy',
      sort_order: 0,
    })
    .onConflict(['questionnaire_id', 'key'])
    .update({
      question: 'Do you agree to the privacy policy?',
      description: 'Privacy policy agreement',
    });

  return await knex('assigned_questionnaires').insert({
    questionnaire_id,
    eligible: 'NEVER_CHECKED',
  });
};

运行报错信息

Error while executing "/test/seeds/adding_default_user_consent_questionnaire.js" 
seed: knex(...).insert(...).onConflict(...).update is not a function

已实现的替代方案(通过判断数据存在性实现Upsert)

/**
 * @param { import("knex").Knex } knex
 * @returns { Promise<void> }
 */
exports.seed = async function (knex) {
  let questionnaire_id;

  // Check if a USER_CONSENT questionnaire already exists
  let [questionnaire] = await knex('questionnaires')
    .where({ type: 'USER_CONSENT' })
    .select('id');

  // If the questionnaire exists, update it; otherwise, insert a new one
  if (questionnaire) {
    questionnaire_id = questionnaire.id;
    await knex('questionnaires')
      .where({ id: questionnaire_id })
      .update({
        title: 'User Consent Questionnaire',
        description: 'This questionnaire is used to obtain user consent to the privacy',
        eligibility_criteria: '[{"agree_privacy":"yes"}]',
        type: 'USER_CONSENT',
        status: 'ACTIVE',
      });
  } else {
    [{ id: questionnaire_id }] = await knex('questionnaires')
      .insert({
        title: 'User Consent Questionnaire',
        description: 'This questionnaire is used to obtain user consent to the privacy',
        eligibility_criteria: '[{"agree_privacy":"yes"}]',
        type: 'USER_CONSENT',
        status: 'ACTIVE',
      })
      .returning('id');
  }

  // Check if the consent question already exists
  let [question] = await knex('questions')
    .where({
      questionnaire_id,
      key: 'agree_privacy'
    })
    .select('id');

  // If the question exists, update it; otherwise, insert a new one
  if (question) {
    await knex('questions')
      .where({ id: question.id })
      .update({
        questionnaire_id,
        question: 'Do you consent to the privacy requirements?',
        description: 'privacy consent',
        type: 'yesno',
        key: 'agree_privacy',
        sort_order: 1,
      });
  } else {
    await knex('questions')
      .insert({
        questionnaire_id,
        question: 'Do you consent to the privacy requirements?',
        description: 'privacy consent',
        type: 'yesno',
        key: 'agree_privacy',
        sort_order: 1,
      });
  }

  // Check if the assigned questionnaire already exists
  let [assignedQuestionnaire] = await knex('assigned_questionnaires')
    .where({ questionnaire_id })
    .select('id');

  // If the assigned questionnaire exists, update it; otherwise, insert a new one
  if (assignedQuestionnaire) {
    return await knex('assigned_questionnaires')
      .where({ id: assignedQuestionnaire.id })
      .update({
        questionnaire_id,
        eligible: 'NEVER_CHECKED',
      });
  } else {
    return await knex('assigned_questionnaires').insert({
      questionnaire_id,
      eligible: 'NEVER_CHECKED',
    });
  }
};

报错原因解析

你原代码报错的核心原因是Knex针对PostgreSQL的Upsert语法,在onConflict()之后需要用merge()方法来定义冲突时的更新字段,而不是update()。

Knex的API设计中,update()是独立的查询方法(用于单独的UPDATE语句),并不属于onConflict()链式调用的一部分。对应PostgreSQL的INSERT ... ON CONFLICT ... DO UPDATE SET语法,Knex提供的是.merge([updateFields])方法来实现。

把原代码中的.update({...})替换成.merge({...}),就能正确执行Upsert逻辑,无需通过查询判断数据是否存在的方式实现。

比如修正后的问卷Upsert代码片段:

const [questionnaire] = await knex('questionnaires')
  .insert({
    title: 'User Consent Questionnaire',
    description: 'This questionnaire is used to obtain user consent to the privacy',
    eligibility_criteria: '[{"privacy_policy":"yes"}]',
    created_by: '0',
    type: 'USER_CONSENT',
    status: 'ACTIVE',
  })
  .onConflict(['type'])
  .merge({
    title: 'Privacy Consent', 
    description: 'The user consenting to the privacy agreement',
  })
  .returning('id');

内容的提问来源于stack exchange,提问作者Jakub

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 03:15:42