reactjsnode.jspostgresqlexpresssupabase

Upsert only columns that have a value that is not undefined or null using @supabase/ssr


I am trying to upsert a table when a web hook is triggered. This web hook only returns the updated fields, so sometimes name and notify fields are missing. The only field that is always coming from the web hook is the phone field.

Right now, when I perform an upsert and the name field is missing from the web hook, it is updating my row but removing and setting to null the name field.

select() and update() afterwards is not possible strategy here because there are a lot of items coming from that web hook.

How can I fix it so that upsert does not set to null fields that are not present if they already have a value?

  const contactsToUpsert = nextContacts.map((contact: {phone: string, name?: string, notify?: string}) => ({
    user_id: settings.user_id,
    phone,
    ...(contact.name && { name: contact.name }),
    ...(contact.notify && { notify: contact.notify })
}));

const { error } = await supabase
    .from('contacts')
    .upsert(contactsToUpsert, {
        onConflict: 'user_id,phone',
        defaultToNull: false
    });

Solution

  • There is no way to do it using the supabase client. The route one has to go is to either write a custom supabase function or use select() and iterate over the range of item it returns. By default supabase has a set of 1000 items it returns.

    Here's the solution I implemented:

          const prevContacts = new Map()
          let hasMore = true
          let offset = 0
          while(hasMore) {
            const { data: prevContactsData } = await supabase
              .from('contacts')
              .select('phone, name, notify, id, user_id')
              .eq('user_id', settings.user_id)
              .range(offset, offset + 1000)
            offset += 1000
            hasMore = !!prevContactsData && prevContactsData.length === 1000
            if(prevContactsData) {
              prevContactsData.forEach((contact: {phone: string, name?: string, notify?: string}) => {
                prevContacts.set(contact.phone, contact)
              })
            }
          }
    
    
          const contactsToUpsert: {name: string, notify?: string, user_id: string, phone: string}[] = []
          nextContacts.forEach((contact: {phone: string, name?: string, notify?: string}) => {
              const phoneNumber = contact.phone
              const prevContact = prevContacts.get(phoneNumber)
    
              contactsToUpsert.push({
                user_id: settings.user_id,
                phone: phoneNumber,
                name: contact.name || prevContact?.name,
                notify: contact.notify || prevContact?.notify
              })
          });
    
        const { error } = await supabase
            .from('contacts')
            .upsert(contactsToUpsert, {
                onConflict: 'user_id,phone'
            });