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

如何从两个长度不同的对象数组生成CategoryItems关联数组?

GraphQL CSV导入:生成分类-条目关联数组并插入

我知道这类话题已经讨论过很多次,但针对我的具体场景还是有疑问。我在GraphQL resolver中处理CSV导入数据,需要完成三次数据库插入操作。

原始输入数据格式

[{
    name: 'Item 1',
    price: 0,
    quantity: 1,
    url: null,
    user_id: 1,
    category_name: 'Category 1',
    qty: null
  },
  // ... 其他原始数据
]

格式化后的数组

items数组

[{
    id: 677,
    name: 'Item 1',
    price: 0,
    quantity: 1,
    url: null,
    user_id: 1,
    created_at: '2022-07-28T18:00:22.810136+00:00',
    updated_at: '2022-07-28T18:00:22.810136+00:00',
  },
  // ... 其他条目
]

categories数组

[{
    id: 148,
    name: 'Category 1',
    list_id: 2,
    updated_at: '2022-07-28T18:00:23.09351+00:00',
    created_at: '2022-07-28T18:00:23.09351+00:00',
  },
  // ... 其他分类
]

期望的CategoryItems关联数组

[{
    category_id: 148,
    item_id: 677,
    position: 1
  },
  { category_id: 148, item_id: 678, position: 2 },
  { category_id: 149, item_id: 679, position: 1 },
]

完整Resolver代码(已补全关联数组生成逻辑)

import { Context } from '@/context';
import { CsvImportInput, CsvImportResponse } from '@/generated/types';
import { genericError } from '@/helpers/errors';
import { getUserId } from '@/helpers/utils';
import formatCsv from '@/helpers/formatCsv';
import { getUniqueListBy } from '@/helpers/functions';
import supabase from '@/lib/supabase';

const csvImport = async (_parent: any, { input }: { input: CsvImportInput }, context: Context): Promise<CsvImportResponse> => {
  const userId = getUserId(context.token);

  if (!userId) {
    return {
      success: false,
      ...genericError({ message: 'Must supply user id', path: 'Csv import resolver: try' }),
    };
  }

  const { data, list_id } = input;

  try {
    const formattedData = formatCsv(data, userId);

    // bulk insert items
    const { data: bulkInsertData, error: bulkInsertError } = await supabase
      .from('items')
      .insert(formattedData);
    console.log({ bulkInsertData });
    console.log({ bulkInsertError });

    if (!bulkInsertData || bulkInsertError) {
      return {
        success: false,
        ...genericError({ message: 'Error importing items', path: 'Csv import resolver: try' }),
      };
    }

    // if not attaching to specific list
    if (!list_id) {
      return {
        success: true,
        errors: null,
      };
    } else {
      // create categories
      const newCategories = data.map(item => ({ name: item.category_name, list_id }));
      const uniqueCategories = getUniqueListBy(newCategories, 'name');
      console.log('uniqueCategories: ', uniqueCategories);

      const { data: categoriesData, error: categoriesError } = await supabase
        .from('categories')
        .insert(uniqueCategories);
      console.log({ categoriesData });
      console.log({ categoriesError });

      if (!categoriesData || categoriesError) {
        return {
          success: false,
          ...genericError({ message: 'Error importing categories', path: 'Csv import resolver: try' }),
        };
      }

      // create category_items - 补全逻辑
      // 建立分类名称到ID的映射,提升查找效率
      const categoryNameToId = new Map(categoriesData.map(cat => [cat.name, cat.id]));
      
      // 按分类名称分组插入后的条目
      const itemsByCategory: Record<string, {id: number}[]> = {};
      data.forEach((rawItem, index) => {
        const categoryName = rawItem.category_name;
        const item = bulkInsertData[index];
        if (!itemsByCategory[categoryName]) {
          itemsByCategory[categoryName] = [];
        }
        itemsByCategory[categoryName].push(item);
      });

      // 生成符合要求的关联数组
      const newCategoryItems = Object.entries(itemsByCategory).flatMap(([categoryName, items]) => {
        const categoryId = categoryNameToId.get(categoryName);
        if (!categoryId) return []; // 处理分类未找到的异常情况
        return items.map((item, posIndex) => ({
          category_id: categoryId,
          item_id: item.id,
          position: posIndex + 1 // position从1开始自增
        }));
      });

      // 批量插入关联表
      const { data: categoryItemsData, error: categoryItemsError } = await supabase
        .from('category_items')
        .insert(newCategoryItems);

      if (!categoryItemsData || categoryItemsError) {
        return {
          success: false,
          ...genericError({ message: 'Error importing category items', path: 'Csv import resolver: try' }),
        };
      }
    }

    return {
      success: true,
      errors: null,
    };
  } catch (err) {
    console.log({ err });
    return {
      success: false,
      ...genericError({ message: 'Error importing items', path: 'Csv import resolver: catch' }),
    };
  }
};

export default csvImport;

关键逻辑说明

  1. 分类映射表:用Map存储分类名称与ID的对应关系,避免多次遍历数组查找,提升性能。
  2. 按分类分组:结合原始数据的分类名称,将插入后的条目按分类归类,保证同一分类下的条目顺序与原始CSV一致。
  3. 生成关联条目:对每个分类下的条目,按顺序分配从1开始的position值,最后合并成符合要求的关联数组。
  4. 异常处理:补充了分类未找到的容错逻辑,以及关联表插入的错误处理,保证流程的健壮性。

内容的提问来源于stack exchange,提问作者J. Jackson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 12:36:19