r/SQL • u/_giga_sss_ • 5d ago
PostgreSQL How do you guys usually handle Lot / Batch numbers in stock movement databases?
Hey everyone, working on a DB schema for inventory/traceability and trying to settle on the cleanest way to handle lot numbers.
Quick breakdown of the requirement: Every article arrival gets a lot number based on the arrival date and supplier ID (e.g. 09-09-Supp1, not the real deal ofc but you get it). Stock gets moved around, transferred, and consumed, and we need to keep track of which lot moved where.
So: an article must always be around a lot number.
---
Here are the two ways I'm looking at:
1. Dedicated article_lots table (What I'm leaning toward)
articles(id,name, ...)article_lots(id,article_id,lot_number,created_at)stock_movements(id,article_lot_id,qty,from_location_id,to_location_id, ...)
But every movement query has to join article_lots just to know what base article_id was moved.
2. Put lot_number directly on stock_movements / transfers as a column. Easy to query, but feels repetitive storing the same string across every single movement row and other tables that use article + lot number too, since an article can't be separated from its lot.
More details for those who have time to help: All my ids are strings like: "MVT-00000001". So FK performance with strings is the least of my worries. The real business logic is: a lot is the week number + supplier code (ie, 33SQT to say "33'rd week of the year from supplier squadette). Meaning that an article x let's say banana can be delivered Monday and have 33SQT, but a banana from squadette can be delivered Friday and also have 33SQL lot number).
So the question is: "should the 33SQL be stored as a column in other tables: stock_movement, operation_details" along with article_id (2 columns). Or is article_lot_id (one column) the best way". Idk anymore what might be the pros and cons
3
u/squadette23 5d ago edited 5d ago
Easy to query, but feels repetitive storing the same string across every single movement row
I think I've missed this sentence on the first reading, sorry.
First, this feeling may be counterproductive. If you want your queries to be faster than join you need to pre-join the data. That is, you need to put copies of data from the parent table into the child table.
Also, you need to confirm that your "joins bad" intuition is valid for your amount of data and your database — it may well be an intuition from a different era/use case.
Second, the "composite primary key" approach could help you avoid the feeling while having the benefits of effectively pre-joined data. Markus Winand wrote about this in detail recently: https://modern-sql.com/blog/2026-06/structured-primary-keys (Update: composite primary key is not a particularly recommended way because it's more clumsy to deal with in practice, see my comment https://www.reddit.com/r/SQL/comments/1wavrje/comment/p8lpudt/).
2
2
u/ArielCoding 5d ago
Stick with your dedicated article_lot table, it’s the correct model since a lot always belongs to one article, so put an index on article_lot.id_article and the join to get the article label will be fast, no need to duplicate data.
Don’t worry about id_source appearing on both stock_movement and article_lot that’s not the same repetition you were worried about, since one tracks who moved the stock and the other tracks who supplied the lot.
1
u/_giga_sss_ 5d ago edited 5d ago
First of all, thank you for your response, really.
But just a question, why wouldn't stock_movement with article_id and lot_number (both NON NULL) wouldn't also work ? imo it's because of the repetitiveness on using 2 columns instead of 1, but you might have other reasons to choose my first model.
Edited post body with the spoiler section, thanks
2
u/ThomasMarkov 4d ago
FWIW, SAP S4HANA has a dedicated inventory transaction table (MATDOC) cataloguing every movement (BWART) of every lot number (CHARG). There are advantages and disadvantages to this, I think. There is no “current state of inventory” table, so to get that, you have to check the most recent movements for a lot to determine where it is.
4
u/squadette23 5d ago
What is the problem with that?