I'm currently creating a stock management system that uses multiple warehouses (with sub locations) and since this is my first big project I would love some feedback.
Let me show you what I have done so far...
Link as Im still new here
You first need to create a Warehouse, then you can create a Location within that Warehouse.
You can also create an ItemType (ItemGroup), then you can create an Item for that group.
Once you have an Item and Location you can add Stock, the Stock table has a composite key so that duplicates cant be added. I also added a constraint so that you could not enter an Item of the wrong ItemType, same constraint on the Warehouses/Location.
I then need to keep records of each piece of stock, SerialisedItems and NonSerialisedItems. Example: If non serialised stock is added with a quantity of 10 then I currently create 10 rows inside the NonSerialisedItems table (1) that are set to ‘in stock’ with the relevant stock information. If they change the amount of stock then rows would be deleted or added (2).
I could also do with a Van table somewhere that is similar to Warehouse, but think I would have to change the Warehouse table to something like Storage that references two tables, Warehouse and Van?
(1) I currently have a TransactionScope on my page adding x number of rows, Is this the best way to handle that?
(2) The Quantity amount in the Stock table would have to count the number of rows for that item and then update the Quantity each time stock is added or removed, any problems here? - Both Questions Fixed - Only create rows for serialised items.
Any other problems?
Well that’s what I have done, if its good or terrible let me know.
Also if there are any pitfalls I should be looking out for that would also be great to know.
Thanks
[EDIT]
Thanks to Neville K I have made a few changes...
Link to new and improved database
This seems to make a lot more sense! Think I had been staring at it to long yesterday!
Firstly, this is pretty much a solved problem - the best resource I know of is the "data model resource book" series; there's a very flexible model for stock maintenance apps in there.
Secondly, your design is not very normalized, and relies on a lot of duplication. Not sure what the reasoning is, but usually, the "stock" table would have a link to "item", but not "itemType" - the fact that an item belongs to an item type is already captured in the relationship between item and item type, and you don't need to duplicate it. The same goes for location and warehouse.
The key change I'd suggest is the concept of a stock transaction, rather than a single "stock" table.
Something along the lines of
TransactionID date itemID locationID quantity
------------------------------------------------------------------
1 1/1/12 1 1 10
2 1/2/12 1 1 -1
3 1/3/12 1 1 20
To find out the current stock for an item, you sum(quantity) group by item; to find the current stock for an item broken down by location, you sum(quantity) group by item, location.
On the first of Jan, there were 10 items in stock; on the second, 1 item was removed, leaving a stock of 9; on the 3rd, 20 new items came into stock, giving a balance of 29.
This design allows you to track change over time, which is a common requirement; it also provides an audit trail by creating transaction meta data (operatorID, invoice number etc.).
Related
I'm writing a simple transactional database to practice my T-SQL skills.
If I sell an umbrella in my sales.orderdetails table and it's getting the current retailprice of that umbrella from the items table and putting it in the invoice, how do I keep from having incorrect historical report data 6 months from now when I jack up the retail price of the umbrella by $10?
How do i store that umbrella sold price in the orderdetails table so it's unaffected by any changes in the items table in the future?
I know you can use an SCD for a datawarehouse for this kind of issue but was wondering how to do it in an OLTP system. Computed persisted column? Can't seem to get that to work in the object explorer when I try to enter the items.retailprice as the computed value for the salesorderdetails.cost column.
The way I have seen this done in the past, without using a technique like SCD, was to have the order detail have the price that was charged and then use a foreign key to another table, possibly products or productprices, that contains the current price.
In a full-on transactional system, you'd want the order detail row to record full retail (MSRP, or what have you), current price (in case you had the item posted at a discount that day), and price charged (in case the customer used a promo/coupon code to reduce the price themselves). Unless you log all three, you're at the mercy of whatever the price changes to tomorrow or next week or next year, which makes for bad analytics.
You probably also want to capture current cost of goods, too, since that's subject to change over time, especially in an average costing scenario. Otherwise, margin calculations will be suspect.
But then, yes, a foreign key or keys to any other descriptive tables for those less ephemeral characteristics of the product.
Right now, I am designing the database, as such I don't have any code. I am looking to use sql server, asp.net if that is relevant.
I have a big number of stores and a big number of products too, both in some thousands. For the same pId, prices may vary by sId. I would build it like this:
1. one "store" table containing fields (sId, name, location),
2. one "products" table containing fields (pId, name size, category, sub-category) and
3. "max(sId)" number of price tables containing fields (pId, mrp, availability).
where max(sId) is the total number of stores.
I would rather not make "max(pId)" number of tables containing fields (sId, mrp, availability) as I need to provide a UI to each store so that they can update the details about product prices and availability at their respective stores. I also need to display some products of a particular store but I never need to display some stores for any specific product. That is, search for stores by product is not required, but listing of products by store would be required.
Is this a good way or can I do better?
You appear to be on the right track and I will offer some recommendations. Although there is no requirement to display some stores for any specific products, you should always think about how the requirements will change and how your system can handle that. Build your system so that you can answer questions like these easily - What stores have product ABC priced under $3/piece?
Store table should contain, as you mentioned, information about stores. Take Aaron Bertrand's comment seriously. Name the fields in a way that the next developer can read and figure out what it is. User StoreID instead of sID.
StoreID StoreName ...other fields
------- --------------
1 North Chicago
2 East Los Angeles
Product table should contain information about products. It would be better to store category and sub-category into a different table.
ProductID ProductName ...other fields
--------- --------------
1 Bread
2 Soap
Categories can be located in its own table with hierarchal structure. See Hierarchal Data and how to use hierarchyid data type. This may help in finding out the depth of each top level category and help management decide if they are going overboard with categorization and making life miserable for everybody, including themselves unknowingly.
Many-to-many ProductCategory table can link products to categories. Also keep a history table. When a product's category is changed, keep track of what it was and what it is set to. It may help in answering questions such as - How many products were moved from Agriculture to Construction category in the last 6 months?
Many-to-many StoreProductPrice can bring together store and product and a price can be defined there. Also remember - prices may differ by customers also. Some customers may get discounts at a certain level. Although this may be too much to discuss here, it should be kept in the back of the mind in case a requirement to support customer discount structure comes up.
StoreProductID StoreID ProductID Price
-------------- ------- --------- -----
1 1 1 $4.00
2 1 2 $1.00
3 2 1 $4.05
4 2 2 $1.02
Availability of the product should be done through the inventory management database table(s). For example, you may have a master table of Warehouse and master table of Location. Bringing them together would be WearhouseLocation table. A WarehouseProduct table may bring together warehouse, product and units available.
Alternatively, your production or procurement facility might be dumping data into ProcuredProduct table. Your manufacturing unit might be putting locks on a subset of products while building something out of it. Your sales unit might be putting locks on a subset of products they are trying to sell. In other words, your products may be continually get allocated. You may run queries to find out availability of a certain product and that can be a little taxing. During any such allocation, the number of available units can be updated in a single table (which contains calculated available products that you can comfortably rely on).
So...depending on your customer's needs, the system you are building can get fairly complicated. I am recommending that you think about these things and keep your database structure flexible to anticipated changes. Normalization is a good thing, and de-normalization has its place also. Use them wisely.
I need a table that stores items and tracks when an item is...
queued
marked for shipping
shipped
marked for return
returned
The table also needs to tell me how many items a customer...
has received in a month
has at home at this time
I tried using inheritance. I assigned each step a typeId (from 1 to 5, each id representing the current step in the workflow). This approach is not ideal because updating a workflow step erases history.
For example, if an item moves from shipped (typeId 3) to returned (typeId 5) we lose an accurate count for shipped items.
How should I approach this? Also, I prefer to keep the data in one table unless I get a compelling reason not to.
Update
Items are mailed to customers incrementally. There are limits to how many items a customer can receive within a month period, and limits on how many items a customer can have at home at any given time. For this example, let's assume 4 for earlier and 2 for the latter. A customers queue can have any number of items. For this reason, items in the queue need to be ranked so the order of items sent can be according to the customers preference.
Items that have shipped already will need to fall out of ranking (the customer can no longer modify rank after an item is sent).
No inheritance here. Time fields are actually date-time. A row is entered into the Tracking table when a customer adds an item to the queue. Other time columns from TimeMarkedShip to TimeReturned are NULL until the action happens. The TimeQueued is part of the primary key in order to allow a customer to rent an item more than once (sounds like video rentals to me).
To count items that a customer has at home you could use something like
select count(1)
from Tracking
where CustomerID = 1755
and TimeShipped is not NULL
and TimeReturned is NULL ;
Rather than a typeID for the current step, it looks like you need a boolean column for each step. Then when you want to count the "net shipped" items, you can subtract the "returned" items from the "shipped" items.
In addition, if you want to track the history, you could make each of these steps a nullable date field, so that you can see that an item was shipped on 3/5/11 and returned on 4/1/11. This may help if you're using this table in other ways, such as managing your shipping/receiving or billing. A NULL, of course, would indicate that the step has not been performed (yet).
I'm looking to design an inventory database that tracks a snack bar. As this would be a single person/computer access and need to be easily movable to another system, I plan to use SQLite as the DB engine. The basic concept is to track inventory bought from a wholesale warehouse such as Sams Club, and then keep track of the inventory.
The main obstacle I'm trying to overcome is how to track bulk vs individual items in the products database. For example if a bulk item is purchased, let us say a 24 pack of coke, how do I maintain in the product database, the bulk item and that it contains 24 of the individual items. The solution would be fairly easy if all bulk items only contained multiple of 1 item, but in variety packs, such as a carton of chips that contains 5 different individual items all with separate UPCs, the solution becomes a bit more difficult.
So far I have come up with the multiple pass approach where the DB would be scanned multiple times to obtain all of the information.
Product_Table
SKU: INT
Name: TEXT
Brand: TEXT
PurchasePrice: REAL
UPC: BIGINT
DESC: TEXT
BULK: BOOLEAN
BulkList: TEXT // comma separated list of SKUs for each individual item
BulkQty: TEXT // comma separated list corresponding to each SKU above representing the quantity
Transaction_Table
SKU: INT
Qty: INT
// Other stuff but that is the essential
When I add a bulk item to the inventory (A Positive Quantity Transaction), it should instead add all of it's individual items, as I can't think of any time I would keep in stock to sell the bulk item. I would like to keep the bulk items in the database however, to help receiving and adding them into the inventory.
one way to do it is to create a 1:N mapping between bulk objects and their contents:
create table bulk_item (
bulk_product_id integer not null,
item_product_id integer not null,
qty integer not null,
primary key(bulk_product_id, item_product_id),
foreign key(bulk_product_id) references product(sku),
foreign key(item_product_id) references product(sku)
);
A comma-separated list is certainly fine (it might make it harder to do certain queries such as find all bulk objects that contain this SKU etc...).
I have to both agree and disagree with jspcal. I agree with the "bulk_item" table, but I would not say that it's "fine" to use a comma separated list. I suspect that they were only being polite and would not endorse a design that isn't in first normal form.
The design that jspcal has suggested is commonly called "Bill of Materials" and is the only sane way to approach a problem like composite products.
In order to use this effectively with your transaction table, you should include a transaction type code along with the SKU and quantity. There are different reasons why your stock in any given SKU might go up or down. The most common are receiving new stock and customers buying stock. However, there are other things like manual inventory adjustments to take into consideration clerical errors and shrinkage. There are also stock conversions like when you decide to bust up a variety pack into individual products for sale. Don't think you can count on whether the quantity is positive or negative to give you enough information to be able to make sense of your inventory levels and how (and why) they've changed.
The invoice database design, might look something like this...
http://www.databaseanswers.org/data_models/invoices_and_payments/index.htm
Now If the user decides to change/revise the product code/description
It will change the previous order and invoice produce code/description :(
What do you do? Copy the product code description to the invoice table instead?
You basically have two options:
either you make your Products table "time-enabled" (also known as "temporal database"), e.g. you keep the "previous" state of your individual product in your table, and you give every entry a ValidFrom / ValidTo pair of dates. That way, if you change your product, you get a new entry, and the previous one remains untouched, referenced from those invoices that used it; only the ValidTo date for the product gets updated
or:
you could copy the products (at least those bits you need for your invoice) to the invoice - that'll make sure you always know what the product looked like when you created the invoice - but this will cause lots of data duplication (not recommended)
See this other Stackoverflow question on temporal databases as another input, and also check out this article on Simple-Talk: Database Design: A Point in Time Architecture