简体   繁体   English

如何有效地替换克隆对象中 id 字段的值?

[英]How to efficiently replace values for id fields in an object that was cloned?

Suppose I have two records in a table:假设我在一个表中有两条记录:

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

If I clone these two objects, I need to end up with the following:如果我克隆这两个对象,我需要得到以下结果:

[
    {
        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
            }
        ]
    },
]

How can I effectively achieve this?我怎样才能有效地实现这一目标?

What I am currently doing is cycling through all records and running an INSERT for each.我目前正在做的是循环浏览所有记录并为每个记录运行一个INSERT For each element, I track its oldId and newId and afterwards, I query for all records that have one of these newId s.对于每个元素,我跟踪其oldIdnewId ,然后查询具有这些newId之一的所有记录。 Then, I replace the values.然后,我替换了这些值。 Obviously, this is fine for two records, but if I have thousands of records that need to be cloned, each containing thousands of associations, this can hit performance very hard.显然,这对于两条记录来说很好,但如果我有数千条记录需要克隆,每条记录都包含数千个关联,这会对性能造成很大的影响。

Here is the code.这是代码。 The function cloneElements is the initiator.函数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 });
}

Assuming you won't duplicate enough in a lifetime to hit the int constraint.假设您一生中复制的次数不足以达到 int 约束。

The maximum number of available ids given the int constraint is +2147483627 , which is approximately 2.1B records.给定 int 约束的最大可用 id 数为 +2147483627 ,大约为 2.1B 条记录。

If we don't re-use ids, we can skip querying the database for that new id, because the new id is just an offset of the previous one.如果我们不重复使用 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))
  );
}

There is a more optimized way to get the offset, especially if you intend to clone only a part of the saved records, see: https://www.postgresql.org/message-id/DF9C8135-DC67-11D7-B235-000A959C9730@starlett.lv有一种更优化的方法来获取偏移量,特别是如果您打算只克隆一部分保存的记录,请参阅: https ://www.postgresql.org/message-id/DF9C8135-DC67-11D7-B235-000A959C9730 @starlett.lv


Edit: It feels strange that you have range on ids, on your database design.编辑:感觉很奇怪,你的数据库设计有 id 范围。 I think you can also solve that "cloning a list of entities" purely with SQL ( Insert into ... select from with an increment).我认为您也可以纯粹使用 SQL 来解决“克隆实体列表”( Insert into ... select from with an increment)。 Also, you actually don't need to set the ID in JS, if you create them in the right order, the generated id should be correct.另外,你实际上不需要在JS中设置ID,如果你按照正确的顺序创建它们,生成的ID应该是正确的。

For cloning, You just need to do like below,对于克隆,您只需要执行以下操作,

 let arrayFormat = [ { id: 1, name: 'Michael', associations: [ { from: 1, to: 2 } ] }, { id: 2, name: 'John', associations: [ { from: 2, to: 1 } ] }, ]; let arrayLen = arrayFormat.length let tempArr = JSON.parse(JSON.stringify(arrayFormat)) let id = arrayFormat.length tempArr.map((arr, index) => { if(index === arrayLen) { return false } id += 1 arr.id = id if(arr.id % 2 === 0) { arr.associations[0].from = arr.id arr.associations[0].to = arr.id - 1 } else { arr.associations[0].from = arr.id arr.associations[0].to = arr.id + 1 } arrayFormat.push(arr) }) console.log(JSON.stringify(arrayFormat))

I would do this by creating an interim lookup table.我会通过创建一个临时查找表来做到这一点。 In js it could look like this:在 js 中它可能看起来像这样:

const lookup = new Map();

In Postgres it could look like this:在 Postgres 中,它可能看起来像这样:

CREATE TEMPORARY TABLE IF NOT EXISTS lookup(old_id int NOT NULL, new_id int NOT NULL);

Then when you clone, you create this association:然后,当您克隆时,您会创建此关联:

lookup.set(old_id, new_id);

and / or:和/或:

INSERT INTO lookup(old_id,new_id) values($new_id,$old_id);

Then, I would use a recursive idempotent function to get, or clone-and-get records as needed, giving us O(1) on each non-virgin lookup:然后,我将使用递归幂等函数来获取或根据需要克隆并获取记录,从而在每次非原始查找时为我们提供 O(1):

const lookup = new Map();
const getOrClone = (oldId) => {
    let rowPromise = lookup.get(oldId);
    if (!rowPromise) {
        const getOrCloneImpl = async () => {
            let oldRow = await db.sql(`SELECT * FROM Elements WHERE id = ${oldId}`);
            const row = await db.sql(`INSERT INTO Elements ...stuff from OldRow`);
            // @note: row now contains a new unique ID generated by Postgres

            if ("associations" in row) {
                // call self recursively to update "association" IDs
                let newAssociations = row.associations.map(tuple => {
                    const fromRow = await getOrClone(tuple.from);
                    const toRow = await = getOrClone(tuple.to);
                    return {
                      from: fromRow.id,
                      to: toRow.id
                    };
                });
                row.associations = await Promise.all(newAssociations);
            }
            return row;
        };
        rowPromise = getOrCloneImpl();
        lookup.set(oldId, rowPromise);
    }
    return rowPromise;
};

So that the final solution might look something like this:所以最终的解决方案可能看起来像这样:

const cloneElements = async (recordIds) => {
    const promises = recordIds.map(getOrClone);
    await Promise.all(promises);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM