【问题标题】:How to efficiently replace values for id fields in an object that was cloned?如何有效地替换克隆对象中 id 字段的值?
【发布时间】:2022-06-10 17:37:30
【问题描述】:

假设我在一个表中有两条记录:

[
    {
        id: 1,
        name: 'Michael',
        associations: [
            {
                from: 1,
                to: 2
            }
        ]
    },
    {
        id: 2,
        name: 'John',
        associations: [
            {
                from: 2,
                to: 1
            }
        ]
    },
]

如果我克隆这两个对象,我需要得到以下结果:

[
    {
        id: 1,
        name: 'Michael',
        associations: [
            {
                from: 1,
                to: 2
            }
        ]
    },
    {
        id: 2,
        name: 'John',
        associations: [
            {
                from: 2,
                to: 1
            }
        ]
    },
    {
        id: 3,
        name: 'Michael',
        associations: [
            {
                from: 3,
                to: 4
            }
        ]
    },
    {
        id: 4,
        name: 'John',
        associations: [
            {
                from: 4,
                to: 3
            }
        ]
    },
]

我怎样才能有效地做到这一点?

我目前正在做的是循环浏览所有记录并为每个记录运行INSERT。对于每个元素,我跟踪它的oldIdnewId,然后,我查询具有这些newIds 之一的所有记录。然后,我替换了这些值。显然,这对于两条记录来说很好,但如果我有数千条记录需要克隆,每条记录都包含数千个关联,这会对性能造成很大影响。

这里是代码。函数cloneElements是发起者。

const updateElementAssociationsById = async ({ id, associations }) => {
    return dbQuery(id, associations); // UPDATE ... SET associations WHERE id
}

const handleUpdateAssociations = async ({ data }) => {
    const promises = [];
    const records = await dbQuery(data.map(({ newId }) => newId)) ; // SELECT * FROM ... WHERE id in ANY 

    records.forEach(({ id, ...record }) => {
        const associations = [];

        record.associations.forEach((association) => {
            const sourceElement = data.find(({ oldId }) => oldId === association.from);
            const targetElement = data.find(({ oldId }) => oldId === association.to)

            association.from = sourceElement.newId;
            association.to = targetElement.newId;

            associations.push(association);
        });

        promises.push(updateElementAssociationsById({ id, associations }))
    });

    await Promise.all(promises);
}

const createElement = async ({ element, data }) => {
    const newElement = await dbQuery(element); // INSERT INTO ... RETURNING *;

    data.push({
        oldId: element.id,
        newId: newElement.id,
    });   
}

const cloneElements = async (records) => {
    const promises = [];
    const data = [];

    records.forEach((element) => {
        promises.push(createElement({ element, records, data }));
    });

    await Promise.all(promises);

    await handleUpdateAssociations({ data });
}

【问题讨论】:

  • 您的问题是关于 JS 还是数据库?感觉好像少了一个标签。
  • 我添加了我正在使用的数据库作为标签,但这主要是一个 JS 问题
  • 请正确描述克隆逻辑。
  • 我从现有实体中提取 id,然后创建具有完全相同属性的新实体(id 除外)。然后,我查询这些新创建的实体并根据data 映射替换fromto 值。

标签: javascript postgresql


【解决方案1】:

假设您一生中复制的次数不足以达到 int 约束。

给定 int 约束的最大可用 id 数为 +2147483627 ,大约为 2.1B 条记录。

如果我们不重复使用 id,我们可以跳过查询数据库中的新 id,因为新 id 只是前一个的偏移量。

async function cloneElements(records) {
  // get highest unique ID
  // assuming the records are ordered by id, you can just get the last element of the array.
  const highestID = records[records.length-1].id;

  // offset everything.
  const newRecords = records.map(record => ({
    id: record.id + highestID,
    ...record, // "name" key
    associations: record.associations.map(asso => ({
      from: asso.from + highestID,
      to: asso.to + highestID,
    }))
  }));
  
  await Promise.all(
    newRecords.map(newRecord => dbQuery(newRecord))
  );
}

有一种更优化的方法来获取偏移量,特别是如果您打算只克隆部分保存的记录,请参阅:https://www.postgresql.org/message-id/DF9C8135-DC67-11D7-B235-000A959C9730@starlett.lv


编辑:感觉很奇怪,你的数据库设计有 id 范围。 我认为您也可以纯粹解决“克隆实体列表” 使用 SQL(Insert into ... select from 有增量)。还有,你 实际上不需要在JS中设置ID,如果你在创建它们 顺序正确,生成的id应该是正确的。

【讨论】:

  • 这在 id 空间上非常浪费 - 每次有效地将最大 id 翻倍,这意味着您只能复制对象大约 30 次。我不认为 OP 打算克隆他表中的 all 对象,而只是选择少数几个。
  • 此外,这种方法将对为id 列定义的任何序列造成严重破坏
  • @Bergi,他可以手动更新为 id 定义的序列吗? (都在一个事务中)。另外,在我看来associations 是一个范围,这意味着重复的记录需要保留在一个块中。这意味着,如果他复制 10 条记录,他需要找到一个有 10 个连续可用 id 的地方(如果他重复使用 id,请记住)。如果他想复制一些选定的记录,我的代码应该仍然可以在他改变获取最高 ID 的方式的条件下工作。
【解决方案2】:

对于克隆,你只需要像下面这样,

let arrayFormat = [
    {
        id: 1,
        name: 'Michael',
        associations: [
            {
                from: 1,
                to: 2
            }
        ]
    },
    {
        id: 2,
        name: 'John',
        associations: [
            {
                from: 2,
                to: 1
            }
        ]
    },
];

let arrayLen = arrayFormat.length
arrayFormat.map((arr, index) => {
  if(index === arrayLen) {
   return false
  } 
  arrayFormat.push(arr)
})

console.log(JSON.stringify(arrayFormat))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-01-16
    • 2011-10-14
    • 2012-07-02
    • 1970-01-01
    • 2017-11-16
    • 2012-10-09
    • 1970-01-01
    • 2013-10-21
    相关资源
    最近更新 更多