Skip to content

CRUD - Update Record

Updating a record is straightforward:

  1. query the row
  2. modify attributes
  3. commit
python
with app.app_context():
    user = User.query.filter_by(username="ravi").first()
    if user:
        user.username = "ravik"
        db.session.commit()
  • Update timestamps on every change
  • Validate before saving (especially on user-editable fields)

If a record doesn’t exist, decide your behavior:

  • return 404 for web routes
  • return 400 or 404 for APIs

Example in a route:

python
user = User.query.get_or_404(user_id)
user.username = new_username
db.session.commit()

In APIs, you often implement PATCH semantics.

Be careful to only update provided fields and validate them.

There is no update() call for a loaded object. You change the attribute and the session notices:

update.py
row = db.session.scalar(sa.select(User).where(User.name == "ada"))
row.age = 37
 
db.session.dirty        # IdentitySet([<User 1>])   <- the session is tracking it
db.session.commit()     # issues the UPDATE

Measured: db.session.dirty was non-empty before the commit, and the stored age was 37 afterwards.

diagram Diagram mermaid

The unit-of-work pattern is what makes this work: the session compares each tracked object against the values it loaded, and writes only the columns that actually changed.

bulk.py
result = db.session.execute(
    sa.update(User).where(User.age < 18).values(active=False)
)
db.session.commit()
result.rowcount        # how many rows the database matched

This is one statement, so it is far faster than loading rows and mutating them. The trade-off is that it bypasses the ORM: objects already in the session keep their old values, and any Python-level defaults or validation on the model do not run.

upsert.py
user = db.session.scalar(sa.select(User).where(User.name == name))
if user is None:
    user = User(name=name)
    db.session.add(user)
user.age = age
db.session.commit()

Correct for a single writer, and racy under concurrency — two requests can both find None. A unique constraint turns the race into an IntegrityError you can catch and retry, which is the difference between a duplicate row and a handled collision.

sketch Loaded, mutated, committed p5.js
The session tracks a loaded object and writes only what changed. A bulk update bypasses it, leaving loaded objects stale until they are expired.
pch.quizTag pch.quizDefaultTitle
  1. How do you update a loaded ORM object?

    pch.quizShowAnswer

    B — assign to the attribute and commit; the session tracks the change and issues the UPDATE — Measured db.session.dirty holding the object after the assignment. The unit-of-work pattern compares against the loaded values and writes only the columns that changed.

  2. An object is loaded with views = 1, then a raw UPDATE sets views = 99. What does obj.views report?

    pch.quizShowAnswer

    B — 1, because the session returns the instance from its identity map — Measured 1. Call db.session.expire(obj) — or expire_all() — so the next attribute access re-reads from the database, which then measured 99.

  3. What is the trade-off of sa.update(...) as a bulk statement?

    pch.quizShowAnswer

    B — it is much faster but bypasses the ORM, so loaded objects go stale and model-level defaults or validation do not run — One statement replaces load-and-mutate, which is a large win on many rows. The cost is that nothing at the Python level participates.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading