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

使用Supabase和React Upsert对象数组仅插入首个元素的问题

问题:Supabase中实现NFT数组的Upsert功能

我是SQL新手,正在用Supabase开发Web应用,尝试将ownedNFTs对象数组存入数据库,但目前仅能插入数组的首个元素。目标是实现完整数组的upsert功能——检测到变化时将对应对象添加到数组中。

当前实现代码:

const nakedDB =  () => {
    try {
      setLoading(true);
      ownedNFTs.forEach(async (nft) => {
          
        let { data, error, status } = await supabase.from("Users").upsert({
          wallet_address: wallet?.accounts[0].address,
          NFTS: {
            name: nft?.metadata?.name || "",
            description: nft?.metadata?.description || "",
            image: nft?.metadata?.image || "",
            externalUrl: nft?.metadata?.external_url || "",
            contractAddress: nft?.contract.address || "",
            tokenId: nft?.id.tokenId || "",
            blockchain: "Ethereum",
            metaverse: nft?.metadata?.name.split(" ")[0] || "",
            metadata: nft?.metadata || "",
            symbol: nft?.contract.symbol || "",
          },
        }).eq('wallet_address', wallet?.accounts[0].address);
        console.log(data)
        if (error && status !== 406) {
          throw error;
        }
      });
   
    } catch (error) {
      console.table(error);
    } finally {
      setLoading(false);
    }
  };

我曾参考Supabase的批量upsert文档,但那是用于批量创建多行数据;也尝试过标准for循环和当前的forEach(),均未达到预期效果。


解决方案

核心问题分析

之前的代码失效,本质是因为Users表的NFTS字段不是数组类型(比如jsonb[]),每次upsert都用单个NFT对象覆盖字段值,加上forEach不等待异步操作完成,最终只保留了单个元素。


方案1:用PostgreSQL数组操作实现合并(推荐)

步骤1:确认/修改表字段类型

先确保Users表的NFTS字段是jsonb[]类型,若不是则执行SQL修改:

ALTER TABLE Users ALTER COLUMN NFTS TYPE jsonb[] USING NFTS::jsonb[];

步骤2:修改前端代码

无需循环单个NFT,直接将整个数组传入,结合PostgreSQL的数组合并逻辑完成upsert:

const nakedDB = async () => {
  try {
    setLoading(true);
    const walletAddress = wallet?.accounts[0].address;
    if (!walletAddress) return;

    // 转换ownedNFTs为符合数据库结构的数组
    const nftsArray = ownedNFTs.map(nft => ({
      name: nft?.metadata?.name || "",
      description: nft?.metadata?.description || "",
      image: nft?.metadata?.image || "",
      externalUrl: nft?.metadata?.external_url || "",
      contractAddress: nft?.contract.address || "",
      tokenId: nft?.id.tokenId || "",
      blockchain: "Ethereum",
      metaverse: nft?.metadata?.name?.split(" ")[0] || "",
      metadata: nft?.metadata || "",
      symbol: nft?.contract.symbol || ""
    }));

    // 执行upsert,合并数组
    const { data, error, status } = await supabase
      .from("Users")
      .upsert(
        {
          wallet_address: walletAddress,
          NFTS: nftsArray
        },
        {
          onConflict: "wallet_address",
          update: {
            // 合并现有数组与新数组,如需去重则添加array_distinct
            NFTS: "array_cat(NFTS, excluded.NFTS)"
            // 去重写法:NFTS: "array_distinct(array_cat(NFTS, excluded.NFTS))"
          }
        }
      )
      .eq("wallet_address", walletAddress);

    if (error && status !== 406) throw error;
    console.log(data);
  } catch (error) {
    console.table(error);
  } finally {
    setLoading(false);
  }
};

方案2:自定义函数实现精准去重(按NFT唯一标识)

如果需要根据contractAddress和tokenId判断NFT唯一性(而非整个对象完全一致),可以创建PostgreSQL自定义函数:

CREATE OR REPLACE FUNCTION merge_nfts(existing jsonb[], new_nfts jsonb[])
RETURNS jsonb[] AS $$
BEGIN
  FOREACH nft IN ARRAY new_nfts LOOP
    -- 仅添加数据库中不存在的NFT
    IF NOT EXISTS (
      SELECT 1 FROM unnest(existing) e
      WHERE e->>'contractAddress' = nft->>'contractAddress'
        AND e->>'tokenId' = nft->>'tokenId'
    ) THEN
      existing = array_append(existing, nft);
    END IF;
  END LOOP;
  RETURN existing;
END;
$$ LANGUAGE plpgsql;

之后修改前端代码的update配置:

update: {
  NFTS: "merge_nfts(NFTS, excluded.NFTS)"
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 15:35:21