I'm currently working on a program that keeps track of my company's stock inventory, using ms Access 2010. I'm having a hard time getting the query, intended to show inventory, to display the information I want. The problem seems to be that the query pulls the same record multiple times, inflating the sums of reserved and sold product.
Background: My company stocks steel bars. We offer to cut the bars into pieces. From an inventory side, We want to track the length of each bar, from the moment it comes in to the warehouse, through it's time in the warehouse (where it might get cut into smaller pieces), until the entire bar is sold and gone.
Database: The query giving problems, is consulting 3 tables;
(BatchNumber and BarNo combined, is the primary key)
Sales
Reservation (a seller kan reserve some material, when a customer signals interest, but needs time to decide)
I'd like to pull information from the three tables into one list, that displays: -Barstock.orginial length As Received - Sales.Quantity sold As Sold - Recieved - Sold As On Stock - reservation.Quantity Reserved As Reserved - On Stock - Reserved As Available.
The problem is that I suck at sql. I've looked into union and inner join to the best of my ability, but my efforts have been in vain. I usually rely on the design view to produce the Sql statements I need. With design view, I've come up with the following Sql:
SELECT
BarStock.BatchNo
, BarStock.BarNo
, First(BarStock.OrgLength) AS Recieved
, Sum(Sales.QtySold) AS SumAvQtySold
, [Recieved]-[SumAvQtySold] AS [On Stock]
, Sum(Reservation.QtyReserved) AS Reserved
, ([On Stock]-[Reserved])*[Skjemaer]![Inventory]![unitvalg] AS Available
FROM
(BarStock
INNER JOIN Reservation ON (BarStock.BarNo = Reservation.BarNo) AND (BarStock.BatchNo = Reservation.BatchNo)
)
INNER JOIN Sales ON (BarStock.BarNo = Sales.BarNo) AND (BarStock.BatchNo = Sales.BatchNo)
GROUP BY
BarStock.BatchNo
, BarStock.BarNo
I know that the query is pulling the same record multiple times because; - when I remove the GROUP BY term, I get several records that are exactley the same. - There are however, only one instance of these records in the corresponding tables.
I hope I've been able to explain myself properly, please ask if I need to elaborate on anything.
Thank you for taking the time to look at my problem!
You are getting duplicates because more than one row matches your conditions. To prevent duplicates use the DISTINCT keyword: SELECT DISTINCT respid, cq4_1, dma etc...
Set a field's Indexed property to Yes (No duplicates) Select the field that you want to make sure has unique values. In the Field Properties pane at the bottom of the table design view, on the General tab, set the Indexed property to Yes (No duplicates). Save the changes to your table.
!!! Checking some assumptions
From your database schema, it seems that:
Sales
records for a given BatchNumber/BarNo
(for instance, I can imagine that multiple customers may have bought subsections of the same bar).Reservation
records for a given BatchNumber/BarNo
(for instance, multiple sections of the same bar could be 'reserved')To check if you do indeed have multiple records in those tables, try something like:
SELECT CountOfDuplicates
FROM (SELECT COUNT(*) AS CountOfDuplicates
FROM Sales
GROUP BY BatchNumber & "," & BarNo)
WHERE CountOfDuplicates > 1
If the query returns some records, then there are duplicates and it's probably why your query is returning incorrect values.
Now, the trick to your make your query work is to really think about what is the main data you want to show, and start from that:
So it's clear, your central data is the list of bars in stock.
Rather than try to pull everything into a single large query straight away, it's best to create simple queries for each of those goals and make sure we get the proper data in each case.
From what you explain, each individual bar is recorded in the BarStock
table.
As I said in my comment, from what I understand, all bars that are delivered have a single record in the BarStock
table, without duplicates. So your main list against which your inventory should be measured is the BarStock
table:
SELECT BatchNumber,
BarNo,
OrgLength
FROM BarStock
Again, this should be pretty straightforward: we just need to find out how much total length was sold for each BatchNumber/BarNo
pair:
SELECT BatchNumber,
BarNo,
Sum(QtySold) AS SumAvQtySold
FROM Sales
GROUP BY BatchNumber, BarNo
Same as for Sales:
SELECT BatchNumber,
BarNo,
SUM(QtyReserved) AS Reserved
FROM Reservation
GROUP BY BatchNumber, BarNo
Now, we should be able to combine the first 2 queries into one. I'm not trying to optimise, just to make the data work together:
SELECT BarStock.BatchNumber,
BarStock.BarNo,
BarStock.OrgLength,
S.SumAvQtySold,
(BarStock.OrgLength - Nz(S.SumAvQtySold)) AS OnStock
FROM BarStock
LEFT JOIN (SELECT BatchNumber,
BarNo,
Sum(QtySold) AS SumAvQtySold
FROM Sales
GROUP BY BatchNumber, BarNo) AS S
ON (BarStock.BatchNumber = S.BatchNumber) AND (BarStock.BarNo = S.BarNo)
We do a LEFT JOIN
because there might be bars in stock that have not yet been sold.
If we did an INNER JOIN
, we wold have missed these in the final report, leading us to believe that these bars were never there in the first place.
We can now wrap the whole query in another LEFT JOIN
against the reserved bars to get our final result:
SELECT BS.BatchNumber,
BS.BarNo,
BS.OrgLength,
BS.SumAvQtySold,
BS.OnStock,
R.Reserved,
(OnStock - Nz(Reserved)) AS Available
FROM (SELECT BarStock.BatchNumber,
BarStock.BarNo,
BarStock.OrgLength,
S.SumAvQtySold,
(BarStock.OrgLength - Nz(S.SumAvQtySold)) AS OnStock
FROM BarStock
LEFT JOIN (SELECT BatchNumber,
BarNo,
SUM(QtySold) AS SumAvQtySold
FROM Sales
GROUP BY BatchNumber,
BarNo) AS S
ON (BarStock.BatchNumber = S.BatchNumber) AND (BarStock.BarNo = S.BarNo)) AS BS
LEFT JOIN (SELECT BatchNumber,
BarNo,
SUM(QtyReserved) AS Reserved
FROM Reservation
GROUP BY BatchNumber,
BarNo) AS R
ON (BS.BatchNumber = R.BatchNumber) AND (BS.BarNo = R.BarNo)
Note the use of Nz()
for items that are on the right side of the join: if there is no Sales
or Reservation
data for a given BatchNumber/BarNo
pair, the values for SumAvQtySold
and Reserved
will be Null
and will render OnStock
and Available
null as well, regardless of the actual quantity in stock, which would not be the result we expect.
Using the Query designer in Access, you would have had to create the 3 queries separately and then combine them.
Note though that the Query Designed isn't very good at dealing with multiple LEFT
and RIGHT
joins, so I don't think you could have written the whole thing in one go.
I believe you should read the information that @Remou gave you in his comments.
To me, there are some unfortunate design choices for this database: getting basic stock data should be as easy as s simple SUM()
on the column that hold inventory records.
Usually, a simple way to track inventory is to keep track of each stock transaction:
So if you need to know the complete stock at hand for all items, all you need to do is something like:
SELECT BarID,
Sum(Quantity)
FROM StockTransaction
GROUP BY BarID
In your case, while BatchNumber/BarNo
is your natural key, keeping them in a separate Bar
table would have some advantages:
Bar.ID
to get back the Bar.BatchNumber
and Bar.BarNo
anywhere you need it.BarID
as a foreign key in your BarStock
, Sales
and Reservation
tables. It makes joins easier without having to mess with the complexities of compound keys.There are things that Access allows that are not really good practice, such as spaces in table names and fields, which end up making things less readable (at least because you need to keep them between []
), less consistent with VBA variable names that represent these fields, and incompatible with other database that don't accept anything other than alphanumerical characters for table and field names (should you wish to up-size later or interface your database with other apps).
Also, help yourself by sticking to a single naming convention, and keep it consistent:
Part
instead of Parts
, but it's just a convention (that has its own reasons).Received
not Recieved
. That mistake alone may cost you when debugging why some query or VBA code doesn't work, just because someone made a typo.ID
column. Usually, this will be an auto-increment that guarantees uniqueness of each record in the table. If you keep that convention, then foreign keys become easy to guess and to read and you never have to worry about some business requirement changing the fact that you could suddenly find yourself with 2 identical BatchNumbers
, for some reason you can't fathom right now. There are lots of debates about database design, but there are certain 'rules' that everyone agrees with, so my recommendation should be to strive for:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With