2

I’m working on a Node.js/Express backend using PostgreSQL (Neon serverless), TypeScript, and Zod for validation. I have a Students table with columns:

id UUID PRIMARY KEY,
firstName TEXT NOT NULL,
lastName TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
courseId UUID NOT NULL

I defined the following Zod schema for partial updates:

const updateSchema = z.object({
  firstName: z.string(),
  lastName: z.string(),
  email: z.string(),
  courseId: z.uuid(),
}).partial();

In my controller, I want to update only the fields provided in the request body. For example:

  • PATCH /students/10 with body { firstName: "John" } → only updates firstName.
  • PATCH /students/10 with body { email: "[email protected]", courseId: "uuid-123" } → updates email and courseId.

Currently, my update query looks like this:

await sql`
  UPDATE Students 
  SET firstName = ${fields.firstName},
      lastName = ${fields.lastName},
      email = ${fields.email},
      courseId = ${fields.courseId}
  WHERE id = ${id}
  RETURNING *;
`;

But this always tries to update all fields, even if they’re null or not provided in the request body.

the sql is coming from

import { neon } from "@neondatabase/serverless";

const sql=neon(
    `postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}/${PGDATABASE}?sslmode=require&channel_binding=require`
);

How can I use Neon to dynamically construct the SET clause in PostgreSQL so that just the supplied (non-null/non-undefined) fields are updated?

6
  • 1) The query is doing what you asked it do, UPDATE all the non-id fields. 2) Where is the sql in await sql UPDATE Students ...` coming from? Commented Sep 4 at 14:54
  • @AdrianKlaver Thank you for the feedback. The query is updating the non-id fields. My requirement is for example: I want to update only "firstName" for a scenario so I will provide only firstName in the request body. The query will work but it replaces the remaining fields with NULL. Commented Sep 4 at 15:22
  • I got that, you need to answer 2) from above. Commented Sep 4 at 15:26
  • I have edited the question, you can find it in the last code block Commented Sep 4 at 15:33
  • 1
    Take a look at neon.com/docs/serverless/serverless-driver, the part starting with SQL template queries are fully composable, ..... Commented Sep 4 at 17:57

1 Answer 1

1

You can wrap them in coalesce():

UPDATE Students 
SET firstName = coalesce(${fields.firstName},firstName),
    lastName = coalesce(${fields.lastName},lastName),
    email = coalesce(${fields.email},email),
    courseId = coalesce(${fields.courseId},curseId)
WHERE id = ${id}
RETURNING *;

That way, if the param is null, the column falls back to its current value.

It might be a good idea to check for a case when all your params are null to prevent a needless operation, unless you want this to intentionally fire some trigger even then.

Otherwise, it's best to not execute that statement at all, unless you have some something new for at least one of the fields; each update, empty or not, has to be cascaded to indexes and TOAST, plus it bloats the table, the toast and the indexes by leaving the old, invalidated row version for (auto)vacuum to clean up later.

Sign up to request clarification or add additional context in comments.

2 Comments

Notice that this approach prevents you from updating the fields to become NULL. You may want to have {} not patch anything, but {"courseId": null} to remove the course. You would not make this distinction for NOT NULL columns, but in the OP's example everything except the id is nullable.
Good point, thanks. I'll add this when I'm back at the keyboard along with the missing demo.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.